paper_trail 5.2.3 → 6.0.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 +12 -20
- data/.rubocop.yml +8 -0
- data/.rubocop_todo.yml +0 -8
- data/.travis.yml +7 -5
- data/Appraisals +8 -17
- data/CHANGELOG.md +46 -0
- data/README.md +47 -26
- data/doc/triage.md +27 -0
- data/gemfiles/{ar4.gemfile → ar_4.2.gemfile} +0 -0
- data/gemfiles/{ar5.gemfile → ar_5.0.gemfile} +0 -0
- data/gemfiles/ar_master.gemfile +9 -0
- data/lib/paper_trail/cleaner.rb +1 -1
- data/lib/paper_trail/config.rb +1 -15
- data/lib/paper_trail/frameworks/rails/controller.rb +1 -1
- data/lib/paper_trail/frameworks/rspec.rb +5 -0
- data/lib/paper_trail/has_paper_trail.rb +1 -292
- data/lib/paper_trail/model_config.rb +17 -7
- data/lib/paper_trail/record_history.rb +1 -9
- data/lib/paper_trail/record_trail.rb +148 -56
- data/lib/paper_trail/version_concern.rb +7 -7
- data/lib/paper_trail/version_number.rb +3 -3
- data/lib/paper_trail.rb +5 -14
- data/paper_trail.gemspec +2 -1
- data/spec/models/widget_spec.rb +16 -3
- data/test/dummy/app/models/song.rb +27 -22
- data/test/test_helper.rb +21 -23
- data/test/unit/associations_test.rb +1 -1
- data/test/unit/cleaner_test.rb +0 -37
- data/test/unit/inheritance_column_test.rb +2 -4
- data/test/unit/model_test.rb +56 -29
- data/test/unit/serializer_test.rb +11 -12
- data/test/unit/version_test.rb +0 -7
- metadata +10 -13
- data/gemfiles/ar3.gemfile +0 -19
- data/spec/models/fluxor_spec.rb +0 -17
- data/test/unit/timestamp_test.rb +0 -41
@@ -103,7 +103,7 @@ module PaperTrail
|
|
103
103
|
user_for_paper_trail is present, but whodunnit has not been set.
|
104
104
|
PaperTrail no longer adds the set_paper_trail_whodunnit callback for
|
105
105
|
you. To continue recording whodunnit, please add this before_action
|
106
|
-
callback to your ApplicationController
|
106
|
+
callback to your ApplicationController. For more information,
|
107
107
|
please see https://git.io/vrTsk
|
108
108
|
EOS
|
109
109
|
end
|
@@ -27,3 +27,8 @@ RSpec::Matchers.define :have_a_version_with do |attributes|
|
|
27
27
|
# check if the model has a version with the specified attributes
|
28
28
|
match { |actual| actual.versions.where_object(attributes).any? }
|
29
29
|
end
|
30
|
+
|
31
|
+
RSpec::Matchers.define :have_a_version_with_changes do |attributes|
|
32
|
+
# check if the model has a version changes with the specified attributes
|
33
|
+
match { |actual| actual.versions.where_object_changes(attributes).any? }
|
34
|
+
end
|
@@ -69,306 +69,15 @@ module PaperTrail
|
|
69
69
|
def paper_trail
|
70
70
|
::PaperTrail::ModelConfig.new(self)
|
71
71
|
end
|
72
|
-
|
73
|
-
# @api private
|
74
|
-
def paper_trail_deprecate(new_method, old_method = nil)
|
75
|
-
old = old_method.nil? ? new_method : old_method
|
76
|
-
msg = format("Use paper_trail.%s instead of %s", new_method, old)
|
77
|
-
::ActiveSupport::Deprecation.warn(msg, caller(2))
|
78
|
-
end
|
79
|
-
|
80
|
-
# @deprecated
|
81
|
-
def paper_trail_on_destroy(*args)
|
82
|
-
paper_trail_deprecate "on_destroy", "paper_trail_on_destroy"
|
83
|
-
paper_trail.on_destroy(*args)
|
84
|
-
end
|
85
|
-
|
86
|
-
# @deprecated
|
87
|
-
def paper_trail_on_update
|
88
|
-
paper_trail_deprecate "on_update", "paper_trail_on_update"
|
89
|
-
paper_trail.on_update
|
90
|
-
end
|
91
|
-
|
92
|
-
# @deprecated
|
93
|
-
def paper_trail_on_create
|
94
|
-
paper_trail_deprecate "on_create", "paper_trail_on_create"
|
95
|
-
paper_trail.on_create
|
96
|
-
end
|
97
|
-
|
98
|
-
# @deprecated
|
99
|
-
def paper_trail_off!
|
100
|
-
paper_trail_deprecate "disable", "paper_trail_off!"
|
101
|
-
paper_trail.disable
|
102
|
-
end
|
103
|
-
|
104
|
-
# @deprecated
|
105
|
-
def paper_trail_on!
|
106
|
-
paper_trail_deprecate "enable", "paper_trail_on!"
|
107
|
-
paper_trail.enable
|
108
|
-
end
|
109
|
-
|
110
|
-
# @deprecated
|
111
|
-
def paper_trail_enabled_for_model?
|
112
|
-
paper_trail_deprecate "enabled?", "paper_trail_enabled_for_model?"
|
113
|
-
paper_trail.enabled?
|
114
|
-
end
|
115
|
-
|
116
|
-
# @deprecated
|
117
|
-
def paper_trail_version_class
|
118
|
-
paper_trail_deprecate "version_class", "paper_trail_version_class"
|
119
|
-
paper_trail.version_class
|
120
|
-
end
|
121
72
|
end
|
122
73
|
|
123
74
|
# Wrap the following methods in a module so we can include them only in the
|
124
75
|
# ActiveRecord models that declare `has_paper_trail`.
|
125
76
|
module InstanceMethods
|
77
|
+
# @api public
|
126
78
|
def paper_trail
|
127
79
|
::PaperTrail::RecordTrail.new(self)
|
128
80
|
end
|
129
|
-
|
130
|
-
# @deprecated
|
131
|
-
def live?
|
132
|
-
self.class.paper_trail_deprecate "live?"
|
133
|
-
paper_trail.live?
|
134
|
-
end
|
135
|
-
|
136
|
-
# @deprecated
|
137
|
-
def paper_trail_originator
|
138
|
-
self.class.paper_trail_deprecate "originator", "paper_trail_originator"
|
139
|
-
paper_trail.originator
|
140
|
-
end
|
141
|
-
|
142
|
-
# @deprecated
|
143
|
-
def originator
|
144
|
-
self.class.paper_trail_deprecate "originator"
|
145
|
-
paper_trail.originator
|
146
|
-
end
|
147
|
-
|
148
|
-
# @deprecated
|
149
|
-
def clear_rolled_back_versions
|
150
|
-
self.class.paper_trail_deprecate "clear_rolled_back_versions"
|
151
|
-
paper_trail.clear_rolled_back_versions
|
152
|
-
end
|
153
|
-
|
154
|
-
# @deprecated
|
155
|
-
def source_version
|
156
|
-
self.class.paper_trail_deprecate "source_version"
|
157
|
-
paper_trail.source_version
|
158
|
-
end
|
159
|
-
|
160
|
-
# @deprecated
|
161
|
-
def version_at(*args)
|
162
|
-
self.class.paper_trail_deprecate "version_at"
|
163
|
-
paper_trail.version_at(*args)
|
164
|
-
end
|
165
|
-
|
166
|
-
# @deprecated
|
167
|
-
def versions_between(start_time, end_time, _reify_options = {})
|
168
|
-
self.class.paper_trail_deprecate "versions_between"
|
169
|
-
paper_trail.versions_between(start_time, end_time)
|
170
|
-
end
|
171
|
-
|
172
|
-
# @deprecated
|
173
|
-
def previous_version
|
174
|
-
self.class.paper_trail_deprecate "previous_version"
|
175
|
-
paper_trail.previous_version
|
176
|
-
end
|
177
|
-
|
178
|
-
# @deprecated
|
179
|
-
def next_version
|
180
|
-
self.class.paper_trail_deprecate "next_version"
|
181
|
-
paper_trail.next_version
|
182
|
-
end
|
183
|
-
|
184
|
-
# @deprecated
|
185
|
-
def paper_trail_enabled_for_model?
|
186
|
-
self.class.paper_trail_deprecate "enabled_for_model?", "paper_trail_enabled_for_model?"
|
187
|
-
paper_trail.enabled_for_model?
|
188
|
-
end
|
189
|
-
|
190
|
-
# @deprecated
|
191
|
-
def without_versioning(method = nil, &block)
|
192
|
-
self.class.paper_trail_deprecate "without_versioning"
|
193
|
-
paper_trail.without_versioning(method, &block)
|
194
|
-
end
|
195
|
-
|
196
|
-
# @deprecated
|
197
|
-
def appear_as_new_record(&block)
|
198
|
-
self.class.paper_trail_deprecate "appear_as_new_record"
|
199
|
-
paper_trail.appear_as_new_record(&block)
|
200
|
-
end
|
201
|
-
|
202
|
-
# @deprecated
|
203
|
-
def whodunnit(value, &block)
|
204
|
-
self.class.paper_trail_deprecate "whodunnit"
|
205
|
-
paper_trail.whodunnit(value, &block)
|
206
|
-
end
|
207
|
-
|
208
|
-
# @deprecated
|
209
|
-
def touch_with_version(name = nil)
|
210
|
-
self.class.paper_trail_deprecate "touch_with_version"
|
211
|
-
paper_trail.touch_with_version(name)
|
212
|
-
end
|
213
|
-
|
214
|
-
# `record_create` is deprecated in favor of `paper_trail.record_create`,
|
215
|
-
# but does not yet print a deprecation warning. When the `after_create`
|
216
|
-
# callback is registered (by ModelConfig#on_create) we still refer to this
|
217
|
-
# method by name, e.g.
|
218
|
-
#
|
219
|
-
# @model_class.after_create :record_create, if: :save_version?
|
220
|
-
#
|
221
|
-
# instead of using the preferred method `paper_trail.record_create`, e.g.
|
222
|
-
#
|
223
|
-
# @model_class.after_create { |r| r.paper_trail.record_create if r.save_version?}
|
224
|
-
#
|
225
|
-
# We still register the callback by name so that, if someone calls
|
226
|
-
# `has_paper_trail` twice, the callback will *not* be registered twice.
|
227
|
-
# Our own test suite calls `has_paper_trail` many times for the same
|
228
|
-
# class.
|
229
|
-
#
|
230
|
-
# In the future, perhaps we should require that users only set up
|
231
|
-
# PT once per class.
|
232
|
-
#
|
233
|
-
# @deprecated
|
234
|
-
def record_create
|
235
|
-
paper_trail.record_create
|
236
|
-
end
|
237
|
-
|
238
|
-
# See deprecation comment for `record_create`
|
239
|
-
# @deprecated
|
240
|
-
def record_update(force = nil)
|
241
|
-
paper_trail.record_update(force)
|
242
|
-
end
|
243
|
-
|
244
|
-
# @deprecated
|
245
|
-
def pt_record_object_changes?
|
246
|
-
self.class.paper_trail_deprecate "record_object_changes?", "pt_record_object_changes?"
|
247
|
-
paper_trail.record_object_changes?
|
248
|
-
end
|
249
|
-
|
250
|
-
# @deprecated
|
251
|
-
def pt_recordable_object
|
252
|
-
self.class.paper_trail_deprecate "recordable_object", "pt_recordable_object"
|
253
|
-
paper_trail.recordable_object
|
254
|
-
end
|
255
|
-
|
256
|
-
# @deprecated
|
257
|
-
def pt_recordable_object_changes
|
258
|
-
self.class.paper_trail_deprecate "recordable_object_changes", "pt_recordable_object_changes"
|
259
|
-
paper_trail.recordable_object_changes
|
260
|
-
end
|
261
|
-
|
262
|
-
# @deprecated
|
263
|
-
def changes_for_paper_trail
|
264
|
-
self.class.paper_trail_deprecate "changes", "changes_for_paper_trail"
|
265
|
-
paper_trail.changes
|
266
|
-
end
|
267
|
-
|
268
|
-
# See deprecation comment for `record_create`
|
269
|
-
# @deprecated
|
270
|
-
def clear_version_instance!
|
271
|
-
paper_trail.clear_version_instance
|
272
|
-
end
|
273
|
-
|
274
|
-
# See deprecation comment for `record_create`
|
275
|
-
# @deprecated
|
276
|
-
def reset_timestamp_attrs_for_update_if_needed!
|
277
|
-
paper_trail.reset_timestamp_attrs_for_update_if_needed
|
278
|
-
end
|
279
|
-
|
280
|
-
# See deprecation comment for `record_create`
|
281
|
-
# @deprecated
|
282
|
-
def record_destroy
|
283
|
-
paper_trail.record_destroy
|
284
|
-
end
|
285
|
-
|
286
|
-
# @deprecated
|
287
|
-
def save_associations(version)
|
288
|
-
self.class.paper_trail_deprecate "save_associations"
|
289
|
-
paper_trail.save_associations(version)
|
290
|
-
end
|
291
|
-
|
292
|
-
# @deprecated
|
293
|
-
def save_associations_belongs_to(version)
|
294
|
-
self.class.paper_trail_deprecate "save_associations_belongs_to"
|
295
|
-
paper_trail.save_associations_belongs_to(version)
|
296
|
-
end
|
297
|
-
|
298
|
-
# @deprecated
|
299
|
-
def save_associations_has_and_belongs_to_many(version)
|
300
|
-
self.class.paper_trail_deprecate(
|
301
|
-
"save_associations_habtm",
|
302
|
-
"save_associations_has_and_belongs_to_many"
|
303
|
-
)
|
304
|
-
paper_trail.save_associations_habtm(version)
|
305
|
-
end
|
306
|
-
|
307
|
-
# @deprecated
|
308
|
-
# @api private
|
309
|
-
def reset_transaction_id
|
310
|
-
::ActiveSupport::Deprecation.warn(
|
311
|
-
"reset_transaction_id is deprecated, use PaperTrail.clear_transaction_id"
|
312
|
-
)
|
313
|
-
PaperTrail.clear_transaction_id
|
314
|
-
end
|
315
|
-
|
316
|
-
# @deprecated
|
317
|
-
# @api private
|
318
|
-
def merge_metadata(data)
|
319
|
-
self.class.paper_trail_deprecate "merge_metadata"
|
320
|
-
paper_trail.merge_metadata(data)
|
321
|
-
end
|
322
|
-
|
323
|
-
# @deprecated
|
324
|
-
def attributes_before_change
|
325
|
-
self.class.paper_trail_deprecate "attributes_before_change"
|
326
|
-
paper_trail.attributes_before_change
|
327
|
-
end
|
328
|
-
|
329
|
-
# @deprecated
|
330
|
-
def object_attrs_for_paper_trail
|
331
|
-
self.class.paper_trail_deprecate "object_attrs_for_paper_trail"
|
332
|
-
paper_trail.object_attrs_for_paper_trail
|
333
|
-
end
|
334
|
-
|
335
|
-
# @deprecated
|
336
|
-
def changed_notably?
|
337
|
-
self.class.paper_trail_deprecate "changed_notably?"
|
338
|
-
paper_trail.changed_notably?
|
339
|
-
end
|
340
|
-
|
341
|
-
# @deprecated
|
342
|
-
def ignored_attr_has_changed?
|
343
|
-
self.class.paper_trail_deprecate "ignored_attr_has_changed?"
|
344
|
-
paper_trail.ignored_attr_has_changed?
|
345
|
-
end
|
346
|
-
|
347
|
-
# @deprecated
|
348
|
-
def notably_changed
|
349
|
-
self.class.paper_trail_deprecate "notably_changed"
|
350
|
-
paper_trail.notably_changed
|
351
|
-
end
|
352
|
-
|
353
|
-
# @deprecated
|
354
|
-
def changed_and_not_ignored
|
355
|
-
self.class.paper_trail_deprecate "changed_and_not_ignored"
|
356
|
-
paper_trail.changed_and_not_ignored
|
357
|
-
end
|
358
|
-
|
359
|
-
# The new method is named "enabled?" for consistency.
|
360
|
-
# @deprecated
|
361
|
-
def paper_trail_switched_on?
|
362
|
-
self.class.paper_trail_deprecate "enabled?", "paper_trail_switched_on?"
|
363
|
-
paper_trail.enabled?
|
364
|
-
end
|
365
|
-
|
366
|
-
# @deprecated
|
367
|
-
# @api private
|
368
|
-
def save_version?
|
369
|
-
self.class.paper_trail_deprecate "save_version?"
|
370
|
-
paper_trail.save_version?
|
371
|
-
end
|
372
81
|
end
|
373
82
|
end
|
374
83
|
end
|
@@ -31,7 +31,9 @@ module PaperTrail
|
|
31
31
|
|
32
32
|
# Adds a callback that records a version after a "create" event.
|
33
33
|
def on_create
|
34
|
-
@model_class.after_create
|
34
|
+
@model_class.after_create { |r|
|
35
|
+
r.paper_trail.record_create if r.paper_trail.save_version?
|
36
|
+
}
|
35
37
|
return if @model_class.paper_trail_options[:on].include?(:create)
|
36
38
|
@model_class.paper_trail_options[:on] << :create
|
37
39
|
end
|
@@ -46,8 +48,10 @@ module PaperTrail
|
|
46
48
|
::ActiveSupport::Deprecation.warn(E_CANNOT_RECORD_AFTER_DESTROY)
|
47
49
|
end
|
48
50
|
|
49
|
-
@model_class.send
|
50
|
-
|
51
|
+
@model_class.send(
|
52
|
+
"#{recording_order}_destroy",
|
53
|
+
->(r) { r.paper_trail.record_destroy if r.paper_trail.save_version? }
|
54
|
+
)
|
51
55
|
|
52
56
|
return if @model_class.paper_trail_options[:on].include?(:destroy)
|
53
57
|
@model_class.paper_trail_options[:on] << :destroy
|
@@ -55,9 +59,15 @@ module PaperTrail
|
|
55
59
|
|
56
60
|
# Adds a callback that records a version after an "update" event.
|
57
61
|
def on_update
|
58
|
-
@model_class.before_save
|
59
|
-
|
60
|
-
|
62
|
+
@model_class.before_save(on: :update) { |r|
|
63
|
+
r.paper_trail.reset_timestamp_attrs_for_update_if_needed
|
64
|
+
}
|
65
|
+
@model_class.after_update { |r|
|
66
|
+
r.paper_trail.record_update(nil) if r.paper_trail.save_version?
|
67
|
+
}
|
68
|
+
@model_class.after_update { |r|
|
69
|
+
r.paper_trail.clear_version_instance
|
70
|
+
}
|
61
71
|
return if @model_class.paper_trail_options[:on].include?(:update)
|
62
72
|
@model_class.paper_trail_options[:on] << :update
|
63
73
|
end
|
@@ -133,7 +143,7 @@ module PaperTrail
|
|
133
143
|
end
|
134
144
|
|
135
145
|
# Adds callbacks to record changes to habtm associations such that on save
|
136
|
-
# the previous version of the association (if changed) can be
|
146
|
+
# the previous version of the association (if changed) can be reconstructed.
|
137
147
|
def setup_callbacks_for_habtm(join_tables)
|
138
148
|
@model_class.send :attr_accessor, :paper_trail_habtm
|
139
149
|
@model_class.class_attribute :paper_trail_save_join_tables
|
@@ -26,7 +26,7 @@ module PaperTrail
|
|
26
26
|
@versions.select(primary_key).order(primary_key.asc)
|
27
27
|
else
|
28
28
|
@versions.
|
29
|
-
select([
|
29
|
+
select([table[:created_at], primary_key]).
|
30
30
|
order(@version_class.timestamp_sort_order)
|
31
31
|
end
|
32
32
|
end
|
@@ -45,13 +45,5 @@ module PaperTrail
|
|
45
45
|
def table
|
46
46
|
@version_class.arel_table
|
47
47
|
end
|
48
|
-
|
49
|
-
# @return - Arel::Attribute - Attribute representing the timestamp column
|
50
|
-
# of the version table, usually named `created_at` (the rails convention)
|
51
|
-
# but not always.
|
52
|
-
# @api private
|
53
|
-
def timestamp
|
54
|
-
table[PaperTrail.timestamp_field]
|
55
|
-
end
|
56
48
|
end
|
57
49
|
end
|
@@ -1,26 +1,48 @@
|
|
1
1
|
module PaperTrail
|
2
2
|
# Represents the "paper trail" for a single record.
|
3
3
|
class RecordTrail
|
4
|
+
RAILS_GTE_5_1 = ::ActiveRecord::VERSION::MAJOR >= 5 && ::ActiveRecord::VERSION::MINOR >= 1
|
5
|
+
|
4
6
|
def initialize(record)
|
5
7
|
@record = record
|
8
|
+
@in_after_callback = false
|
6
9
|
end
|
7
10
|
|
8
11
|
# Utility method for reifying. Anything executed inside the block will
|
9
12
|
# appear like a new record.
|
13
|
+
#
|
14
|
+
# > .. as best as I can tell, the purpose of
|
15
|
+
# > appear_as_new_record was to attempt to prevent the callbacks in
|
16
|
+
# > AutosaveAssociation (which is the module responsible for persisting
|
17
|
+
# > foreign key changes earlier than most people want most of the time
|
18
|
+
# > because backwards compatibility or the maintainer hates himself or
|
19
|
+
# > something) from running. By also stubbing out persisted? we can
|
20
|
+
# > actually prevent those. A more stable option might be to use suppress
|
21
|
+
# > instead, similar to the other branch in reify_has_one.
|
22
|
+
# > -Sean Griffin (https://github.com/airblade/paper_trail/pull/899)
|
23
|
+
#
|
10
24
|
def appear_as_new_record
|
11
25
|
@record.instance_eval {
|
12
26
|
alias :old_new_record? :new_record?
|
13
27
|
alias :new_record? :present?
|
28
|
+
alias :old_persisted? :persisted?
|
29
|
+
alias :persisted? :nil?
|
14
30
|
}
|
15
31
|
yield
|
16
|
-
@record.instance_eval {
|
32
|
+
@record.instance_eval {
|
33
|
+
alias :new_record? :old_new_record?
|
34
|
+
alias :persisted? :old_persisted?
|
35
|
+
}
|
17
36
|
end
|
18
37
|
|
19
38
|
def attributes_before_change
|
20
|
-
|
21
|
-
@record.class.column_names.include?(k)
|
22
|
-
|
23
|
-
|
39
|
+
Hash[@record.attributes.map do |k, v|
|
40
|
+
if @record.class.column_names.include?(k)
|
41
|
+
[k, attribute_in_previous_version(k)]
|
42
|
+
else
|
43
|
+
[k, v]
|
44
|
+
end
|
45
|
+
end]
|
24
46
|
end
|
25
47
|
|
26
48
|
def changed_and_not_ignored
|
@@ -34,7 +56,7 @@ module PaperTrail
|
|
34
56
|
}
|
35
57
|
end
|
36
58
|
skip = @record.paper_trail_options[:skip]
|
37
|
-
|
59
|
+
changed_in_latest_version - ignore - skip
|
38
60
|
end
|
39
61
|
|
40
62
|
# Invoked after rollbacks to ensure versions records are not created for
|
@@ -65,7 +87,7 @@ module PaperTrail
|
|
65
87
|
|
66
88
|
# @api private
|
67
89
|
def changes
|
68
|
-
notable_changes =
|
90
|
+
notable_changes = changes_in_latest_version.delete_if { |k, _v|
|
69
91
|
!notably_changed.include?(k)
|
70
92
|
}
|
71
93
|
AttributeSerializers::ObjectChangesAttribute.
|
@@ -87,7 +109,7 @@ module PaperTrail
|
|
87
109
|
# changed.
|
88
110
|
def ignored_attr_has_changed?
|
89
111
|
ignored = @record.paper_trail_options[:ignore] + @record.paper_trail_options[:skip]
|
90
|
-
ignored.any? && (
|
112
|
+
ignored.any? && (changed_in_latest_version & ignored).any?
|
91
113
|
end
|
92
114
|
|
93
115
|
# Returns true if this instance is the current, live one;
|
@@ -107,9 +129,9 @@ module PaperTrail
|
|
107
129
|
# If it is an attribute that is changing in an existing object,
|
108
130
|
# be sure to grab the current version.
|
109
131
|
if @record.has_attribute?(v) &&
|
110
|
-
|
132
|
+
attribute_changed_in_latest_version?(v) &&
|
111
133
|
data[:event] != "create"
|
112
|
-
|
134
|
+
attribute_in_previous_version(v)
|
113
135
|
else
|
114
136
|
@record.send(v)
|
115
137
|
end
|
@@ -164,35 +186,36 @@ module PaperTrail
|
|
164
186
|
end
|
165
187
|
|
166
188
|
def record_create
|
189
|
+
@in_after_callback = true
|
167
190
|
return unless enabled?
|
191
|
+
versions_assoc = @record.send(@record.class.versions_association_name)
|
192
|
+
version = versions_assoc.create! data_for_create
|
193
|
+
update_transaction_id(version)
|
194
|
+
save_associations(version)
|
195
|
+
ensure
|
196
|
+
@in_after_callback = false
|
197
|
+
end
|
198
|
+
|
199
|
+
# Returns data for record create
|
200
|
+
# @api private
|
201
|
+
def data_for_create
|
168
202
|
data = {
|
169
203
|
event: @record.paper_trail_event || "create",
|
170
204
|
whodunnit: PaperTrail.whodunnit
|
171
205
|
}
|
172
206
|
if @record.respond_to?(:updated_at)
|
173
|
-
data[
|
207
|
+
data[:created_at] = @record.updated_at
|
174
208
|
end
|
175
209
|
if record_object_changes? && changed_notably?
|
176
210
|
data[:object_changes] = recordable_object_changes
|
177
211
|
end
|
178
212
|
add_transaction_id_to(data)
|
179
|
-
|
180
|
-
version = versions_assoc.create! merge_metadata(data)
|
181
|
-
update_transaction_id(version)
|
182
|
-
save_associations(version)
|
213
|
+
merge_metadata(data)
|
183
214
|
end
|
184
215
|
|
185
216
|
def record_destroy
|
186
217
|
if enabled? && !@record.new_record?
|
187
|
-
|
188
|
-
item_id: @record.id,
|
189
|
-
item_type: @record.class.base_class.name,
|
190
|
-
event: @record.paper_trail_event || "destroy",
|
191
|
-
object: recordable_object,
|
192
|
-
whodunnit: PaperTrail.whodunnit
|
193
|
-
}
|
194
|
-
add_transaction_id_to(data)
|
195
|
-
version = @record.class.paper_trail.version_class.create(merge_metadata(data))
|
218
|
+
version = @record.class.paper_trail.version_class.create(data_for_destroy)
|
196
219
|
if version.errors.any?
|
197
220
|
log_version_errors(version, :destroy)
|
198
221
|
else
|
@@ -204,6 +227,20 @@ module PaperTrail
|
|
204
227
|
end
|
205
228
|
end
|
206
229
|
|
230
|
+
# Returns data for record destroy
|
231
|
+
# @api private
|
232
|
+
def data_for_destroy
|
233
|
+
data = {
|
234
|
+
item_id: @record.id,
|
235
|
+
item_type: @record.class.base_class.name,
|
236
|
+
event: @record.paper_trail_event || "destroy",
|
237
|
+
object: recordable_object,
|
238
|
+
whodunnit: PaperTrail.whodunnit
|
239
|
+
}
|
240
|
+
add_transaction_id_to(data)
|
241
|
+
merge_metadata(data)
|
242
|
+
end
|
243
|
+
|
207
244
|
# Returns a boolean indicating whether to store serialized version diffs
|
208
245
|
# in the `object_changes` column of the version record.
|
209
246
|
# @api private
|
@@ -213,21 +250,10 @@ module PaperTrail
|
|
213
250
|
end
|
214
251
|
|
215
252
|
def record_update(force)
|
253
|
+
@in_after_callback = true
|
216
254
|
if enabled? && (force || changed_notably?)
|
217
|
-
data = {
|
218
|
-
event: @record.paper_trail_event || "update",
|
219
|
-
object: recordable_object,
|
220
|
-
whodunnit: PaperTrail.whodunnit
|
221
|
-
}
|
222
|
-
if @record.respond_to?(:updated_at)
|
223
|
-
data[PaperTrail.timestamp_field] = @record.updated_at
|
224
|
-
end
|
225
|
-
if record_object_changes?
|
226
|
-
data[:object_changes] = recordable_object_changes
|
227
|
-
end
|
228
|
-
add_transaction_id_to(data)
|
229
255
|
versions_assoc = @record.send(@record.class.versions_association_name)
|
230
|
-
version = versions_assoc.create(
|
256
|
+
version = versions_assoc.create(data_for_update)
|
231
257
|
if version.errors.any?
|
232
258
|
log_version_errors(version, :update)
|
233
259
|
else
|
@@ -235,6 +261,26 @@ module PaperTrail
|
|
235
261
|
save_associations(version)
|
236
262
|
end
|
237
263
|
end
|
264
|
+
ensure
|
265
|
+
@in_after_callback = false
|
266
|
+
end
|
267
|
+
|
268
|
+
# Returns data for record update
|
269
|
+
# @api private
|
270
|
+
def data_for_update
|
271
|
+
data = {
|
272
|
+
event: @record.paper_trail_event || "update",
|
273
|
+
object: recordable_object,
|
274
|
+
whodunnit: PaperTrail.whodunnit
|
275
|
+
}
|
276
|
+
if @record.respond_to?(:updated_at)
|
277
|
+
data[:created_at] = @record.updated_at
|
278
|
+
end
|
279
|
+
if record_object_changes?
|
280
|
+
data[:object_changes] = recordable_object_changes
|
281
|
+
end
|
282
|
+
add_transaction_id_to(data)
|
283
|
+
merge_metadata(data)
|
238
284
|
end
|
239
285
|
|
240
286
|
# Returns an object which can be assigned to the `object` attribute of a
|
@@ -309,23 +355,19 @@ module PaperTrail
|
|
309
355
|
end
|
310
356
|
end
|
311
357
|
|
358
|
+
# When a record is created, updated, or destroyed, we determine what the
|
359
|
+
# HABTM associations looked like before any changes were made, by using
|
360
|
+
# the `paper_trail_habtm` data structure. Then, we create
|
361
|
+
# `VersionAssociation` records for each of the associated records.
|
312
362
|
def save_associations_habtm(version)
|
313
|
-
# Use the :added and :removed keys to extrapolate the HABTM associations
|
314
|
-
# to before any changes were made
|
315
363
|
@record.class.reflect_on_all_associations(:has_and_belongs_to_many).each do |a|
|
316
|
-
next unless
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
assoc_ids =
|
324
|
-
@record.send(a.name).to_a.map(&:id) +
|
325
|
-
(@record.paper_trail_habtm.try(:[], a.name).try(:[], :removed) || []) -
|
326
|
-
(@record.paper_trail_habtm.try(:[], a.name).try(:[], :added) || [])
|
327
|
-
assoc_ids.each do |id|
|
328
|
-
PaperTrail::VersionAssociation.create(assoc_version_args.merge(foreign_key_id: id))
|
364
|
+
next unless save_habtm_association?(a)
|
365
|
+
habtm_assoc_ids(a).each do |id|
|
366
|
+
PaperTrail::VersionAssociation.create(
|
367
|
+
version_id: version.transaction_id,
|
368
|
+
foreign_key_name: a.name,
|
369
|
+
foreign_key_id: id
|
370
|
+
)
|
329
371
|
end
|
330
372
|
end
|
331
373
|
end
|
@@ -360,7 +402,7 @@ module PaperTrail
|
|
360
402
|
attributes.each { |column|
|
361
403
|
@record.send(:write_attribute, column, current_time)
|
362
404
|
}
|
363
|
-
|
405
|
+
record_update(true) unless will_record_after_update?
|
364
406
|
@record.save!(validate: false)
|
365
407
|
end
|
366
408
|
|
@@ -376,9 +418,7 @@ module PaperTrail
|
|
376
418
|
# Returns the objects (not Versions) as they were between the given times.
|
377
419
|
def versions_between(start_time, end_time)
|
378
420
|
versions = send(@record.class.versions_association_name).between(start_time, end_time)
|
379
|
-
versions.collect { |version|
|
380
|
-
version_at(version.send(PaperTrail.timestamp_field))
|
381
|
-
}
|
421
|
+
versions.collect { |version| version_at(version.created_at) }
|
382
422
|
end
|
383
423
|
|
384
424
|
# Executes the given method or block without creating a new version.
|
@@ -416,6 +456,51 @@ module PaperTrail
|
|
416
456
|
data[:transaction_id] = PaperTrail.transaction_id
|
417
457
|
end
|
418
458
|
|
459
|
+
# @api private
|
460
|
+
def attribute_changed_in_latest_version?(attr_name)
|
461
|
+
if @in_after_callback && RAILS_GTE_5_1
|
462
|
+
@record.saved_change_to_attribute?(attr_name.to_s)
|
463
|
+
else
|
464
|
+
@record.attribute_changed?(attr_name.to_s)
|
465
|
+
end
|
466
|
+
end
|
467
|
+
|
468
|
+
# @api private
|
469
|
+
def attribute_in_previous_version(attr_name)
|
470
|
+
if @in_after_callback && RAILS_GTE_5_1
|
471
|
+
@record.attribute_before_last_save(attr_name.to_s)
|
472
|
+
else
|
473
|
+
@record.attribute_was(attr_name.to_s)
|
474
|
+
end
|
475
|
+
end
|
476
|
+
|
477
|
+
# @api private
|
478
|
+
def changed_in_latest_version
|
479
|
+
if @in_after_callback && RAILS_GTE_5_1
|
480
|
+
@record.saved_changes.keys
|
481
|
+
else
|
482
|
+
@record.changed
|
483
|
+
end
|
484
|
+
end
|
485
|
+
|
486
|
+
# @api private
|
487
|
+
def changes_in_latest_version
|
488
|
+
if @in_after_callback && RAILS_GTE_5_1
|
489
|
+
@record.saved_changes
|
490
|
+
else
|
491
|
+
@record.changes
|
492
|
+
end
|
493
|
+
end
|
494
|
+
|
495
|
+
# Given a HABTM association, returns an array of ids.
|
496
|
+
# @api private
|
497
|
+
def habtm_assoc_ids(habtm_assoc)
|
498
|
+
current = @record.send(habtm_assoc.name).to_a.map(&:id) # TODO: `pluck` would use less memory
|
499
|
+
removed = @record.paper_trail_habtm.try(:[], habtm_assoc.name).try(:[], :removed) || []
|
500
|
+
added = @record.paper_trail_habtm.try(:[], habtm_assoc.name).try(:[], :added) || []
|
501
|
+
current + removed - added
|
502
|
+
end
|
503
|
+
|
419
504
|
def log_version_errors(version, action)
|
420
505
|
version.logger.warn(
|
421
506
|
"Unable to create version for #{action} of #{@record.class.name}" +
|
@@ -423,6 +508,13 @@ module PaperTrail
|
|
423
508
|
)
|
424
509
|
end
|
425
510
|
|
511
|
+
# Returns true if the given HABTM association should be saved.
|
512
|
+
# @api private
|
513
|
+
def save_habtm_association?(assoc)
|
514
|
+
@record.class.paper_trail_save_join_tables.include?(assoc.name) ||
|
515
|
+
assoc.klass.paper_trail.enabled?
|
516
|
+
end
|
517
|
+
|
426
518
|
# Returns true if `save` will cause `record_update`
|
427
519
|
# to be called via the `after_update` callback.
|
428
520
|
def will_record_after_update?
|