paper_trail 10.3.1 → 13.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/LICENSE +20 -0
- data/lib/generators/paper_trail/install/install_generator.rb +25 -7
- data/lib/generators/paper_trail/install/templates/create_versions.rb.erb +4 -2
- data/lib/generators/paper_trail/migration_generator.rb +5 -4
- data/lib/generators/paper_trail/update_item_subtype/update_item_subtype_generator.rb +4 -2
- data/lib/paper_trail/attribute_serializers/attribute_serializer_factory.rb +24 -10
- data/lib/paper_trail/attribute_serializers/cast_attribute_serializer.rb +14 -46
- data/lib/paper_trail/compatibility.rb +3 -3
- data/lib/paper_trail/config.rb +0 -33
- data/lib/paper_trail/errors.rb +33 -0
- data/lib/paper_trail/events/base.rb +68 -68
- data/lib/paper_trail/events/destroy.rb +1 -1
- data/lib/paper_trail/events/update.rb +23 -4
- data/lib/paper_trail/frameworks/active_record.rb +9 -2
- data/lib/paper_trail/frameworks/rails/controller.rb +1 -9
- data/lib/paper_trail/frameworks/rails/railtie.rb +30 -0
- data/lib/paper_trail/frameworks/rails.rb +1 -2
- data/lib/paper_trail/has_paper_trail.rb +1 -1
- data/lib/paper_trail/model_config.rb +49 -46
- data/lib/paper_trail/queries/versions/where_attribute_changes.rb +50 -0
- data/lib/paper_trail/queries/versions/where_object.rb +1 -1
- data/lib/paper_trail/queries/versions/where_object_changes.rb +8 -13
- data/lib/paper_trail/queries/versions/where_object_changes_from.rb +57 -0
- data/lib/paper_trail/queries/versions/where_object_changes_to.rb +57 -0
- data/lib/paper_trail/record_trail.rb +7 -7
- data/lib/paper_trail/reifier.rb +41 -26
- data/lib/paper_trail/request.rb +0 -3
- data/lib/paper_trail/serializers/json.rb +0 -10
- data/lib/paper_trail/serializers/yaml.rb +19 -13
- data/lib/paper_trail/type_serializers/postgres_array_serializer.rb +1 -14
- data/lib/paper_trail/version_concern.rb +86 -41
- data/lib/paper_trail/version_number.rb +3 -3
- data/lib/paper_trail.rb +17 -40
- metadata +104 -43
- data/lib/paper_trail/frameworks/rails/engine.rb +0 -45
@@ -9,13 +9,23 @@ module PaperTrail
|
|
9
9
|
extend self # makes all instance methods become module methods as well
|
10
10
|
|
11
11
|
def load(string)
|
12
|
-
|
12
|
+
if use_safe_load?
|
13
|
+
::YAML.safe_load(
|
14
|
+
string,
|
15
|
+
permitted_classes: ::ActiveRecord.yaml_column_permitted_classes,
|
16
|
+
aliases: true
|
17
|
+
)
|
18
|
+
elsif ::YAML.respond_to?(:unsafe_load)
|
19
|
+
::YAML.unsafe_load(string)
|
20
|
+
else
|
21
|
+
::YAML.load(string)
|
22
|
+
end
|
13
23
|
end
|
14
24
|
|
15
25
|
# @param object (Hash | HashWithIndifferentAccess) - Coming from
|
16
26
|
# `recordable_object` `object` will be a plain `Hash`. However, due to
|
17
|
-
# recent [memory optimizations](https://
|
18
|
-
# `recordable_object_changes`, it will be a `HashWithIndifferentAccess`.
|
27
|
+
# recent [memory optimizations](https://github.com/paper-trail-gem/paper_trail/pull/1189),
|
28
|
+
# when coming from `recordable_object_changes`, it will be a `HashWithIndifferentAccess`.
|
19
29
|
def dump(object)
|
20
30
|
object = object.to_hash if object.is_a?(HashWithIndifferentAccess)
|
21
31
|
::YAML.dump object
|
@@ -27,16 +37,12 @@ module PaperTrail
|
|
27
37
|
arel_field.matches("%\n#{field}: #{value}\n%")
|
28
38
|
end
|
29
39
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
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
|
+
private
|
41
|
+
|
42
|
+
# `use_yaml_unsafe_load` was added in 7.0.3.1, will be removed in 7.1.0?
|
43
|
+
def use_safe_load?
|
44
|
+
defined?(ActiveRecord.use_yaml_unsafe_load) &&
|
45
|
+
!ActiveRecord.use_yaml_unsafe_load
|
40
46
|
end
|
41
47
|
end
|
42
48
|
end
|
@@ -11,15 +11,12 @@ module PaperTrail
|
|
11
11
|
end
|
12
12
|
|
13
13
|
def serialize(array)
|
14
|
-
return serialize_with_ar(array) if active_record_pre_502?
|
15
14
|
array
|
16
15
|
end
|
17
16
|
|
18
17
|
def deserialize(array)
|
19
|
-
return deserialize_with_ar(array) if active_record_pre_502?
|
20
|
-
|
21
18
|
case array
|
22
|
-
# Needed for legacy
|
19
|
+
# Needed for legacy data. If serialized array is a string
|
23
20
|
# then it was serialized with Rails < 5.0.2.
|
24
21
|
when ::String then deserialize_with_ar(array)
|
25
22
|
else array
|
@@ -28,16 +25,6 @@ module PaperTrail
|
|
28
25
|
|
29
26
|
private
|
30
27
|
|
31
|
-
def active_record_pre_502?
|
32
|
-
::ActiveRecord.gem_version < Gem::Version.new("5.0.2")
|
33
|
-
end
|
34
|
-
|
35
|
-
def serialize_with_ar(array)
|
36
|
-
ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Array.
|
37
|
-
new(@subtype, @delimiter).
|
38
|
-
serialize(array)
|
39
|
-
end
|
40
|
-
|
41
28
|
def deserialize_with_ar(array)
|
42
29
|
ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Array.
|
43
30
|
new(@subtype, @delimiter).
|
@@ -1,8 +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"
|
7
|
+
require "paper_trail/queries/versions/where_object_changes_from"
|
8
|
+
require "paper_trail/queries/versions/where_object_changes_to"
|
6
9
|
|
7
10
|
module PaperTrail
|
8
11
|
# Originally, PaperTrail did not provide this module, and all of this
|
@@ -12,23 +15,20 @@ module PaperTrail
|
|
12
15
|
module VersionConcern
|
13
16
|
extend ::ActiveSupport::Concern
|
14
17
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
end
|
18
|
+
E_YAML_PERMITTED_CLASSES = <<-EOS.squish.freeze
|
19
|
+
PaperTrail encountered a Psych::DisallowedClass error during
|
20
|
+
deserialization of YAML column, indicating that
|
21
|
+
yaml_column_permitted_classes has not been configured correctly. %s
|
22
|
+
EOS
|
21
23
|
|
24
|
+
included do
|
25
|
+
belongs_to :item, polymorphic: true, optional: true, inverse_of: false
|
22
26
|
validates_presence_of :event
|
23
27
|
after_create :enforce_version_limit!
|
24
28
|
end
|
25
29
|
|
26
30
|
# :nodoc:
|
27
31
|
module ClassMethods
|
28
|
-
def item_subtype_column_present?
|
29
|
-
column_names.include?("item_subtype")
|
30
|
-
end
|
31
|
-
|
32
32
|
def with_item_keys(item_type, item_id)
|
33
33
|
where item_type: item_type, item_id: item_id
|
34
34
|
end
|
@@ -46,7 +46,7 @@ module PaperTrail
|
|
46
46
|
end
|
47
47
|
|
48
48
|
def not_creates
|
49
|
-
where
|
49
|
+
where.not(event: "create")
|
50
50
|
end
|
51
51
|
|
52
52
|
def between(start_time, end_time)
|
@@ -64,6 +64,18 @@ module PaperTrail
|
|
64
64
|
end
|
65
65
|
end
|
66
66
|
|
67
|
+
# Given an attribute like `"name"`, query the `versions.object_changes`
|
68
|
+
# column for any changes that modified the provided attribute.
|
69
|
+
#
|
70
|
+
# @api public
|
71
|
+
def where_attribute_changes(attribute)
|
72
|
+
unless attribute.is_a?(String) || attribute.is_a?(Symbol)
|
73
|
+
raise ArgumentError, "expected to receive a String or Symbol"
|
74
|
+
end
|
75
|
+
|
76
|
+
Queries::Versions::WhereAttributeChanges.new(self, attribute).execute
|
77
|
+
end
|
78
|
+
|
67
79
|
# Given a hash of attributes like `name: 'Joan'`, query the
|
68
80
|
# `versions.objects` column.
|
69
81
|
#
|
@@ -120,6 +132,36 @@ module PaperTrail
|
|
120
132
|
Queries::Versions::WhereObjectChanges.new(self, args).execute
|
121
133
|
end
|
122
134
|
|
135
|
+
# Given a hash of attributes like `name: 'Joan'`, query the
|
136
|
+
# `versions.objects_changes` column for changes where the version changed
|
137
|
+
# from the hash of attributes to other values.
|
138
|
+
#
|
139
|
+
# This is useful for finding versions where the attribute started with a
|
140
|
+
# known value and changed to something else. This is in comparison to
|
141
|
+
# `where_object_changes` which will find both the changes before and
|
142
|
+
# after.
|
143
|
+
#
|
144
|
+
# @api public
|
145
|
+
def where_object_changes_from(args = {})
|
146
|
+
raise ArgumentError, "expected to receive a Hash" unless args.is_a?(Hash)
|
147
|
+
Queries::Versions::WhereObjectChangesFrom.new(self, args).execute
|
148
|
+
end
|
149
|
+
|
150
|
+
# Given a hash of attributes like `name: 'Joan'`, query the
|
151
|
+
# `versions.objects_changes` column for changes where the version changed
|
152
|
+
# to the hash of attributes from other values.
|
153
|
+
#
|
154
|
+
# This is useful for finding versions where the attribute started with an
|
155
|
+
# unknown value and changed to a known value. This is in comparison to
|
156
|
+
# `where_object_changes` which will find both the changes before and
|
157
|
+
# after.
|
158
|
+
#
|
159
|
+
# @api public
|
160
|
+
def where_object_changes_to(args = {})
|
161
|
+
raise ArgumentError, "expected to receive a Hash" unless args.is_a?(Hash)
|
162
|
+
Queries::Versions::WhereObjectChangesTo.new(self, args).execute
|
163
|
+
end
|
164
|
+
|
123
165
|
def primary_key_is_int?
|
124
166
|
@primary_key_is_int ||= columns_hash[primary_key].type == :integer
|
125
167
|
rescue StandardError # TODO: Rescue something more specific
|
@@ -145,6 +187,7 @@ module PaperTrail
|
|
145
187
|
# Default: false.
|
146
188
|
# @return `ActiveRecord::Relation`
|
147
189
|
# @api public
|
190
|
+
# rubocop:disable Style/OptionalBooleanParameter
|
148
191
|
def preceding(obj, timestamp_arg = false)
|
149
192
|
if timestamp_arg != true && primary_key_is_int?
|
150
193
|
preceding_by_id(obj)
|
@@ -152,6 +195,7 @@ module PaperTrail
|
|
152
195
|
preceding_by_timestamp(obj)
|
153
196
|
end
|
154
197
|
end
|
198
|
+
# rubocop:enable Style/OptionalBooleanParameter
|
155
199
|
|
156
200
|
# Returns versions after `obj`.
|
157
201
|
#
|
@@ -160,6 +204,7 @@ module PaperTrail
|
|
160
204
|
# Default: false.
|
161
205
|
# @return `ActiveRecord::Relation`
|
162
206
|
# @api public
|
207
|
+
# rubocop:disable Style/OptionalBooleanParameter
|
163
208
|
def subsequent(obj, timestamp_arg = false)
|
164
209
|
if timestamp_arg != true && primary_key_is_int?
|
165
210
|
subsequent_by_id(obj)
|
@@ -167,6 +212,7 @@ module PaperTrail
|
|
167
212
|
subsequent_by_timestamp(obj)
|
168
213
|
end
|
169
214
|
end
|
215
|
+
# rubocop:enable Style/OptionalBooleanParameter
|
170
216
|
|
171
217
|
private
|
172
218
|
|
@@ -205,18 +251,8 @@ module PaperTrail
|
|
205
251
|
|
206
252
|
# Restore the item from this version.
|
207
253
|
#
|
208
|
-
# Optionally this can also restore all :has_one and :has_many (including
|
209
|
-
# has_many :through) associations as they were "at the time", if they are
|
210
|
-
# also being versioned by PaperTrail.
|
211
|
-
#
|
212
254
|
# Options:
|
213
255
|
#
|
214
|
-
# - :has_one
|
215
|
-
# - `true` - Also reify has_one associations.
|
216
|
-
# - `false - Default.
|
217
|
-
# - :has_many
|
218
|
-
# - `true` - Also reify has_many and has_many :through associations.
|
219
|
-
# - `false` - Default.
|
220
256
|
# - :mark_for_destruction
|
221
257
|
# - `true` - Mark the has_one/has_many associations that did not exist in
|
222
258
|
# the reified version for destruction, instead of removing them.
|
@@ -232,7 +268,7 @@ module PaperTrail
|
|
232
268
|
#
|
233
269
|
def reify(options = {})
|
234
270
|
unless self.class.column_names.include? "object"
|
235
|
-
raise "reify
|
271
|
+
raise Error, "reify requires an object column"
|
236
272
|
end
|
237
273
|
return nil if object.nil?
|
238
274
|
::PaperTrail::Reifier.reify(self, options)
|
@@ -258,13 +294,6 @@ module PaperTrail
|
|
258
294
|
end
|
259
295
|
alias version_author terminator
|
260
296
|
|
261
|
-
def sibling_versions(reload = false)
|
262
|
-
if reload || !defined?(@sibling_versions) || @sibling_versions.nil?
|
263
|
-
@sibling_versions = self.class.with_item_keys(item_type, item_id)
|
264
|
-
end
|
265
|
-
@sibling_versions
|
266
|
-
end
|
267
|
-
|
268
297
|
def next
|
269
298
|
@next ||= sibling_versions.subsequent(self).first
|
270
299
|
end
|
@@ -274,8 +303,9 @@ module PaperTrail
|
|
274
303
|
end
|
275
304
|
|
276
305
|
# Returns an integer representing the chronological position of the
|
277
|
-
# version among its siblings
|
278
|
-
#
|
306
|
+
# version among its siblings. The "create" event, for example, has an index
|
307
|
+
# of 0.
|
308
|
+
#
|
279
309
|
# @api public
|
280
310
|
def index
|
281
311
|
@index ||= RecordHistory.new(sibling_versions, self.class).index(self)
|
@@ -285,7 +315,7 @@ module PaperTrail
|
|
285
315
|
|
286
316
|
# @api private
|
287
317
|
def load_changeset
|
288
|
-
if PaperTrail.config.object_changes_adapter
|
318
|
+
if PaperTrail.config.object_changes_adapter.respond_to?(:load_changeset)
|
289
319
|
return PaperTrail.config.object_changes_adapter.load_changeset(self)
|
290
320
|
end
|
291
321
|
|
@@ -324,7 +354,10 @@ module PaperTrail
|
|
324
354
|
else
|
325
355
|
begin
|
326
356
|
PaperTrail.serializer.load(object_changes)
|
327
|
-
rescue StandardError
|
357
|
+
rescue StandardError => e
|
358
|
+
if defined?(::Psych::Exception) && e.instance_of?(::Psych::Exception)
|
359
|
+
::Kernel.warn format(E_YAML_PERMITTED_CLASSES, e)
|
360
|
+
end
|
328
361
|
{}
|
329
362
|
end
|
330
363
|
end
|
@@ -342,20 +375,32 @@ module PaperTrail
|
|
342
375
|
excess_versions.map(&:destroy)
|
343
376
|
end
|
344
377
|
|
378
|
+
# @api private
|
379
|
+
def sibling_versions
|
380
|
+
@sibling_versions ||= self.class.with_item_keys(item_type, item_id)
|
381
|
+
end
|
382
|
+
|
345
383
|
# See docs section 2.e. Limiting the Number of Versions Created.
|
346
384
|
# The version limit can be global or per-model.
|
347
385
|
#
|
348
386
|
# @api private
|
349
|
-
#
|
350
|
-
# TODO: Duplication: similar `constantize` in Reifier#version_reification_class
|
351
387
|
def version_limit
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
388
|
+
klass = item.class
|
389
|
+
if limit_option?(klass)
|
390
|
+
klass.paper_trail_options[:limit]
|
391
|
+
elsif base_class_limit_option?(klass)
|
392
|
+
klass.base_class.paper_trail_options[:limit]
|
393
|
+
else
|
394
|
+
PaperTrail.config.version_limit
|
357
395
|
end
|
358
|
-
|
396
|
+
end
|
397
|
+
|
398
|
+
def limit_option?(klass)
|
399
|
+
klass.respond_to?(:paper_trail_options) && klass.paper_trail_options.key?(:limit)
|
400
|
+
end
|
401
|
+
|
402
|
+
def base_class_limit_option?(klass)
|
403
|
+
klass.respond_to?(:base_class) && limit_option?(klass.base_class)
|
359
404
|
end
|
360
405
|
end
|
361
406
|
end
|
@@ -7,9 +7,9 @@ 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 =
|
11
|
-
MINOR =
|
12
|
-
TINY =
|
10
|
+
MAJOR = 13
|
11
|
+
MINOR = 0
|
12
|
+
TINY = 0
|
13
13
|
|
14
14
|
# Set PRE to nil unless it's a pre-release (beta, rc, etc.)
|
15
15
|
PRE = nil
|
data/lib/paper_trail.rb
CHANGED
@@ -8,40 +8,26 @@
|
|
8
8
|
# can revisit this decision.
|
9
9
|
require "active_support/all"
|
10
10
|
|
11
|
-
|
12
|
-
# all of those files, but it seems easier to troubleshoot if we just make sure
|
13
|
-
# AR is loaded here before loading *any* of PT. See discussion of
|
14
|
-
# performance/simplicity tradeoff for activesupport above.
|
15
|
-
require "active_record"
|
16
|
-
|
17
|
-
require "request_store"
|
11
|
+
require "paper_trail/errors"
|
18
12
|
require "paper_trail/cleaner"
|
19
13
|
require "paper_trail/compatibility"
|
20
14
|
require "paper_trail/config"
|
21
|
-
require "paper_trail/has_paper_trail"
|
22
15
|
require "paper_trail/record_history"
|
23
|
-
require "paper_trail/reifier"
|
24
16
|
require "paper_trail/request"
|
25
|
-
require "paper_trail/version_concern"
|
26
17
|
require "paper_trail/version_number"
|
27
18
|
require "paper_trail/serializers/json"
|
28
|
-
require "paper_trail/serializers/yaml"
|
29
19
|
|
30
20
|
# An ActiveRecord extension that tracks changes to your models, for auditing or
|
31
21
|
# versioning.
|
32
22
|
module PaperTrail
|
33
|
-
E_RAILS_NOT_LOADED = <<-EOS.squish.freeze
|
34
|
-
PaperTrail has been loaded too early, before rails is loaded. This can
|
35
|
-
happen when another gem defines the ::Rails namespace, then PT is loaded,
|
36
|
-
all before rails is loaded. You may want to reorder your Gemfile, or defer
|
37
|
-
the loading of PT by using `require: false` and a manual require elsewhere.
|
38
|
-
EOS
|
39
23
|
E_TIMESTAMP_FIELD_CONFIG = <<-EOS.squish.freeze
|
40
24
|
PaperTrail.timestamp_field= has been removed, without replacement. It is no
|
41
25
|
longer configurable. The timestamp column in the versions table must now be
|
42
26
|
named created_at.
|
43
27
|
EOS
|
44
28
|
|
29
|
+
RAILS_GTE_7_0 = ::ActiveRecord.gem_version >= ::Gem::Version.new("7.0.0")
|
30
|
+
|
45
31
|
extend PaperTrail::Cleaner
|
46
32
|
|
47
33
|
class << self
|
@@ -85,7 +71,7 @@ module PaperTrail
|
|
85
71
|
#
|
86
72
|
# @api public
|
87
73
|
def request(options = nil, &block)
|
88
|
-
if options.nil? && !
|
74
|
+
if options.nil? && !block
|
89
75
|
Request
|
90
76
|
else
|
91
77
|
Request.with(options, &block)
|
@@ -95,7 +81,7 @@ module PaperTrail
|
|
95
81
|
# Set the field which records when a version was created.
|
96
82
|
# @api public
|
97
83
|
def timestamp_field=(_field_name)
|
98
|
-
raise
|
84
|
+
raise Error, E_TIMESTAMP_FIELD_CONFIG
|
99
85
|
end
|
100
86
|
|
101
87
|
# Set the PaperTrail serializer. This setting affects all threads.
|
@@ -126,27 +112,18 @@ module PaperTrail
|
|
126
112
|
end
|
127
113
|
end
|
128
114
|
|
129
|
-
#
|
130
|
-
#
|
131
|
-
#
|
132
|
-
#
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
#
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
if defined?(::Rails.application)
|
142
|
-
require "paper_trail/frameworks/rails"
|
143
|
-
else
|
144
|
-
::Kernel.warn(::PaperTrail::E_RAILS_NOT_LOADED)
|
145
|
-
end
|
115
|
+
# PT is built on ActiveRecord, but does not require Rails. If Rails is defined,
|
116
|
+
# our Railtie makes sure not to load the AR-dependent parts of PT until AR is
|
117
|
+
# ready. A typical Rails `application.rb` has:
|
118
|
+
#
|
119
|
+
# ```
|
120
|
+
# require 'rails/all' # Defines `Rails`
|
121
|
+
# Bundler.require(*Rails.groups) # require 'paper_trail' (this file)
|
122
|
+
# ```
|
123
|
+
#
|
124
|
+
# Non-rails applications should take similar care to load AR before PT.
|
125
|
+
if defined?(Rails)
|
126
|
+
require "paper_trail/frameworks/rails"
|
146
127
|
else
|
147
128
|
require "paper_trail/frameworks/active_record"
|
148
129
|
end
|
149
|
-
|
150
|
-
if defined?(::ActiveRecord)
|
151
|
-
::PaperTrail::Compatibility.check_activerecord(::ActiveRecord.gem_version)
|
152
|
-
end
|