paper_trail 12.0.0 → 12.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f2ce7f019e3413c485beff4eefa957f759b084fe0d29b0d9f3451b6ba3e4befe
4
- data.tar.gz: 3f73870b42c582f683936eee21fe288b17d915d1bfbff5327ac50dd8e9766732
3
+ metadata.gz: e0f6ef6591b15081a76d93a70d959527f8cdc53cc3a6343ab6f1b7c40ef6695e
4
+ data.tar.gz: 202ee3126567ab99b39f8c76039fb7e20a4eb5e10e0e0880e867f19f228ace63
5
5
  SHA512:
6
- metadata.gz: 6134547d0c2c69f94962aea4f868aec8d456bbb0afc4588b9b07971074a8d50476f29a59acb41d3e058dea581678d0c7129d8e92a04c370fa2016dbd6bf544b7
7
- data.tar.gz: a6ca4e41cf2f6bce774894a9927dc0b3e4abbcbbdbb39fbf4671d029398f26999ce8927143f1f14279a6ad8aa70194025f5b6e653780560a0cc4870dce9b754e
6
+ metadata.gz: c144126a2d8cec5537ef218b6637ee2ff6e6863de2e935facf6ad020dba6c2fb46bc5c13ed2ab674e9230c264d6e87905415ebc3c1e4bee5fbe50b1e73285663
7
+ data.tar.gz: 96530aebe8af35a95d47b6a5deadd3797b2d9255b28156f24a9f1be67fe629b254747d39ccad4d8f5d348d9fd74300b4b865ff9b32125c0951b14cf5d13ed60f
@@ -20,6 +20,12 @@ module PaperTrail
20
20
  default: false,
21
21
  desc: "Store changeset (diff) with each version"
22
22
  )
23
+ class_option(
24
+ :uuid,
25
+ type: :boolean,
26
+ default: false,
27
+ desc: "Use uuid instead of bigint for item_id type (use only if tables use UUIDs)"
28
+ )
23
29
 
24
30
  desc "Generates (but does not run) a migration to add a versions table." \
25
31
  " See section 5.c. Generators in README.md for more information."
@@ -28,7 +34,8 @@ module PaperTrail
28
34
  add_paper_trail_migration(
29
35
  "create_versions",
30
36
  item_type_options: item_type_options,
31
- versions_table_options: versions_table_options
37
+ versions_table_options: versions_table_options,
38
+ item_id_type_options: item_id_type_options
32
39
  )
33
40
  if options.with_changes?
34
41
  add_paper_trail_migration("add_object_changes_to_versions")
@@ -37,13 +44,18 @@ module PaperTrail
37
44
 
38
45
  private
39
46
 
47
+ # To use uuid instead of integer for primary key
48
+ def item_id_type_options
49
+ options.uuid? ? "string" : "bigint"
50
+ end
51
+
40
52
  # MySQL 5.6 utf8mb4 limit is 191 chars for keys used in indexes.
41
53
  # See https://github.com/paper-trail-gem/paper_trail/issues/651
42
54
  def item_type_options
43
55
  if mysql?
44
- ", { null: false, limit: 191 }"
56
+ ", null: false, limit: 191"
45
57
  else
46
- ", { null: false }"
58
+ ", null: false"
47
59
  end
48
60
  end
49
61
 
@@ -11,7 +11,7 @@ class CreateVersions < ActiveRecord::Migration<%= migration_version %>
11
11
  def change
12
12
  create_table :versions<%= versions_table_options %> do |t|
13
13
  t.string :item_type<%= item_type_options %>
14
- t.bigint :item_id, null: false
14
+ t.<%= item_id_type_options %> :item_id, null: false
15
15
  t.string :event, null: false
16
16
  t.string :whodunnit
17
17
  t.text :object, limit: TEXT_BYTES
@@ -28,7 +28,9 @@ class CreateVersions < ActiveRecord::Migration<%= migration_version %>
28
28
  # MySQL users should also upgrade to at least rails 4.2, which is the first
29
29
  # version of ActiveRecord with support for fractional seconds in MySQL.
30
30
  # (https://github.com/rails/rails/pull/14359)
31
- #
31
+ #
32
+ # MySQL users should use the following line for `created_at`
33
+ # t.datetime :created_at, limit: 6
32
34
  t.datetime :created_at
33
35
  end
34
36
  add_index :versions, %i(item_type item_id)
@@ -7,8 +7,10 @@ module PaperTrail
7
7
  class UpdateItemSubtypeGenerator < MigrationGenerator
8
8
  source_root File.expand_path("templates", __dir__)
9
9
 
10
- desc "Generates (but does not run) a migration to update item_subtype for STI entries in an "\
11
- "existing versions table."
10
+ desc(
11
+ "Generates (but does not run) a migration to update item_subtype for "\
12
+ "STI entries in an existing versions table."
13
+ )
12
14
 
13
15
  def create_migration_file
14
16
  add_paper_trail_migration("update_versions_for_item_subtype", sti_type_options: options)
@@ -32,6 +32,12 @@ 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)
36
+ # Because Rails 7 time attribute throws a delegation error when you deserialize
37
+ # it with the factory.
38
+ # See ActiveRecord::Type::Time::Value crashes when loaded from YAML on rails 7.0
39
+ # https://github.com/rails/rails/issues/43966
40
+ val.instance_variable_get(:@time)
35
41
  else
36
42
  AttributeSerializerFactory.for(@klass, attr).deserialize(val)
37
43
  end
@@ -8,7 +8,7 @@ module PaperTrail
8
8
  #
9
9
  # It is not safe to assume that a new version of rails will be compatible with
10
10
  # PaperTrail. PT is only compatible with the versions of rails that it is
11
- # tested against. See `.travis.yml`.
11
+ # tested against. See `.github/workflows/test.yml`.
12
12
  #
13
13
  # However, as of
14
14
  # [#1213](https://github.com/paper-trail-gem/paper_trail/pull/1213) our
@@ -18,7 +18,7 @@ module PaperTrail
18
18
  # versions.
19
19
  module Compatibility
20
20
  ACTIVERECORD_GTE = ">= 5.2" # enforced in gemspec
21
- ACTIVERECORD_LT = "< 6.2" # not enforced in gemspec
21
+ ACTIVERECORD_LT = "< 7.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
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PaperTrail
4
+ # Generic PaperTrail exception.
5
+ # @api public
6
+ class Error < StandardError
7
+ end
8
+
9
+ # An unexpected option, perhaps a typo, was passed to a public API method.
10
+ # @api public
11
+ class InvalidOption < Error
12
+ end
13
+
14
+ # The application's database schema is not supported.
15
+ # @api public
16
+ class UnsupportedSchema < Error
17
+ end
18
+
19
+ # The application's database column type is not supported.
20
+ # @api public
21
+ class UnsupportedColumnType < UnsupportedSchema
22
+ def initialize(method:, expected:, actual:)
23
+ super(
24
+ format(
25
+ "%s expected %s column, got %s",
26
+ method,
27
+ expected,
28
+ actual
29
+ )
30
+ )
31
+ end
32
+ end
33
+ end
@@ -109,19 +109,25 @@ module PaperTrail
109
109
  @changed_in_latest_version ||= changes_in_latest_version.keys
110
110
  end
111
111
 
112
- # Rails 5.1 changed the API of `ActiveRecord::Dirty`. See
113
- # https://github.com/paper-trail-gem/paper_trail/pull/899
112
+ # Memoized to reduce memory usage
114
113
  #
115
114
  # @api private
116
115
  def changes_in_latest_version
117
- # Memoized to reduce memory usage
118
- @changes_in_latest_version ||= begin
119
- if @in_after_callback
120
- @record.saved_changes
121
- else
122
- @record.changes
123
- end
116
+ @changes_in_latest_version ||= load_changes_in_latest_version
117
+ end
118
+
119
+ # @api private
120
+ def evaluate_only
121
+ only = @record.paper_trail_options[:only].dup
122
+ # Remove Hash arguments and then evaluate whether the attributes (the
123
+ # keys of the hash) should also get pushed into the collection.
124
+ only.delete_if do |obj|
125
+ obj.is_a?(Hash) &&
126
+ obj.each { |attr, condition|
127
+ only << attr if condition.respond_to?(:call) && condition.call(@record)
128
+ }
124
129
  end
130
+ only
125
131
  end
126
132
 
127
133
  # An attributed is "ignored" if it is listed in the `:ignore` option
@@ -134,6 +140,18 @@ module PaperTrail
134
140
  ignored.any? && (changed_in_latest_version & ignored).any?
135
141
  end
136
142
 
143
+ # Rails 5.1 changed the API of `ActiveRecord::Dirty`. See
144
+ # https://github.com/paper-trail-gem/paper_trail/pull/899
145
+ #
146
+ # @api private
147
+ def load_changes_in_latest_version
148
+ if @in_after_callback
149
+ @record.saved_changes
150
+ else
151
+ @record.changes
152
+ end
153
+ end
154
+
137
155
  # PT 10 has a new optional column, `item_subtype`
138
156
  #
139
157
  # @api private
@@ -178,20 +196,28 @@ module PaperTrail
178
196
  if value.respond_to?(:call)
179
197
  value.call(@record)
180
198
  elsif value.is_a?(Symbol) && @record.respond_to?(value, true)
181
- # If it is an attribute that is changing in an existing object,
182
- # be sure to grab the current version.
183
- if event != "create" &&
184
- @record.has_attribute?(value) &&
185
- attribute_changed_in_latest_version?(value)
186
- attribute_in_previous_version(value, false)
187
- else
188
- @record.send(value)
189
- end
199
+ metadatum_from_model_method(event, value)
190
200
  else
191
201
  value
192
202
  end
193
203
  end
194
204
 
205
+ # The model method can either be an attribute or a non-attribute method.
206
+ #
207
+ # If it is an attribute that is changing in an existing object,
208
+ # be sure to grab the correct version.
209
+ #
210
+ # @api private
211
+ def metadatum_from_model_method(event, method)
212
+ if event != "create" &&
213
+ @record.has_attribute?(method) &&
214
+ attribute_changed_in_latest_version?(method)
215
+ attribute_in_previous_version(method, false)
216
+ else
217
+ @record.send(method)
218
+ end
219
+ end
220
+
195
221
  # @api private
196
222
  def notable_changes
197
223
  changes_in_latest_version.delete_if { |k, _v|
@@ -203,16 +229,9 @@ module PaperTrail
203
229
  def notably_changed
204
230
  # Memoized to reduce memory usage
205
231
  @notably_changed ||= begin
206
- only = @record.paper_trail_options[:only].dup
207
- # Remove Hash arguments and then evaluate whether the attributes (the
208
- # keys of the hash) should also get pushed into the collection.
209
- only.delete_if do |obj|
210
- obj.is_a?(Hash) &&
211
- obj.each { |attr, condition|
212
- only << attr if condition.respond_to?(:call) && condition.call(@record)
213
- }
214
- end
215
- only.empty? ? changed_and_not_ignored : (changed_and_not_ignored & only)
232
+ only = evaluate_only
233
+ cani = changed_and_not_ignored
234
+ only.empty? ? cani : (cani & only)
216
235
  end
217
236
  end
218
237
 
@@ -35,16 +35,35 @@ module PaperTrail
35
35
  if record_object?
36
36
  data[:object] = recordable_object(@is_touch)
37
37
  end
38
- if record_object_changes?
39
- changes = @force_changes.nil? ? notable_changes : @force_changes
40
- data[:object_changes] = prepare_object_changes(changes)
41
- end
38
+ merge_object_changes_into(data)
42
39
  merge_item_subtype_into(data)
43
40
  merge_metadata_into(data)
44
41
  end
45
42
 
43
+ # If it is a touch event, and changed are empty, it is assumed to be
44
+ # implicit `touch` mutation, and will a version is created.
45
+ #
46
+ # See https://github.com/rails/rails/commit/dcb825902d79d0f6baba956f7c6ec5767611353e
47
+ #
48
+ # @api private
49
+ def changed_notably?
50
+ if @is_touch && changes_in_latest_version.empty?
51
+ true
52
+ else
53
+ super
54
+ end
55
+ end
56
+
46
57
  private
47
58
 
59
+ # @api private
60
+ def merge_object_changes_into(data)
61
+ if record_object_changes?
62
+ changes = @force_changes.nil? ? notable_changes : @force_changes
63
+ data[:object_changes] = prepare_object_changes(changes)
64
+ end
65
+ end
66
+
48
67
  # `touch` cannot record `object_changes` because rails' `touch` does not
49
68
  # perform dirty-tracking. Specifically, methods from `Dirty`, like
50
69
  # `saved_changes`, return the same values before and after `touch`.
@@ -18,11 +18,6 @@ module PaperTrail
18
18
  `abstract_class`. This is fine, but all application models must be
19
19
  configured to use concrete (not abstract) version models.
20
20
  STR
21
- E_MODEL_LIMIT_REQUIRES_ITEM_SUBTYPE = <<~STR.squish.freeze
22
- To use PaperTrail's per-model limit in your %s model, you must have an
23
- item_subtype column in your versions table. See documentation sections
24
- 2.e.1 Per-model limit, and 4.b.1 The optional item_subtype column.
25
- STR
26
21
  DPR_PASSING_ASSOC_NAME_DIRECTLY_TO_VERSIONS_OPTION = <<~STR.squish
27
22
  Passing versions association name as `has_paper_trail versions: %{versions_name}`
28
23
  is deprecated. Use `has_paper_trail versions: {name: %{versions_name}}` instead.
@@ -45,22 +40,14 @@ module PaperTrail
45
40
  @model_class.after_create { |r|
46
41
  r.paper_trail.record_create if r.paper_trail.save_version?
47
42
  }
48
- return if @model_class.paper_trail_options[:on].include?(:create)
49
- @model_class.paper_trail_options[:on] << :create
43
+ append_option_uniquely(:on, :create)
50
44
  end
51
45
 
52
46
  # Adds a callback that records a version before or after a "destroy" event.
53
47
  #
54
48
  # @api public
55
49
  def on_destroy(recording_order = "before")
56
- unless %w[after before].include?(recording_order.to_s)
57
- raise ArgumentError, 'recording order can only be "after" or "before"'
58
- end
59
-
60
- if recording_order.to_s == "after" && cannot_record_after_destroy?
61
- raise E_CANNOT_RECORD_AFTER_DESTROY
62
- end
63
-
50
+ assert_valid_recording_order_for_on_destroy(recording_order)
64
51
  @model_class.send(
65
52
  "#{recording_order}_destroy",
66
53
  lambda do |r|
@@ -68,9 +55,7 @@ module PaperTrail
68
55
  r.paper_trail.record_destroy(recording_order)
69
56
  end
70
57
  )
71
-
72
- return if @model_class.paper_trail_options[:on].include?(:destroy)
73
- @model_class.paper_trail_options[:on] << :destroy
58
+ append_option_uniquely(:on, :destroy)
74
59
  end
75
60
 
76
61
  # Adds a callback that records a version after an "update" event.
@@ -92,19 +77,27 @@ module PaperTrail
92
77
  @model_class.after_update { |r|
93
78
  r.paper_trail.clear_version_instance
94
79
  }
95
- return if @model_class.paper_trail_options[:on].include?(:update)
96
- @model_class.paper_trail_options[:on] << :update
80
+ append_option_uniquely(:on, :update)
97
81
  end
98
82
 
99
83
  # Adds a callback that records a version after a "touch" event.
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:
87
+ # https://github.com/paper-trail-gem/paper_trail/issues/1121
88
+ # https://github.com/paper-trail-gem/paper_trail/issues/1161
89
+ # https://github.com/paper-trail-gem/paper_trail/pull/1285
90
+ #
100
91
  # @api public
101
92
  def on_touch
102
93
  @model_class.after_touch { |r|
103
- r.paper_trail.record_update(
104
- force: true,
105
- in_after_callback: true,
106
- is_touch: true
107
- )
94
+ if r.paper_trail.save_version?
95
+ r.paper_trail.record_update(
96
+ force: RAILS_LT_6_0,
97
+ in_after_callback: true,
98
+ is_touch: true
99
+ )
100
+ end
108
101
  }
109
102
  end
110
103
 
@@ -117,7 +110,6 @@ module PaperTrail
117
110
  @model_class.send :include, ::PaperTrail::Model::InstanceMethods
118
111
  setup_options(options)
119
112
  setup_associations(options)
120
- check_presence_of_item_subtype_column(options)
121
113
  @model_class.after_rollback { paper_trail.clear_rolled_back_versions }
122
114
  setup_callbacks_from_options options[:on]
123
115
  end
@@ -129,26 +121,37 @@ module PaperTrail
129
121
 
130
122
  private
131
123
 
124
+ RAILS_LT_6_0 = ::ActiveRecord.gem_version < ::Gem::Version.new("6.0.0")
125
+ private_constant :RAILS_LT_6_0
126
+
127
+ # @api private
128
+ def append_option_uniquely(option, value)
129
+ collection = @model_class.paper_trail_options.fetch(option)
130
+ return if collection.include?(value)
131
+ collection << value
132
+ end
133
+
132
134
  # Raises an error if the provided class is an `abstract_class`.
133
135
  # @api private
134
136
  def assert_concrete_activerecord_class(class_name)
135
137
  if class_name.constantize.abstract_class?
136
- raise format(E_HPT_ABSTRACT_CLASS, @model_class, class_name)
138
+ raise Error, format(E_HPT_ABSTRACT_CLASS, @model_class, class_name)
137
139
  end
138
140
  end
139
141
 
140
- def cannot_record_after_destroy?
141
- ::ActiveRecord::Base.belongs_to_required_by_default
142
+ # @api private
143
+ def assert_valid_recording_order_for_on_destroy(recording_order)
144
+ unless %w[after before].include?(recording_order.to_s)
145
+ raise ArgumentError, 'recording order can only be "after" or "before"'
146
+ end
147
+
148
+ if recording_order.to_s == "after" && cannot_record_after_destroy?
149
+ raise Error, E_CANNOT_RECORD_AFTER_DESTROY
150
+ end
142
151
  end
143
152
 
144
- # Some options require the presence of the `item_subtype` column. Currently
145
- # only `limit`, but in the future there may be others.
146
- #
147
- # @api private
148
- def check_presence_of_item_subtype_column(options)
149
- return unless options.key?(:limit)
150
- return if version_class.item_subtype_column_present?
151
- raise format(E_MODEL_LIMIT_REQUIRES_ITEM_SUBTYPE, @model_class.name)
153
+ def cannot_record_after_destroy?
154
+ ::ActiveRecord::Base.belongs_to_required_by_default
152
155
  end
153
156
 
154
157
  def check_version_class_name(options)
@@ -206,6 +209,14 @@ module PaperTrail
206
209
  options
207
210
  end
208
211
 
212
+ # Process an `ignore`, `skip`, or `only` option.
213
+ def event_attribute_option(option_name)
214
+ [@model_class.paper_trail_options[option_name]].
215
+ flatten.
216
+ compact.
217
+ map { |attr| attr.is_a?(Hash) ? attr.stringify_keys : attr.to_s }
218
+ end
219
+
209
220
  def get_versions_scope(options)
210
221
  options[:versions][:scope] || -> { order(model.timestamp_sort_order) }
211
222
  end
@@ -240,12 +251,8 @@ module PaperTrail
240
251
  @model_class.paper_trail_options = options.dup
241
252
 
242
253
  %i[ignore skip only].each do |k|
243
- @model_class.paper_trail_options[k] = [@model_class.paper_trail_options[k]].
244
- flatten.
245
- compact.
246
- map { |attr| attr.is_a?(Hash) ? attr.stringify_keys : attr.to_s }
254
+ @model_class.paper_trail_options[k] = event_attribute_option(k)
247
255
  end
248
-
249
256
  @model_class.paper_trail_options[:meta] ||= {}
250
257
  end
251
258
  end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PaperTrail
4
+ module Queries
5
+ module Versions
6
+ # For public API documentation, see `where_attribute_changes` in
7
+ # `paper_trail/version_concern.rb`.
8
+ # @api private
9
+ class WhereAttributeChanges
10
+ # - version_model_class - The class that VersionConcern was mixed into.
11
+ # - attribute - An attribute that changed. See the public API
12
+ # documentation for details.
13
+ # @api private
14
+ def initialize(version_model_class, attribute)
15
+ @version_model_class = version_model_class
16
+ @attribute = attribute
17
+ end
18
+
19
+ # @api private
20
+ def execute
21
+ if PaperTrail.config.object_changes_adapter.respond_to?(:where_attribute_changes)
22
+ return PaperTrail.config.object_changes_adapter.where_attribute_changes(
23
+ @version_model_class, @attribute
24
+ )
25
+ end
26
+ column_type = @version_model_class.columns_hash["object_changes"].type
27
+ case column_type
28
+ when :jsonb, :json
29
+ json
30
+ else
31
+ raise UnsupportedColumnType.new(
32
+ method: "where_attribute_changes",
33
+ expected: "json or jsonb",
34
+ actual: column_type
35
+ )
36
+ end
37
+ end
38
+
39
+ private
40
+
41
+ # @api private
42
+ def json
43
+ sql = "object_changes -> ? IS NOT NULL"
44
+
45
+ @version_model_class.where(sql, @attribute)
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -19,7 +19,7 @@ module PaperTrail
19
19
  # @api private
20
20
  def execute
21
21
  column = @version_model_class.columns_hash["object"]
22
- raise "where_object can't be called without an object column" unless column
22
+ raise Error, "where_object requires an object column" unless column
23
23
 
24
24
  case column.type
25
25
  when :jsonb
@@ -28,13 +28,18 @@ module PaperTrail
28
28
  @version_model_class, @attributes
29
29
  )
30
30
  end
31
- case @version_model_class.columns_hash["object_changes"].type
31
+ column_type = @version_model_class.columns_hash["object_changes"].type
32
+ case column_type
32
33
  when :jsonb
33
34
  jsonb
34
35
  when :json
35
36
  json
36
37
  else
37
- text
38
+ raise UnsupportedColumnType.new(
39
+ method: "where_object_changes",
40
+ expected: "json or jsonb",
41
+ actual: column_type
42
+ )
38
43
  end
39
44
  end
40
45
 
@@ -59,16 +64,6 @@ module PaperTrail
59
64
  @attributes.each { |field, value| @attributes[field] = [value] }
60
65
  @version_model_class.where("object_changes @> ?", @attributes.to_json)
61
66
  end
62
-
63
- # @api private
64
- def text
65
- arel_field = @version_model_class.arel_table[:object_changes]
66
- where_conditions = @attributes.map { |field, value|
67
- ::PaperTrail.serializer.where_object_changes_condition(arel_field, field, value)
68
- }
69
- where_conditions = where_conditions.reduce { |a, e| a.and(e) }
70
- @version_model_class.where(where_conditions)
71
- end
72
67
  end
73
68
  end
74
69
  end
@@ -23,12 +23,16 @@ module PaperTrail
23
23
  @version_model_class, @attributes
24
24
  )
25
25
  end
26
-
27
- case @version_model_class.columns_hash["object_changes"].type
26
+ column_type = @version_model_class.columns_hash["object_changes"].type
27
+ case column_type
28
28
  when :jsonb, :json
29
29
  json
30
30
  else
31
- text
31
+ raise UnsupportedColumnType.new(
32
+ method: "where_object_changes_from",
33
+ expected: "json or jsonb",
34
+ actual: column_type
35
+ )
32
36
  end
33
37
  end
34
38
 
@@ -47,18 +51,6 @@ module PaperTrail
47
51
  sql = predicates.join(" and ")
48
52
  @version_model_class.where(sql, *values)
49
53
  end
50
-
51
- # @api private
52
- def text
53
- arel_field = @version_model_class.arel_table[:object_changes]
54
-
55
- where_conditions = @attributes.map do |field, value|
56
- ::PaperTrail.serializer.where_object_changes_from_condition(arel_field, field, value)
57
- end
58
-
59
- where_conditions = where_conditions.reduce { |a, e| a.and(e) }
60
- @version_model_class.where(where_conditions)
61
- end
62
54
  end
63
55
  end
64
56
  end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PaperTrail
4
+ module Queries
5
+ module Versions
6
+ # For public API documentation, see `where_object_changes_to` in
7
+ # `paper_trail/version_concern.rb`.
8
+ # @api private
9
+ class WhereObjectChangesTo
10
+ # - version_model_class - The class that VersionConcern was mixed into.
11
+ # - attributes - A `Hash` of attributes and values. See the public API
12
+ # documentation for details.
13
+ # @api private
14
+ def initialize(version_model_class, attributes)
15
+ @version_model_class = version_model_class
16
+ @attributes = attributes
17
+ end
18
+
19
+ # @api private
20
+ def execute
21
+ if PaperTrail.config.object_changes_adapter.respond_to?(:where_object_changes_to)
22
+ return PaperTrail.config.object_changes_adapter.where_object_changes_to(
23
+ @version_model_class, @attributes
24
+ )
25
+ end
26
+ column_type = @version_model_class.columns_hash["object_changes"].type
27
+ case column_type
28
+ when :jsonb, :json
29
+ json
30
+ else
31
+ raise UnsupportedColumnType.new(
32
+ method: "where_object_changes_to",
33
+ expected: "json or jsonb",
34
+ actual: column_type
35
+ )
36
+ end
37
+ end
38
+
39
+ private
40
+
41
+ # @api private
42
+ def json
43
+ predicates = []
44
+ values = []
45
+ @attributes.each do |field, value|
46
+ predicates.push(
47
+ "(object_changes->>? ILIKE ?)"
48
+ )
49
+ values.concat([field, "[%#{value.to_json}]"])
50
+ end
51
+ sql = predicates.join(" and ")
52
+ @version_model_class.where(sql, *values)
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -194,15 +194,17 @@ module PaperTrail
194
194
  # Save, and create a version record regardless of options such as `:on`,
195
195
  # `:if`, or `:unless`.
196
196
  #
197
- # Arguments are passed to `save`.
197
+ # `in_after_callback`: Indicates if this method is being called within an
198
+ # `after` callback. Defaults to `false`.
199
+ # `options`: Optional arguments passed to `save`.
198
200
  #
199
201
  # This is an "update" event. That is, we record the same data we would in
200
202
  # the case of a normal AR `update`.
201
- def save_with_version(**options)
203
+ def save_with_version(in_after_callback: false, **options)
202
204
  ::PaperTrail.request(enabled: false) do
203
205
  @record.save(**options)
204
206
  end
205
- record_update(force: true, in_after_callback: false, is_touch: false)
207
+ record_update(force: true, in_after_callback: in_after_callback, is_touch: false)
206
208
  end
207
209
 
208
210
  # Like the `update_column` method from `ActiveRecord::Persistence`, but also
@@ -283,7 +285,7 @@ module PaperTrail
283
285
  def log_version_errors(version, action)
284
286
  version.logger&.warn(
285
287
  "Unable to create version for #{action} of #{@record.class.name}" \
286
- "##{@record.id}: " + version.errors.full_messages.join(", ")
288
+ "##{@record.id}: " + version.errors.full_messages.join(", ")
287
289
  )
288
290
  end
289
291
 
@@ -60,9 +60,7 @@ module PaperTrail
60
60
  model = if options[:dup] == true || version.event == "destroy"
61
61
  klass.new
62
62
  else
63
- find_cond = { klass.primary_key => version.item_id }
64
-
65
- version.item || klass.unscoped.where(find_cond).first || klass.new
63
+ version.item || init_model_by_finding_item_id(klass, version) || klass.new
66
64
  end
67
65
 
68
66
  if options[:unversioned_attributes] == :nil && !model.new_record?
@@ -72,6 +70,11 @@ module PaperTrail
72
70
  model
73
71
  end
74
72
 
73
+ # @api private
74
+ def init_model_by_finding_item_id(klass, version)
75
+ klass.unscoped.where(klass.primary_key => version.item_id).first
76
+ end
77
+
75
78
  # Look for attributes that exist in `model` and not in this version.
76
79
  # These attributes should be set to nil. Modifies `attrs`.
77
80
  # @api private
@@ -109,21 +112,35 @@ module PaperTrail
109
112
  end
110
113
 
111
114
  # Given a `version`, return the class to reify. This method supports
112
- # Single Table Inheritance (STI) with custom inheritance columns.
115
+ # Single Table Inheritance (STI) with custom inheritance columns and
116
+ # custom inheritance column values.
113
117
  #
114
118
  # For example, imagine a `version` whose `item_type` is "Animal". The
115
119
  # `animals` table is an STI table (it has cats and dogs) and it has a
116
120
  # custom inheritance column, `species`. If `attrs["species"]` is "Dog",
117
121
  # this method returns the constant `Dog`. If `attrs["species"]` is blank,
118
- # this method returns the constant `Animal`. You can see this particular
119
- # example in action in `spec/models/animal_spec.rb`.
122
+ # this method returns the constant `Animal`.
120
123
  #
121
- # TODO: Duplication: similar `constantize` in VersionConcern#version_limit
124
+ # The values contained in the inheritance columns may be non-camelized
125
+ # strings (e.g. 'dog' instead of 'Dog'). To reify classes in this case
126
+ # we need to call the parents class `sti_class_for` method to retrieve
127
+ # the correct record class.
128
+ #
129
+ # You can see these particular examples in action in
130
+ # `spec/models/animal_spec.rb` and `spec/models/plant_spec.rb`
122
131
  def version_reification_class(version, attrs)
123
- inheritance_column_name = version.item_type.constantize.inheritance_column
132
+ clazz = version.item_type.constantize
133
+ inheritance_column_name = clazz.inheritance_column
124
134
  inher_col_value = attrs[inheritance_column_name]
125
- class_name = inher_col_value.blank? ? version.item_type : inher_col_value
126
- class_name.constantize
135
+ return clazz if inher_col_value.blank?
136
+
137
+ # Rails 6.1 adds a public method for clients to use to customize STI classes. If that
138
+ # method is not available, fall back to using the private one
139
+ if clazz.public_methods.include?(:sti_class_for)
140
+ return clazz.sti_class_for(inher_col_value)
141
+ end
142
+
143
+ clazz.send(:find_sti_class, inher_col_value)
127
144
  end
128
145
  end
129
146
  end
@@ -12,9 +12,6 @@ module PaperTrail
12
12
  #
13
13
  # @api private
14
14
  module Request
15
- class InvalidOption < RuntimeError
16
- end
17
-
18
15
  class << self
19
16
  # Sets any data from the controller that you want PaperTrail to store.
20
17
  # See also `PaperTrail::Rails::Controller#info_for_paper_trail`.
@@ -31,24 +31,6 @@ module PaperTrail
31
31
  arel_field.matches("%\"#{field}\":#{json_value}%")
32
32
  end
33
33
  end
34
-
35
- def where_object_changes_condition(*)
36
- raise <<-STR.squish.freeze
37
- where_object_changes no longer supports reading JSON from a text
38
- column. The old implementation was inaccurate, returning more records
39
- than you wanted. This feature was deprecated in 7.1.0 and removed in
40
- 8.0.0. The json and jsonb datatypes are still supported. See the
41
- discussion at https://github.com/paper-trail-gem/paper_trail/issues/803
42
- STR
43
- end
44
-
45
- # Raises an exception as this operation is not allowed from text columns.
46
- def where_object_changes_from_condition(*)
47
- raise <<-STR.squish.freeze
48
- where_object_changes_from does not support reading JSON from a text
49
- column. The json and jsonb datatypes are supported.
50
- STR
51
- end
52
34
  end
53
35
  end
54
36
  end
@@ -9,7 +9,7 @@ module PaperTrail
9
9
  extend self # makes all instance methods become module methods as well
10
10
 
11
11
  def load(string)
12
- ::YAML.load string
12
+ ::YAML.respond_to?(:unsafe_load) ? ::YAML.unsafe_load(string) : ::YAML.load(string)
13
13
  end
14
14
 
15
15
  # @param object (Hash | HashWithIndifferentAccess) - Coming from
@@ -26,26 +26,6 @@ module PaperTrail
26
26
  def where_object_condition(arel_field, field, value)
27
27
  arel_field.matches("%\n#{field}: #{value}\n%")
28
28
  end
29
-
30
- # Returns a SQL LIKE condition to be used to match the given field and
31
- # value in the serialized `object_changes`.
32
- def where_object_changes_condition(*)
33
- raise <<-STR.squish.freeze
34
- where_object_changes no longer supports reading YAML from a text
35
- column. The old implementation was inaccurate, returning more records
36
- than you wanted. This feature was deprecated in 8.1.0 and removed in
37
- 9.0.0. The json and jsonb datatypes are still supported. See
38
- discussion at https://github.com/paper-trail-gem/paper_trail/pull/997
39
- STR
40
- end
41
-
42
- # Raises an exception as this operation is not allowed with YAML.
43
- def where_object_changes_from_condition(*)
44
- raise <<-STR.squish.freeze
45
- where_object_changes_from does not support reading YAML from a text
46
- column. The json and jsonb datatypes are supported.
47
- STR
48
- end
49
29
  end
50
30
  end
51
31
  end
@@ -1,9 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "paper_trail/attribute_serializers/object_changes_attribute"
4
+ require "paper_trail/queries/versions/where_attribute_changes"
4
5
  require "paper_trail/queries/versions/where_object"
5
6
  require "paper_trail/queries/versions/where_object_changes"
6
7
  require "paper_trail/queries/versions/where_object_changes_from"
8
+ require "paper_trail/queries/versions/where_object_changes_to"
7
9
 
8
10
  module PaperTrail
9
11
  # Originally, PaperTrail did not provide this module, and all of this
@@ -14,17 +16,13 @@ module PaperTrail
14
16
  extend ::ActiveSupport::Concern
15
17
 
16
18
  included do
17
- belongs_to :item, polymorphic: true, optional: true
19
+ belongs_to :item, polymorphic: true, optional: true, inverse_of: false
18
20
  validates_presence_of :event
19
21
  after_create :enforce_version_limit!
20
22
  end
21
23
 
22
24
  # :nodoc:
23
25
  module ClassMethods
24
- def item_subtype_column_present?
25
- column_names.include?("item_subtype")
26
- end
27
-
28
26
  def with_item_keys(item_type, item_id)
29
27
  where item_type: item_type, item_id: item_id
30
28
  end
@@ -42,7 +40,7 @@ module PaperTrail
42
40
  end
43
41
 
44
42
  def not_creates
45
- where "event <> ?", "create"
43
+ where.not(event: "create")
46
44
  end
47
45
 
48
46
  def between(start_time, end_time)
@@ -60,6 +58,18 @@ module PaperTrail
60
58
  end
61
59
  end
62
60
 
61
+ # Given an attribute like `"name"`, query the `versions.object_changes`
62
+ # column for any changes that modified the provided attribute.
63
+ #
64
+ # @api public
65
+ def where_attribute_changes(attribute)
66
+ unless attribute.is_a?(String) || attribute.is_a?(Symbol)
67
+ raise ArgumentError, "expected to receive a String or Symbol"
68
+ end
69
+
70
+ Queries::Versions::WhereAttributeChanges.new(self, attribute).execute
71
+ end
72
+
63
73
  # Given a hash of attributes like `name: 'Joan'`, query the
64
74
  # `versions.objects` column.
65
75
  #
@@ -131,6 +141,21 @@ module PaperTrail
131
141
  Queries::Versions::WhereObjectChangesFrom.new(self, args).execute
132
142
  end
133
143
 
144
+ # Given a hash of attributes like `name: 'Joan'`, query the
145
+ # `versions.objects_changes` column for changes where the version changed
146
+ # to the hash of attributes from other values.
147
+ #
148
+ # This is useful for finding versions where the attribute started with an
149
+ # unknown value and changed to a known value. This is in comparison to
150
+ # `where_object_changes` which will find both the changes before and
151
+ # after.
152
+ #
153
+ # @api public
154
+ def where_object_changes_to(args = {})
155
+ raise ArgumentError, "expected to receive a Hash" unless args.is_a?(Hash)
156
+ Queries::Versions::WhereObjectChangesTo.new(self, args).execute
157
+ end
158
+
134
159
  def primary_key_is_int?
135
160
  @primary_key_is_int ||= columns_hash[primary_key].type == :integer
136
161
  rescue StandardError # TODO: Rescue something more specific
@@ -237,7 +262,7 @@ module PaperTrail
237
262
  #
238
263
  def reify(options = {})
239
264
  unless self.class.column_names.include? "object"
240
- raise "reify can't be called without an object column"
265
+ raise Error, "reify requires an object column"
241
266
  end
242
267
  return nil if object.nil?
243
268
  ::PaperTrail::Reifier.reify(self, options)
@@ -350,16 +375,23 @@ module PaperTrail
350
375
  # The version limit can be global or per-model.
351
376
  #
352
377
  # @api private
353
- #
354
- # TODO: Duplication: similar `constantize` in Reifier#version_reification_class
355
378
  def version_limit
356
- if self.class.item_subtype_column_present?
357
- klass = (item_subtype || item_type).constantize
358
- if klass&.paper_trail_options&.key?(:limit)
359
- return klass.paper_trail_options[:limit]
360
- end
379
+ klass = item.class
380
+ if limit_option?(klass)
381
+ klass.paper_trail_options[:limit]
382
+ elsif base_class_limit_option?(klass)
383
+ klass.base_class.paper_trail_options[:limit]
384
+ else
385
+ PaperTrail.config.version_limit
361
386
  end
362
- PaperTrail.config.version_limit
387
+ end
388
+
389
+ def limit_option?(klass)
390
+ klass.respond_to?(:paper_trail_options) && klass.paper_trail_options.key?(:limit)
391
+ end
392
+
393
+ def base_class_limit_option?(klass)
394
+ klass.respond_to?(:base_class) && limit_option?(klass.base_class)
363
395
  end
364
396
  end
365
397
  end
@@ -8,7 +8,7 @@ module PaperTrail
8
8
  # People are encouraged to use `PaperTrail.gem_version` instead.
9
9
  module VERSION
10
10
  MAJOR = 12
11
- MINOR = 0
11
+ MINOR = 3
12
12
  TINY = 0
13
13
 
14
14
  # Set PRE to nil unless it's a pre-release (beta, rc, etc.)
data/lib/paper_trail.rb CHANGED
@@ -8,6 +8,7 @@
8
8
  # can revisit this decision.
9
9
  require "active_support/all"
10
10
 
11
+ require "paper_trail/errors"
11
12
  require "paper_trail/cleaner"
12
13
  require "paper_trail/compatibility"
13
14
  require "paper_trail/config"
@@ -25,6 +26,8 @@ module PaperTrail
25
26
  named created_at.
26
27
  EOS
27
28
 
29
+ RAILS_GTE_7_0 = ::ActiveRecord.gem_version >= ::Gem::Version.new("7.0.0")
30
+
28
31
  extend PaperTrail::Cleaner
29
32
 
30
33
  class << self
@@ -68,7 +71,7 @@ module PaperTrail
68
71
  #
69
72
  # @api public
70
73
  def request(options = nil, &block)
71
- if options.nil? && !block_given?
74
+ if options.nil? && !block
72
75
  Request
73
76
  else
74
77
  Request.with(options, &block)
@@ -78,7 +81,7 @@ module PaperTrail
78
81
  # Set the field which records when a version was created.
79
82
  # @api public
80
83
  def timestamp_field=(_field_name)
81
- raise(E_TIMESTAMP_FIELD_CONFIG)
84
+ raise Error, E_TIMESTAMP_FIELD_CONFIG
82
85
  end
83
86
 
84
87
  # Set the PaperTrail serializer. This setting affects all threads.
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: 12.0.0
4
+ version: 12.3.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: 2021-03-29 00:00:00.000000000 Z
13
+ date: 2022-03-13 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: activerecord
@@ -46,42 +46,42 @@ dependencies:
46
46
  requirements:
47
47
  - - "~>"
48
48
  - !ruby/object:Gem::Version
49
- version: '2.2'
49
+ version: 2.4.1
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.2'
56
+ version: 2.4.1
57
57
  - !ruby/object:Gem::Dependency
58
58
  name: byebug
59
59
  requirement: !ruby/object:Gem::Requirement
60
60
  requirements:
61
61
  - - "~>"
62
62
  - !ruby/object:Gem::Version
63
- version: '11.0'
63
+ version: '11.1'
64
64
  type: :development
65
65
  prerelease: false
66
66
  version_requirements: !ruby/object:Gem::Requirement
67
67
  requirements:
68
68
  - - "~>"
69
69
  - !ruby/object:Gem::Version
70
- version: '11.0'
70
+ version: '11.1'
71
71
  - !ruby/object:Gem::Dependency
72
72
  name: ffaker
73
73
  requirement: !ruby/object:Gem::Requirement
74
74
  requirements:
75
75
  - - "~>"
76
76
  - !ruby/object:Gem::Version
77
- version: '2.11'
77
+ version: '2.20'
78
78
  type: :development
79
79
  prerelease: false
80
80
  version_requirements: !ruby/object:Gem::Requirement
81
81
  requirements:
82
82
  - - "~>"
83
83
  - !ruby/object:Gem::Version
84
- version: '2.11'
84
+ version: '2.20'
85
85
  - !ruby/object:Gem::Dependency
86
86
  name: generator_spec
87
87
  requirement: !ruby/object:Gem::Requirement
@@ -102,14 +102,14 @@ dependencies:
102
102
  requirements:
103
103
  - - "~>"
104
104
  - !ruby/object:Gem::Version
105
- version: 0.9.14
105
+ version: 1.0.0
106
106
  type: :development
107
107
  prerelease: false
108
108
  version_requirements: !ruby/object:Gem::Requirement
109
109
  requirements:
110
110
  - - "~>"
111
111
  - !ruby/object:Gem::Version
112
- version: 0.9.14
112
+ version: 1.0.0
113
113
  - !ruby/object:Gem::Dependency
114
114
  name: rails
115
115
  requirement: !ruby/object:Gem::Requirement
@@ -144,118 +144,140 @@ dependencies:
144
144
  requirements:
145
145
  - - "~>"
146
146
  - !ruby/object:Gem::Version
147
- version: '4.0'
147
+ version: 5.0.2
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: '4.0'
154
+ version: 5.0.2
155
155
  - !ruby/object:Gem::Dependency
156
156
  name: rubocop
157
157
  requirement: !ruby/object:Gem::Requirement
158
158
  requirements:
159
159
  - - "~>"
160
160
  - !ruby/object:Gem::Version
161
- version: 1.11.0
161
+ version: 1.22.2
162
162
  type: :development
163
163
  prerelease: false
164
164
  version_requirements: !ruby/object:Gem::Requirement
165
165
  requirements:
166
166
  - - "~>"
167
167
  - !ruby/object:Gem::Version
168
- version: 1.11.0
168
+ version: 1.22.2
169
169
  - !ruby/object:Gem::Dependency
170
- name: rubocop-rails
170
+ name: rubocop-packaging
171
171
  requirement: !ruby/object:Gem::Requirement
172
172
  requirements:
173
173
  - - "~>"
174
174
  - !ruby/object:Gem::Version
175
- version: 2.9.1
175
+ version: 0.5.1
176
176
  type: :development
177
177
  prerelease: false
178
178
  version_requirements: !ruby/object:Gem::Requirement
179
179
  requirements:
180
180
  - - "~>"
181
181
  - !ruby/object:Gem::Version
182
- version: 2.9.1
182
+ version: 0.5.1
183
183
  - !ruby/object:Gem::Dependency
184
- name: rubocop-packaging
184
+ name: rubocop-performance
185
185
  requirement: !ruby/object:Gem::Requirement
186
186
  requirements:
187
187
  - - "~>"
188
188
  - !ruby/object:Gem::Version
189
- version: 0.5.1
189
+ version: 1.11.5
190
190
  type: :development
191
191
  prerelease: false
192
192
  version_requirements: !ruby/object:Gem::Requirement
193
193
  requirements:
194
194
  - - "~>"
195
195
  - !ruby/object:Gem::Version
196
- version: 0.5.1
196
+ version: 1.11.5
197
197
  - !ruby/object:Gem::Dependency
198
- name: rubocop-performance
198
+ name: rubocop-rails
199
+ requirement: !ruby/object:Gem::Requirement
200
+ requirements:
201
+ - - "~>"
202
+ - !ruby/object:Gem::Version
203
+ version: 2.12.4
204
+ type: :development
205
+ prerelease: false
206
+ version_requirements: !ruby/object:Gem::Requirement
207
+ requirements:
208
+ - - "~>"
209
+ - !ruby/object:Gem::Version
210
+ version: 2.12.4
211
+ - !ruby/object:Gem::Dependency
212
+ name: rubocop-rake
199
213
  requirement: !ruby/object:Gem::Requirement
200
214
  requirements:
201
215
  - - "~>"
202
216
  - !ruby/object:Gem::Version
203
- version: 1.10.1
217
+ version: 0.6.0
204
218
  type: :development
205
219
  prerelease: false
206
220
  version_requirements: !ruby/object:Gem::Requirement
207
221
  requirements:
208
222
  - - "~>"
209
223
  - !ruby/object:Gem::Version
210
- version: 1.10.1
224
+ version: 0.6.0
211
225
  - !ruby/object:Gem::Dependency
212
226
  name: rubocop-rspec
213
227
  requirement: !ruby/object:Gem::Requirement
214
228
  requirements:
215
229
  - - "~>"
216
230
  - !ruby/object:Gem::Version
217
- version: 2.2.0
231
+ version: 2.5.0
218
232
  type: :development
219
233
  prerelease: false
220
234
  version_requirements: !ruby/object:Gem::Requirement
221
235
  requirements:
222
236
  - - "~>"
223
237
  - !ruby/object:Gem::Version
224
- version: 2.2.0
238
+ version: 2.5.0
239
+ - !ruby/object:Gem::Dependency
240
+ name: simplecov
241
+ requirement: !ruby/object:Gem::Requirement
242
+ requirements:
243
+ - - "~>"
244
+ - !ruby/object:Gem::Version
245
+ version: 0.21.2
246
+ type: :development
247
+ prerelease: false
248
+ version_requirements: !ruby/object:Gem::Requirement
249
+ requirements:
250
+ - - "~>"
251
+ - !ruby/object:Gem::Version
252
+ version: 0.21.2
225
253
  - !ruby/object:Gem::Dependency
226
254
  name: mysql2
227
255
  requirement: !ruby/object:Gem::Requirement
228
256
  requirements:
229
257
  - - "~>"
230
258
  - !ruby/object:Gem::Version
231
- version: '0.5'
259
+ version: 0.5.3
232
260
  type: :development
233
261
  prerelease: false
234
262
  version_requirements: !ruby/object:Gem::Requirement
235
263
  requirements:
236
264
  - - "~>"
237
265
  - !ruby/object:Gem::Version
238
- version: '0.5'
266
+ version: 0.5.3
239
267
  - !ruby/object:Gem::Dependency
240
268
  name: pg
241
269
  requirement: !ruby/object:Gem::Requirement
242
270
  requirements:
243
- - - ">="
244
- - !ruby/object:Gem::Version
245
- version: '0.18'
246
- - - "<"
271
+ - - "~>"
247
272
  - !ruby/object:Gem::Version
248
- version: '2.0'
273
+ version: '1.2'
249
274
  type: :development
250
275
  prerelease: false
251
276
  version_requirements: !ruby/object:Gem::Requirement
252
277
  requirements:
253
- - - ">="
254
- - !ruby/object:Gem::Version
255
- version: '0.18'
256
- - - "<"
278
+ - - "~>"
257
279
  - !ruby/object:Gem::Version
258
- version: '2.0'
280
+ version: '1.2'
259
281
  - !ruby/object:Gem::Dependency
260
282
  name: sqlite3
261
283
  requirement: !ruby/object:Gem::Requirement
@@ -297,6 +319,7 @@ files:
297
319
  - lib/paper_trail/cleaner.rb
298
320
  - lib/paper_trail/compatibility.rb
299
321
  - lib/paper_trail/config.rb
322
+ - lib/paper_trail/errors.rb
300
323
  - lib/paper_trail/events/base.rb
301
324
  - lib/paper_trail/events/create.rb
302
325
  - lib/paper_trail/events/destroy.rb
@@ -311,9 +334,11 @@ files:
311
334
  - lib/paper_trail/frameworks/rspec/helpers.rb
312
335
  - lib/paper_trail/has_paper_trail.rb
313
336
  - lib/paper_trail/model_config.rb
337
+ - lib/paper_trail/queries/versions/where_attribute_changes.rb
314
338
  - lib/paper_trail/queries/versions/where_object.rb
315
339
  - lib/paper_trail/queries/versions/where_object_changes.rb
316
340
  - lib/paper_trail/queries/versions/where_object_changes_from.rb
341
+ - lib/paper_trail/queries/versions/where_object_changes_to.rb
317
342
  - lib/paper_trail/record_history.rb
318
343
  - lib/paper_trail/record_trail.rb
319
344
  - lib/paper_trail/reifier.rb
@@ -335,15 +360,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
335
360
  requirements:
336
361
  - - ">="
337
362
  - !ruby/object:Gem::Version
338
- version: 2.5.0
363
+ version: 2.6.0
339
364
  required_rubygems_version: !ruby/object:Gem::Requirement
340
365
  requirements:
341
366
  - - ">="
342
367
  - !ruby/object:Gem::Version
343
368
  version: 1.3.6
344
369
  requirements: []
345
- rubyforge_project:
346
- rubygems_version: 2.7.6.2
370
+ rubygems_version: 3.2.22
347
371
  signing_key:
348
372
  specification_version: 4
349
373
  summary: Track changes to your models.