paper_trail 5.2.3 → 6.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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?
|