Skip to content

Commit a6a9969

Browse files
authored
Provide bang versions of meta methods (#261)
1 parent 57d3579 commit a6a9969

File tree

4 files changed

+184
-18
lines changed

4 files changed

+184
-18
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
## master (unreleased)
44

5+
- Add block-less versions of `with_responsible` and `with_metata`. ([@atomaka][])
6+
57
## 1.4.1 (2025-06-05)
68

79
- Don't drop functions in the upgrade migration. ([@palkan][])
@@ -416,3 +418,4 @@ This is a quick fix for a more general problem (see [#59](https://github.com/pal
416418
[@SparLaimor]: https://github.com/SparLaimor
417419
[@tagirahmad]: https://github.com/tagirahmad
418420
[@tylerhunt]: https://github.com/tylerhunt
421+
[@atomaka]: https://github.com/atomaka

README.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ Other requirements:
4242
- [Basic API](#basic-api)
4343
- [Track meta information](#track-meta-information)
4444
- [Track responsibility](#track-responsibility)
45+
- [Persisted metadata](#persisted-metadata)
4546
- [Disable logging temporary](#disable-logging-temporary)
4647
- [Reset log](#reset-log)
4748
- [Creating full snapshot instead of diffs](#full-snapshots)
@@ -419,6 +420,26 @@ Logidze.with_responsible(user.id, transactional: false) do
419420
end
420421
```
421422

423+
#### Persisted metadata
424+
425+
You can also set metadata and responsibility that persists for the database connection using the bang versions of these methods:
426+
427+
```ruby
428+
Logidze.with_meta!({ip: request.ip})
429+
post.save!
430+
431+
Logidze.with_responsible!(user.id)
432+
post.save!
433+
```
434+
435+
This persisted information needs to be explicitly cleared.
436+
437+
```ruby
438+
Logidze.clear_meta!
439+
```
440+
441+
**Important:** Persisted metadata is set at the connection level and will affect all subsequent operations on that connection until cleared. Always ensure you call `clear_meta!` when done, especially in web applications where connections are reused.
442+
422443
### Disable logging temporary
423444

424445
If you want to make update without logging (e.g., mass update), you can turn it off the following way:

lib/logidze/meta.rb

Lines changed: 76 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,67 @@ def with_responsible(responsible_id, transactional: true, &block)
1515
with_meta(meta, transactional: transactional, &block)
1616
end
1717

18-
class MetaWrapper # :nodoc:
19-
def self.wrap_with(meta, &block)
20-
new(meta, &block).perform
18+
def with_meta!(meta)
19+
return if meta.nil?
20+
21+
if Thread.current[:logidze_in_block]
22+
raise StandardError, "with_meta! cannot be called from within a with_meta block"
2123
end
2224

23-
attr_reader :meta, :block
25+
MetaForConnection.new(meta).set!
26+
end
27+
28+
def clear_meta!
29+
MetaForConnection.new({}).clear!
30+
end
31+
32+
def with_responsible!(responsible_id)
33+
return if responsible_id.nil?
34+
35+
meta = {Logidze::History::Version::META_RESPONSIBLE => responsible_id}
36+
with_meta!(meta)
37+
end
38+
39+
class MetaBase # :nodoc:
40+
attr_reader :meta
2441

2542
delegate :connection, to: ActiveRecord::Base
2643

27-
def initialize(meta, &block)
44+
def initialize(meta)
2845
@meta = meta
46+
end
47+
48+
def current_meta
49+
meta_stack.reduce(:merge) || {}
50+
end
51+
52+
def meta_stack
53+
Thread.current[:meta] ||= []
54+
Thread.current[:meta]
55+
end
56+
57+
def encode_meta(value)
58+
connection.quote(ActiveSupport::JSON.encode(value))
59+
end
60+
61+
def pg_reset_meta_param(prev_meta)
62+
if prev_meta.empty?
63+
pg_clear_meta_param
64+
else
65+
pg_set_meta_param(prev_meta)
66+
end
67+
end
68+
end
69+
70+
class MetaWrapper < MetaBase # :nodoc:
71+
def self.wrap_with(meta, &block)
72+
new(meta, &block).perform
73+
end
74+
75+
attr_reader :block
76+
77+
def initialize(meta, &block)
78+
super(meta)
2979
@block = block
3080
end
3181

@@ -38,36 +88,42 @@ def perform
3888

3989
def call_block_in_meta_context
4090
prev_meta = current_meta
91+
was_in_block = Thread.current[:logidze_in_block]
4192

4293
meta_stack.push(meta)
94+
Thread.current[:logidze_in_block] = true
4395

4496
pg_set_meta_param(current_meta)
4597
result = block.call
4698
result
4799
ensure
48100
pg_reset_meta_param(prev_meta)
49101
meta_stack.pop
102+
Thread.current[:logidze_in_block] = was_in_block
50103
end
104+
end
51105

52-
def current_meta
53-
meta_stack.reduce(:merge) || {}
106+
class MetaForConnection < MetaBase # :nodoc:
107+
def set!
108+
return if meta.nil?
109+
110+
meta_stack.push(meta)
111+
pg_set_meta_param(current_meta)
54112
end
55113

56-
def meta_stack
57-
Thread.current[:meta] ||= []
58-
Thread.current[:meta]
114+
def clear!
115+
meta_stack.clear
116+
pg_clear_meta_param
59117
end
60118

61-
def encode_meta(value)
62-
connection.quote(ActiveSupport::JSON.encode(value))
119+
private
120+
121+
def pg_set_meta_param(value)
122+
connection.execute("SET logidze.meta = #{encode_meta(value)};")
63123
end
64124

65-
def pg_reset_meta_param(prev_meta)
66-
if prev_meta.empty?
67-
pg_clear_meta_param
68-
else
69-
pg_set_meta_param(prev_meta)
70-
end
125+
def pg_clear_meta_param
126+
connection.execute("SET logidze.meta TO DEFAULT;")
71127
end
72128
end
73129

@@ -99,7 +155,9 @@ def pg_clear_meta_param
99155
end
100156
end
101157

158+
private_constant :MetaBase
102159
private_constant :MetaWrapper
160+
private_constant :MetaForConnection
103161
private_constant :MetaWithTransaction
104162
private_constant :MetaWithoutTransaction
105163
end

spec/integration/meta_spec.rb

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -376,4 +376,88 @@
376376
end
377377
end
378378
end
379+
380+
describe ".with_meta!" do
381+
subject { User.create!(name: "test", age: 10, active: false) }
382+
383+
after { Logidze.clear_meta! }
384+
385+
context "setting meta for connection" do
386+
it "sets meta for connection and persists across operations" do
387+
Logidze.with_meta!(meta)
388+
389+
expect(subject.reload.meta).to eq(meta)
390+
end
391+
392+
it "handles nil" do
393+
Logidze.with_meta!(nil)
394+
395+
expect(subject.reload.meta).to be_nil
396+
expect(subject.log_data.current_version.data.keys).not_to include(Logidze::History::Version::META)
397+
end
398+
399+
it "cannot be called inside block version" do
400+
Logidze.with_meta(meta) do
401+
expect { Logidze.with_meta!(meta2) }.to raise_error(StandardError, /cannot be called from within a with_meta block/)
402+
end
403+
end
404+
end
405+
end
406+
407+
describe ".with_responsible!" do
408+
let(:responsible) { User.create!(name: "owner") }
409+
410+
subject { User.create!(name: "test", age: 10, active: false) }
411+
412+
after { Logidze.clear_meta! }
413+
414+
context "setting responsible for connection" do
415+
it "sets responsible for connection and persists across operations" do
416+
Logidze.with_responsible!(responsible.id)
417+
418+
expect(subject.reload.whodunnit).to eq(responsible)
419+
420+
subject.update!(age: 11)
421+
expect(subject.reload.whodunnit).to eq(responsible)
422+
end
423+
424+
it "handles nil responsible_id" do
425+
Logidze.with_responsible!(nil)
426+
427+
expect(subject.reload.whodunnit).to be_nil
428+
end
429+
430+
it "can be cleared with clear_meta!" do
431+
Logidze.with_responsible!(responsible.id)
432+
433+
expect(subject.reload.whodunnit).to eq(responsible)
434+
435+
Logidze.clear_meta!
436+
437+
subject.update!(age: 12)
438+
expect(subject.reload.whodunnit).to be_nil
439+
end
440+
441+
it "can be changed to a different responsible" do
442+
responsible2 = User.create!(name: "owner2")
443+
444+
Logidze.with_responsible!(responsible.id)
445+
expect(subject.reload.whodunnit).to eq(responsible)
446+
447+
Logidze.with_responsible!(responsible2.id)
448+
subject.update!(age: 11)
449+
expect(subject.reload.whodunnit).to eq(responsible2)
450+
end
451+
452+
it "can add to existing metadata" do
453+
Logidze.with_meta!(meta)
454+
455+
Logidze.with_responsible!(responsible.id)
456+
expect(subject.reload.whodunnit).to eq(responsible)
457+
expect(subject.meta).to eq(meta.merge(
458+
Logidze::History::Version::META_RESPONSIBLE => responsible.id
459+
))
460+
end
461+
end
462+
end
379463
end

0 commit comments

Comments
 (0)