paper_trail 13.0.0 → 16.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 776e912c4a06b036014b0301b30a1438974c00b8e63b6e0b78fbffe4dfca7824
4
- data.tar.gz: 691060404fbb2b4a3f66cf40c0b6187df7a9e8439854097ffbad433a7bdd6e75
3
+ metadata.gz: 0a834abeee917bf2c090c6ef7f6da09437b12aff37c0bcf94f8ff88ca1434203
4
+ data.tar.gz: 6fdf12293b452fe7dcce563e5f4547d9ae0081399b5cf6885308ea451f29b6ed
5
5
  SHA512:
6
- metadata.gz: 7110ad57841a431cb50318a79db55ba65f279b839d72009dc2aef961a57c9b044d4d8d706062bbff4cea7fe9b7b2db0a3d83c966518eeffddd66cca6f9e63ae3
7
- data.tar.gz: d5ebe8b0a5f8dd4df889b1e6a71e81ab5fb1e752a034dd400decd5ef1bdb91441bce9b6061980db7d443796309995c745063175bf7cdb85819028f45fc20dc88
6
+ metadata.gz: 82fbf4c2ec002d52ee5b4023cbef6a16ef5530e04852a4a3d65fb38b468395016db6230e2f89c199a13549bb9da98a1f53a2b29148a94455bc6470ea7912dcaf
7
+ data.tar.gz: 9944b2f2e66c9fdc10fe2ecf6f9c560b73da21e49d98504f826158e92faeef5b9cad7a4a1cb1f033217c1f66e230d4c8e2df034c4751298a93df5bf946ef108e
@@ -35,7 +35,8 @@ module PaperTrail
35
35
  "create_versions",
36
36
  item_type_options: item_type_options,
37
37
  versions_table_options: versions_table_options,
38
- item_id_type_options: item_id_type_options
38
+ item_id_type_options: item_id_type_options,
39
+ version_table_primary_key_type: version_table_primary_key_type
39
40
  )
40
41
  if options.with_changes?
41
42
  add_paper_trail_migration("add_object_changes_to_versions")
@@ -49,6 +50,15 @@ module PaperTrail
49
50
  options.uuid? ? "string" : "bigint"
50
51
  end
51
52
 
53
+ # To use uuid for version table primary key
54
+ def version_table_primary_key_type
55
+ if options.uuid?
56
+ ", id: :uuid"
57
+ else
58
+ ""
59
+ end
60
+ end
61
+
52
62
  # MySQL 5.6 utf8mb4 limit is 191 chars for keys used in indexes.
53
63
  # See https://github.com/paper-trail-gem/paper_trail/issues/651
54
64
  def item_type_options
@@ -9,12 +9,10 @@ class CreateVersions < ActiveRecord::Migration<%= migration_version %>
9
9
  TEXT_BYTES = 1_073_741_823
10
10
 
11
11
  def change
12
- create_table :versions<%= versions_table_options %> do |t|
13
- t.string :item_type<%= item_type_options %>
14
- t.<%= item_id_type_options %> :item_id, null: false
15
- t.string :event, null: false
12
+ create_table :versions<%= versions_table_options %><%= version_table_primary_key_type %> do |t|
13
+ # Consider using bigint type for performance if you are going to store only numeric ids.
14
+ # t.bigint :whodunnit
16
15
  t.string :whodunnit
17
- t.text :object, limit: TEXT_BYTES
18
16
 
19
17
  # Known issue in MySQL: fractional second precision
20
18
  # -------------------------------------------------
@@ -32,6 +30,11 @@ class CreateVersions < ActiveRecord::Migration<%= migration_version %>
32
30
  # MySQL users should use the following line for `created_at`
33
31
  # t.datetime :created_at, limit: 6
34
32
  t.datetime :created_at
33
+
34
+ t.<%= item_id_type_options %> :item_id, null: false
35
+ t.string :item_type<%= item_type_options %>
36
+ t.string :event, null: false
37
+ t.text :object, limit: TEXT_BYTES
35
38
  end
36
39
  add_index :versions, %i[item_type item_id]
37
40
  end
@@ -32,7 +32,7 @@ module PaperTrail
32
32
  if defined_enums[attr] && val.is_a?(::String)
33
33
  # Because PT 4 used to save the string version of enums to `object_changes`
34
34
  val
35
- elsif PaperTrail::RAILS_GTE_7_0 && val.is_a?(ActiveRecord::Type::Time::Value)
35
+ elsif PaperTrail.active_record_gte_7_0? && val.is_a?(ActiveRecord::Type::Time::Value)
36
36
  # Because Rails 7 time attribute throws a delegation error when you deserialize
37
37
  # it with the factory.
38
38
  # See ActiveRecord::Type::Time::Value crashes when loaded from YAML on rails 7.0
@@ -8,6 +8,12 @@ module PaperTrail
8
8
  class ObjectAttribute
9
9
  def initialize(model_class)
10
10
  @model_class = model_class
11
+
12
+ # ActiveRecord since 7.0 has a built-in encryption mechanism
13
+ @encrypted_attributes =
14
+ if PaperTrail.active_record_gte_7_0?
15
+ @model_class.encrypted_attributes&.map(&:to_s)
16
+ end
11
17
  end
12
18
 
13
19
  def serialize(attributes)
@@ -23,14 +29,18 @@ module PaperTrail
23
29
  # Modifies `attributes` in place.
24
30
  # TODO: Return a new hash instead.
25
31
  def alter(attributes, serialization_method)
26
- # Don't serialize before values before inserting into columns of type
32
+ # Don't serialize non-encrypted before values before inserting into columns of type
27
33
  # `JSON` on `PostgreSQL` databases.
28
- return attributes if object_col_is_json?
34
+ attributes_to_serialize =
35
+ object_col_is_json? ? attributes.slice(*@encrypted_attributes) : attributes
36
+ return attributes if attributes_to_serialize.blank?
29
37
 
30
38
  serializer = CastAttributeSerializer.new(@model_class)
31
- attributes.each do |key, value|
39
+ attributes_to_serialize.each do |key, value|
32
40
  attributes[key] = serializer.send(serialization_method, key, value)
33
41
  end
42
+
43
+ attributes
34
44
  end
35
45
 
36
46
  def object_col_is_json?
@@ -8,6 +8,12 @@ module PaperTrail
8
8
  class ObjectChangesAttribute
9
9
  def initialize(item_class)
10
10
  @item_class = item_class
11
+
12
+ # ActiveRecord since 7.0 has a built-in encryption mechanism
13
+ @encrypted_attributes =
14
+ if PaperTrail.active_record_gte_7_0?
15
+ @item_class.encrypted_attributes&.map(&:to_s)
16
+ end
11
17
  end
12
18
 
13
19
  def serialize(changes)
@@ -23,17 +29,21 @@ module PaperTrail
23
29
  # Modifies `changes` in place.
24
30
  # TODO: Return a new hash instead.
25
31
  def alter(changes, serialization_method)
26
- # Don't serialize before values before inserting into columns of type
32
+ # Don't serialize non-encrypted before values before inserting into columns of type
27
33
  # `JSON` on `PostgreSQL` databases.
28
- return changes if object_changes_col_is_json?
34
+ changes_to_serialize =
35
+ object_changes_col_is_json? ? changes.slice(*@encrypted_attributes) : changes.clone
36
+ return changes if changes_to_serialize.blank?
29
37
 
30
38
  serializer = CastAttributeSerializer.new(@item_class)
31
- changes.clone.each do |key, change|
39
+ changes_to_serialize.each do |key, change|
32
40
  # `change` is an Array with two elements, representing before and after.
33
41
  changes[key] = Array(change).map do |value|
34
42
  serializer.send(serialization_method, key, value)
35
43
  end
36
44
  end
45
+
46
+ changes
37
47
  end
38
48
 
39
49
  def object_changes_col_is_json?
@@ -17,8 +17,8 @@ module PaperTrail
17
17
  # newer rails versions. Most PT users should avoid incompatible rails
18
18
  # versions.
19
19
  module Compatibility
20
- ACTIVERECORD_GTE = ">= 5.2" # enforced in gemspec
21
- ACTIVERECORD_LT = "< 7.1" # not enforced in gemspec
20
+ ACTIVERECORD_GTE = ">= 6.1" # enforced in gemspec
21
+ ACTIVERECORD_LT = "< 8.1" # not enforced in gemspec
22
22
 
23
23
  E_INCOMPATIBLE_AR = <<-EOS
24
24
  PaperTrail %s is not compatible with ActiveRecord %s. We allow PT
@@ -14,7 +14,8 @@ module PaperTrail
14
14
  :object_changes_adapter,
15
15
  :serializer,
16
16
  :version_limit,
17
- :has_paper_trail_defaults
17
+ :has_paper_trail_defaults,
18
+ :version_error_behavior
18
19
  )
19
20
 
20
21
  def initialize
@@ -25,6 +26,7 @@ module PaperTrail
25
26
  # Variables which affect all threads, whose access is *not* synchronized.
26
27
  @serializer = PaperTrail::Serializers::YAML
27
28
  @has_paper_trail_defaults = {}
29
+ @version_error_behavior = :legacy
28
30
  end
29
31
 
30
32
  # Indicates whether PaperTrail is on or off. Default: true.
@@ -22,6 +22,19 @@ module PaperTrail
22
22
  #
23
23
  # @api private
24
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
+
25
38
  # @api private
26
39
  def initialize(record, in_after_callback)
27
40
  @record = record
@@ -44,6 +57,13 @@ module PaperTrail
44
57
 
45
58
  private
46
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
+
47
67
  # Rails 5.1 changed the API of `ActiveRecord::Dirty`. See
48
68
  # https://github.com/paper-trail-gem/paper_trail/pull/899
49
69
  #
@@ -175,7 +195,9 @@ module PaperTrail
175
195
  #
176
196
  # @api private
177
197
  def merge_metadata_from_controller_into(data)
178
- data.merge(PaperTrail.request.controller_info || {})
198
+ metadata = PaperTrail.request.controller_info || {}
199
+ metadata.keys.each { |k| assert_metadatum_key_is_permitted(k) }
200
+ data.merge(metadata)
179
201
  end
180
202
 
181
203
  # Updates `data` from the model's `meta` option.
@@ -183,6 +205,7 @@ module PaperTrail
183
205
  # @api private
184
206
  def merge_metadata_from_model_into(data)
185
207
  @record.paper_trail_options[:meta].each do |k, v|
208
+ assert_metadatum_key_is_permitted(k)
186
209
  data[k] = model_metadatum(v, data[:event])
187
210
  end
188
211
  end
@@ -221,7 +244,7 @@ module PaperTrail
221
244
  # @api private
222
245
  def notable_changes
223
246
  changes_in_latest_version.delete_if { |k, _v|
224
- !notably_changed.include?(k)
247
+ notably_changed.exclude?(k)
225
248
  }
226
249
  end
227
250
 
@@ -29,9 +29,6 @@ module PaperTrail
29
29
  event: @record.paper_trail_event || "update",
30
30
  whodunnit: PaperTrail.request.whodunnit
31
31
  }
32
- if @record.respond_to?(:updated_at)
33
- data[:created_at] = @record.updated_at
34
- end
35
32
  if record_object?
36
33
  data[:object] = recordable_object(@is_touch)
37
34
  end
@@ -10,7 +10,7 @@ module PaperTrail
10
10
  # We specify `before: "load_config_initializers"` to ensure that the PT
11
11
  # initializer happens before "app initializers" (those defined in
12
12
  # the app's `config/initalizers`).
13
- initializer "paper_trail", before: "load_config_initializers" do
13
+ initializer "paper_trail", before: "load_config_initializers" do |app|
14
14
  # `on_load` is a "lazy load hook". It "declares a block that will be
15
15
  # executed when a Rails component is fully loaded". (See
16
16
  # `active_support/lazy_load_hooks.rb`)
@@ -25,6 +25,10 @@ module PaperTrail
25
25
  ActiveSupport.on_load(:active_record) do
26
26
  require "paper_trail/frameworks/active_record"
27
27
  end
28
+
29
+ if Gem::Version.new(::Rails::VERSION::STRING) >= Gem::Version.new("7.1")
30
+ app.deprecators[:paper_trail] = PaperTrail.deprecator
31
+ end
28
32
  end
29
33
  end
30
34
  end
@@ -53,6 +53,10 @@ module PaperTrail
53
53
  # - A Hash - options passed to `has_many`, plus `name:` and `scope:`.
54
54
  # - :version - The name to use for the method which returns the version
55
55
  # the instance was reified from. Default is `:version`.
56
+ # - :synchronize_version_creation_timestamp - By default, paper trail
57
+ # sets the `created_at` field for a new Version equal to the `updated_at`
58
+ # column of the model being updated. If you instead want `created_at` to
59
+ # populate with the current timestamp, set this option to `false`.
56
60
  #
57
61
  # Plugins like the experimental `paper_trail-association_tracking` gem
58
62
  # may accept additional options.
@@ -64,6 +68,8 @@ module PaperTrail
64
68
  #
65
69
  # @api public
66
70
  def has_paper_trail(options = {})
71
+ raise Error, "has_paper_trail must be called only once" if self < InstanceMethods
72
+
67
73
  defaults = PaperTrail.config.has_paper_trail_defaults
68
74
  paper_trail.setup(defaults.merge(options))
69
75
  end
@@ -82,8 +82,9 @@ module PaperTrail
82
82
 
83
83
  # Adds a callback that records a version after a "touch" event.
84
84
  #
85
- # Rails < 6.0 has a bug where dirty-tracking does not occur during
86
- # a `touch`. (https://github.com/rails/rails/issues/33429) See also:
85
+ # Rails < 6.0 (no longer supported by PT) had a bug where dirty-tracking
86
+ # did not occur during a `touch`.
87
+ # (https://github.com/rails/rails/issues/33429) See also:
87
88
  # https://github.com/paper-trail-gem/paper_trail/issues/1121
88
89
  # https://github.com/paper-trail-gem/paper_trail/issues/1161
89
90
  # https://github.com/paper-trail-gem/paper_trail/pull/1285
@@ -93,7 +94,7 @@ module PaperTrail
93
94
  @model_class.after_touch { |r|
94
95
  if r.paper_trail.save_version?
95
96
  r.paper_trail.record_update(
96
- force: RAILS_LT_6_0,
97
+ force: false,
97
98
  in_after_callback: true,
98
99
  is_touch: true
99
100
  )
@@ -121,9 +122,6 @@ module PaperTrail
121
122
 
122
123
  private
123
124
 
124
- RAILS_LT_6_0 = ::ActiveRecord.gem_version < ::Gem::Version.new("6.0.0")
125
- private_constant :RAILS_LT_6_0
126
-
127
125
  # @api private
128
126
  def append_option_uniquely(option, value)
129
127
  collection = @model_class.paper_trail_options.fetch(option)
@@ -158,7 +156,7 @@ module PaperTrail
158
156
  # @api private - `version_class_name`
159
157
  @model_class.class_attribute :version_class_name
160
158
  if options[:class_name]
161
- ::ActiveSupport::Deprecation.warn(
159
+ PaperTrail.deprecator.warn(
162
160
  format(
163
161
  DPR_CLASS_NAME_OPTION,
164
162
  class_name: options[:class_name].inspect
@@ -194,7 +192,7 @@ module PaperTrail
194
192
  def ensure_versions_option_is_hash(options)
195
193
  unless options[:versions].is_a?(Hash)
196
194
  if options[:versions]
197
- ::ActiveSupport::Deprecation.warn(
195
+ PaperTrail.deprecator.warn(
198
196
  format(
199
197
  DPR_PASSING_ASSOC_NAME_DIRECTLY_TO_VERSIONS_OPTION,
200
198
  versions_name: options[:versions].inspect
@@ -15,7 +15,7 @@ module PaperTrail
15
15
  @version_model_class = version_model_class
16
16
 
17
17
  # Currently, this `deep_dup` is necessary because the `jsonb` branch
18
- # modifies `@attributes`, and that would be a nasty suprise for
18
+ # modifies `@attributes`, and that would be a nasty surprise for
19
19
  # consumers of this class.
20
20
  # TODO: Stop modifying `@attributes`, then remove `deep_dup`.
21
21
  @attributes = attributes.deep_dup
@@ -25,14 +25,6 @@ module PaperTrail
25
25
  @record.send("#{@record.class.version_association_name}=", nil)
26
26
  end
27
27
 
28
- # Is PT enabled for this particular record?
29
- # @api private
30
- def enabled?
31
- PaperTrail.enabled? &&
32
- PaperTrail.request.enabled? &&
33
- PaperTrail.request.enabled_for_model?(@record.class)
34
- end
35
-
36
28
  # Returns true if this instance is the current, live one;
37
29
  # returns false if this instance came from a previous version.
38
30
  def live?
@@ -72,16 +64,11 @@ module PaperTrail
72
64
  # of versions_assoc.build?, the association cache is unaware. So, we
73
65
  # invalidate the `versions` association cache with `reset`.
74
66
  versions.reset
67
+ rescue StandardError => e
68
+ handle_version_errors e, version, :create
75
69
  end
76
70
  end
77
71
 
78
- # PT-AT extends this method to add its transaction id.
79
- #
80
- # @api private
81
- def data_for_create
82
- {}
83
- end
84
-
85
72
  # `recording_order` is "after" or "before". See ModelConfig#on_destroy.
86
73
  #
87
74
  # @api private
@@ -96,23 +83,22 @@ module PaperTrail
96
83
  # `data_for_destroy` but PT-AT still does.
97
84
  data = event.data.merge(data_for_destroy)
98
85
 
99
- version = @record.class.paper_trail.version_class.create(data)
100
- if version.errors.any?
101
- log_version_errors(version, :destroy)
102
- else
86
+ version = @record.class.paper_trail.version_class.new(data)
87
+ begin
88
+ version.save!
103
89
  assign_and_reset_version_association(version)
104
90
  version
91
+ rescue StandardError => e
92
+ handle_version_errors e, version, :destroy
105
93
  end
106
94
  end
107
95
 
108
- # PT-AT extends this method to add its transaction id.
109
- #
110
- # @api private
111
- def data_for_destroy
112
- {}
113
- end
114
-
115
96
  # @api private
97
+ # @param force [boolean] Insert a `Version` even if `@record` has not
98
+ # `changed_notably?`.
99
+ # @param in_after_callback [boolean] True when called from an `after_update`
100
+ # or `after_touch` callback.
101
+ # @param is_touch [boolean] True when called from an `after_touch` callback.
116
102
  # @return - The created version object, so that plugins can use it, e.g.
117
103
  # paper_trail-association_tracking
118
104
  def record_update(force:, in_after_callback:, is_touch:)
@@ -125,51 +111,18 @@ module PaperTrail
125
111
  )
126
112
  return unless version
127
113
 
128
- if version.save
114
+ begin
115
+ version.save!
129
116
  # Because the version object was created using version_class.new instead
130
117
  # of versions_assoc.build?, the association cache is unaware. So, we
131
118
  # invalidate the `versions` association cache with `reset`.
132
119
  versions.reset
133
120
  version
134
- else
135
- log_version_errors(version, :update)
136
- end
137
- end
138
-
139
- # PT-AT extends this method to add its transaction id.
140
- #
141
- # @api private
142
- def data_for_update
143
- {}
144
- end
145
-
146
- # @api private
147
- # @return - The created version object, so that plugins can use it, e.g.
148
- # paper_trail-association_tracking
149
- def record_update_columns(changes)
150
- return unless enabled?
151
- event = Events::Update.new(@record, false, false, changes)
152
-
153
- # Merge data from `Event` with data from PT-AT. We no longer use
154
- # `data_for_update_columns` but PT-AT still does.
155
- data = event.data.merge(data_for_update_columns)
156
-
157
- versions_assoc = @record.send(@record.class.versions_association_name)
158
- version = versions_assoc.create(data)
159
- if version.errors.any?
160
- log_version_errors(version, :update)
161
- else
162
- version
121
+ rescue StandardError => e
122
+ handle_version_errors e, version, :update
163
123
  end
164
124
  end
165
125
 
166
- # PT-AT extends this method to add its transaction id.
167
- #
168
- # @api private
169
- def data_for_update_columns
170
- {}
171
- end
172
-
173
126
  # Invoked via callback when a user attempts to persist a reified
174
127
  # `Version`.
175
128
  def reset_timestamp_attrs_for_update_if_needed
@@ -269,11 +222,23 @@ module PaperTrail
269
222
  def build_version_on_update(force:, in_after_callback:, is_touch:)
270
223
  event = Events::Update.new(@record, in_after_callback, is_touch, nil)
271
224
  return unless force || event.changed_notably?
225
+ data = event.data
226
+
227
+ # Copy the (recently set) `updated_at` from the record to the `created_at`
228
+ # of the `Version`. Without this feature, these two timestamps would
229
+ # differ by a few milliseconds. To some people, it seems a little
230
+ # unnatural to tamper with creation timestamps in this way. But, this
231
+ # feature has existed for a long time, almost a decade now, and some users
232
+ # may rely on it now.
233
+ if @record.respond_to?(:updated_at) &&
234
+ @record.paper_trail_options[:synchronize_version_creation_timestamp] != false
235
+ data[:created_at] = @record.updated_at
236
+ end
272
237
 
273
238
  # Merge data from `Event` with data from PT-AT. We no longer use
274
239
  # `data_for_update` but PT-AT still does. To save memory, we use `merge!`
275
240
  # instead of `merge`.
276
- data = event.data.merge!(data_for_update)
241
+ data.merge!(data_for_update)
277
242
 
278
243
  # Using `version_class.new` reduces memory usage compared to
279
244
  # `versions_assoc.build`. It's a trade-off though. We have to clear
@@ -282,6 +247,42 @@ module PaperTrail
282
247
  @record.class.paper_trail.version_class.new(data)
283
248
  end
284
249
 
250
+ # PT-AT extends this method to add its transaction id.
251
+ #
252
+ # @api public
253
+ def data_for_create
254
+ {}
255
+ end
256
+
257
+ # PT-AT extends this method to add its transaction id.
258
+ #
259
+ # @api public
260
+ def data_for_destroy
261
+ {}
262
+ end
263
+
264
+ # PT-AT extends this method to add its transaction id.
265
+ #
266
+ # @api public
267
+ def data_for_update
268
+ {}
269
+ end
270
+
271
+ # PT-AT extends this method to add its transaction id.
272
+ #
273
+ # @api public
274
+ def data_for_update_columns
275
+ {}
276
+ end
277
+
278
+ # Is PT enabled for this particular record?
279
+ # @api private
280
+ def enabled?
281
+ PaperTrail.enabled? &&
282
+ PaperTrail.request.enabled? &&
283
+ PaperTrail.request.enabled_for_model?(@record.class)
284
+ end
285
+
285
286
  def log_version_errors(version, action)
286
287
  version.logger&.warn(
287
288
  "Unable to create version for #{action} of #{@record.class.name}" \
@@ -289,6 +290,47 @@ module PaperTrail
289
290
  )
290
291
  end
291
292
 
293
+ # Centralized handler for version errors
294
+ # @api private
295
+ def handle_version_errors(e, version, action)
296
+ case PaperTrail.config.version_error_behavior
297
+ when :legacy
298
+ # legacy behavior was to raise on create and log on update/delete
299
+ if action == :create
300
+ raise e
301
+ else
302
+ log_version_errors(version, action)
303
+ end
304
+ when :log
305
+ log_version_errors(version, action)
306
+ when :exception
307
+ raise e
308
+ when :silent
309
+ # noop
310
+ end
311
+ end
312
+
313
+ # @api private
314
+ # @return - The created version object, so that plugins can use it, e.g.
315
+ # paper_trail-association_tracking
316
+ def record_update_columns(changes)
317
+ return unless enabled?
318
+ data = Events::Update.new(@record, false, false, changes).data
319
+
320
+ # Merge data from `Event` with data from PT-AT. We no longer use
321
+ # `data_for_update_columns` but PT-AT still does.
322
+ data.merge!(data_for_update_columns)
323
+
324
+ versions_assoc = @record.send(@record.class.versions_association_name)
325
+ version = versions_assoc.new(data)
326
+ begin
327
+ version.save!
328
+ version
329
+ rescue StandardError => e
330
+ handle_version_errors e, version, :update
331
+ end
332
+ end
333
+
292
334
  def version
293
335
  @record.public_send(@record.class.version_association_name)
294
336
  end
@@ -75,28 +75,6 @@ module PaperTrail
75
75
  !!store.fetch(:"enabled_for_#{model}", true)
76
76
  end
77
77
 
78
- # @api private
79
- def merge(options)
80
- options.to_h.each do |k, v|
81
- store[k] = v
82
- end
83
- end
84
-
85
- # @api private
86
- def set(options)
87
- store.clear
88
- merge(options)
89
- end
90
-
91
- # Returns a deep copy of the internal hash from our RequestStore. Keys are
92
- # all symbols. Values are mostly primitives, but whodunnit can be a Proc.
93
- # We cannot use Marshal.dump here because it doesn't support Proc. It is
94
- # unclear exactly how `deep_dup` handles a Proc, but it doesn't complain.
95
- # @api private
96
- def to_h
97
- store.deep_dup
98
- end
99
-
100
78
  # Temporarily set `options` and execute a block.
101
79
  # @api private
102
80
  def with(options)
@@ -133,6 +111,19 @@ module PaperTrail
133
111
 
134
112
  private
135
113
 
114
+ # @api private
115
+ def merge(options)
116
+ options.to_h.each do |k, v|
117
+ store[k] = v
118
+ end
119
+ end
120
+
121
+ # @api private
122
+ def set(options)
123
+ store.clear
124
+ merge(options)
125
+ end
126
+
136
127
  # Returns a Hash, initializing with default values if necessary.
137
128
  # @api private
138
129
  def store
@@ -141,6 +132,15 @@ module PaperTrail
141
132
  }
142
133
  end
143
134
 
135
+ # Returns a deep copy of the internal hash from our RequestStore. Keys are
136
+ # all symbols. Values are mostly primitives, but whodunnit can be a Proc.
137
+ # We cannot use Marshal.dump here because it doesn't support Proc. It is
138
+ # unclear exactly how `deep_dup` handles a Proc, but it doesn't complain.
139
+ # @api private
140
+ def to_h
141
+ store.deep_dup
142
+ end
143
+
144
144
  # Provide a helpful error message if someone has a typo in one of their
145
145
  # option keys. We don't validate option values here. That's traditionally
146
146
  # been handled with casting (`to_s`, `!!`) in the accessor method.
@@ -12,7 +12,7 @@ module PaperTrail
12
12
  if use_safe_load?
13
13
  ::YAML.safe_load(
14
14
  string,
15
- permitted_classes: ::ActiveRecord.yaml_column_permitted_classes,
15
+ permitted_classes: yaml_column_permitted_classes,
16
16
  aliases: true
17
17
  )
18
18
  elsif ::YAML.respond_to?(:unsafe_load)
@@ -39,10 +39,29 @@ module PaperTrail
39
39
 
40
40
  private
41
41
 
42
- # `use_yaml_unsafe_load` was added in 7.0.3.1, will be removed in 7.1.0?
43
42
  def use_safe_load?
44
- defined?(ActiveRecord.use_yaml_unsafe_load) &&
45
- !ActiveRecord.use_yaml_unsafe_load
43
+ if ::ActiveRecord.gem_version >= Gem::Version.new("7.0.3.1")
44
+ # `use_yaml_unsafe_load` may be removed in the future, at which point
45
+ # safe loading will be the default.
46
+ !defined?(ActiveRecord.use_yaml_unsafe_load) || !ActiveRecord.use_yaml_unsafe_load
47
+ elsif defined?(ActiveRecord::Base.use_yaml_unsafe_load)
48
+ # Rails 5.2.8.1, 6.0.5.1, 6.1.6.1
49
+ !ActiveRecord::Base.use_yaml_unsafe_load
50
+ else
51
+ false
52
+ end
53
+ end
54
+
55
+ def yaml_column_permitted_classes
56
+ if defined?(ActiveRecord.yaml_column_permitted_classes)
57
+ # Rails >= 7.0.3.1
58
+ ActiveRecord.yaml_column_permitted_classes
59
+ elsif defined?(ActiveRecord::Base.yaml_column_permitted_classes)
60
+ # Rails 5.2.8.1, 6.0.5.1, 6.1.6.1
61
+ ActiveRecord::Base.yaml_column_permitted_classes
62
+ else
63
+ []
64
+ end
46
65
  end
47
66
  end
48
67
  end
@@ -7,7 +7,7 @@ module PaperTrail
7
7
  # because of this confusion, but it's not worth the breaking change.
8
8
  # People are encouraged to use `PaperTrail.gem_version` instead.
9
9
  module VERSION
10
- MAJOR = 13
10
+ MAJOR = 16
11
11
  MINOR = 0
12
12
  TINY = 0
13
13
 
data/lib/paper_trail.rb CHANGED
@@ -8,6 +8,12 @@
8
8
  # can revisit this decision.
9
9
  require "active_support/all"
10
10
 
11
+ # We used to `require "active_record"` here, but that was [replaced with a
12
+ # Railtie](https://github.com/paper-trail-gem/paper_trail/pull/1281) in PT 12.
13
+ # As a result, we cannot reference `ActiveRecord` in this file (ie. until our
14
+ # Railtie has loaded). If we did, it would cause [problems with non-Rails
15
+ # projects](https://github.com/paper-trail-gem/paper_trail/pull/1401).
16
+
11
17
  require "paper_trail/errors"
12
18
  require "paper_trail/cleaner"
13
19
  require "paper_trail/compatibility"
@@ -26,8 +32,6 @@ module PaperTrail
26
32
  named created_at.
27
33
  EOS
28
34
 
29
- RAILS_GTE_7_0 = ::ActiveRecord.gem_version >= ::Gem::Version.new("7.0.0")
30
-
31
35
  extend PaperTrail::Cleaner
32
36
 
33
37
  class << self
@@ -98,7 +102,7 @@ module PaperTrail
98
102
 
99
103
  # Returns PaperTrail's global configuration object, a singleton. These
100
104
  # settings affect all threads.
101
- # @api private
105
+ # @api public
102
106
  def config
103
107
  @config ||= PaperTrail::Config.instance
104
108
  yield @config if block_given?
@@ -106,9 +110,18 @@ module PaperTrail
106
110
  end
107
111
  alias configure config
108
112
 
113
+ # @api public
109
114
  def version
110
115
  VERSION::STRING
111
116
  end
117
+
118
+ def active_record_gte_7_0?
119
+ @active_record_gte_7_0 ||= ::ActiveRecord.gem_version >= ::Gem::Version.new("7.0.0")
120
+ end
121
+
122
+ def deprecator
123
+ @deprecator ||= ActiveSupport::Deprecation.new("16.0", "PaperTrail")
124
+ end
112
125
  end
113
126
  end
114
127
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: paper_trail
3
3
  version: !ruby/object:Gem::Version
4
- version: 13.0.0
4
+ version: 16.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andy Stewart
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2022-08-16 00:00:00.000000000 Z
13
+ date: 2024-11-08 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: activerecord
@@ -18,42 +18,42 @@ dependencies:
18
18
  requirements:
19
19
  - - ">="
20
20
  - !ruby/object:Gem::Version
21
- version: '5.2'
21
+ version: '6.1'
22
22
  type: :runtime
23
23
  prerelease: false
24
24
  version_requirements: !ruby/object:Gem::Requirement
25
25
  requirements:
26
26
  - - ">="
27
27
  - !ruby/object:Gem::Version
28
- version: '5.2'
28
+ version: '6.1'
29
29
  - !ruby/object:Gem::Dependency
30
30
  name: request_store
31
31
  requirement: !ruby/object:Gem::Requirement
32
32
  requirements:
33
33
  - - "~>"
34
34
  - !ruby/object:Gem::Version
35
- version: '1.1'
35
+ version: '1.4'
36
36
  type: :runtime
37
37
  prerelease: false
38
38
  version_requirements: !ruby/object:Gem::Requirement
39
39
  requirements:
40
40
  - - "~>"
41
41
  - !ruby/object:Gem::Version
42
- version: '1.1'
42
+ version: '1.4'
43
43
  - !ruby/object:Gem::Dependency
44
44
  name: appraisal
45
45
  requirement: !ruby/object:Gem::Requirement
46
46
  requirements:
47
47
  - - "~>"
48
48
  - !ruby/object:Gem::Version
49
- version: 2.4.1
49
+ version: '2.5'
50
50
  type: :development
51
51
  prerelease: false
52
52
  version_requirements: !ruby/object:Gem::Requirement
53
53
  requirements:
54
54
  - - "~>"
55
55
  - !ruby/object:Gem::Version
56
- version: 2.4.1
56
+ version: '2.5'
57
57
  - !ruby/object:Gem::Dependency
58
58
  name: byebug
59
59
  requirement: !ruby/object:Gem::Requirement
@@ -116,14 +116,14 @@ dependencies:
116
116
  requirements:
117
117
  - - ">="
118
118
  - !ruby/object:Gem::Version
119
- version: '5.2'
119
+ version: '6.1'
120
120
  type: :development
121
121
  prerelease: false
122
122
  version_requirements: !ruby/object:Gem::Requirement
123
123
  requirements:
124
124
  - - ">="
125
125
  - !ruby/object:Gem::Version
126
- version: '5.2'
126
+ version: '6.1'
127
127
  - !ruby/object:Gem::Dependency
128
128
  name: rake
129
129
  requirement: !ruby/object:Gem::Requirement
@@ -144,14 +144,14 @@ dependencies:
144
144
  requirements:
145
145
  - - "~>"
146
146
  - !ruby/object:Gem::Version
147
- version: 5.0.2
147
+ version: 6.0.3
148
148
  type: :development
149
149
  prerelease: false
150
150
  version_requirements: !ruby/object:Gem::Requirement
151
151
  requirements:
152
152
  - - "~>"
153
153
  - !ruby/object:Gem::Version
154
- version: 5.0.2
154
+ version: 6.0.3
155
155
  - !ruby/object:Gem::Dependency
156
156
  name: rubocop
157
157
  requirement: !ruby/object:Gem::Requirement
@@ -351,7 +351,8 @@ files:
351
351
  homepage: https://github.com/paper-trail-gem/paper_trail
352
352
  licenses:
353
353
  - MIT
354
- metadata: {}
354
+ metadata:
355
+ changelog_uri: https://github.com/paper-trail-gem/paper_trail/blob/master/CHANGELOG.md
355
356
  post_install_message:
356
357
  rdoc_options: []
357
358
  require_paths:
@@ -360,14 +361,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
360
361
  requirements:
361
362
  - - ">="
362
363
  - !ruby/object:Gem::Version
363
- version: 2.6.0
364
+ version: 3.0.0
364
365
  required_rubygems_version: !ruby/object:Gem::Requirement
365
366
  requirements:
366
367
  - - ">="
367
368
  - !ruby/object:Gem::Version
368
369
  version: 1.3.6
369
370
  requirements: []
370
- rubygems_version: 3.3.7
371
+ rubygems_version: 3.4.19
371
372
  signing_key:
372
373
  specification_version: 4
373
374
  summary: Track changes to your models.