paper_trail 9.2.0 → 10.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "paper_trail/events/base"
4
+
5
+ module PaperTrail
6
+ module Events
7
+ # See docs in `Base`.
8
+ #
9
+ # @api private
10
+ class Create < Base
11
+ # Return attributes of nascent `Version` record.
12
+ #
13
+ # @api private
14
+ def data
15
+ data = {
16
+ event: @record.paper_trail_event || "create",
17
+ whodunnit: PaperTrail.request.whodunnit
18
+ }
19
+ if @record.respond_to?(:updated_at)
20
+ data[:created_at] = @record.updated_at
21
+ end
22
+ if record_object_changes? && changed_notably?
23
+ changes = notable_changes
24
+ data[:object_changes] = prepare_object_changes(changes)
25
+ end
26
+ merge_item_subtype_into(data)
27
+ merge_metadata_into(data)
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "paper_trail/events/base"
4
+
5
+ module PaperTrail
6
+ module Events
7
+ # See docs in `Base`.
8
+ #
9
+ # @api private
10
+ class Destroy < Base
11
+ # Return attributes of nascent `Version` record.
12
+ #
13
+ # @api private
14
+ def data
15
+ data = {
16
+ item_id: @record.id,
17
+ item_type: @record.class.base_class.name,
18
+ event: @record.paper_trail_event || "destroy",
19
+ whodunnit: PaperTrail.request.whodunnit
20
+ }
21
+ if record_object?
22
+ data[:object] = recordable_object(false)
23
+ end
24
+ if record_object_changes?
25
+ # Rails' implementation returns nothing on destroy :/
26
+ changes = @record.attributes.map { |attr, value| [attr, [value, nil]] }.to_h
27
+ data[:object_changes] = prepare_object_changes(changes)
28
+ end
29
+ merge_item_subtype_into(data)
30
+ merge_metadata_into(data)
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "paper_trail/events/base"
4
+
5
+ module PaperTrail
6
+ module Events
7
+ # See docs in `Base`.
8
+ #
9
+ # @api private
10
+ class Update < Base
11
+ # - is_touch - [boolean] - Used in the two situations that are touch-like:
12
+ # - `after_touch` we call `RecordTrail#record_update`
13
+ # - force_changes - [Hash] - Only used by `RecordTrail#update_columns`,
14
+ # because there dirty-tracking is off, so it has to track its own changes.
15
+ #
16
+ # @api private
17
+ def initialize(record, in_after_callback, is_touch, force_changes)
18
+ super(record, in_after_callback)
19
+ @is_touch = is_touch
20
+ @force_changes = force_changes
21
+ end
22
+
23
+ # Return attributes of nascent `Version` record.
24
+ #
25
+ # @api private
26
+ def data
27
+ data = {
28
+ event: @record.paper_trail_event || "update",
29
+ whodunnit: PaperTrail.request.whodunnit
30
+ }
31
+ if @record.respond_to?(:updated_at)
32
+ data[:created_at] = @record.updated_at
33
+ end
34
+ if record_object?
35
+ data[:object] = recordable_object(@is_touch)
36
+ end
37
+ if record_object_changes?
38
+ changes = @force_changes.nil? ? notable_changes : @force_changes
39
+ data[:object_changes] = prepare_object_changes(changes)
40
+ end
41
+ merge_item_subtype_into(data)
42
+ merge_metadata_into(data)
43
+ end
44
+
45
+ private
46
+
47
+ # `touch` cannot record `object_changes` because rails' `touch` does not
48
+ # perform dirty-tracking. Specifically, methods from `Dirty`, like
49
+ # `saved_changes`, return the same values before and after `touch`.
50
+ #
51
+ # See https://github.com/rails/rails/issues/33429
52
+ #
53
+ # @api private
54
+ def record_object_changes?
55
+ !@is_touch && super
56
+ end
57
+ end
58
+ end
59
+ end
@@ -51,8 +51,6 @@ module PaperTrail
51
51
  # is `:versions`.
52
52
  # - :version - The name to use for the method which returns the version
53
53
  # the instance was reified from. Default is `:version`.
54
- # - :save_changes - Whether or not to save changes to the object_changes
55
- # column if it exists. Default is true
56
54
  # - :join_tables - If the model has a has_and_belongs_to_many relation
57
55
  # with an unpapertrailed model, passing the name of the association to
58
56
  # the join_tables option will paper trail the join table but not save
@@ -4,27 +4,6 @@ module PaperTrail
4
4
  # Configures an ActiveRecord model, mostly at application boot time, but also
5
5
  # sometimes mid-request, with methods like enable/disable.
6
6
  class ModelConfig
7
- DPR_DISABLE = <<-STR.squish.freeze
8
- MyModel.paper_trail.disable is deprecated, use
9
- PaperTrail.request.disable_model(MyModel). This new API makes it clear
10
- that only the current request is affected, not all threads. Also, all
11
- other request-variables now go through the same `request` method, so this
12
- new API is more consistent.
13
- STR
14
- DPR_ENABLE = <<-STR.squish.freeze
15
- MyModel.paper_trail.enable is deprecated, use
16
- PaperTrail.request.enable_model(MyModel). This new API makes it clear
17
- that only the current request is affected, not all threads. Also, all
18
- other request-variables now go through the same `request` method, so this
19
- new API is more consistent.
20
- STR
21
- DPR_ENABLED = <<-STR.squish.freeze
22
- MyModel.paper_trail.enabled? is deprecated, use
23
- PaperTrail.request.enabled_for_model?(MyModel). This new API makes it clear
24
- that this is a setting specific to the current request, not all threads.
25
- Also, all other request-variables now go through the same `request`
26
- method, so this new API is more consistent.
27
- STR
28
7
  E_CANNOT_RECORD_AFTER_DESTROY = <<-STR.strip_heredoc.freeze
29
8
  paper_trail.on_destroy(:after) is incompatible with ActiveRecord's
30
9
  belongs_to_required_by_default. Use on_destroy(:before)
@@ -44,24 +23,6 @@ module PaperTrail
44
23
  @model_class = model_class
45
24
  end
46
25
 
47
- # @deprecated
48
- def disable
49
- ::ActiveSupport::Deprecation.warn(DPR_DISABLE, caller(1))
50
- ::PaperTrail.request.disable_model(@model_class)
51
- end
52
-
53
- # @deprecated
54
- def enable
55
- ::ActiveSupport::Deprecation.warn(DPR_ENABLE, caller(1))
56
- ::PaperTrail.request.enable_model(@model_class)
57
- end
58
-
59
- # @deprecated
60
- def enabled?
61
- ::ActiveSupport::Deprecation.warn(DPR_ENABLED, caller(1))
62
- ::PaperTrail.request.enabled_for_model?(@model_class)
63
- end
64
-
65
26
  # Adds a callback that records a version after a "create" event.
66
27
  #
67
28
  # @api public
@@ -169,22 +130,31 @@ module PaperTrail
169
130
  end
170
131
 
171
132
  def setup_associations(options)
133
+ # @api private - version_association_name
172
134
  @model_class.class_attribute :version_association_name
173
135
  @model_class.version_association_name = options[:version] || :version
174
136
 
175
137
  # The version this instance was reified from.
138
+ # @api public
176
139
  @model_class.send :attr_accessor, @model_class.version_association_name
177
140
 
141
+ # @api private - `version_class_name` - However, `rails_admin` has been
142
+ # using it since 2014 (see `rails_admin/extensions/paper_trail/auditing_adapter.rb`,
143
+ # https://github.com/sferik/rails_admin/commit/959e1bd4e47e0369d264b58bbbe972ff863767cd)
144
+ # In PR _____ () we ask them to use `paper_trail_options` instead.
178
145
  @model_class.class_attribute :version_class_name
179
146
  @model_class.version_class_name = options[:class_name] || "PaperTrail::Version"
180
147
 
148
+ # @api private - versions_association_name
181
149
  @model_class.class_attribute :versions_association_name
182
150
  @model_class.versions_association_name = options[:versions] || :versions
183
151
 
152
+ # @api public - paper_trail_event
184
153
  @model_class.send :attr_accessor, :paper_trail_event
185
154
 
186
155
  assert_concrete_activerecord_class(@model_class.version_class_name)
187
156
 
157
+ # @api public
188
158
  @model_class.has_many(
189
159
  @model_class.versions_association_name,
190
160
  -> { order(model.timestamp_sort_order) },
@@ -200,6 +170,9 @@ module PaperTrail
200
170
  end
201
171
 
202
172
  def setup_options(options)
173
+ # @api public - paper_trail_options - Let's encourage plugins to use
174
+ # eg. `paper_trail_options[:class_name]` rather than `version_class_name`
175
+ # because the former is documented and the latter is not.
203
176
  @model_class.class_attribute :paper_trail_options
204
177
  @model_class.paper_trail_options = options.dup
205
178
 
@@ -211,9 +184,6 @@ module PaperTrail
211
184
  end
212
185
 
213
186
  @model_class.paper_trail_options[:meta] ||= {}
214
- if @model_class.paper_trail_options[:save_changes].nil?
215
- @model_class.paper_trail_options[:save_changes] = true
216
- end
217
187
  end
218
188
  end
219
189
  end
@@ -18,7 +18,10 @@ module PaperTrail
18
18
 
19
19
  # @api private
20
20
  def execute
21
- case @version_model_class.columns_hash["object"].type
21
+ column = @version_model_class.columns_hash["object"]
22
+ raise "where_object can't be called without an object column" unless column
23
+
24
+ case column.type
22
25
  when :jsonb
23
26
  jsonb
24
27
  when :json
@@ -23,7 +23,7 @@ module PaperTrail
23
23
 
24
24
  # @api private
25
25
  def execute
26
- if PaperTrail.config.object_changes_adapter
26
+ if PaperTrail.config.object_changes_adapter&.respond_to?(:where_object_changes)
27
27
  return PaperTrail.config.object_changes_adapter.where_object_changes(
28
28
  @version_model_class, @attributes
29
29
  )
@@ -1,72 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "paper_trail/events/create"
4
+ require "paper_trail/events/destroy"
5
+ require "paper_trail/events/update"
6
+
3
7
  module PaperTrail
4
8
  # Represents the "paper trail" for a single record.
5
9
  class RecordTrail
6
- DPR_TOUCH_WITH_VERSION = <<-STR.squish.freeze
7
- my_model.paper_trail.touch_with_version is deprecated, please use
8
- my_model.paper_trail.save_with_version, which is slightly different. It's
9
- a save, not a touch, so make sure you understand the difference by reading
10
- the ActiveRecord documentation for both.
11
- STR
12
- DPR_WHODUNNIT = <<-STR.squish.freeze
13
- my_model_instance.paper_trail.whodunnit('John') is deprecated,
14
- please use PaperTrail.request(whodunnit: 'John')
15
- STR
16
- DPR_WITHOUT_VERSIONING = <<-STR
17
- my_model_instance.paper_trail.without_versioning is deprecated, without
18
- an exact replacement. To disable versioning for a particular model,
19
-
20
- ```
21
- PaperTrail.request.disable_model(Banana)
22
- # changes to Banana model do not create versions,
23
- # but eg. changes to Kiwi model do.
24
- PaperTrail.request.enable_model(Banana)
25
- ```
26
-
27
- Or, you may want to disable all models,
28
-
29
- ```
30
- PaperTrail.request.enabled = false
31
- # no versions created
32
- PaperTrail.request.enabled = true
33
-
34
- # or, with a block,
35
- PaperTrail.request(enabled: false) do
36
- # no versions created
37
- end
38
- ```
39
- STR
40
-
41
10
  RAILS_GTE_5_1 = ::ActiveRecord.gem_version >= ::Gem::Version.new("5.1.0.beta1")
42
11
 
43
12
  def initialize(record)
44
13
  @record = record
45
- @in_after_callback = false
46
- end
47
-
48
- def attributes_before_change(is_touch)
49
- Hash[@record.attributes.map do |k, v|
50
- if @record.class.column_names.include?(k)
51
- [k, attribute_in_previous_version(k, is_touch)]
52
- else
53
- [k, v]
54
- end
55
- end]
56
- end
57
-
58
- def changed_and_not_ignored
59
- ignore = @record.paper_trail_options[:ignore].dup
60
- # Remove Hash arguments and then evaluate whether the attributes (the
61
- # keys of the hash) should also get pushed into the collection.
62
- ignore.delete_if do |obj|
63
- obj.is_a?(Hash) &&
64
- obj.each { |attr, condition|
65
- ignore << attr if condition.respond_to?(:call) && condition.call(@record)
66
- }
67
- end
68
- skip = @record.paper_trail_options[:skip]
69
- changed_in_latest_version - ignore - skip
70
14
  end
71
15
 
72
16
  # Invoked after rollbacks to ensure versions records are not created for
@@ -83,29 +27,6 @@ module PaperTrail
83
27
  @record.send("#{@record.class.version_association_name}=", nil)
84
28
  end
85
29
 
86
- # Determines whether it is appropriate to generate a new version
87
- # instance. A timestamp-only update (e.g. only `updated_at` changed) is
88
- # considered notable unless an ignored attribute was also changed.
89
- def changed_notably?
90
- if ignored_attr_has_changed?
91
- timestamps = @record.send(:timestamp_attributes_for_update_in_model).map(&:to_s)
92
- (notably_changed - timestamps).any?
93
- else
94
- notably_changed.any?
95
- end
96
- end
97
-
98
- # @api private
99
- def changes
100
- notable_changes = changes_in_latest_version.delete_if { |k, _v|
101
- !notably_changed.include?(k)
102
- }
103
- AttributeSerializers::ObjectChangesAttribute.
104
- new(@record.class).
105
- serialize(notable_changes)
106
- notable_changes.to_hash
107
- end
108
-
109
30
  # Is PT enabled for this particular record?
110
31
  # @api private
111
32
  def enabled?
@@ -114,77 +35,12 @@ module PaperTrail
114
35
  PaperTrail.request.enabled_for_model?(@record.class)
115
36
  end
116
37
 
117
- # Not sure why, but this method was mentioned in the README in the past,
118
- # so we need to deprecate it properly.
119
- # @deprecated
120
- def enabled_for_model?
121
- ::ActiveSupport::Deprecation.warn(
122
- "MyModel#paper_trail.enabled_for_model? is deprecated, use " \
123
- "PaperTrail.request.enabled_for_model?(MyModel) instead.",
124
- caller(1)
125
- )
126
- PaperTrail.request.enabled_for_model?(@record.class)
127
- end
128
-
129
- # An attributed is "ignored" if it is listed in the `:ignore` option
130
- # and/or the `:skip` option. Returns true if an ignored attribute has
131
- # changed.
132
- def ignored_attr_has_changed?
133
- ignored = @record.paper_trail_options[:ignore] + @record.paper_trail_options[:skip]
134
- ignored.any? && (changed_in_latest_version & ignored).any?
135
- end
136
-
137
38
  # Returns true if this instance is the current, live one;
138
39
  # returns false if this instance came from a previous version.
139
40
  def live?
140
41
  source_version.nil?
141
42
  end
142
43
 
143
- # Updates `data` from the model's `meta` option and from `controller_info`.
144
- # Metadata is always recorded; that means all three events (create, update,
145
- # destroy) and `update_columns`.
146
- # @api private
147
- def merge_metadata_into(data)
148
- merge_metadata_from_model_into(data)
149
- merge_metadata_from_controller_into(data)
150
- end
151
-
152
- # Updates `data` from `controller_info`.
153
- # @api private
154
- def merge_metadata_from_controller_into(data)
155
- data.merge(PaperTrail.request.controller_info || {})
156
- end
157
-
158
- # Updates `data` from the model's `meta` option.
159
- # @api private
160
- def merge_metadata_from_model_into(data)
161
- @record.paper_trail_options[:meta].each do |k, v|
162
- data[k] = model_metadatum(v, data[:event])
163
- end
164
- end
165
-
166
- # Given a `value` from the model's `meta` option, returns an object to be
167
- # persisted. The `value` can be a simple scalar value, but it can also
168
- # be a symbol that names a model method, or even a Proc.
169
- # @api private
170
- def model_metadatum(value, event)
171
- if value.respond_to?(:call)
172
- value.call(@record)
173
- elsif value.is_a?(Symbol) && @record.respond_to?(value, true)
174
- # If it is an attribute that is changing in an existing object,
175
- # be sure to grab the current version.
176
- if event != "create" &&
177
- @record.has_attribute?(value) &&
178
- attribute_changed_in_latest_version?(value)
179
- attribute_in_previous_version(value, false)
180
- else
181
- @record.send(value)
182
- end
183
- else
184
- value
185
- end
186
- end
187
-
188
44
  # Returns the object (not a Version) as it became next.
189
45
  # NOTE: if self (the item) was not reified from a version, i.e. it is the
190
46
  # "live" item, we return nil. Perhaps we should return self instead?
@@ -195,30 +51,6 @@ module PaperTrail
195
51
  nil
196
52
  end
197
53
 
198
- def notably_changed
199
- only = @record.paper_trail_options[:only].dup
200
- # Remove Hash arguments and then evaluate whether the attributes (the
201
- # keys of the hash) should also get pushed into the collection.
202
- only.delete_if do |obj|
203
- obj.is_a?(Hash) &&
204
- obj.each { |attr, condition|
205
- only << attr if condition.respond_to?(:call) && condition.call(@record)
206
- }
207
- end
208
- only.empty? ? changed_and_not_ignored : (changed_and_not_ignored & only)
209
- end
210
-
211
- # Returns hash of attributes (with appropriate attributes serialized),
212
- # omitting attributes to be skipped.
213
- #
214
- # @api private
215
- def object_attrs_for_paper_trail(is_touch)
216
- attrs = attributes_before_change(is_touch).
217
- except(*@record.paper_trail_options[:skip])
218
- AttributeSerializers::ObjectAttribute.new(@record.class).serialize(attrs)
219
- attrs
220
- end
221
-
222
54
  # Returns who put `@record` into its current state.
223
55
  #
224
56
  # @api public
@@ -234,28 +66,22 @@ module PaperTrail
234
66
  end
235
67
 
236
68
  def record_create
237
- @in_after_callback = true
238
69
  return unless enabled?
70
+ event = Events::Create.new(@record, true)
71
+
72
+ # Merge data from `Event` with data from PT-AT. We no longer use
73
+ # `data_for_create` but PT-AT still does.
74
+ data = event.data.merge(data_for_create)
75
+
239
76
  versions_assoc = @record.send(@record.class.versions_association_name)
240
- versions_assoc.create! data_for_create
241
- ensure
242
- @in_after_callback = false
77
+ versions_assoc.create!(data)
243
78
  end
244
79
 
245
- # Returns data for record create
80
+ # PT-AT extends this method to add its transaction id.
81
+ #
246
82
  # @api private
247
83
  def data_for_create
248
- data = {
249
- event: @record.paper_trail_event || "create",
250
- whodunnit: PaperTrail.request.whodunnit
251
- }
252
- if @record.respond_to?(:updated_at)
253
- data[:created_at] = @record.updated_at
254
- end
255
- if record_object_changes? && changed_notably?
256
- data[:object_changes] = recordable_object_changes(changes)
257
- end
258
- merge_metadata_into(data)
84
+ {}
259
85
  end
260
86
 
261
87
  # `recording_order` is "after" or "before". See ModelConfig#on_destroy.
@@ -264,77 +90,56 @@ module PaperTrail
264
90
  # @return - The created version object, so that plugins can use it, e.g.
265
91
  # paper_trail-association_tracking
266
92
  def record_destroy(recording_order)
267
- @in_after_callback = recording_order == "after"
268
- if enabled? && !@record.new_record?
269
- version = @record.class.paper_trail.version_class.create(data_for_destroy)
270
- if version.errors.any?
271
- log_version_errors(version, :destroy)
272
- else
273
- @record.send("#{@record.class.version_association_name}=", version)
274
- @record.send(@record.class.versions_association_name).reset
275
- version
276
- end
93
+ return unless enabled? && !@record.new_record?
94
+ in_after_callback = recording_order == "after"
95
+ event = Events::Destroy.new(@record, in_after_callback)
96
+
97
+ # Merge data from `Event` with data from PT-AT. We no longer use
98
+ # `data_for_destroy` but PT-AT still does.
99
+ data = event.data.merge(data_for_destroy)
100
+
101
+ version = @record.class.paper_trail.version_class.create(data)
102
+ if version.errors.any?
103
+ log_version_errors(version, :destroy)
104
+ else
105
+ assign_and_reset_version_association(version)
106
+ version
277
107
  end
278
- ensure
279
- @in_after_callback = false
280
108
  end
281
109
 
282
- # Returns data for record destroy
110
+ # PT-AT extends this method to add its transaction id.
111
+ #
283
112
  # @api private
284
113
  def data_for_destroy
285
- data = {
286
- item_id: @record.id,
287
- item_type: @record.class.base_class.name,
288
- event: @record.paper_trail_event || "destroy",
289
- object: recordable_object(false),
290
- whodunnit: PaperTrail.request.whodunnit
291
- }
292
- merge_metadata_into(data)
293
- end
294
-
295
- # Returns a boolean indicating whether to store serialized version diffs
296
- # in the `object_changes` column of the version record.
297
- # @api private
298
- def record_object_changes?
299
- @record.paper_trail_options[:save_changes] &&
300
- @record.class.paper_trail.version_class.column_names.include?("object_changes")
114
+ {}
301
115
  end
302
116
 
303
117
  # @api private
304
118
  # @return - The created version object, so that plugins can use it, e.g.
305
119
  # paper_trail-association_tracking
306
120
  def record_update(force:, in_after_callback:, is_touch:)
307
- @in_after_callback = in_after_callback
308
- if enabled? && (force || changed_notably?)
309
- versions_assoc = @record.send(@record.class.versions_association_name)
310
- version = versions_assoc.create(data_for_update(is_touch))
311
- if version.errors.any?
312
- log_version_errors(version, :update)
313
- else
314
- version
315
- end
121
+ return unless enabled?
122
+ event = Events::Update.new(@record, in_after_callback, is_touch, nil)
123
+ return unless force || event.changed_notably?
124
+
125
+ # Merge data from `Event` with data from PT-AT. We no longer use
126
+ # `data_for_update` but PT-AT still does.
127
+ data = event.data.merge(data_for_update)
128
+
129
+ versions_assoc = @record.send(@record.class.versions_association_name)
130
+ version = versions_assoc.create(data)
131
+ if version.errors.any?
132
+ log_version_errors(version, :update)
133
+ else
134
+ version
316
135
  end
317
- ensure
318
- @in_after_callback = false
319
136
  end
320
137
 
321
- # Used during `record_update`, returns a hash of data suitable for an AR
322
- # `create`. That is, all the attributes of the nascent `Version` record.
138
+ # PT-AT extends this method to add its transaction id.
323
139
  #
324
140
  # @api private
325
- def data_for_update(is_touch)
326
- data = {
327
- event: @record.paper_trail_event || "update",
328
- object: recordable_object(is_touch),
329
- whodunnit: PaperTrail.request.whodunnit
330
- }
331
- if @record.respond_to?(:updated_at)
332
- data[:created_at] = @record.updated_at
333
- end
334
- if record_object_changes?
335
- data[:object_changes] = recordable_object_changes(changes)
336
- end
337
- merge_metadata_into(data)
141
+ def data_for_update
142
+ {}
338
143
  end
339
144
 
340
145
  # @api private
@@ -342,8 +147,14 @@ module PaperTrail
342
147
  # paper_trail-association_tracking
343
148
  def record_update_columns(changes)
344
149
  return unless enabled?
150
+ event = Events::Update.new(@record, false, false, changes)
151
+
152
+ # Merge data from `Event` with data from PT-AT. We no longer use
153
+ # `data_for_update_columns` but PT-AT still does.
154
+ data = event.data.merge(data_for_update_columns)
155
+
345
156
  versions_assoc = @record.send(@record.class.versions_association_name)
346
- version = versions_assoc.create(data_for_update_columns(changes))
157
+ version = versions_assoc.create(data)
347
158
  if version.errors.any?
348
159
  log_version_errors(version, :update)
349
160
  else
@@ -351,52 +162,11 @@ module PaperTrail
351
162
  end
352
163
  end
353
164
 
354
- # Returns data for record_update_columns
355
- # @api private
356
- def data_for_update_columns(changes)
357
- data = {
358
- event: @record.paper_trail_event || "update",
359
- object: recordable_object(false),
360
- whodunnit: PaperTrail.request.whodunnit
361
- }
362
- if record_object_changes?
363
- data[:object_changes] = recordable_object_changes(changes)
364
- end
365
- merge_metadata_into(data)
366
- end
367
-
368
- # Returns an object which can be assigned to the `object` attribute of a
369
- # nascent version record. If the `object` column is a postgres `json`
370
- # column, then a hash can be used in the assignment, otherwise the column
371
- # is a `text` column, and we must perform the serialization here, using
372
- # `PaperTrail.serializer`.
165
+ # PT-AT extends this method to add its transaction id.
373
166
  #
374
167
  # @api private
375
- def recordable_object(is_touch)
376
- if @record.class.paper_trail.version_class.object_col_is_json?
377
- object_attrs_for_paper_trail(is_touch)
378
- else
379
- PaperTrail.serializer.dump(object_attrs_for_paper_trail(is_touch))
380
- end
381
- end
382
-
383
- # Returns an object which can be assigned to the `object_changes`
384
- # attribute of a nascent version record. If the `object_changes` column is
385
- # a postgres `json` column, then a hash can be used in the assignment,
386
- # otherwise the column is a `text` column, and we must perform the
387
- # serialization here, using `PaperTrail.serializer`.
388
- #
389
- # @api private
390
- def recordable_object_changes(changes)
391
- if PaperTrail.config.object_changes_adapter
392
- changes = PaperTrail.config.object_changes_adapter.diff(changes)
393
- end
394
-
395
- if @record.class.paper_trail.version_class.object_changes_col_is_json?
396
- changes
397
- else
398
- PaperTrail.serializer.dump(changes)
399
- end
168
+ def data_for_update_columns
169
+ {}
400
170
  end
401
171
 
402
172
  # Invoked via callback when a user attempts to persist a reified
@@ -420,38 +190,6 @@ module PaperTrail
420
190
  version
421
191
  end
422
192
 
423
- # Mimics the `touch` method from `ActiveRecord::Persistence` (without
424
- # actually calling `touch`), but also creates a version.
425
- #
426
- # A version is created regardless of options such as `:on`, `:if`, or
427
- # `:unless`.
428
- #
429
- # This is an "update" event. That is, we record the same data we would in
430
- # the case of a normal AR `update`.
431
- #
432
- # Some advanced PT users disable all callbacks (eg. `has_paper_trail(on:
433
- # [])`) and use only this method, giving them complete control over when
434
- # version records are inserted. It's unclear under which specific
435
- # circumstances this technique should be adopted.
436
- #
437
- # @deprecated
438
- def touch_with_version(name = nil)
439
- ::ActiveSupport::Deprecation.warn(DPR_TOUCH_WITH_VERSION, caller(1))
440
- unless @record.persisted?
441
- raise ::ActiveRecord::ActiveRecordError, "can not touch on a new record object"
442
- end
443
- attributes = @record.send :timestamp_attributes_for_update_in_model
444
- attributes << name if name
445
- current_time = @record.send :current_time_from_proper_timezone
446
- attributes.each { |column|
447
- @record.send(:write_attribute, column, current_time)
448
- }
449
- ::PaperTrail.request(enabled: false) do
450
- @record.save!(validate: false)
451
- end
452
- record_update(force: true, in_after_callback: false, is_touch: false)
453
- end
454
-
455
193
  # Save, and create a version record regardless of options such as `:on`,
456
194
  # `:if`, or `:unless`.
457
195
  #
@@ -459,12 +197,6 @@ module PaperTrail
459
197
  #
460
198
  # This is an "update" event. That is, we record the same data we would in
461
199
  # the case of a normal AR `update`.
462
- #
463
- # In older versions of PaperTrail, a method named `touch_with_version` was
464
- # used for this purpose. `save_with_version` is not exactly the same.
465
- # First, the arguments are different. It passes all arguments to `save`.
466
- # Second, it doesn't set any timestamp attributes prior to the `save` the
467
- # way `touch_with_version` did.
468
200
  def save_with_version(*args)
469
201
  ::PaperTrail.request(enabled: false) do
470
202
  @record.save(*args)
@@ -483,9 +215,10 @@ module PaperTrail
483
215
  # creates a version to record those changes.
484
216
  # @api public
485
217
  def update_columns(attributes)
486
- # `@record.update_columns` skips dirty tracking, so we can't just use `@record.changes` or
487
- # @record.saved_changes` from `ActiveModel::Dirty`. We need to build our own hash with the
488
- # changes that will be made directly to the database.
218
+ # `@record.update_columns` skips dirty-tracking, so we can't just use
219
+ # `@record.changes` or @record.saved_changes` from `ActiveModel::Dirty`.
220
+ # We need to build our own hash with the changes that will be made
221
+ # directly to the database.
489
222
  changes = {}
490
223
  attributes.each do |k, v|
491
224
  changes[k] = [@record[k], v]
@@ -509,92 +242,12 @@ module PaperTrail
509
242
  versions.collect { |version| version_at(version.created_at) }
510
243
  end
511
244
 
512
- # Executes the given method or block without creating a new version.
513
- # @deprecated
514
- def without_versioning(method = nil)
515
- ::ActiveSupport::Deprecation.warn(DPR_WITHOUT_VERSIONING, caller(1))
516
- paper_trail_was_enabled = PaperTrail.request.enabled_for_model?(@record.class)
517
- PaperTrail.request.disable_model(@record.class)
518
- if method
519
- if respond_to?(method)
520
- public_send(method)
521
- else
522
- @record.send(method)
523
- end
524
- else
525
- yield @record
526
- end
527
- ensure
528
- PaperTrail.request.enable_model(@record.class) if paper_trail_was_enabled
529
- end
530
-
531
- # @deprecated
532
- def whodunnit(value)
533
- raise ArgumentError, "expected to receive a block" unless block_given?
534
- ::ActiveSupport::Deprecation.warn(DPR_WHODUNNIT, caller(1))
535
- ::PaperTrail.request(whodunnit: value) do
536
- yield @record
537
- end
538
- end
539
-
540
245
  private
541
246
 
542
- # Rails 5.1 changed the API of `ActiveRecord::Dirty`. See
543
- # https://github.com/paper-trail-gem/paper_trail/pull/899
544
- #
545
247
  # @api private
546
- def attribute_changed_in_latest_version?(attr_name)
547
- if @in_after_callback && RAILS_GTE_5_1
548
- @record.saved_change_to_attribute?(attr_name.to_s)
549
- else
550
- @record.attribute_changed?(attr_name.to_s)
551
- end
552
- end
553
-
554
- # Rails 5.1 changed the API of `ActiveRecord::Dirty`. See
555
- # https://github.com/paper-trail-gem/paper_trail/pull/899
556
- #
557
- # Event can be any of the three (create, update, destroy).
558
- #
559
- # @api private
560
- def attribute_in_previous_version(attr_name, is_touch)
561
- if RAILS_GTE_5_1
562
- if @in_after_callback && !is_touch
563
- # For most events, we want the original value of the attribute, before
564
- # the last save.
565
- @record.attribute_before_last_save(attr_name.to_s)
566
- else
567
- # We are either performing a `record_destroy` or a
568
- # `record_update(is_touch: true)`.
569
- @record.attribute_in_database(attr_name.to_s)
570
- end
571
- else
572
- @record.attribute_was(attr_name.to_s)
573
- end
574
- end
575
-
576
- # Rails 5.1 changed the API of `ActiveRecord::Dirty`. See
577
- # https://github.com/paper-trail-gem/paper_trail/pull/899
578
- #
579
- # @api private
580
- def changed_in_latest_version
581
- if @in_after_callback && RAILS_GTE_5_1
582
- @record.saved_changes.keys
583
- else
584
- @record.changed
585
- end
586
- end
587
-
588
- # Rails 5.1 changed the API of `ActiveRecord::Dirty`. See
589
- # https://github.com/paper-trail-gem/paper_trail/pull/899
590
- #
591
- # @api private
592
- def changes_in_latest_version
593
- if @in_after_callback && RAILS_GTE_5_1
594
- @record.saved_changes
595
- else
596
- @record.changes
597
- end
248
+ def assign_and_reset_version_association(version)
249
+ @record.send("#{@record.class.version_association_name}=", version)
250
+ @record.send(@record.class.versions_association_name).reset
598
251
  end
599
252
 
600
253
  def log_version_errors(version, action)