paper_trail 9.2.0 → 10.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.
@@ -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)