paper_trail 9.2.0 → 14.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/LICENSE +20 -0
- data/lib/generators/paper_trail/install/USAGE +3 -0
- data/lib/generators/paper_trail/{install_generator.rb → install/install_generator.rb} +27 -38
- data/lib/generators/paper_trail/{templates → install/templates}/create_versions.rb.erb +5 -3
- data/lib/generators/paper_trail/migration_generator.rb +38 -0
- data/lib/generators/paper_trail/update_item_subtype/USAGE +4 -0
- data/lib/generators/paper_trail/update_item_subtype/templates/update_versions_for_item_subtype.rb.erb +85 -0
- data/lib/generators/paper_trail/update_item_subtype/update_item_subtype_generator.rb +19 -0
- data/lib/paper_trail/attribute_serializers/attribute_serializer_factory.rb +24 -10
- data/lib/paper_trail/attribute_serializers/cast_attribute_serializer.rb +17 -45
- data/lib/paper_trail/compatibility.rb +51 -0
- data/lib/paper_trail/config.rb +9 -2
- data/lib/paper_trail/errors.rb +33 -0
- data/lib/paper_trail/events/base.rb +343 -0
- data/lib/paper_trail/events/create.rb +32 -0
- data/lib/paper_trail/events/destroy.rb +42 -0
- data/lib/paper_trail/events/update.rb +76 -0
- data/lib/paper_trail/frameworks/active_record.rb +9 -2
- data/lib/paper_trail/frameworks/rails/controller.rb +1 -9
- data/lib/paper_trail/frameworks/rails/railtie.rb +30 -0
- data/lib/paper_trail/frameworks/rails.rb +1 -2
- data/lib/paper_trail/has_paper_trail.rb +20 -17
- data/lib/paper_trail/model_config.rb +124 -87
- data/lib/paper_trail/queries/versions/where_attribute_changes.rb +50 -0
- data/lib/paper_trail/queries/versions/where_object.rb +4 -1
- data/lib/paper_trail/queries/versions/where_object_changes.rb +9 -14
- data/lib/paper_trail/queries/versions/where_object_changes_from.rb +57 -0
- data/lib/paper_trail/queries/versions/where_object_changes_to.rb +57 -0
- data/lib/paper_trail/record_trail.rb +137 -436
- data/lib/paper_trail/reifier.rb +41 -25
- data/lib/paper_trail/request.rb +22 -25
- data/lib/paper_trail/serializers/json.rb +0 -10
- data/lib/paper_trail/serializers/yaml.rb +41 -11
- data/lib/paper_trail/type_serializers/postgres_array_serializer.rb +1 -15
- data/lib/paper_trail/version_concern.rb +152 -62
- data/lib/paper_trail/version_number.rb +2 -2
- data/lib/paper_trail.rb +23 -123
- metadata +152 -61
- data/lib/generators/paper_trail/USAGE +0 -2
- data/lib/paper_trail/frameworks/rails/engine.rb +0 -14
- /data/lib/generators/paper_trail/{templates → install/templates}/add_object_changes_to_versions.rb.erb +0 -0
@@ -0,0 +1,343 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PaperTrail
|
4
|
+
module Events
|
5
|
+
# We refer to times in the lifecycle of a record as "events". There are
|
6
|
+
# three events:
|
7
|
+
#
|
8
|
+
# - create
|
9
|
+
# - `after_create` we call `RecordTrail#record_create`
|
10
|
+
# - update
|
11
|
+
# - `after_update` we call `RecordTrail#record_update`
|
12
|
+
# - `after_touch` we call `RecordTrail#record_update`
|
13
|
+
# - `RecordTrail#save_with_version` calls `RecordTrail#record_update`
|
14
|
+
# - `RecordTrail#update_columns` is also referred to as an update, though
|
15
|
+
# it uses `RecordTrail#record_update_columns` rather than
|
16
|
+
# `RecordTrail#record_update`
|
17
|
+
# - destroy
|
18
|
+
# - `before_destroy` or `after_destroy` we call `RecordTrail#record_destroy`
|
19
|
+
#
|
20
|
+
# The value inserted into the `event` column of the versions table can also
|
21
|
+
# be overridden by the user, with `paper_trail_event`.
|
22
|
+
#
|
23
|
+
# @api private
|
24
|
+
class Base
|
25
|
+
E_FORBIDDEN_METADATA_KEY = <<-EOS.squish
|
26
|
+
Forbidden metadata key: %s. As of PT 14, the following metadata keys are
|
27
|
+
forbidden: %s
|
28
|
+
EOS
|
29
|
+
FORBIDDEN_METADATA_KEYS = %i[
|
30
|
+
created_at
|
31
|
+
id
|
32
|
+
item_id
|
33
|
+
item_subtype
|
34
|
+
item_type
|
35
|
+
updated_at
|
36
|
+
].freeze
|
37
|
+
|
38
|
+
# @api private
|
39
|
+
def initialize(record, in_after_callback)
|
40
|
+
@record = record
|
41
|
+
@in_after_callback = in_after_callback
|
42
|
+
end
|
43
|
+
|
44
|
+
# Determines whether it is appropriate to generate a new version
|
45
|
+
# instance. A timestamp-only update (e.g. only `updated_at` changed) is
|
46
|
+
# considered notable unless an ignored attribute was also changed.
|
47
|
+
#
|
48
|
+
# @api private
|
49
|
+
def changed_notably?
|
50
|
+
if ignored_attr_has_changed?
|
51
|
+
timestamps = @record.send(:timestamp_attributes_for_update_in_model).map(&:to_s)
|
52
|
+
(notably_changed - timestamps).any?
|
53
|
+
else
|
54
|
+
notably_changed.any?
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
# @api private
|
61
|
+
def assert_metadatum_key_is_permitted(key)
|
62
|
+
return unless FORBIDDEN_METADATA_KEYS.include?(key.to_sym)
|
63
|
+
raise PaperTrail::InvalidOption,
|
64
|
+
format(E_FORBIDDEN_METADATA_KEY, key, FORBIDDEN_METADATA_KEYS)
|
65
|
+
end
|
66
|
+
|
67
|
+
# Rails 5.1 changed the API of `ActiveRecord::Dirty`. See
|
68
|
+
# https://github.com/paper-trail-gem/paper_trail/pull/899
|
69
|
+
#
|
70
|
+
# @api private
|
71
|
+
def attribute_changed_in_latest_version?(attr_name)
|
72
|
+
if @in_after_callback
|
73
|
+
@record.saved_change_to_attribute?(attr_name.to_s)
|
74
|
+
else
|
75
|
+
@record.attribute_changed?(attr_name.to_s)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# @api private
|
80
|
+
def nonskipped_attributes_before_change(is_touch)
|
81
|
+
record_attributes = @record.attributes.except(*@record.paper_trail_options[:skip])
|
82
|
+
record_attributes.each_key do |k|
|
83
|
+
if @record.class.column_names.include?(k)
|
84
|
+
record_attributes[k] = attribute_in_previous_version(k, is_touch)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# Rails 5.1 changed the API of `ActiveRecord::Dirty`. See
|
90
|
+
# https://github.com/paper-trail-gem/paper_trail/pull/899
|
91
|
+
#
|
92
|
+
# Event can be any of the three (create, update, destroy).
|
93
|
+
#
|
94
|
+
# @api private
|
95
|
+
def attribute_in_previous_version(attr_name, is_touch)
|
96
|
+
if @in_after_callback && !is_touch
|
97
|
+
# For most events, we want the original value of the attribute, before
|
98
|
+
# the last save.
|
99
|
+
@record.attribute_before_last_save(attr_name.to_s)
|
100
|
+
else
|
101
|
+
# We are either performing a `record_destroy` or a
|
102
|
+
# `record_update(is_touch: true)`.
|
103
|
+
@record.attribute_in_database(attr_name.to_s)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
# @api private
|
108
|
+
def calculated_ignored_array
|
109
|
+
ignore = @record.paper_trail_options[:ignore].dup
|
110
|
+
# Remove Hash arguments and then evaluate whether the attributes (the
|
111
|
+
# keys of the hash) should also get pushed into the collection.
|
112
|
+
ignore.delete_if do |obj|
|
113
|
+
obj.is_a?(Hash) &&
|
114
|
+
obj.each { |attr, condition|
|
115
|
+
ignore << attr if condition.respond_to?(:call) && condition.call(@record)
|
116
|
+
}
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
# @api private
|
121
|
+
def changed_and_not_ignored
|
122
|
+
skip = @record.paper_trail_options[:skip]
|
123
|
+
(changed_in_latest_version - calculated_ignored_array) - skip
|
124
|
+
end
|
125
|
+
|
126
|
+
# @api private
|
127
|
+
def changed_in_latest_version
|
128
|
+
# Memoized to reduce memory usage
|
129
|
+
@changed_in_latest_version ||= changes_in_latest_version.keys
|
130
|
+
end
|
131
|
+
|
132
|
+
# Memoized to reduce memory usage
|
133
|
+
#
|
134
|
+
# @api private
|
135
|
+
def changes_in_latest_version
|
136
|
+
@changes_in_latest_version ||= load_changes_in_latest_version
|
137
|
+
end
|
138
|
+
|
139
|
+
# @api private
|
140
|
+
def evaluate_only
|
141
|
+
only = @record.paper_trail_options[:only].dup
|
142
|
+
# Remove Hash arguments and then evaluate whether the attributes (the
|
143
|
+
# keys of the hash) should also get pushed into the collection.
|
144
|
+
only.delete_if do |obj|
|
145
|
+
obj.is_a?(Hash) &&
|
146
|
+
obj.each { |attr, condition|
|
147
|
+
only << attr if condition.respond_to?(:call) && condition.call(@record)
|
148
|
+
}
|
149
|
+
end
|
150
|
+
only
|
151
|
+
end
|
152
|
+
|
153
|
+
# An attributed is "ignored" if it is listed in the `:ignore` option
|
154
|
+
# and/or the `:skip` option. Returns true if an ignored attribute has
|
155
|
+
# changed.
|
156
|
+
#
|
157
|
+
# @api private
|
158
|
+
def ignored_attr_has_changed?
|
159
|
+
ignored = calculated_ignored_array + @record.paper_trail_options[:skip]
|
160
|
+
ignored.any? && (changed_in_latest_version & ignored).any?
|
161
|
+
end
|
162
|
+
|
163
|
+
# Rails 5.1 changed the API of `ActiveRecord::Dirty`. See
|
164
|
+
# https://github.com/paper-trail-gem/paper_trail/pull/899
|
165
|
+
#
|
166
|
+
# @api private
|
167
|
+
def load_changes_in_latest_version
|
168
|
+
if @in_after_callback
|
169
|
+
@record.saved_changes
|
170
|
+
else
|
171
|
+
@record.changes
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
# PT 10 has a new optional column, `item_subtype`
|
176
|
+
#
|
177
|
+
# @api private
|
178
|
+
def merge_item_subtype_into(data)
|
179
|
+
if @record.class.paper_trail.version_class.columns_hash.key?("item_subtype")
|
180
|
+
data.merge!(item_subtype: @record.class.name)
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
# Updates `data` from the model's `meta` option and from `controller_info`.
|
185
|
+
# Metadata is always recorded; that means all three events (create, update,
|
186
|
+
# destroy) and `update_columns`.
|
187
|
+
#
|
188
|
+
# @api private
|
189
|
+
def merge_metadata_into(data)
|
190
|
+
merge_metadata_from_model_into(data)
|
191
|
+
merge_metadata_from_controller_into(data)
|
192
|
+
end
|
193
|
+
|
194
|
+
# Updates `data` from `controller_info`.
|
195
|
+
#
|
196
|
+
# @api private
|
197
|
+
def merge_metadata_from_controller_into(data)
|
198
|
+
metadata = PaperTrail.request.controller_info || {}
|
199
|
+
metadata.keys.each { |k| assert_metadatum_key_is_permitted(k) }
|
200
|
+
data.merge(metadata)
|
201
|
+
end
|
202
|
+
|
203
|
+
# Updates `data` from the model's `meta` option.
|
204
|
+
#
|
205
|
+
# @api private
|
206
|
+
def merge_metadata_from_model_into(data)
|
207
|
+
@record.paper_trail_options[:meta].each do |k, v|
|
208
|
+
assert_metadatum_key_is_permitted(k)
|
209
|
+
data[k] = model_metadatum(v, data[:event])
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
# Given a `value` from the model's `meta` option, returns an object to be
|
214
|
+
# persisted. The `value` can be a simple scalar value, but it can also
|
215
|
+
# be a symbol that names a model method, or even a Proc.
|
216
|
+
#
|
217
|
+
# @api private
|
218
|
+
def model_metadatum(value, event)
|
219
|
+
if value.respond_to?(:call)
|
220
|
+
value.call(@record)
|
221
|
+
elsif value.is_a?(Symbol) && @record.respond_to?(value, true)
|
222
|
+
metadatum_from_model_method(event, value)
|
223
|
+
else
|
224
|
+
value
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
# The model method can either be an attribute or a non-attribute method.
|
229
|
+
#
|
230
|
+
# If it is an attribute that is changing in an existing object,
|
231
|
+
# be sure to grab the correct version.
|
232
|
+
#
|
233
|
+
# @api private
|
234
|
+
def metadatum_from_model_method(event, method)
|
235
|
+
if event != "create" &&
|
236
|
+
@record.has_attribute?(method) &&
|
237
|
+
attribute_changed_in_latest_version?(method)
|
238
|
+
attribute_in_previous_version(method, false)
|
239
|
+
else
|
240
|
+
@record.send(method)
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
# @api private
|
245
|
+
def notable_changes
|
246
|
+
changes_in_latest_version.delete_if { |k, _v|
|
247
|
+
notably_changed.exclude?(k)
|
248
|
+
}
|
249
|
+
end
|
250
|
+
|
251
|
+
# @api private
|
252
|
+
def notably_changed
|
253
|
+
# Memoized to reduce memory usage
|
254
|
+
@notably_changed ||= begin
|
255
|
+
only = evaluate_only
|
256
|
+
cani = changed_and_not_ignored
|
257
|
+
only.empty? ? cani : (cani & only)
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
# Returns hash of attributes (with appropriate attributes serialized),
|
262
|
+
# omitting attributes to be skipped.
|
263
|
+
#
|
264
|
+
# @api private
|
265
|
+
def object_attrs_for_paper_trail(is_touch)
|
266
|
+
attrs = nonskipped_attributes_before_change(is_touch)
|
267
|
+
AttributeSerializers::ObjectAttribute.new(@record.class).serialize(attrs)
|
268
|
+
attrs
|
269
|
+
end
|
270
|
+
|
271
|
+
# @api private
|
272
|
+
def prepare_object_changes(changes)
|
273
|
+
changes = serialize_object_changes(changes)
|
274
|
+
recordable_object_changes(changes)
|
275
|
+
end
|
276
|
+
|
277
|
+
# Returns an object which can be assigned to the `object_changes`
|
278
|
+
# attribute of a nascent version record. If the `object_changes` column is
|
279
|
+
# a postgres `json` column, then a hash can be used in the assignment,
|
280
|
+
# otherwise the column is a `text` column, and we must perform the
|
281
|
+
# serialization here, using `PaperTrail.serializer`.
|
282
|
+
#
|
283
|
+
# @api private
|
284
|
+
# @param changes HashWithIndifferentAccess
|
285
|
+
def recordable_object_changes(changes)
|
286
|
+
if PaperTrail.config.object_changes_adapter.respond_to?(:diff)
|
287
|
+
# We'd like to avoid the `to_hash` here, because it increases memory
|
288
|
+
# usage, but that would be a breaking change because
|
289
|
+
# `object_changes_adapter` expects a plain `Hash`, not a
|
290
|
+
# `HashWithIndifferentAccess`.
|
291
|
+
changes = PaperTrail.config.object_changes_adapter.diff(changes.to_hash)
|
292
|
+
end
|
293
|
+
|
294
|
+
if @record.class.paper_trail.version_class.object_changes_col_is_json?
|
295
|
+
changes
|
296
|
+
else
|
297
|
+
PaperTrail.serializer.dump(changes)
|
298
|
+
end
|
299
|
+
end
|
300
|
+
|
301
|
+
# Returns a boolean indicating whether to store serialized version diffs
|
302
|
+
# in the `object_changes` column of the version record.
|
303
|
+
#
|
304
|
+
# @api private
|
305
|
+
def record_object_changes?
|
306
|
+
@record.class.paper_trail.version_class.column_names.include?("object_changes")
|
307
|
+
end
|
308
|
+
|
309
|
+
# Returns a boolean indicating whether to store the original object during save.
|
310
|
+
#
|
311
|
+
# @api private
|
312
|
+
def record_object?
|
313
|
+
@record.class.paper_trail.version_class.column_names.include?("object")
|
314
|
+
end
|
315
|
+
|
316
|
+
# Returns an object which can be assigned to the `object` attribute of a
|
317
|
+
# nascent version record. If the `object` column is a postgres `json`
|
318
|
+
# column, then a hash can be used in the assignment, otherwise the column
|
319
|
+
# is a `text` column, and we must perform the serialization here, using
|
320
|
+
# `PaperTrail.serializer`.
|
321
|
+
#
|
322
|
+
# @api private
|
323
|
+
def recordable_object(is_touch)
|
324
|
+
if @record.class.paper_trail.version_class.object_col_is_json?
|
325
|
+
object_attrs_for_paper_trail(is_touch)
|
326
|
+
else
|
327
|
+
PaperTrail.serializer.dump(object_attrs_for_paper_trail(is_touch))
|
328
|
+
end
|
329
|
+
end
|
330
|
+
|
331
|
+
# @api private
|
332
|
+
def serialize_object_changes(changes)
|
333
|
+
AttributeSerializers::ObjectChangesAttribute.
|
334
|
+
new(@record.class).
|
335
|
+
serialize(changes)
|
336
|
+
|
337
|
+
# We'd like to convert this `HashWithIndifferentAccess` to a plain
|
338
|
+
# `Hash`, but we don't, to save memory.
|
339
|
+
changes
|
340
|
+
end
|
341
|
+
end
|
342
|
+
end
|
343
|
+
end
|
@@ -0,0 +1,32 @@
|
|
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
|
+
item: @record,
|
17
|
+
event: @record.paper_trail_event || "create",
|
18
|
+
whodunnit: PaperTrail.request.whodunnit
|
19
|
+
}
|
20
|
+
if @record.respond_to?(:updated_at)
|
21
|
+
data[:created_at] = @record.updated_at
|
22
|
+
end
|
23
|
+
if record_object_changes? && changed_notably?
|
24
|
+
changes = notable_changes
|
25
|
+
data[:object_changes] = prepare_object_changes(changes)
|
26
|
+
end
|
27
|
+
merge_item_subtype_into(data)
|
28
|
+
merge_metadata_into(data)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,42 @@
|
|
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
|
+
data[:object_changes] = prepare_object_changes(notable_changes)
|
26
|
+
end
|
27
|
+
merge_item_subtype_into(data)
|
28
|
+
merge_metadata_into(data)
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
# Rails' implementation (eg. `@record.saved_changes`) returns nothing on
|
34
|
+
# destroy, so we have to build the hash we want.
|
35
|
+
#
|
36
|
+
# @override
|
37
|
+
def changes_in_latest_version
|
38
|
+
@record.attributes.transform_values { |value| [value, nil] }
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,76 @@
|
|
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
|
+
item: @record,
|
29
|
+
event: @record.paper_trail_event || "update",
|
30
|
+
whodunnit: PaperTrail.request.whodunnit
|
31
|
+
}
|
32
|
+
if record_object?
|
33
|
+
data[:object] = recordable_object(@is_touch)
|
34
|
+
end
|
35
|
+
merge_object_changes_into(data)
|
36
|
+
merge_item_subtype_into(data)
|
37
|
+
merge_metadata_into(data)
|
38
|
+
end
|
39
|
+
|
40
|
+
# If it is a touch event, and changed are empty, it is assumed to be
|
41
|
+
# implicit `touch` mutation, and will a version is created.
|
42
|
+
#
|
43
|
+
# See https://github.com/rails/rails/commit/dcb825902d79d0f6baba956f7c6ec5767611353e
|
44
|
+
#
|
45
|
+
# @api private
|
46
|
+
def changed_notably?
|
47
|
+
if @is_touch && changes_in_latest_version.empty?
|
48
|
+
true
|
49
|
+
else
|
50
|
+
super
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
# @api private
|
57
|
+
def merge_object_changes_into(data)
|
58
|
+
if record_object_changes?
|
59
|
+
changes = @force_changes.nil? ? notable_changes : @force_changes
|
60
|
+
data[:object_changes] = prepare_object_changes(changes)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# `touch` cannot record `object_changes` because rails' `touch` does not
|
65
|
+
# perform dirty-tracking. Specifically, methods from `Dirty`, like
|
66
|
+
# `saved_changes`, return the same values before and after `touch`.
|
67
|
+
#
|
68
|
+
# See https://github.com/rails/rails/issues/33429
|
69
|
+
#
|
70
|
+
# @api private
|
71
|
+
def record_object_changes?
|
72
|
+
!@is_touch && super
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -1,5 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
#
|
4
|
-
#
|
3
|
+
# Either ActiveRecord has already been loaded by the Lazy Load Hook in our
|
4
|
+
# Railtie, or else we load it now.
|
5
|
+
require "active_record"
|
6
|
+
::PaperTrail::Compatibility.check_activerecord(::ActiveRecord.gem_version)
|
7
|
+
|
8
|
+
# Now we can load the parts of PT that depend on AR.
|
9
|
+
require "paper_trail/has_paper_trail"
|
10
|
+
require "paper_trail/reifier"
|
5
11
|
require "paper_trail/frameworks/active_record/models/paper_trail/version"
|
12
|
+
ActiveRecord::Base.include PaperTrail::Model
|
@@ -25,9 +25,7 @@ module PaperTrail
|
|
25
25
|
# @api public
|
26
26
|
def user_for_paper_trail
|
27
27
|
return unless defined?(current_user)
|
28
|
-
|
29
|
-
rescue NoMethodError
|
30
|
-
current_user
|
28
|
+
current_user.try(:id) || current_user
|
31
29
|
end
|
32
30
|
|
33
31
|
# Returns any information about the controller or request that you
|
@@ -103,9 +101,3 @@ module PaperTrail
|
|
103
101
|
end
|
104
102
|
end
|
105
103
|
end
|
106
|
-
|
107
|
-
if defined?(::ActionController)
|
108
|
-
::ActiveSupport.on_load(:action_controller) do
|
109
|
-
include ::PaperTrail::Rails::Controller
|
110
|
-
end
|
111
|
-
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PaperTrail
|
4
|
+
# Represents code to load within Rails framework. See documentation in
|
5
|
+
# `railties/lib/rails/railtie.rb`.
|
6
|
+
# @api private
|
7
|
+
class Railtie < ::Rails::Railtie
|
8
|
+
# PaperTrail only has one initializer.
|
9
|
+
#
|
10
|
+
# We specify `before: "load_config_initializers"` to ensure that the PT
|
11
|
+
# initializer happens before "app initializers" (those defined in
|
12
|
+
# the app's `config/initalizers`).
|
13
|
+
initializer "paper_trail", before: "load_config_initializers" do
|
14
|
+
# `on_load` is a "lazy load hook". It "declares a block that will be
|
15
|
+
# executed when a Rails component is fully loaded". (See
|
16
|
+
# `active_support/lazy_load_hooks.rb`)
|
17
|
+
ActiveSupport.on_load(:action_controller) do
|
18
|
+
require "paper_trail/frameworks/rails/controller"
|
19
|
+
|
20
|
+
# Mix our extensions into `ActionController::Base`, which is `self`
|
21
|
+
# because of the `class_eval` in `lazy_load_hooks.rb`.
|
22
|
+
include PaperTrail::Rails::Controller
|
23
|
+
end
|
24
|
+
|
25
|
+
ActiveSupport.on_load(:active_record) do
|
26
|
+
require "paper_trail/frameworks/active_record"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -12,7 +12,7 @@ module PaperTrail
|
|
12
12
|
# `.paper_trail` and `#paper_trail`.
|
13
13
|
module Model
|
14
14
|
def self.included(base)
|
15
|
-
base.
|
15
|
+
base.extend ClassMethods
|
16
16
|
end
|
17
17
|
|
18
18
|
# :nodoc:
|
@@ -23,18 +23,18 @@ module PaperTrail
|
|
23
23
|
# Options:
|
24
24
|
#
|
25
25
|
# - :on - The events to track (optional; defaults to all of them). Set
|
26
|
-
# to an array of `:create`, `:update`, `:destroy` as desired.
|
27
|
-
# - :class_name - The name of a custom Version class
|
28
|
-
#
|
26
|
+
# to an array of `:create`, `:update`, `:destroy` and `:touch` as desired.
|
27
|
+
# - :class_name (deprecated) - The name of a custom Version class that
|
28
|
+
# includes `PaperTrail::VersionConcern`.
|
29
29
|
# - :ignore - An array of attributes for which a new `Version` will not be
|
30
|
-
# created if only they change. It can also
|
30
|
+
# created if only they change. It can also accept a Hash as an
|
31
31
|
# argument where the key is the attribute to ignore (a `String` or
|
32
32
|
# `Symbol`), which will only be ignored if the value is a `Proc` which
|
33
33
|
# returns truthily.
|
34
34
|
# - :if, :unless - Procs that allow to specify conditions when to save
|
35
35
|
# versions for an object.
|
36
36
|
# - :only - Inverse of `ignore`. A new `Version` will be created only
|
37
|
-
# for these attributes if supplied it can also
|
37
|
+
# for these attributes if supplied it can also accept a Hash as an
|
38
38
|
# argument where the key is the attribute to track (a `String` or
|
39
39
|
# `Symbol`), which will only be counted if the value is a `Proc` which
|
40
40
|
# returns truthily.
|
@@ -47,22 +47,25 @@ module PaperTrail
|
|
47
47
|
# are called with `self`, i.e. the model with the paper trail). See
|
48
48
|
# `PaperTrail::Controller.info_for_paper_trail` for how to store data
|
49
49
|
# from the controller.
|
50
|
-
# - :versions -
|
51
|
-
#
|
50
|
+
# - :versions - Either,
|
51
|
+
# - A String (deprecated) - The name to use for the versions
|
52
|
+
# association. Default is `:versions`.
|
53
|
+
# - A Hash - options passed to `has_many`, plus `name:` and `scope:`.
|
52
54
|
# - :version - The name to use for the method which returns the version
|
53
55
|
# the instance was reified from. Default is `:version`.
|
54
|
-
#
|
55
|
-
#
|
56
|
-
#
|
57
|
-
#
|
58
|
-
#
|
59
|
-
#
|
60
|
-
#
|
61
|
-
#
|
56
|
+
#
|
57
|
+
# Plugins like the experimental `paper_trail-association_tracking` gem
|
58
|
+
# may accept additional options.
|
59
|
+
#
|
60
|
+
# You can define a default set of options via the configurable
|
61
|
+
# `PaperTrail.config.has_paper_trail_defaults` hash in your applications
|
62
|
+
# initializer. The hash can contain any of the following options and will
|
63
|
+
# provide an overridable default for all models.
|
62
64
|
#
|
63
65
|
# @api public
|
64
66
|
def has_paper_trail(options = {})
|
65
|
-
|
67
|
+
defaults = PaperTrail.config.has_paper_trail_defaults
|
68
|
+
paper_trail.setup(defaults.merge(options))
|
66
69
|
end
|
67
70
|
|
68
71
|
# @api public
|