paper_trail 11.1.0 → 15.2.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/lib/generators/paper_trail/install/install_generator.rb +29 -5
- data/lib/generators/paper_trail/install/templates/create_versions.rb.erb +11 -6
- 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 +10 -10
- data/lib/paper_trail/attribute_serializers/object_attribute.rb +13 -3
- data/lib/paper_trail/attribute_serializers/object_changes_attribute.rb +13 -3
- data/lib/paper_trail/compatibility.rb +3 -3
- data/lib/paper_trail/errors.rb +33 -0
- data/lib/paper_trail/events/base.rb +84 -64
- 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 +0 -6
- data/lib/paper_trail/frameworks/rails/railtie.rb +34 -0
- data/lib/paper_trail/frameworks/rails.rb +1 -2
- data/lib/paper_trail/has_paper_trail.rb +4 -0
- data/lib/paper_trail/model_config.rb +51 -45
- 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 +81 -64
- data/lib/paper_trail/reifier.rb +27 -10
- 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 +74 -22
- data/lib/paper_trail/version_number.rb +2 -2
- data/lib/paper_trail.rb +30 -40
- metadata +98 -45
- data/Gemfile +0 -4
- data/lib/paper_trail/frameworks/rails/engine.rb +0 -45
- data/paper_trail.gemspec +0 -69
@@ -0,0 +1,34 @@
|
|
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 |app|
|
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
|
+
|
29
|
+
if Gem::Version.new(::Rails::VERSION::STRING) >= Gem::Version.new("7.1")
|
30
|
+
app.deprecators[:paper_trail] = PaperTrail.deprecator
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -53,6 +53,10 @@ module PaperTrail
|
|
53
53
|
# - A Hash - options passed to `has_many`, plus `name:` and `scope:`.
|
54
54
|
# - :version - The name to use for the method which returns the version
|
55
55
|
# the instance was reified from. Default is `:version`.
|
56
|
+
# - :synchronize_version_creation_timestamp - By default, paper trail
|
57
|
+
# sets the `created_at` field for a new Version equal to the `updated_at`
|
58
|
+
# column of the model being updated. If you instead want `created_at` to
|
59
|
+
# populate with the current timestamp, set this option to `false`.
|
56
60
|
#
|
57
61
|
# Plugins like the experimental `paper_trail-association_tracking` gem
|
58
62
|
# may accept additional options.
|
@@ -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
|
-
|
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
|
-
|
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,28 @@ module PaperTrail
|
|
92
77
|
@model_class.after_update { |r|
|
93
78
|
r.paper_trail.clear_version_instance
|
94
79
|
}
|
95
|
-
|
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 (no longer supported by PT) had a bug where dirty-tracking
|
86
|
+
# did not occur during a `touch`.
|
87
|
+
# (https://github.com/rails/rails/issues/33429) See also:
|
88
|
+
# https://github.com/paper-trail-gem/paper_trail/issues/1121
|
89
|
+
# https://github.com/paper-trail-gem/paper_trail/issues/1161
|
90
|
+
# https://github.com/paper-trail-gem/paper_trail/pull/1285
|
91
|
+
#
|
100
92
|
# @api public
|
101
93
|
def on_touch
|
102
94
|
@model_class.after_touch { |r|
|
103
|
-
r.paper_trail.
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
95
|
+
if r.paper_trail.save_version?
|
96
|
+
r.paper_trail.record_update(
|
97
|
+
force: false,
|
98
|
+
in_after_callback: true,
|
99
|
+
is_touch: true
|
100
|
+
)
|
101
|
+
end
|
108
102
|
}
|
109
103
|
end
|
110
104
|
|
@@ -117,44 +111,52 @@ module PaperTrail
|
|
117
111
|
@model_class.send :include, ::PaperTrail::Model::InstanceMethods
|
118
112
|
setup_options(options)
|
119
113
|
setup_associations(options)
|
120
|
-
check_presence_of_item_subtype_column(options)
|
121
114
|
@model_class.after_rollback { paper_trail.clear_rolled_back_versions }
|
122
115
|
setup_callbacks_from_options options[:on]
|
123
116
|
end
|
124
117
|
|
118
|
+
# @api private
|
125
119
|
def version_class
|
126
|
-
@
|
120
|
+
@version_class ||= @model_class.version_class_name.constantize
|
127
121
|
end
|
128
122
|
|
129
123
|
private
|
130
124
|
|
125
|
+
# @api private
|
126
|
+
def append_option_uniquely(option, value)
|
127
|
+
collection = @model_class.paper_trail_options.fetch(option)
|
128
|
+
return if collection.include?(value)
|
129
|
+
collection << value
|
130
|
+
end
|
131
|
+
|
131
132
|
# Raises an error if the provided class is an `abstract_class`.
|
132
133
|
# @api private
|
133
134
|
def assert_concrete_activerecord_class(class_name)
|
134
135
|
if class_name.constantize.abstract_class?
|
135
|
-
raise format(E_HPT_ABSTRACT_CLASS, @model_class, class_name)
|
136
|
+
raise Error, format(E_HPT_ABSTRACT_CLASS, @model_class, class_name)
|
136
137
|
end
|
137
138
|
end
|
138
139
|
|
139
|
-
|
140
|
-
|
140
|
+
# @api private
|
141
|
+
def assert_valid_recording_order_for_on_destroy(recording_order)
|
142
|
+
unless %w[after before].include?(recording_order.to_s)
|
143
|
+
raise ArgumentError, 'recording order can only be "after" or "before"'
|
144
|
+
end
|
145
|
+
|
146
|
+
if recording_order.to_s == "after" && cannot_record_after_destroy?
|
147
|
+
raise Error, E_CANNOT_RECORD_AFTER_DESTROY
|
148
|
+
end
|
141
149
|
end
|
142
150
|
|
143
|
-
|
144
|
-
|
145
|
-
#
|
146
|
-
# @api private
|
147
|
-
def check_presence_of_item_subtype_column(options)
|
148
|
-
return unless options.key?(:limit)
|
149
|
-
return if version_class.item_subtype_column_present?
|
150
|
-
raise format(E_MODEL_LIMIT_REQUIRES_ITEM_SUBTYPE, @model_class.name)
|
151
|
+
def cannot_record_after_destroy?
|
152
|
+
::ActiveRecord::Base.belongs_to_required_by_default
|
151
153
|
end
|
152
154
|
|
153
155
|
def check_version_class_name(options)
|
154
156
|
# @api private - `version_class_name`
|
155
157
|
@model_class.class_attribute :version_class_name
|
156
158
|
if options[:class_name]
|
157
|
-
|
159
|
+
PaperTrail.deprecator.warn(
|
158
160
|
format(
|
159
161
|
DPR_CLASS_NAME_OPTION,
|
160
162
|
class_name: options[:class_name].inspect
|
@@ -190,7 +192,7 @@ module PaperTrail
|
|
190
192
|
def ensure_versions_option_is_hash(options)
|
191
193
|
unless options[:versions].is_a?(Hash)
|
192
194
|
if options[:versions]
|
193
|
-
|
195
|
+
PaperTrail.deprecator.warn(
|
194
196
|
format(
|
195
197
|
DPR_PASSING_ASSOC_NAME_DIRECTLY_TO_VERSIONS_OPTION,
|
196
198
|
versions_name: options[:versions].inspect
|
@@ -205,6 +207,14 @@ module PaperTrail
|
|
205
207
|
options
|
206
208
|
end
|
207
209
|
|
210
|
+
# Process an `ignore`, `skip`, or `only` option.
|
211
|
+
def event_attribute_option(option_name)
|
212
|
+
[@model_class.paper_trail_options[option_name]].
|
213
|
+
flatten.
|
214
|
+
compact.
|
215
|
+
map { |attr| attr.is_a?(Hash) ? attr.stringify_keys : attr.to_s }
|
216
|
+
end
|
217
|
+
|
208
218
|
def get_versions_scope(options)
|
209
219
|
options[:versions][:scope] || -> { order(model.timestamp_sort_order) }
|
210
220
|
end
|
@@ -239,12 +249,8 @@ module PaperTrail
|
|
239
249
|
@model_class.paper_trail_options = options.dup
|
240
250
|
|
241
251
|
%i[ignore skip only].each do |k|
|
242
|
-
@model_class.paper_trail_options[k] =
|
243
|
-
flatten.
|
244
|
-
compact.
|
245
|
-
map { |attr| attr.is_a?(Hash) ? attr.stringify_keys : attr.to_s }
|
252
|
+
@model_class.paper_trail_options[k] = event_attribute_option(k)
|
246
253
|
end
|
247
|
-
|
248
254
|
@model_class.paper_trail_options[:meta] ||= {}
|
249
255
|
end
|
250
256
|
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
|
22
|
+
raise Error, "where_object requires an object column" unless column
|
23
23
|
|
24
24
|
case column.type
|
25
25
|
when :jsonb
|
@@ -15,7 +15,7 @@ module PaperTrail
|
|
15
15
|
@version_model_class = version_model_class
|
16
16
|
|
17
17
|
# Currently, this `deep_dup` is necessary because the `jsonb` branch
|
18
|
-
# modifies `@attributes`, and that would be a nasty
|
18
|
+
# modifies `@attributes`, and that would be a nasty surprise for
|
19
19
|
# consumers of this class.
|
20
20
|
# TODO: Stop modifying `@attributes`, then remove `deep_dup`.
|
21
21
|
@attributes = attributes.deep_dup
|
@@ -23,18 +23,23 @@ 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
|
)
|
30
30
|
end
|
31
|
-
|
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
|
-
|
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
|
@@ -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_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
|
+
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_from",
|
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
|
@@ -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
|
@@ -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
|
@@ -27,14 +25,6 @@ module PaperTrail
|
|
27
25
|
@record.send("#{@record.class.version_association_name}=", nil)
|
28
26
|
end
|
29
27
|
|
30
|
-
# Is PT enabled for this particular record?
|
31
|
-
# @api private
|
32
|
-
def enabled?
|
33
|
-
PaperTrail.enabled? &&
|
34
|
-
PaperTrail.request.enabled? &&
|
35
|
-
PaperTrail.request.enabled_for_model?(@record.class)
|
36
|
-
end
|
37
|
-
|
38
28
|
# Returns true if this instance is the current, live one;
|
39
29
|
# returns false if this instance came from a previous version.
|
40
30
|
def live?
|
@@ -77,13 +67,6 @@ module PaperTrail
|
|
77
67
|
end
|
78
68
|
end
|
79
69
|
|
80
|
-
# PT-AT extends this method to add its transaction id.
|
81
|
-
#
|
82
|
-
# @api private
|
83
|
-
def data_for_create
|
84
|
-
{}
|
85
|
-
end
|
86
|
-
|
87
70
|
# `recording_order` is "after" or "before". See ModelConfig#on_destroy.
|
88
71
|
#
|
89
72
|
# @api private
|
@@ -107,14 +90,12 @@ module PaperTrail
|
|
107
90
|
end
|
108
91
|
end
|
109
92
|
|
110
|
-
# PT-AT extends this method to add its transaction id.
|
111
|
-
#
|
112
|
-
# @api private
|
113
|
-
def data_for_destroy
|
114
|
-
{}
|
115
|
-
end
|
116
|
-
|
117
93
|
# @api private
|
94
|
+
# @param force [boolean] Insert a `Version` even if `@record` has not
|
95
|
+
# `changed_notably?`.
|
96
|
+
# @param in_after_callback [boolean] True when called from an `after_update`
|
97
|
+
# or `after_touch` callback.
|
98
|
+
# @param is_touch [boolean] True when called from an `after_touch` callback.
|
118
99
|
# @return - The created version object, so that plugins can use it, e.g.
|
119
100
|
# paper_trail-association_tracking
|
120
101
|
def record_update(force:, in_after_callback:, is_touch:)
|
@@ -138,40 +119,6 @@ module PaperTrail
|
|
138
119
|
end
|
139
120
|
end
|
140
121
|
|
141
|
-
# PT-AT extends this method to add its transaction id.
|
142
|
-
#
|
143
|
-
# @api private
|
144
|
-
def data_for_update
|
145
|
-
{}
|
146
|
-
end
|
147
|
-
|
148
|
-
# @api private
|
149
|
-
# @return - The created version object, so that plugins can use it, e.g.
|
150
|
-
# paper_trail-association_tracking
|
151
|
-
def record_update_columns(changes)
|
152
|
-
return unless enabled?
|
153
|
-
event = Events::Update.new(@record, false, false, changes)
|
154
|
-
|
155
|
-
# Merge data from `Event` with data from PT-AT. We no longer use
|
156
|
-
# `data_for_update_columns` but PT-AT still does.
|
157
|
-
data = event.data.merge(data_for_update_columns)
|
158
|
-
|
159
|
-
versions_assoc = @record.send(@record.class.versions_association_name)
|
160
|
-
version = versions_assoc.create(data)
|
161
|
-
if version.errors.any?
|
162
|
-
log_version_errors(version, :update)
|
163
|
-
else
|
164
|
-
version
|
165
|
-
end
|
166
|
-
end
|
167
|
-
|
168
|
-
# PT-AT extends this method to add its transaction id.
|
169
|
-
#
|
170
|
-
# @api private
|
171
|
-
def data_for_update_columns
|
172
|
-
{}
|
173
|
-
end
|
174
|
-
|
175
122
|
# Invoked via callback when a user attempts to persist a reified
|
176
123
|
# `Version`.
|
177
124
|
def reset_timestamp_attrs_for_update_if_needed
|
@@ -196,15 +143,17 @@ module PaperTrail
|
|
196
143
|
# Save, and create a version record regardless of options such as `:on`,
|
197
144
|
# `:if`, or `:unless`.
|
198
145
|
#
|
199
|
-
#
|
146
|
+
# `in_after_callback`: Indicates if this method is being called within an
|
147
|
+
# `after` callback. Defaults to `false`.
|
148
|
+
# `options`: Optional arguments passed to `save`.
|
200
149
|
#
|
201
150
|
# This is an "update" event. That is, we record the same data we would in
|
202
151
|
# the case of a normal AR `update`.
|
203
|
-
def save_with_version(
|
152
|
+
def save_with_version(in_after_callback: false, **options)
|
204
153
|
::PaperTrail.request(enabled: false) do
|
205
|
-
@record.save(
|
154
|
+
@record.save(**options)
|
206
155
|
end
|
207
|
-
record_update(force: true, in_after_callback:
|
156
|
+
record_update(force: true, in_after_callback: in_after_callback, is_touch: false)
|
208
157
|
end
|
209
158
|
|
210
159
|
# Like the `update_column` method from `ActiveRecord::Persistence`, but also
|
@@ -269,11 +218,23 @@ module PaperTrail
|
|
269
218
|
def build_version_on_update(force:, in_after_callback:, is_touch:)
|
270
219
|
event = Events::Update.new(@record, in_after_callback, is_touch, nil)
|
271
220
|
return unless force || event.changed_notably?
|
221
|
+
data = event.data
|
222
|
+
|
223
|
+
# Copy the (recently set) `updated_at` from the record to the `created_at`
|
224
|
+
# of the `Version`. Without this feature, these two timestamps would
|
225
|
+
# differ by a few milliseconds. To some people, it seems a little
|
226
|
+
# unnatural to tamper with creation timestamps in this way. But, this
|
227
|
+
# feature has existed for a long time, almost a decade now, and some users
|
228
|
+
# may rely on it now.
|
229
|
+
if @record.respond_to?(:updated_at) &&
|
230
|
+
@record.paper_trail_options[:synchronize_version_creation_timestamp] != false
|
231
|
+
data[:created_at] = @record.updated_at
|
232
|
+
end
|
272
233
|
|
273
234
|
# Merge data from `Event` with data from PT-AT. We no longer use
|
274
235
|
# `data_for_update` but PT-AT still does. To save memory, we use `merge!`
|
275
236
|
# instead of `merge`.
|
276
|
-
data
|
237
|
+
data.merge!(data_for_update)
|
277
238
|
|
278
239
|
# Using `version_class.new` reduces memory usage compared to
|
279
240
|
# `versions_assoc.build`. It's a trade-off though. We have to clear
|
@@ -282,13 +243,69 @@ module PaperTrail
|
|
282
243
|
@record.class.paper_trail.version_class.new(data)
|
283
244
|
end
|
284
245
|
|
246
|
+
# PT-AT extends this method to add its transaction id.
|
247
|
+
#
|
248
|
+
# @api public
|
249
|
+
def data_for_create
|
250
|
+
{}
|
251
|
+
end
|
252
|
+
|
253
|
+
# PT-AT extends this method to add its transaction id.
|
254
|
+
#
|
255
|
+
# @api public
|
256
|
+
def data_for_destroy
|
257
|
+
{}
|
258
|
+
end
|
259
|
+
|
260
|
+
# PT-AT extends this method to add its transaction id.
|
261
|
+
#
|
262
|
+
# @api public
|
263
|
+
def data_for_update
|
264
|
+
{}
|
265
|
+
end
|
266
|
+
|
267
|
+
# PT-AT extends this method to add its transaction id.
|
268
|
+
#
|
269
|
+
# @api public
|
270
|
+
def data_for_update_columns
|
271
|
+
{}
|
272
|
+
end
|
273
|
+
|
274
|
+
# Is PT enabled for this particular record?
|
275
|
+
# @api private
|
276
|
+
def enabled?
|
277
|
+
PaperTrail.enabled? &&
|
278
|
+
PaperTrail.request.enabled? &&
|
279
|
+
PaperTrail.request.enabled_for_model?(@record.class)
|
280
|
+
end
|
281
|
+
|
285
282
|
def log_version_errors(version, action)
|
286
283
|
version.logger&.warn(
|
287
284
|
"Unable to create version for #{action} of #{@record.class.name}" \
|
288
|
-
|
285
|
+
"##{@record.id}: " + version.errors.full_messages.join(", ")
|
289
286
|
)
|
290
287
|
end
|
291
288
|
|
289
|
+
# @api private
|
290
|
+
# @return - The created version object, so that plugins can use it, e.g.
|
291
|
+
# paper_trail-association_tracking
|
292
|
+
def record_update_columns(changes)
|
293
|
+
return unless enabled?
|
294
|
+
data = Events::Update.new(@record, false, false, changes).data
|
295
|
+
|
296
|
+
# Merge data from `Event` with data from PT-AT. We no longer use
|
297
|
+
# `data_for_update_columns` but PT-AT still does.
|
298
|
+
data.merge!(data_for_update_columns)
|
299
|
+
|
300
|
+
versions_assoc = @record.send(@record.class.versions_association_name)
|
301
|
+
version = versions_assoc.create(data)
|
302
|
+
if version.errors.any?
|
303
|
+
log_version_errors(version, :update)
|
304
|
+
else
|
305
|
+
version
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
292
309
|
def version
|
293
310
|
@record.public_send(@record.class.version_association_name)
|
294
311
|
end
|