paper_trail 9.2.0 → 12.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/LICENSE +20 -0
- data/lib/generators/paper_trail/install/USAGE +3 -0
- data/lib/generators/paper_trail/{install_generator.rb → install/install_generator.rb} +15 -38
- data/lib/generators/paper_trail/{templates → install/templates}/add_object_changes_to_versions.rb.erb +0 -0
- data/lib/generators/paper_trail/{templates → install/templates}/create_versions.rb.erb +5 -3
- data/lib/generators/paper_trail/migration_generator.rb +38 -0
- data/lib/generators/paper_trail/update_item_subtype/USAGE +4 -0
- data/lib/generators/paper_trail/update_item_subtype/templates/update_versions_for_item_subtype.rb.erb +85 -0
- data/lib/generators/paper_trail/update_item_subtype/update_item_subtype_generator.rb +19 -0
- data/lib/paper_trail/attribute_serializers/attribute_serializer_factory.rb +24 -10
- data/lib/paper_trail/attribute_serializers/cast_attribute_serializer.rb +14 -46
- data/lib/paper_trail/compatibility.rb +51 -0
- data/lib/paper_trail/config.rb +9 -2
- data/lib/paper_trail/errors.rb +33 -0
- data/lib/paper_trail/events/base.rb +320 -0
- data/lib/paper_trail/events/create.rb +32 -0
- data/lib/paper_trail/events/destroy.rb +42 -0
- data/lib/paper_trail/events/update.rb +65 -0
- data/lib/paper_trail/frameworks/active_record.rb +9 -2
- data/lib/paper_trail/frameworks/rails/controller.rb +1 -9
- data/lib/paper_trail/frameworks/rails/railtie.rb +30 -0
- data/lib/paper_trail/frameworks/rails.rb +1 -2
- data/lib/paper_trail/has_paper_trail.rb +20 -17
- data/lib/paper_trail/model_config.rb +127 -87
- data/lib/paper_trail/queries/versions/where_attribute_changes.rb +50 -0
- data/lib/paper_trail/queries/versions/where_object.rb +4 -1
- data/lib/paper_trail/queries/versions/where_object_changes.rb +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 +94 -411
- data/lib/paper_trail/reifier.rb +41 -25
- data/lib/paper_trail/request.rb +0 -3
- data/lib/paper_trail/serializers/json.rb +0 -10
- data/lib/paper_trail/serializers/yaml.rb +6 -13
- data/lib/paper_trail/type_serializers/postgres_array_serializer.rb +1 -15
- data/lib/paper_trail/version_concern.rb +142 -61
- data/lib/paper_trail/version_number.rb +1 -1
- data/lib/paper_trail.rb +18 -123
- metadata +147 -56
- data/lib/generators/paper_trail/USAGE +0 -2
- data/lib/paper_trail/frameworks/rails/engine.rb +0 -14
data/lib/paper_trail/reifier.rb
CHANGED
@@ -52,26 +52,29 @@ module PaperTrail
|
|
52
52
|
# not the actual subclass. If `type` is present but empty, the class is
|
53
53
|
# the base class.
|
54
54
|
def init_model(attrs, options, version)
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
model = item_found
|
69
|
-
init_unversioned_attrs(attrs, model)
|
70
|
-
end
|
55
|
+
klass = version_reification_class(version, attrs)
|
56
|
+
|
57
|
+
# The `dup` option and destroyed version always returns a new object,
|
58
|
+
# otherwise we should attempt to load item or to look for the item
|
59
|
+
# outside of default scope(s).
|
60
|
+
model = if options[:dup] == true || version.event == "destroy"
|
61
|
+
klass.new
|
62
|
+
else
|
63
|
+
version.item || init_model_by_finding_item_id(klass, version) || klass.new
|
64
|
+
end
|
65
|
+
|
66
|
+
if options[:unversioned_attributes] == :nil && !model.new_record?
|
67
|
+
init_unversioned_attrs(attrs, model)
|
71
68
|
end
|
69
|
+
|
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
|
@@ -88,9 +91,7 @@ module PaperTrail
|
|
88
91
|
#
|
89
92
|
# @api private
|
90
93
|
def reify_attribute(k, v, model, version)
|
91
|
-
|
92
|
-
is_enum_without_type_caster = ::ActiveRecord::VERSION::MAJOR < 5 && enums.key?(k)
|
93
|
-
if model.has_attribute?(k) && !is_enum_without_type_caster
|
94
|
+
if model.has_attribute?(k)
|
94
95
|
model[k.to_sym] = v
|
95
96
|
elsif model.respond_to?("#{k}=")
|
96
97
|
model.send("#{k}=", v)
|
@@ -111,20 +112,35 @@ module PaperTrail
|
|
111
112
|
end
|
112
113
|
|
113
114
|
# Given a `version`, return the class to reify. This method supports
|
114
|
-
# Single Table Inheritance (STI) with custom inheritance columns
|
115
|
+
# Single Table Inheritance (STI) with custom inheritance columns and
|
116
|
+
# custom inheritance column values.
|
115
117
|
#
|
116
118
|
# For example, imagine a `version` whose `item_type` is "Animal". The
|
117
119
|
# `animals` table is an STI table (it has cats and dogs) and it has a
|
118
120
|
# custom inheritance column, `species`. If `attrs["species"]` is "Dog",
|
119
121
|
# this method returns the constant `Dog`. If `attrs["species"]` is blank,
|
120
|
-
# this method returns the constant `Animal`.
|
121
|
-
#
|
122
|
+
# this method returns the constant `Animal`.
|
123
|
+
#
|
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.
|
122
128
|
#
|
129
|
+
# You can see these particular examples in action in
|
130
|
+
# `spec/models/animal_spec.rb` and `spec/models/plant_spec.rb`
|
123
131
|
def version_reification_class(version, attrs)
|
124
|
-
|
132
|
+
clazz = version.item_type.constantize
|
133
|
+
inheritance_column_name = clazz.inheritance_column
|
125
134
|
inher_col_value = attrs[inheritance_column_name]
|
126
|
-
|
127
|
-
|
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)
|
128
144
|
end
|
129
145
|
end
|
130
146
|
end
|
data/lib/paper_trail/request.rb
CHANGED
@@ -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,16 +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
34
|
end
|
45
35
|
end
|
46
36
|
end
|
@@ -9,10 +9,15 @@ module PaperTrail
|
|
9
9
|
extend self # makes all instance methods become module methods as well
|
10
10
|
|
11
11
|
def load(string)
|
12
|
-
::YAML.
|
12
|
+
::YAML.respond_to?(:unsafe_load) ? ::YAML.unsafe_load(string) : ::YAML.load(string)
|
13
13
|
end
|
14
14
|
|
15
|
+
# @param object (Hash | HashWithIndifferentAccess) - Coming from
|
16
|
+
# `recordable_object` `object` will be a plain `Hash`. However, due to
|
17
|
+
# recent [memory optimizations](https://git.io/fjeYv), when coming from
|
18
|
+
# `recordable_object_changes`, it will be a `HashWithIndifferentAccess`.
|
15
19
|
def dump(object)
|
20
|
+
object = object.to_hash if object.is_a?(HashWithIndifferentAccess)
|
16
21
|
::YAML.dump object
|
17
22
|
end
|
18
23
|
|
@@ -21,18 +26,6 @@ module PaperTrail
|
|
21
26
|
def where_object_condition(arel_field, field, value)
|
22
27
|
arel_field.matches("%\n#{field}: #{value}\n%")
|
23
28
|
end
|
24
|
-
|
25
|
-
# Returns a SQL LIKE condition to be used to match the given field and
|
26
|
-
# value in the serialized `object_changes`.
|
27
|
-
def where_object_changes_condition(*)
|
28
|
-
raise <<-STR.squish.freeze
|
29
|
-
where_object_changes no longer supports reading YAML from a text
|
30
|
-
column. The old implementation was inaccurate, returning more records
|
31
|
-
than you wanted. This feature was deprecated in 8.1.0 and removed in
|
32
|
-
9.0.0. The json and jsonb datatypes are still supported. See
|
33
|
-
discussion at https://github.com/paper-trail-gem/paper_trail/pull/997
|
34
|
-
STR
|
35
|
-
end
|
36
29
|
end
|
37
30
|
end
|
38
31
|
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,17 +25,6 @@ module PaperTrail
|
|
28
25
|
|
29
26
|
private
|
30
27
|
|
31
|
-
def active_record_pre_502?
|
32
|
-
::ActiveRecord::VERSION::MAJOR < 5 ||
|
33
|
-
(::ActiveRecord::VERSION::MINOR.zero? && ::ActiveRecord::VERSION::TINY < 2)
|
34
|
-
end
|
35
|
-
|
36
|
-
def serialize_with_ar(array)
|
37
|
-
ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Array.
|
38
|
-
new(@subtype, @delimiter).
|
39
|
-
serialize(array)
|
40
|
-
end
|
41
|
-
|
42
28
|
def deserialize_with_ar(array)
|
43
29
|
ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Array.
|
44
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
|
@@ -13,12 +16,7 @@ module PaperTrail
|
|
13
16
|
extend ::ActiveSupport::Concern
|
14
17
|
|
15
18
|
included do
|
16
|
-
|
17
|
-
belongs_to :item, polymorphic: true, optional: true
|
18
|
-
else
|
19
|
-
belongs_to :item, polymorphic: true
|
20
|
-
end
|
21
|
-
|
19
|
+
belongs_to :item, polymorphic: true, optional: true, inverse_of: false
|
22
20
|
validates_presence_of :event
|
23
21
|
after_create :enforce_version_limit!
|
24
22
|
end
|
@@ -42,40 +40,7 @@ module PaperTrail
|
|
42
40
|
end
|
43
41
|
|
44
42
|
def not_creates
|
45
|
-
where
|
46
|
-
end
|
47
|
-
|
48
|
-
# Returns versions after `obj`.
|
49
|
-
#
|
50
|
-
# @param obj - a `Version` or a timestamp
|
51
|
-
# @param timestamp_arg - boolean - When true, `obj` is a timestamp.
|
52
|
-
# Default: false.
|
53
|
-
# @return `ActiveRecord::Relation`
|
54
|
-
# @api public
|
55
|
-
def subsequent(obj, timestamp_arg = false)
|
56
|
-
if timestamp_arg != true && primary_key_is_int?
|
57
|
-
return where(arel_table[primary_key].gt(obj.id)).order(arel_table[primary_key].asc)
|
58
|
-
end
|
59
|
-
|
60
|
-
obj = obj.send(:created_at) if obj.is_a?(self)
|
61
|
-
where(arel_table[:created_at].gt(obj)).order(timestamp_sort_order)
|
62
|
-
end
|
63
|
-
|
64
|
-
# Returns versions before `obj`.
|
65
|
-
#
|
66
|
-
# @param obj - a `Version` or a timestamp
|
67
|
-
# @param timestamp_arg - boolean - When true, `obj` is a timestamp.
|
68
|
-
# Default: false.
|
69
|
-
# @return `ActiveRecord::Relation`
|
70
|
-
# @api public
|
71
|
-
def preceding(obj, timestamp_arg = false)
|
72
|
-
if timestamp_arg != true && primary_key_is_int?
|
73
|
-
return where(arel_table[primary_key].lt(obj.id)).order(arel_table[primary_key].desc)
|
74
|
-
end
|
75
|
-
|
76
|
-
obj = obj.send(:created_at) if obj.is_a?(self)
|
77
|
-
where(arel_table[:created_at].lt(obj)).
|
78
|
-
order(timestamp_sort_order("desc"))
|
43
|
+
where.not(event: "create")
|
79
44
|
end
|
80
45
|
|
81
46
|
def between(start_time, end_time)
|
@@ -93,6 +58,18 @@ module PaperTrail
|
|
93
58
|
end
|
94
59
|
end
|
95
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
|
+
|
96
73
|
# Given a hash of attributes like `name: 'Joan'`, query the
|
97
74
|
# `versions.objects` column.
|
98
75
|
#
|
@@ -149,6 +126,36 @@ module PaperTrail
|
|
149
126
|
Queries::Versions::WhereObjectChanges.new(self, args).execute
|
150
127
|
end
|
151
128
|
|
129
|
+
# Given a hash of attributes like `name: 'Joan'`, query the
|
130
|
+
# `versions.objects_changes` column for changes where the version changed
|
131
|
+
# from the hash of attributes to other values.
|
132
|
+
#
|
133
|
+
# This is useful for finding versions where the attribute started with a
|
134
|
+
# known value and changed to something else. This is in comparison to
|
135
|
+
# `where_object_changes` which will find both the changes before and
|
136
|
+
# after.
|
137
|
+
#
|
138
|
+
# @api public
|
139
|
+
def where_object_changes_from(args = {})
|
140
|
+
raise ArgumentError, "expected to receive a Hash" unless args.is_a?(Hash)
|
141
|
+
Queries::Versions::WhereObjectChangesFrom.new(self, args).execute
|
142
|
+
end
|
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
|
+
|
152
159
|
def primary_key_is_int?
|
153
160
|
@primary_key_is_int ||= columns_hash[primary_key].type == :integer
|
154
161
|
rescue StandardError # TODO: Rescue something more specific
|
@@ -166,6 +173,65 @@ module PaperTrail
|
|
166
173
|
def object_changes_col_is_json?
|
167
174
|
%i[json jsonb].include?(columns_hash["object_changes"].try(:type))
|
168
175
|
end
|
176
|
+
|
177
|
+
# Returns versions before `obj`.
|
178
|
+
#
|
179
|
+
# @param obj - a `Version` or a timestamp
|
180
|
+
# @param timestamp_arg - boolean - When true, `obj` is a timestamp.
|
181
|
+
# Default: false.
|
182
|
+
# @return `ActiveRecord::Relation`
|
183
|
+
# @api public
|
184
|
+
# rubocop:disable Style/OptionalBooleanParameter
|
185
|
+
def preceding(obj, timestamp_arg = false)
|
186
|
+
if timestamp_arg != true && primary_key_is_int?
|
187
|
+
preceding_by_id(obj)
|
188
|
+
else
|
189
|
+
preceding_by_timestamp(obj)
|
190
|
+
end
|
191
|
+
end
|
192
|
+
# rubocop:enable Style/OptionalBooleanParameter
|
193
|
+
|
194
|
+
# Returns versions after `obj`.
|
195
|
+
#
|
196
|
+
# @param obj - a `Version` or a timestamp
|
197
|
+
# @param timestamp_arg - boolean - When true, `obj` is a timestamp.
|
198
|
+
# Default: false.
|
199
|
+
# @return `ActiveRecord::Relation`
|
200
|
+
# @api public
|
201
|
+
# rubocop:disable Style/OptionalBooleanParameter
|
202
|
+
def subsequent(obj, timestamp_arg = false)
|
203
|
+
if timestamp_arg != true && primary_key_is_int?
|
204
|
+
subsequent_by_id(obj)
|
205
|
+
else
|
206
|
+
subsequent_by_timestamp(obj)
|
207
|
+
end
|
208
|
+
end
|
209
|
+
# rubocop:enable Style/OptionalBooleanParameter
|
210
|
+
|
211
|
+
private
|
212
|
+
|
213
|
+
# @api private
|
214
|
+
def preceding_by_id(obj)
|
215
|
+
where(arel_table[primary_key].lt(obj.id)).order(arel_table[primary_key].desc)
|
216
|
+
end
|
217
|
+
|
218
|
+
# @api private
|
219
|
+
def preceding_by_timestamp(obj)
|
220
|
+
obj = obj.send(:created_at) if obj.is_a?(self)
|
221
|
+
where(arel_table[:created_at].lt(obj)).
|
222
|
+
order(timestamp_sort_order("desc"))
|
223
|
+
end
|
224
|
+
|
225
|
+
# @api private
|
226
|
+
def subsequent_by_id(version)
|
227
|
+
where(arel_table[primary_key].gt(version.id)).order(arel_table[primary_key].asc)
|
228
|
+
end
|
229
|
+
|
230
|
+
# @api private
|
231
|
+
def subsequent_by_timestamp(obj)
|
232
|
+
obj = obj.send(:created_at) if obj.is_a?(self)
|
233
|
+
where(arel_table[:created_at].gt(obj)).order(timestamp_sort_order)
|
234
|
+
end
|
169
235
|
end
|
170
236
|
|
171
237
|
# @api private
|
@@ -179,18 +245,8 @@ module PaperTrail
|
|
179
245
|
|
180
246
|
# Restore the item from this version.
|
181
247
|
#
|
182
|
-
# Optionally this can also restore all :has_one and :has_many (including
|
183
|
-
# has_many :through) associations as they were "at the time", if they are
|
184
|
-
# also being versioned by PaperTrail.
|
185
|
-
#
|
186
248
|
# Options:
|
187
249
|
#
|
188
|
-
# - :has_one
|
189
|
-
# - `true` - Also reify has_one associations.
|
190
|
-
# - `false - Default.
|
191
|
-
# - :has_many
|
192
|
-
# - `true` - Also reify has_many and has_many :through associations.
|
193
|
-
# - `false` - Default.
|
194
250
|
# - :mark_for_destruction
|
195
251
|
# - `true` - Mark the has_one/has_many associations that did not exist in
|
196
252
|
# the reified version for destruction, instead of removing them.
|
@@ -205,6 +261,9 @@ module PaperTrail
|
|
205
261
|
# - `:preserve` - Attributes undefined in version record are not modified.
|
206
262
|
#
|
207
263
|
def reify(options = {})
|
264
|
+
unless self.class.column_names.include? "object"
|
265
|
+
raise Error, "reify requires an object column"
|
266
|
+
end
|
208
267
|
return nil if object.nil?
|
209
268
|
::PaperTrail::Reifier.reify(self, options)
|
210
269
|
end
|
@@ -229,13 +288,6 @@ module PaperTrail
|
|
229
288
|
end
|
230
289
|
alias version_author terminator
|
231
290
|
|
232
|
-
def sibling_versions(reload = false)
|
233
|
-
if reload || !defined?(@sibling_versions) || @sibling_versions.nil?
|
234
|
-
@sibling_versions = self.class.with_item_keys(item_type, item_id)
|
235
|
-
end
|
236
|
-
@sibling_versions
|
237
|
-
end
|
238
|
-
|
239
291
|
def next
|
240
292
|
@next ||= sibling_versions.subsequent(self).first
|
241
293
|
end
|
@@ -245,8 +297,9 @@ module PaperTrail
|
|
245
297
|
end
|
246
298
|
|
247
299
|
# Returns an integer representing the chronological position of the
|
248
|
-
# version among its siblings
|
249
|
-
#
|
300
|
+
# version among its siblings. The "create" event, for example, has an index
|
301
|
+
# of 0.
|
302
|
+
#
|
250
303
|
# @api public
|
251
304
|
def index
|
252
305
|
@index ||= RecordHistory.new(sibling_versions, self.class).index(self)
|
@@ -256,7 +309,7 @@ module PaperTrail
|
|
256
309
|
|
257
310
|
# @api private
|
258
311
|
def load_changeset
|
259
|
-
if PaperTrail.config.object_changes_adapter
|
312
|
+
if PaperTrail.config.object_changes_adapter.respond_to?(:load_changeset)
|
260
313
|
return PaperTrail.config.object_changes_adapter.load_changeset(self)
|
261
314
|
end
|
262
315
|
|
@@ -304,7 +357,7 @@ module PaperTrail
|
|
304
357
|
# Enforces the `version_limit`, if set. Default: no limit.
|
305
358
|
# @api private
|
306
359
|
def enforce_version_limit!
|
307
|
-
limit =
|
360
|
+
limit = version_limit
|
308
361
|
return unless limit.is_a? Numeric
|
309
362
|
previous_versions = sibling_versions.not_creates.
|
310
363
|
order(self.class.timestamp_sort_order("asc"))
|
@@ -312,5 +365,33 @@ module PaperTrail
|
|
312
365
|
excess_versions = previous_versions - previous_versions.last(limit)
|
313
366
|
excess_versions.map(&:destroy)
|
314
367
|
end
|
368
|
+
|
369
|
+
# @api private
|
370
|
+
def sibling_versions
|
371
|
+
@sibling_versions ||= self.class.with_item_keys(item_type, item_id)
|
372
|
+
end
|
373
|
+
|
374
|
+
# See docs section 2.e. Limiting the Number of Versions Created.
|
375
|
+
# The version limit can be global or per-model.
|
376
|
+
#
|
377
|
+
# @api private
|
378
|
+
def version_limit
|
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
|
386
|
+
end
|
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)
|
395
|
+
end
|
315
396
|
end
|
316
397
|
end
|
data/lib/paper_trail.rb
CHANGED
@@ -8,39 +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"
|
13
|
+
require "paper_trail/compatibility"
|
19
14
|
require "paper_trail/config"
|
20
|
-
require "paper_trail/has_paper_trail"
|
21
15
|
require "paper_trail/record_history"
|
22
|
-
require "paper_trail/reifier"
|
23
16
|
require "paper_trail/request"
|
24
|
-
require "paper_trail/version_concern"
|
25
17
|
require "paper_trail/version_number"
|
26
18
|
require "paper_trail/serializers/json"
|
27
|
-
require "paper_trail/serializers/yaml"
|
28
19
|
|
29
20
|
# An ActiveRecord extension that tracks changes to your models, for auditing or
|
30
21
|
# versioning.
|
31
22
|
module PaperTrail
|
32
|
-
E_RAILS_NOT_LOADED = <<-EOS.squish.freeze
|
33
|
-
PaperTrail has been loaded too early, before rails is loaded. This can
|
34
|
-
happen when another gem defines the ::Rails namespace, then PT is loaded,
|
35
|
-
all before rails is loaded. You may want to reorder your Gemfile, or defer
|
36
|
-
the loading of PT by using `require: false` and a manual require elsewhere.
|
37
|
-
EOS
|
38
23
|
E_TIMESTAMP_FIELD_CONFIG = <<-EOS.squish.freeze
|
39
24
|
PaperTrail.timestamp_field= has been removed, without replacement. It is no
|
40
25
|
longer configurable. The timestamp column in the versions table must now be
|
41
26
|
named created_at.
|
42
27
|
EOS
|
43
28
|
|
29
|
+
RAILS_GTE_7_0 = ::ActiveRecord.gem_version >= ::Gem::Version.new("7.0.0")
|
30
|
+
|
44
31
|
extend PaperTrail::Cleaner
|
45
32
|
|
46
33
|
class << self
|
@@ -57,46 +44,6 @@ module PaperTrail
|
|
57
44
|
!!PaperTrail.config.enabled
|
58
45
|
end
|
59
46
|
|
60
|
-
# @deprecated
|
61
|
-
def enabled_for_controller=(value)
|
62
|
-
::ActiveSupport::Deprecation.warn(
|
63
|
-
"PaperTrail.enabled_for_controller= is deprecated, " \
|
64
|
-
"use PaperTrail.request.enabled=",
|
65
|
-
caller(1)
|
66
|
-
)
|
67
|
-
request.enabled = value
|
68
|
-
end
|
69
|
-
|
70
|
-
# @deprecated
|
71
|
-
def enabled_for_controller?
|
72
|
-
::ActiveSupport::Deprecation.warn(
|
73
|
-
"PaperTrail.enabled_for_controller? is deprecated, " \
|
74
|
-
"use PaperTrail.request.enabled?",
|
75
|
-
caller(1)
|
76
|
-
)
|
77
|
-
request.enabled?
|
78
|
-
end
|
79
|
-
|
80
|
-
# @deprecated
|
81
|
-
def enabled_for_model(model, value)
|
82
|
-
::ActiveSupport::Deprecation.warn(
|
83
|
-
"PaperTrail.enabled_for_model is deprecated, " \
|
84
|
-
"use PaperTrail.request.enabled_for_model",
|
85
|
-
caller(1)
|
86
|
-
)
|
87
|
-
request.enabled_for_model(model, value)
|
88
|
-
end
|
89
|
-
|
90
|
-
# @deprecated
|
91
|
-
def enabled_for_model?(model)
|
92
|
-
::ActiveSupport::Deprecation.warn(
|
93
|
-
"PaperTrail.enabled_for_model? is deprecated, " \
|
94
|
-
"use PaperTrail.request.enabled_for_model?",
|
95
|
-
caller(1)
|
96
|
-
)
|
97
|
-
request.enabled_for_model?(model)
|
98
|
-
end
|
99
|
-
|
100
47
|
# Returns PaperTrail's `::Gem::Version`, convenient for comparisons. This is
|
101
48
|
# recommended over `::PaperTrail::VERSION::STRING`.
|
102
49
|
#
|
@@ -124,7 +71,7 @@ module PaperTrail
|
|
124
71
|
#
|
125
72
|
# @api public
|
126
73
|
def request(options = nil, &block)
|
127
|
-
if options.nil? && !
|
74
|
+
if options.nil? && !block
|
128
75
|
Request
|
129
76
|
else
|
130
77
|
Request.with(options, &block)
|
@@ -134,54 +81,7 @@ module PaperTrail
|
|
134
81
|
# Set the field which records when a version was created.
|
135
82
|
# @api public
|
136
83
|
def timestamp_field=(_field_name)
|
137
|
-
raise
|
138
|
-
end
|
139
|
-
|
140
|
-
# @deprecated
|
141
|
-
def whodunnit=(value)
|
142
|
-
::ActiveSupport::Deprecation.warn(
|
143
|
-
"PaperTrail.whodunnit= is deprecated, use PaperTrail.request.whodunnit=",
|
144
|
-
caller(1)
|
145
|
-
)
|
146
|
-
request.whodunnit = value
|
147
|
-
end
|
148
|
-
|
149
|
-
# @deprecated
|
150
|
-
def whodunnit(value = nil, &block)
|
151
|
-
if value.nil?
|
152
|
-
::ActiveSupport::Deprecation.warn(
|
153
|
-
"PaperTrail.whodunnit is deprecated, use PaperTrail.request.whodunnit",
|
154
|
-
caller(1)
|
155
|
-
)
|
156
|
-
request.whodunnit
|
157
|
-
elsif block_given?
|
158
|
-
::ActiveSupport::Deprecation.warn(
|
159
|
-
"Passing a block to PaperTrail.whodunnit is deprecated, " \
|
160
|
-
'use PaperTrail.request(whodunnit: "John") do .. end',
|
161
|
-
caller(1)
|
162
|
-
)
|
163
|
-
request(whodunnit: value, &block)
|
164
|
-
else
|
165
|
-
raise ArgumentError, "Invalid arguments"
|
166
|
-
end
|
167
|
-
end
|
168
|
-
|
169
|
-
# @deprecated
|
170
|
-
def controller_info=(value)
|
171
|
-
::ActiveSupport::Deprecation.warn(
|
172
|
-
"PaperTrail.controller_info= is deprecated, use PaperTrail.request.controller_info=",
|
173
|
-
caller(1)
|
174
|
-
)
|
175
|
-
request.controller_info = value
|
176
|
-
end
|
177
|
-
|
178
|
-
# @deprecated
|
179
|
-
def controller_info
|
180
|
-
::ActiveSupport::Deprecation.warn(
|
181
|
-
"PaperTrail.controller_info is deprecated, use PaperTrail.request.controller_info",
|
182
|
-
caller(1)
|
183
|
-
)
|
184
|
-
request.controller_info
|
84
|
+
raise Error, E_TIMESTAMP_FIELD_CONFIG
|
185
85
|
end
|
186
86
|
|
187
87
|
# Set the PaperTrail serializer. This setting affects all threads.
|
@@ -212,23 +112,18 @@ module PaperTrail
|
|
212
112
|
end
|
213
113
|
end
|
214
114
|
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
#
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
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"
|
228
127
|
else
|
229
128
|
require "paper_trail/frameworks/active_record"
|
230
129
|
end
|
231
|
-
|
232
|
-
# https://github.com/paper-trail-gem/paper_trail/issues/1070
|
233
|
-
# https://github.com/westonganger/paper_trail-association_tracking/issues/2
|
234
|
-
require "paper_trail-association_tracking"
|