paper_trail 10.3.1 → 14.0.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/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 +17 -45
- 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 +92 -69
- data/lib/paper_trail/events/destroy.rb +1 -1
- data/lib/paper_trail/events/update.rb +23 -7
- 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 +46 -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 +9 -14
- 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 +80 -64
- data/lib/paper_trail/reifier.rb +41 -26
- data/lib/paper_trail/request.rb +22 -25
- data/lib/paper_trail/serializers/json.rb +0 -10
- data/lib/paper_trail/serializers/yaml.rb +38 -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 +22 -40
- metadata +106 -45
- data/lib/paper_trail/frameworks/rails/engine.rb +0 -45
@@ -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 = 14
|
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,34 +8,24 @@
|
|
8
8
|
# can revisit this decision.
|
9
9
|
require "active_support/all"
|
10
10
|
|
11
|
-
#
|
12
|
-
#
|
13
|
-
#
|
14
|
-
#
|
15
|
-
|
11
|
+
# We used to `require "active_record"` here, but that was [replaced with a
|
12
|
+
# Railtie](https://github.com/paper-trail-gem/paper_trail/pull/1281) in PT 12.
|
13
|
+
# As a result, we cannot reference `ActiveRecord` in this file (ie. until our
|
14
|
+
# Railtie has loaded). If we did, it would cause [problems with non-Rails
|
15
|
+
# projects](https://github.com/paper-trail-gem/paper_trail/pull/1401).
|
16
16
|
|
17
|
-
require "
|
17
|
+
require "paper_trail/errors"
|
18
18
|
require "paper_trail/cleaner"
|
19
19
|
require "paper_trail/compatibility"
|
20
20
|
require "paper_trail/config"
|
21
|
-
require "paper_trail/has_paper_trail"
|
22
21
|
require "paper_trail/record_history"
|
23
|
-
require "paper_trail/reifier"
|
24
22
|
require "paper_trail/request"
|
25
|
-
require "paper_trail/version_concern"
|
26
23
|
require "paper_trail/version_number"
|
27
24
|
require "paper_trail/serializers/json"
|
28
|
-
require "paper_trail/serializers/yaml"
|
29
25
|
|
30
26
|
# An ActiveRecord extension that tracks changes to your models, for auditing or
|
31
27
|
# versioning.
|
32
28
|
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
29
|
E_TIMESTAMP_FIELD_CONFIG = <<-EOS.squish.freeze
|
40
30
|
PaperTrail.timestamp_field= has been removed, without replacement. It is no
|
41
31
|
longer configurable. The timestamp column in the versions table must now be
|
@@ -85,7 +75,7 @@ module PaperTrail
|
|
85
75
|
#
|
86
76
|
# @api public
|
87
77
|
def request(options = nil, &block)
|
88
|
-
if options.nil? && !
|
78
|
+
if options.nil? && !block
|
89
79
|
Request
|
90
80
|
else
|
91
81
|
Request.with(options, &block)
|
@@ -95,7 +85,7 @@ module PaperTrail
|
|
95
85
|
# Set the field which records when a version was created.
|
96
86
|
# @api public
|
97
87
|
def timestamp_field=(_field_name)
|
98
|
-
raise
|
88
|
+
raise Error, E_TIMESTAMP_FIELD_CONFIG
|
99
89
|
end
|
100
90
|
|
101
91
|
# Set the PaperTrail serializer. This setting affects all threads.
|
@@ -112,7 +102,7 @@ module PaperTrail
|
|
112
102
|
|
113
103
|
# Returns PaperTrail's global configuration object, a singleton. These
|
114
104
|
# settings affect all threads.
|
115
|
-
# @api
|
105
|
+
# @api public
|
116
106
|
def config
|
117
107
|
@config ||= PaperTrail::Config.instance
|
118
108
|
yield @config if block_given?
|
@@ -120,33 +110,25 @@ module PaperTrail
|
|
120
110
|
end
|
121
111
|
alias configure config
|
122
112
|
|
113
|
+
# @api public
|
123
114
|
def version
|
124
115
|
VERSION::STRING
|
125
116
|
end
|
126
117
|
end
|
127
118
|
end
|
128
119
|
|
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
|
120
|
+
# PT is built on ActiveRecord, but does not require Rails. If Rails is defined,
|
121
|
+
# our Railtie makes sure not to load the AR-dependent parts of PT until AR is
|
122
|
+
# ready. A typical Rails `application.rb` has:
|
123
|
+
#
|
124
|
+
# ```
|
125
|
+
# require 'rails/all' # Defines `Rails`
|
126
|
+
# Bundler.require(*Rails.groups) # require 'paper_trail' (this file)
|
127
|
+
# ```
|
128
|
+
#
|
129
|
+
# Non-rails applications should take similar care to load AR before PT.
|
130
|
+
if defined?(Rails)
|
131
|
+
require "paper_trail/frameworks/rails"
|
146
132
|
else
|
147
133
|
require "paper_trail/frameworks/active_record"
|
148
134
|
end
|
149
|
-
|
150
|
-
if defined?(::ActiveRecord)
|
151
|
-
::PaperTrail::Compatibility.check_activerecord(::ActiveRecord.gem_version)
|
152
|
-
end
|