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.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +20 -0
  3. data/lib/generators/paper_trail/install/USAGE +3 -0
  4. data/lib/generators/paper_trail/{install_generator.rb → install/install_generator.rb} +27 -38
  5. data/lib/generators/paper_trail/{templates → install/templates}/create_versions.rb.erb +5 -3
  6. data/lib/generators/paper_trail/migration_generator.rb +38 -0
  7. data/lib/generators/paper_trail/update_item_subtype/USAGE +4 -0
  8. data/lib/generators/paper_trail/update_item_subtype/templates/update_versions_for_item_subtype.rb.erb +85 -0
  9. data/lib/generators/paper_trail/update_item_subtype/update_item_subtype_generator.rb +19 -0
  10. data/lib/paper_trail/attribute_serializers/attribute_serializer_factory.rb +24 -10
  11. data/lib/paper_trail/attribute_serializers/cast_attribute_serializer.rb +17 -45
  12. data/lib/paper_trail/compatibility.rb +51 -0
  13. data/lib/paper_trail/config.rb +9 -2
  14. data/lib/paper_trail/errors.rb +33 -0
  15. data/lib/paper_trail/events/base.rb +343 -0
  16. data/lib/paper_trail/events/create.rb +32 -0
  17. data/lib/paper_trail/events/destroy.rb +42 -0
  18. data/lib/paper_trail/events/update.rb +76 -0
  19. data/lib/paper_trail/frameworks/active_record.rb +9 -2
  20. data/lib/paper_trail/frameworks/rails/controller.rb +1 -9
  21. data/lib/paper_trail/frameworks/rails/railtie.rb +30 -0
  22. data/lib/paper_trail/frameworks/rails.rb +1 -2
  23. data/lib/paper_trail/has_paper_trail.rb +20 -17
  24. data/lib/paper_trail/model_config.rb +124 -87
  25. data/lib/paper_trail/queries/versions/where_attribute_changes.rb +50 -0
  26. data/lib/paper_trail/queries/versions/where_object.rb +4 -1
  27. data/lib/paper_trail/queries/versions/where_object_changes.rb +9 -14
  28. data/lib/paper_trail/queries/versions/where_object_changes_from.rb +57 -0
  29. data/lib/paper_trail/queries/versions/where_object_changes_to.rb +57 -0
  30. data/lib/paper_trail/record_trail.rb +137 -436
  31. data/lib/paper_trail/reifier.rb +41 -25
  32. data/lib/paper_trail/request.rb +22 -25
  33. data/lib/paper_trail/serializers/json.rb +0 -10
  34. data/lib/paper_trail/serializers/yaml.rb +41 -11
  35. data/lib/paper_trail/type_serializers/postgres_array_serializer.rb +1 -15
  36. data/lib/paper_trail/version_concern.rb +152 -62
  37. data/lib/paper_trail/version_number.rb +2 -2
  38. data/lib/paper_trail.rb +23 -123
  39. metadata +152 -61
  40. data/lib/generators/paper_trail/USAGE +0 -2
  41. data/lib/paper_trail/frameworks/rails/engine.rb +0 -14
  42. /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
- # This file only needs to be loaded if the gem is being used outside of Rails,
4
- # since otherwise the model(s) will get loaded in via the `Rails::Engine`.
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
- ActiveSupport::VERSION::MAJOR >= 4 ? current_user.try!(:id) : current_user.try(:id)
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
@@ -1,4 +1,3 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "paper_trail/frameworks/rails/controller"
4
- require "paper_trail/frameworks/rails/engine"
3
+ require "paper_trail/frameworks/rails/railtie"
@@ -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.send :extend, ClassMethods
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. This class should
28
- # inherit from `PaperTrail::Version`.
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 aceept a Hash as an
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 aceept a Hash as an
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 - The name to use for the versions association. Default
51
- # is `:versions`.
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
- # - :save_changes - Whether or not to save changes to the object_changes
55
- # column if it exists. Default is true
56
- # - :join_tables - If the model has a has_and_belongs_to_many relation
57
- # with an unpapertrailed model, passing the name of the association to
58
- # the join_tables option will paper trail the join table but not save
59
- # the other model, allowing reification of the association but with the
60
- # other models latest state (if the other model is paper trailed, this
61
- # option does nothing)
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
- paper_trail.setup(options)
67
+ defaults = PaperTrail.config.has_paper_trail_defaults
68
+ paper_trail.setup(defaults.merge(options))
66
69
  end
67
70
 
68
71
  # @api public