paper_trail 10.3.1 → 12.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 +13 -7
- data/lib/generators/paper_trail/install/templates/create_versions.rb.erb +1 -1
- data/lib/generators/paper_trail/migration_generator.rb +5 -4
- data/lib/paper_trail.rb +12 -38
- data/lib/paper_trail/attribute_serializers/attribute_serializer_factory.rb +24 -10
- data/lib/paper_trail/attribute_serializers/cast_attribute_serializer.rb +8 -46
- data/lib/paper_trail/compatibility.rb +2 -2
- data/lib/paper_trail/config.rb +0 -33
- data/lib/paper_trail/events/base.rb +22 -41
- data/lib/paper_trail/events/destroy.rb +1 -1
- data/lib/paper_trail/frameworks/active_record.rb +9 -2
- data/lib/paper_trail/frameworks/rails.rb +1 -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/has_paper_trail.rb +1 -1
- data/lib/paper_trail/model_config.rb +3 -7
- data/lib/paper_trail/queries/versions/where_object_changes.rb +1 -1
- data/lib/paper_trail/queries/versions/where_object_changes_from.rb +65 -0
- data/lib/paper_trail/record_trail.rb +2 -4
- data/lib/paper_trail/reifier.rb +17 -19
- data/lib/paper_trail/serializers/json.rb +8 -0
- data/lib/paper_trail/serializers/yaml.rb +8 -0
- data/lib/paper_trail/type_serializers/postgres_array_serializer.rb +1 -14
- data/lib/paper_trail/version_concern.rb +30 -26
- data/lib/paper_trail/version_number.rb +3 -3
- metadata +76 -39
- data/lib/paper_trail/frameworks/rails/engine.rb +0 -45
@@ -1,5 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
#
|
4
|
-
#
|
3
|
+
# Either ActiveRecord has already been loaded by the Lazy Load Hook in our
|
4
|
+
# Railtie, or else we load it now.
|
5
|
+
require "active_record"
|
6
|
+
::PaperTrail::Compatibility.check_activerecord(::ActiveRecord.gem_version)
|
7
|
+
|
8
|
+
# Now we can load the parts of PT that depend on AR.
|
9
|
+
require "paper_trail/has_paper_trail"
|
10
|
+
require "paper_trail/reifier"
|
5
11
|
require "paper_trail/frameworks/active_record/models/paper_trail/version"
|
12
|
+
ActiveRecord::Base.include PaperTrail::Model
|
@@ -25,9 +25,7 @@ module PaperTrail
|
|
25
25
|
# @api public
|
26
26
|
def user_for_paper_trail
|
27
27
|
return unless defined?(current_user)
|
28
|
-
|
29
|
-
rescue NoMethodError
|
30
|
-
current_user
|
28
|
+
current_user.try(:id) || current_user
|
31
29
|
end
|
32
30
|
|
33
31
|
# Returns any information about the controller or request that you
|
@@ -103,9 +101,3 @@ module PaperTrail
|
|
103
101
|
end
|
104
102
|
end
|
105
103
|
end
|
106
|
-
|
107
|
-
if defined?(::ActionController)
|
108
|
-
::ActiveSupport.on_load(:action_controller) do
|
109
|
-
include ::PaperTrail::Rails::Controller
|
110
|
-
end
|
111
|
-
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PaperTrail
|
4
|
+
# Represents code to load within Rails framework. See documentation in
|
5
|
+
# `railties/lib/rails/railtie.rb`.
|
6
|
+
# @api private
|
7
|
+
class Railtie < ::Rails::Railtie
|
8
|
+
# PaperTrail only has one initializer.
|
9
|
+
#
|
10
|
+
# We specify `before: "load_config_initializers"` to ensure that the PT
|
11
|
+
# initializer happens before "app initializers" (those defined in
|
12
|
+
# the app's `config/initalizers`).
|
13
|
+
initializer "paper_trail", before: "load_config_initializers" do
|
14
|
+
# `on_load` is a "lazy load hook". It "declares a block that will be
|
15
|
+
# executed when a Rails component is fully loaded". (See
|
16
|
+
# `active_support/lazy_load_hooks.rb`)
|
17
|
+
ActiveSupport.on_load(:action_controller) do
|
18
|
+
require "paper_trail/frameworks/rails/controller"
|
19
|
+
|
20
|
+
# Mix our extensions into `ActionController::Base`, which is `self`
|
21
|
+
# because of the `class_eval` in `lazy_load_hooks.rb`.
|
22
|
+
include PaperTrail::Rails::Controller
|
23
|
+
end
|
24
|
+
|
25
|
+
ActiveSupport.on_load(:active_record) do
|
26
|
+
require "paper_trail/frameworks/active_record"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -122,16 +122,13 @@ module PaperTrail
|
|
122
122
|
setup_callbacks_from_options options[:on]
|
123
123
|
end
|
124
124
|
|
125
|
+
# @api private
|
125
126
|
def version_class
|
126
|
-
@
|
127
|
+
@version_class ||= @model_class.version_class_name.constantize
|
127
128
|
end
|
128
129
|
|
129
130
|
private
|
130
131
|
|
131
|
-
def active_record_gem_version
|
132
|
-
Gem::Version.new(ActiveRecord::VERSION::STRING)
|
133
|
-
end
|
134
|
-
|
135
132
|
# Raises an error if the provided class is an `abstract_class`.
|
136
133
|
# @api private
|
137
134
|
def assert_concrete_activerecord_class(class_name)
|
@@ -141,8 +138,7 @@ module PaperTrail
|
|
141
138
|
end
|
142
139
|
|
143
140
|
def cannot_record_after_destroy?
|
144
|
-
|
145
|
-
::ActiveRecord::Base.belongs_to_required_by_default
|
141
|
+
::ActiveRecord::Base.belongs_to_required_by_default
|
146
142
|
end
|
147
143
|
|
148
144
|
# Some options require the presence of the `item_subtype` column. Currently
|
@@ -23,7 +23,7 @@ module PaperTrail
|
|
23
23
|
|
24
24
|
# @api private
|
25
25
|
def execute
|
26
|
-
if PaperTrail.config.object_changes_adapter
|
26
|
+
if PaperTrail.config.object_changes_adapter.respond_to?(:where_object_changes)
|
27
27
|
return PaperTrail.config.object_changes_adapter.where_object_changes(
|
28
28
|
@version_model_class, @attributes
|
29
29
|
)
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PaperTrail
|
4
|
+
module Queries
|
5
|
+
module Versions
|
6
|
+
# For public API documentation, see `where_object_changes_from` in
|
7
|
+
# `paper_trail/version_concern.rb`.
|
8
|
+
# @api private
|
9
|
+
class WhereObjectChangesFrom
|
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_from)
|
22
|
+
return PaperTrail.config.object_changes_adapter.where_object_changes_from(
|
23
|
+
@version_model_class, @attributes
|
24
|
+
)
|
25
|
+
end
|
26
|
+
|
27
|
+
case @version_model_class.columns_hash["object_changes"].type
|
28
|
+
when :jsonb, :json
|
29
|
+
json
|
30
|
+
else
|
31
|
+
text
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
# @api private
|
38
|
+
def json
|
39
|
+
predicates = []
|
40
|
+
values = []
|
41
|
+
@attributes.each do |field, value|
|
42
|
+
predicates.push(
|
43
|
+
"(object_changes->>? ILIKE ?)"
|
44
|
+
)
|
45
|
+
values.concat([field, "[#{value.to_json},%"])
|
46
|
+
end
|
47
|
+
sql = predicates.join(" and ")
|
48
|
+
@version_model_class.where(sql, *values)
|
49
|
+
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
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -7,8 +7,6 @@ require "paper_trail/events/update"
|
|
7
7
|
module PaperTrail
|
8
8
|
# Represents the "paper trail" for a single record.
|
9
9
|
class RecordTrail
|
10
|
-
RAILS_GTE_5_1 = ::ActiveRecord.gem_version >= ::Gem::Version.new("5.1.0.beta1")
|
11
|
-
|
12
10
|
def initialize(record)
|
13
11
|
@record = record
|
14
12
|
end
|
@@ -200,9 +198,9 @@ module PaperTrail
|
|
200
198
|
#
|
201
199
|
# This is an "update" event. That is, we record the same data we would in
|
202
200
|
# the case of a normal AR `update`.
|
203
|
-
def save_with_version(
|
201
|
+
def save_with_version(**options)
|
204
202
|
::PaperTrail.request(enabled: false) do
|
205
|
-
@record.save(
|
203
|
+
@record.save(**options)
|
206
204
|
end
|
207
205
|
record_update(force: true, in_after_callback: false, is_touch: false)
|
208
206
|
end
|
data/lib/paper_trail/reifier.rb
CHANGED
@@ -52,23 +52,23 @@ 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
|
-
|
69
|
-
|
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
|
+
find_cond = { klass.primary_key => version.item_id }
|
64
|
+
|
65
|
+
version.item || klass.unscoped.where(find_cond).first || klass.new
|
66
|
+
end
|
67
|
+
|
68
|
+
if options[:unversioned_attributes] == :nil && !model.new_record?
|
69
|
+
init_unversioned_attrs(attrs, model)
|
71
70
|
end
|
71
|
+
|
72
72
|
model
|
73
73
|
end
|
74
74
|
|
@@ -88,9 +88,7 @@ module PaperTrail
|
|
88
88
|
#
|
89
89
|
# @api private
|
90
90
|
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
|
91
|
+
if model.has_attribute?(k)
|
94
92
|
model[k.to_sym] = v
|
95
93
|
elsif model.respond_to?("#{k}=")
|
96
94
|
model.send("#{k}=", v)
|
@@ -41,6 +41,14 @@ module PaperTrail
|
|
41
41
|
discussion at https://github.com/paper-trail-gem/paper_trail/issues/803
|
42
42
|
STR
|
43
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
|
44
52
|
end
|
45
53
|
end
|
46
54
|
end
|
@@ -38,6 +38,14 @@ module PaperTrail
|
|
38
38
|
discussion at https://github.com/paper-trail-gem/paper_trail/pull/997
|
39
39
|
STR
|
40
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
|
41
49
|
end
|
42
50
|
end
|
43
51
|
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).
|
@@ -3,6 +3,7 @@
|
|
3
3
|
require "paper_trail/attribute_serializers/object_changes_attribute"
|
4
4
|
require "paper_trail/queries/versions/where_object"
|
5
5
|
require "paper_trail/queries/versions/where_object_changes"
|
6
|
+
require "paper_trail/queries/versions/where_object_changes_from"
|
6
7
|
|
7
8
|
module PaperTrail
|
8
9
|
# Originally, PaperTrail did not provide this module, and all of this
|
@@ -13,12 +14,7 @@ module PaperTrail
|
|
13
14
|
extend ::ActiveSupport::Concern
|
14
15
|
|
15
16
|
included do
|
16
|
-
|
17
|
-
belongs_to :item, polymorphic: true, optional: true
|
18
|
-
else
|
19
|
-
belongs_to :item, polymorphic: true
|
20
|
-
end
|
21
|
-
|
17
|
+
belongs_to :item, polymorphic: true, optional: true
|
22
18
|
validates_presence_of :event
|
23
19
|
after_create :enforce_version_limit!
|
24
20
|
end
|
@@ -120,6 +116,21 @@ module PaperTrail
|
|
120
116
|
Queries::Versions::WhereObjectChanges.new(self, args).execute
|
121
117
|
end
|
122
118
|
|
119
|
+
# Given a hash of attributes like `name: 'Joan'`, query the
|
120
|
+
# `versions.objects_changes` column for changes where the version changed
|
121
|
+
# from the hash of attributes to other values.
|
122
|
+
#
|
123
|
+
# This is useful for finding versions where the attribute started with a
|
124
|
+
# known value and changed to something else. This is in comparison to
|
125
|
+
# `where_object_changes` which will find both the changes before and
|
126
|
+
# after.
|
127
|
+
#
|
128
|
+
# @api public
|
129
|
+
def where_object_changes_from(args = {})
|
130
|
+
raise ArgumentError, "expected to receive a Hash" unless args.is_a?(Hash)
|
131
|
+
Queries::Versions::WhereObjectChangesFrom.new(self, args).execute
|
132
|
+
end
|
133
|
+
|
123
134
|
def primary_key_is_int?
|
124
135
|
@primary_key_is_int ||= columns_hash[primary_key].type == :integer
|
125
136
|
rescue StandardError # TODO: Rescue something more specific
|
@@ -145,6 +156,7 @@ module PaperTrail
|
|
145
156
|
# Default: false.
|
146
157
|
# @return `ActiveRecord::Relation`
|
147
158
|
# @api public
|
159
|
+
# rubocop:disable Style/OptionalBooleanParameter
|
148
160
|
def preceding(obj, timestamp_arg = false)
|
149
161
|
if timestamp_arg != true && primary_key_is_int?
|
150
162
|
preceding_by_id(obj)
|
@@ -152,6 +164,7 @@ module PaperTrail
|
|
152
164
|
preceding_by_timestamp(obj)
|
153
165
|
end
|
154
166
|
end
|
167
|
+
# rubocop:enable Style/OptionalBooleanParameter
|
155
168
|
|
156
169
|
# Returns versions after `obj`.
|
157
170
|
#
|
@@ -160,6 +173,7 @@ module PaperTrail
|
|
160
173
|
# Default: false.
|
161
174
|
# @return `ActiveRecord::Relation`
|
162
175
|
# @api public
|
176
|
+
# rubocop:disable Style/OptionalBooleanParameter
|
163
177
|
def subsequent(obj, timestamp_arg = false)
|
164
178
|
if timestamp_arg != true && primary_key_is_int?
|
165
179
|
subsequent_by_id(obj)
|
@@ -167,6 +181,7 @@ module PaperTrail
|
|
167
181
|
subsequent_by_timestamp(obj)
|
168
182
|
end
|
169
183
|
end
|
184
|
+
# rubocop:enable Style/OptionalBooleanParameter
|
170
185
|
|
171
186
|
private
|
172
187
|
|
@@ -205,18 +220,8 @@ module PaperTrail
|
|
205
220
|
|
206
221
|
# Restore the item from this version.
|
207
222
|
#
|
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
223
|
# Options:
|
213
224
|
#
|
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
225
|
# - :mark_for_destruction
|
221
226
|
# - `true` - Mark the has_one/has_many associations that did not exist in
|
222
227
|
# the reified version for destruction, instead of removing them.
|
@@ -258,13 +263,6 @@ module PaperTrail
|
|
258
263
|
end
|
259
264
|
alias version_author terminator
|
260
265
|
|
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
266
|
def next
|
269
267
|
@next ||= sibling_versions.subsequent(self).first
|
270
268
|
end
|
@@ -274,8 +272,9 @@ module PaperTrail
|
|
274
272
|
end
|
275
273
|
|
276
274
|
# Returns an integer representing the chronological position of the
|
277
|
-
# version among its siblings
|
278
|
-
#
|
275
|
+
# version among its siblings. The "create" event, for example, has an index
|
276
|
+
# of 0.
|
277
|
+
#
|
279
278
|
# @api public
|
280
279
|
def index
|
281
280
|
@index ||= RecordHistory.new(sibling_versions, self.class).index(self)
|
@@ -285,7 +284,7 @@ module PaperTrail
|
|
285
284
|
|
286
285
|
# @api private
|
287
286
|
def load_changeset
|
288
|
-
if PaperTrail.config.object_changes_adapter
|
287
|
+
if PaperTrail.config.object_changes_adapter.respond_to?(:load_changeset)
|
289
288
|
return PaperTrail.config.object_changes_adapter.load_changeset(self)
|
290
289
|
end
|
291
290
|
|
@@ -342,6 +341,11 @@ module PaperTrail
|
|
342
341
|
excess_versions.map(&:destroy)
|
343
342
|
end
|
344
343
|
|
344
|
+
# @api private
|
345
|
+
def sibling_versions
|
346
|
+
@sibling_versions ||= self.class.with_item_keys(item_type, item_id)
|
347
|
+
end
|
348
|
+
|
345
349
|
# See docs section 2.e. Limiting the Number of Versions Created.
|
346
350
|
# The version limit can be global or per-model.
|
347
351
|
#
|