paper_trail 5.1.1 → 5.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/CONTRIBUTING.md +9 -7
- data/.rubocop.yml +0 -4
- data/.rubocop_todo.yml +6 -1
- data/CHANGELOG.md +41 -1
- data/README.md +45 -43
- data/lib/paper_trail.rb +5 -0
- data/lib/paper_trail/attribute_serializers/object_attribute.rb +1 -1
- data/lib/paper_trail/attribute_serializers/object_changes_attribute.rb +1 -1
- data/lib/paper_trail/frameworks/rails/controller.rb +7 -2
- data/lib/paper_trail/has_paper_trail.rb +196 -495
- data/lib/paper_trail/model_config.rb +195 -0
- data/lib/paper_trail/record_trail.rb +450 -0
- data/lib/paper_trail/reifier.rb +11 -11
- data/lib/paper_trail/version_number.rb +2 -2
- data/spec/models/boolit_spec.rb +2 -2
- data/spec/models/fluxor_spec.rb +4 -6
- data/spec/models/gadget_spec.rb +5 -7
- data/spec/models/not_on_update_spec.rb +2 -2
- data/spec/models/post_with_status_spec.rb +1 -1
- data/spec/models/widget_spec.rb +36 -66
- data/test/dummy/app/models/callback_modifier.rb +5 -5
- data/test/dummy/app/models/elephant.rb +1 -1
- data/test/functional/thread_safety_test.rb +4 -4
- data/test/unit/cleaner_test.rb +1 -1
- data/test/unit/model_test.rb +58 -48
- data/test/unit/protected_attrs_test.rb +4 -3
- data/test/unit/serializer_test.rb +4 -3
- metadata +4 -2
data/lib/paper_trail/reifier.rb
CHANGED
@@ -65,7 +65,7 @@ module PaperTrail
|
|
65
65
|
# @api private
|
66
66
|
def each_enabled_association(associations)
|
67
67
|
associations.each do |assoc|
|
68
|
-
next unless assoc.klass.
|
68
|
+
next unless assoc.klass.paper_trail.enabled?
|
69
69
|
yield assoc
|
70
70
|
end
|
71
71
|
end
|
@@ -112,7 +112,7 @@ module PaperTrail
|
|
112
112
|
collection_keys = through_collection.map { |through_model|
|
113
113
|
through_model.send(assoc.source_reflection.foreign_key)
|
114
114
|
}
|
115
|
-
version_id_subquery = assoc.klass.
|
115
|
+
version_id_subquery = assoc.klass.paper_trail.version_class.
|
116
116
|
select("MIN(id)").
|
117
117
|
where("item_type = ?", assoc.class_name).
|
118
118
|
where("item_id IN (?)", collection_keys).
|
@@ -140,7 +140,7 @@ module PaperTrail
|
|
140
140
|
# from the point in time identified by `transaction_id` or `version_at`.
|
141
141
|
# @api private
|
142
142
|
def load_version_for_habtm(assoc, id, transaction_id, version_at)
|
143
|
-
assoc.klass.
|
143
|
+
assoc.klass.paper_trail.version_class.
|
144
144
|
where("item_type = ?", assoc.klass.name).
|
145
145
|
where("item_id = ?", id).
|
146
146
|
where("created_at >= ? OR transaction_id = ?", version_at, transaction_id).
|
@@ -153,8 +153,8 @@ module PaperTrail
|
|
153
153
|
# record from the point in time identified by `transaction_id` or `version_at`.
|
154
154
|
# @api private
|
155
155
|
def load_version_for_has_one(assoc, model, transaction_id, version_at)
|
156
|
-
version_table_name = model.class.
|
157
|
-
model.class.
|
156
|
+
version_table_name = model.class.paper_trail.version_class.table_name
|
157
|
+
model.class.paper_trail.version_class.joins(:version_associations).
|
158
158
|
where("version_associations.foreign_key_name = ?", assoc.foreign_key).
|
159
159
|
where("version_associations.foreign_key_id = ?", model.id).
|
160
160
|
where("#{version_table_name}.item_type = ?", assoc.class_name).
|
@@ -263,7 +263,7 @@ module PaperTrail
|
|
263
263
|
if options[:mark_for_destruction]
|
264
264
|
model.send(assoc.name).mark_for_destruction if model.send(assoc.name, true)
|
265
265
|
else
|
266
|
-
model.appear_as_new_record do
|
266
|
+
model.paper_trail.appear_as_new_record do
|
267
267
|
model.send "#{assoc.name}=", nil
|
268
268
|
end
|
269
269
|
end
|
@@ -276,7 +276,7 @@ module PaperTrail
|
|
276
276
|
has_and_belongs_to_many: false
|
277
277
|
)
|
278
278
|
)
|
279
|
-
model.appear_as_new_record do
|
279
|
+
model.paper_trail.appear_as_new_record do
|
280
280
|
without_persisting(child) do
|
281
281
|
model.send "#{assoc.name}=", child
|
282
282
|
end
|
@@ -289,7 +289,7 @@ module PaperTrail
|
|
289
289
|
associations = model.class.reflect_on_all_associations(:belongs_to)
|
290
290
|
each_enabled_association(associations) do |assoc|
|
291
291
|
collection_key = model.send(assoc.association_foreign_key)
|
292
|
-
version = assoc.klass.
|
292
|
+
version = assoc.klass.paper_trail.version_class.
|
293
293
|
where("item_type = ?", assoc.class_name).
|
294
294
|
where("item_id = ?", collection_key).
|
295
295
|
where("created_at >= ? OR transaction_id = ?", options[:version_at], transaction_id).
|
@@ -326,7 +326,7 @@ module PaperTrail
|
|
326
326
|
# Restore the `model`'s has_many associations not associated through
|
327
327
|
# another association.
|
328
328
|
def reify_has_many_directly(transaction_id, associations, model, options = {})
|
329
|
-
version_table_name = model.class.
|
329
|
+
version_table_name = model.class.paper_trail.version_class.table_name
|
330
330
|
each_enabled_association(associations) do |assoc|
|
331
331
|
version_id_subquery = PaperTrail::VersionAssociation.
|
332
332
|
joins(model.class.version_association_name).
|
@@ -366,7 +366,7 @@ module PaperTrail
|
|
366
366
|
|
367
367
|
def reify_has_and_belongs_to_many(transaction_id, model, options = {})
|
368
368
|
model.class.reflect_on_all_associations(:has_and_belongs_to_many).each do |assoc|
|
369
|
-
papertrail_enabled = assoc.klass.
|
369
|
+
papertrail_enabled = assoc.klass.paper_trail.enabled?
|
370
370
|
next unless
|
371
371
|
model.class.paper_trail_save_join_tables.include?(assoc.name) ||
|
372
372
|
papertrail_enabled
|
@@ -429,7 +429,7 @@ module PaperTrail
|
|
429
429
|
#
|
430
430
|
def versions_by_id(klass, version_id_subquery)
|
431
431
|
klass.
|
432
|
-
|
432
|
+
paper_trail.version_class.
|
433
433
|
where("id IN (#{version_id_subquery})").
|
434
434
|
inject({}) { |a, e| a.merge!(e.item_id => e) }
|
435
435
|
end
|
data/spec/models/boolit_spec.rb
CHANGED
@@ -28,7 +28,7 @@ describe Boolit, type: :model do
|
|
28
28
|
end
|
29
29
|
|
30
30
|
it "should still be able to be reified and persisted" do
|
31
|
-
expect { subject.previous_version.save! }.to_not raise_error
|
31
|
+
expect { subject.paper_trail.previous_version.save! }.to_not raise_error
|
32
32
|
end
|
33
33
|
|
34
34
|
context "with `nil` attributes on the live instance" do
|
@@ -40,7 +40,7 @@ describe Boolit, type: :model do
|
|
40
40
|
after { PaperTrail.serializer = PaperTrail::Serializers::YAML }
|
41
41
|
|
42
42
|
it "should not overwrite that attribute during the reification process" do
|
43
|
-
expect(subject.previous_version.name).to be_nil
|
43
|
+
expect(subject.paper_trail.previous_version.name).to be_nil
|
44
44
|
end
|
45
45
|
end
|
46
46
|
end
|
data/spec/models/fluxor_spec.rb
CHANGED
@@ -7,12 +7,10 @@ describe Fluxor, type: :model do
|
|
7
7
|
|
8
8
|
describe "Methods" do
|
9
9
|
describe "Class" do
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
it { expect(subject.paper_trail_enabled_for_model?).to be false }
|
10
|
+
describe ".paper_trail.enabled?" do
|
11
|
+
it "returns false" do
|
12
|
+
expect(Fluxor.paper_trail.enabled?).to eq(false)
|
13
|
+
end
|
16
14
|
end
|
17
15
|
end
|
18
16
|
end
|
data/spec/models/gadget_spec.rb
CHANGED
@@ -28,10 +28,8 @@ describe Gadget, type: :model do
|
|
28
28
|
describe '#changed_notably?' do
|
29
29
|
subject { Gadget.new(created_at: Time.now) }
|
30
30
|
|
31
|
-
it { expect(subject.private_methods).to include(:changed_notably?) }
|
32
|
-
|
33
31
|
context "create events" do
|
34
|
-
it { expect(subject.
|
32
|
+
it { expect(subject.paper_trail.changed_notably?).to be true }
|
35
33
|
end
|
36
34
|
|
37
35
|
context "update events" do
|
@@ -40,12 +38,12 @@ describe Gadget, type: :model do
|
|
40
38
|
context "without update timestamps" do
|
41
39
|
it "should only acknowledge non-ignored attrs" do
|
42
40
|
subject.name = "Wrench"
|
43
|
-
expect(subject.
|
41
|
+
expect(subject.paper_trail.changed_notably?).to be true
|
44
42
|
end
|
45
43
|
|
46
44
|
it "should not acknowledge ignored attr (brand)" do
|
47
45
|
subject.brand = "Acme"
|
48
|
-
expect(subject.
|
46
|
+
expect(subject.paper_trail.changed_notably?).to be false
|
49
47
|
end
|
50
48
|
end
|
51
49
|
|
@@ -53,13 +51,13 @@ describe Gadget, type: :model do
|
|
53
51
|
it "should only acknowledge non-ignored attrs" do
|
54
52
|
subject.name = "Wrench"
|
55
53
|
subject.updated_at = Time.now
|
56
|
-
expect(subject.
|
54
|
+
expect(subject.paper_trail.changed_notably?).to be true
|
57
55
|
end
|
58
56
|
|
59
57
|
it "should not acknowledge ignored attrs and timestamps only" do
|
60
58
|
subject.brand = "Acme"
|
61
59
|
subject.updated_at = Time.now
|
62
|
-
expect(subject.
|
60
|
+
expect(subject.paper_trail.changed_notably?).to be false
|
63
61
|
end
|
64
62
|
end
|
65
63
|
end
|
@@ -5,7 +5,7 @@ describe NotOnUpdate, type: :model do
|
|
5
5
|
let!(:record) { described_class.create! }
|
6
6
|
|
7
7
|
it "should create a version, regardless" do
|
8
|
-
expect { record.touch_with_version }.to change {
|
8
|
+
expect { record.paper_trail.touch_with_version }.to change {
|
9
9
|
PaperTrail::Version.count
|
10
10
|
}.by(+1)
|
11
11
|
end
|
@@ -14,7 +14,7 @@ describe NotOnUpdate, type: :model do
|
|
14
14
|
before = record.updated_at
|
15
15
|
# Travel 1 second because MySQL lacks sub-second resolution
|
16
16
|
Timecop.travel(1) do
|
17
|
-
record.touch_with_version
|
17
|
+
record.paper_trail.touch_with_version
|
18
18
|
end
|
19
19
|
expect(record.updated_at).to be > before
|
20
20
|
end
|
@@ -10,7 +10,7 @@ describe PostWithStatus, type: :model do
|
|
10
10
|
it "should stash the enum value properly in versions" do
|
11
11
|
post.published!
|
12
12
|
post.archived!
|
13
|
-
expect(post.previous_version.published?).to be true
|
13
|
+
expect(post.paper_trail.previous_version.published?).to be true
|
14
14
|
end
|
15
15
|
|
16
16
|
context "storing enum object_changes" do
|
data/spec/models/widget_spec.rb
CHANGED
@@ -64,11 +64,11 @@ describe Widget, type: :model do
|
|
64
64
|
|
65
65
|
subject { widget.versions.last.reify }
|
66
66
|
|
67
|
-
it { expect(subject).not_to be_live }
|
67
|
+
it { expect(subject.paper_trail).not_to be_live }
|
68
68
|
|
69
69
|
it "should clear the `versions_association_name` virtual attribute" do
|
70
70
|
subject.save!
|
71
|
-
expect(subject).to be_live
|
71
|
+
expect(subject.paper_trail).to be_live
|
72
72
|
end
|
73
73
|
|
74
74
|
it "corresponding version should use the widget updated_at" do
|
@@ -140,21 +140,21 @@ describe Widget, type: :model do
|
|
140
140
|
|
141
141
|
describe "Methods" do
|
142
142
|
describe "Instance", versioning: true do
|
143
|
-
describe '#
|
144
|
-
it { is_expected.to respond_to(:paper_trail_originator) }
|
145
|
-
|
143
|
+
describe '#paper_trail.originator' do
|
146
144
|
describe "return value" do
|
147
145
|
let(:orig_name) { FFaker::Name.name }
|
148
146
|
let(:new_name) { FFaker::Name.name }
|
149
147
|
before { PaperTrail.whodunnit = orig_name }
|
150
148
|
|
151
149
|
context "accessed from live model instance" do
|
152
|
-
specify { expect(widget).to be_live }
|
150
|
+
specify { expect(widget.paper_trail).to be_live }
|
153
151
|
|
154
152
|
it "should return the originator for the model at a given state" do
|
155
|
-
expect(widget.
|
156
|
-
widget.whodunnit(new_name) { |w|
|
157
|
-
|
153
|
+
expect(widget.paper_trail.originator).to eq(orig_name)
|
154
|
+
widget.paper_trail.whodunnit(new_name) { |w|
|
155
|
+
w.update_attributes(name: "Elizabeth")
|
156
|
+
}
|
157
|
+
expect(widget.paper_trail.originator).to eq(new_name)
|
158
158
|
end
|
159
159
|
end
|
160
160
|
|
@@ -169,7 +169,7 @@ describe Widget, type: :model do
|
|
169
169
|
let(:reified_widget) { widget.versions[1].reify }
|
170
170
|
|
171
171
|
it "should return the appropriate originator" do
|
172
|
-
expect(reified_widget.
|
172
|
+
expect(reified_widget.paper_trail.originator).to eq(orig_name)
|
173
173
|
end
|
174
174
|
|
175
175
|
it "should not create a new model instance" do
|
@@ -181,7 +181,7 @@ describe Widget, type: :model do
|
|
181
181
|
let(:reified_widget) { widget.versions[1].reify(dup: true) }
|
182
182
|
|
183
183
|
it "should return the appropriate originator" do
|
184
|
-
expect(reified_widget.
|
184
|
+
expect(reified_widget.paper_trail.originator).to eq(orig_name)
|
185
185
|
end
|
186
186
|
|
187
187
|
it "should not create a new model instance" do
|
@@ -192,35 +192,12 @@ describe Widget, type: :model do
|
|
192
192
|
end
|
193
193
|
end
|
194
194
|
|
195
|
-
describe "#originator" do
|
196
|
-
subject { widget }
|
197
|
-
|
198
|
-
it { is_expected.to respond_to(:originator) }
|
199
|
-
|
200
|
-
it "should set the invoke `paper_trail_originator`" do
|
201
|
-
allow(::ActiveSupport::Deprecation).to receive(:warn)
|
202
|
-
is_expected.to receive(:paper_trail_originator)
|
203
|
-
subject.originator
|
204
|
-
end
|
205
|
-
|
206
|
-
it "should display a deprecation warning" do
|
207
|
-
expect(::ActiveSupport::Deprecation).to receive(:warn).
|
208
|
-
with(/Use paper_trail_originator instead of originator/)
|
209
|
-
subject.originator
|
210
|
-
end
|
211
|
-
end
|
212
|
-
|
213
195
|
describe '#version_at' do
|
214
|
-
it { is_expected.to respond_to(:version_at) }
|
215
|
-
|
216
196
|
context "Timestamp argument is AFTER object has been destroyed" do
|
217
|
-
|
197
|
+
it "should return `nil`" do
|
218
198
|
widget.update_attribute(:name, "foobar")
|
219
199
|
widget.destroy
|
220
|
-
|
221
|
-
|
222
|
-
it "should return `nil`" do
|
223
|
-
expect(widget.version_at(Time.now)).to be_nil
|
200
|
+
expect(widget.paper_trail.version_at(Time.now)).to be_nil
|
224
201
|
end
|
225
202
|
end
|
226
203
|
end
|
@@ -231,7 +208,7 @@ describe Widget, type: :model do
|
|
231
208
|
context "no block given" do
|
232
209
|
it "should raise an error" do
|
233
210
|
expect {
|
234
|
-
widget.whodunnit("Ben")
|
211
|
+
widget.paper_trail.whodunnit("Ben")
|
235
212
|
}.to raise_error(ArgumentError, "expected to receive a block")
|
236
213
|
end
|
237
214
|
end
|
@@ -246,7 +223,7 @@ describe Widget, type: :model do
|
|
246
223
|
end
|
247
224
|
|
248
225
|
it "should modify value of `PaperTrail.whodunnit` while executing the block" do
|
249
|
-
widget.whodunnit(new_name) do
|
226
|
+
widget.paper_trail.whodunnit(new_name) do
|
250
227
|
expect(PaperTrail.whodunnit).to eq(new_name)
|
251
228
|
widget.update_attributes(name: "Elizabeth")
|
252
229
|
end
|
@@ -255,7 +232,9 @@ describe Widget, type: :model do
|
|
255
232
|
|
256
233
|
context "after executing the block" do
|
257
234
|
it "reverts value of whodunnit to previous value" do
|
258
|
-
widget.whodunnit(new_name) { |w|
|
235
|
+
widget.paper_trail.whodunnit(new_name) { |w|
|
236
|
+
w.update_attributes(name: "Elizabeth")
|
237
|
+
}
|
259
238
|
expect(PaperTrail.whodunnit).to eq(orig_name)
|
260
239
|
end
|
261
240
|
end
|
@@ -263,7 +242,7 @@ describe Widget, type: :model do
|
|
263
242
|
context "error within block" do
|
264
243
|
it "still reverts the whodunnit value to previous value" do
|
265
244
|
expect {
|
266
|
-
widget.whodunnit(new_name) { raise }
|
245
|
+
widget.paper_trail.whodunnit(new_name) { raise }
|
267
246
|
}.to raise_error(RuntimeError)
|
268
247
|
expect(PaperTrail.whodunnit).to eq(orig_name)
|
269
248
|
end
|
@@ -272,13 +251,11 @@ describe Widget, type: :model do
|
|
272
251
|
end
|
273
252
|
|
274
253
|
describe '#touch_with_version' do
|
275
|
-
it { is_expected.to respond_to(:touch_with_version) }
|
276
|
-
|
277
254
|
it "creates a version" do
|
278
255
|
count = widget.versions.size
|
279
256
|
# Travel 1 second because MySQL lacks sub-second resolution
|
280
257
|
Timecop.travel(1) do
|
281
|
-
widget.touch_with_version
|
258
|
+
widget.paper_trail.touch_with_version
|
282
259
|
end
|
283
260
|
expect(widget.versions.size).to eq(count + 1)
|
284
261
|
end
|
@@ -287,7 +264,7 @@ describe Widget, type: :model do
|
|
287
264
|
time_was = widget.updated_at
|
288
265
|
# Travel 1 second because MySQL lacks sub-second resolution
|
289
266
|
Timecop.travel(1) do
|
290
|
-
widget.touch_with_version
|
267
|
+
widget.paper_trail.touch_with_version
|
291
268
|
end
|
292
269
|
expect(widget.updated_at).to be > time_was
|
293
270
|
end
|
@@ -295,33 +272,26 @@ describe Widget, type: :model do
|
|
295
272
|
end
|
296
273
|
|
297
274
|
describe "Class" do
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
it { expect(subject.paper_trail_enabled_for_model?).to be true }
|
275
|
+
describe ".paper_trail.enabled?" do
|
276
|
+
it "returns true" do
|
277
|
+
expect(Widget.paper_trail.enabled?).to eq(true)
|
278
|
+
end
|
304
279
|
end
|
305
280
|
|
306
|
-
describe
|
307
|
-
it
|
308
|
-
|
309
|
-
|
310
|
-
expect(
|
311
|
-
subject.paper_trail_off!
|
312
|
-
expect(subject.paper_trail_enabled_for_model?).to be false
|
281
|
+
describe ".disable" do
|
282
|
+
it "should set the `paper_trail.enabled?` to `false`" do
|
283
|
+
expect(Widget.paper_trail.enabled?).to eq(true)
|
284
|
+
Widget.paper_trail.disable
|
285
|
+
expect(Widget.paper_trail.enabled?).to eq(false)
|
313
286
|
end
|
314
287
|
end
|
315
288
|
|
316
|
-
describe
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
expect(subject.paper_trail_enabled_for_model?).to be false
|
323
|
-
subject.paper_trail_on!
|
324
|
-
expect(subject.paper_trail_enabled_for_model?).to be true
|
289
|
+
describe ".enable" do
|
290
|
+
it "should set the `paper_trail.enabled?` to `true`" do
|
291
|
+
Widget.paper_trail.disable
|
292
|
+
expect(Widget.paper_trail.enabled?).to eq(false)
|
293
|
+
Widget.paper_trail.enable
|
294
|
+
expect(Widget.paper_trail.enabled?).to eq(true)
|
325
295
|
end
|
326
296
|
end
|
327
297
|
end
|
@@ -17,27 +17,27 @@ end
|
|
17
17
|
|
18
18
|
class BeforeDestroyModifier < CallbackModifier
|
19
19
|
has_paper_trail on: []
|
20
|
-
|
20
|
+
paper_trail.on_destroy :before
|
21
21
|
end
|
22
22
|
|
23
23
|
class AfterDestroyModifier < CallbackModifier
|
24
24
|
has_paper_trail on: []
|
25
|
-
|
25
|
+
paper_trail.on_destroy :after
|
26
26
|
end
|
27
27
|
|
28
28
|
class NoArgDestroyModifier < CallbackModifier
|
29
29
|
has_paper_trail on: []
|
30
|
-
|
30
|
+
paper_trail.on_destroy
|
31
31
|
end
|
32
32
|
|
33
33
|
class UpdateModifier < CallbackModifier
|
34
34
|
has_paper_trail on: []
|
35
|
-
|
35
|
+
paper_trail.on_update
|
36
36
|
end
|
37
37
|
|
38
38
|
class CreateModifier < CallbackModifier
|
39
39
|
has_paper_trail on: []
|
40
|
-
|
40
|
+
paper_trail.on_create
|
41
41
|
end
|
42
42
|
|
43
43
|
class DefaultModifier < CallbackModifier
|