paper_trail 13.0.0 → 16.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/lib/generators/paper_trail/install/install_generator.rb +11 -1
- data/lib/generators/paper_trail/install/templates/create_versions.rb.erb +8 -5
- data/lib/paper_trail/attribute_serializers/cast_attribute_serializer.rb +1 -1
- 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 +2 -2
- data/lib/paper_trail/config.rb +3 -1
- data/lib/paper_trail/events/base.rb +25 -2
- data/lib/paper_trail/events/update.rb +0 -3
- data/lib/paper_trail/frameworks/rails/railtie.rb +5 -1
- data/lib/paper_trail/has_paper_trail.rb +6 -0
- data/lib/paper_trail/model_config.rb +6 -8
- data/lib/paper_trail/queries/versions/where_object_changes.rb +1 -1
- data/lib/paper_trail/record_trail.rb +106 -64
- data/lib/paper_trail/request.rb +22 -22
- data/lib/paper_trail/serializers/yaml.rb +23 -4
- data/lib/paper_trail/version_number.rb +1 -1
- data/lib/paper_trail.rb +16 -3
- metadata +16 -15
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0a834abeee917bf2c090c6ef7f6da09437b12aff37c0bcf94f8ff88ca1434203
|
4
|
+
data.tar.gz: 6fdf12293b452fe7dcce563e5f4547d9ae0081399b5cf6885308ea451f29b6ed
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 82fbf4c2ec002d52ee5b4023cbef6a16ef5530e04852a4a3d65fb38b468395016db6230e2f89c199a13549bb9da98a1f53a2b29148a94455bc6470ea7912dcaf
|
7
|
+
data.tar.gz: 9944b2f2e66c9fdc10fe2ecf6f9c560b73da21e49d98504f826158e92faeef5b9cad7a4a1cb1f033217c1f66e230d4c8e2df034c4751298a93df5bf946ef108e
|
@@ -35,7 +35,8 @@ module PaperTrail
|
|
35
35
|
"create_versions",
|
36
36
|
item_type_options: item_type_options,
|
37
37
|
versions_table_options: versions_table_options,
|
38
|
-
item_id_type_options: item_id_type_options
|
38
|
+
item_id_type_options: item_id_type_options,
|
39
|
+
version_table_primary_key_type: version_table_primary_key_type
|
39
40
|
)
|
40
41
|
if options.with_changes?
|
41
42
|
add_paper_trail_migration("add_object_changes_to_versions")
|
@@ -49,6 +50,15 @@ module PaperTrail
|
|
49
50
|
options.uuid? ? "string" : "bigint"
|
50
51
|
end
|
51
52
|
|
53
|
+
# To use uuid for version table primary key
|
54
|
+
def version_table_primary_key_type
|
55
|
+
if options.uuid?
|
56
|
+
", id: :uuid"
|
57
|
+
else
|
58
|
+
""
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
52
62
|
# MySQL 5.6 utf8mb4 limit is 191 chars for keys used in indexes.
|
53
63
|
# See https://github.com/paper-trail-gem/paper_trail/issues/651
|
54
64
|
def item_type_options
|
@@ -9,12 +9,10 @@ class CreateVersions < ActiveRecord::Migration<%= migration_version %>
|
|
9
9
|
TEXT_BYTES = 1_073_741_823
|
10
10
|
|
11
11
|
def change
|
12
|
-
create_table :versions<%= versions_table_options %> do |t|
|
13
|
-
|
14
|
-
t
|
15
|
-
t.string :event, null: false
|
12
|
+
create_table :versions<%= versions_table_options %><%= version_table_primary_key_type %> do |t|
|
13
|
+
# Consider using bigint type for performance if you are going to store only numeric ids.
|
14
|
+
# t.bigint :whodunnit
|
16
15
|
t.string :whodunnit
|
17
|
-
t.text :object, limit: TEXT_BYTES
|
18
16
|
|
19
17
|
# Known issue in MySQL: fractional second precision
|
20
18
|
# -------------------------------------------------
|
@@ -32,6 +30,11 @@ class CreateVersions < ActiveRecord::Migration<%= migration_version %>
|
|
32
30
|
# MySQL users should use the following line for `created_at`
|
33
31
|
# t.datetime :created_at, limit: 6
|
34
32
|
t.datetime :created_at
|
33
|
+
|
34
|
+
t.<%= item_id_type_options %> :item_id, null: false
|
35
|
+
t.string :item_type<%= item_type_options %>
|
36
|
+
t.string :event, null: false
|
37
|
+
t.text :object, limit: TEXT_BYTES
|
35
38
|
end
|
36
39
|
add_index :versions, %i[item_type item_id]
|
37
40
|
end
|
@@ -32,7 +32,7 @@ module PaperTrail
|
|
32
32
|
if defined_enums[attr] && val.is_a?(::String)
|
33
33
|
# Because PT 4 used to save the string version of enums to `object_changes`
|
34
34
|
val
|
35
|
-
elsif PaperTrail
|
35
|
+
elsif PaperTrail.active_record_gte_7_0? && val.is_a?(ActiveRecord::Type::Time::Value)
|
36
36
|
# Because Rails 7 time attribute throws a delegation error when you deserialize
|
37
37
|
# it with the factory.
|
38
38
|
# See ActiveRecord::Type::Time::Value crashes when loaded from YAML on rails 7.0
|
@@ -8,6 +8,12 @@ module PaperTrail
|
|
8
8
|
class ObjectAttribute
|
9
9
|
def initialize(model_class)
|
10
10
|
@model_class = model_class
|
11
|
+
|
12
|
+
# ActiveRecord since 7.0 has a built-in encryption mechanism
|
13
|
+
@encrypted_attributes =
|
14
|
+
if PaperTrail.active_record_gte_7_0?
|
15
|
+
@model_class.encrypted_attributes&.map(&:to_s)
|
16
|
+
end
|
11
17
|
end
|
12
18
|
|
13
19
|
def serialize(attributes)
|
@@ -23,14 +29,18 @@ module PaperTrail
|
|
23
29
|
# Modifies `attributes` in place.
|
24
30
|
# TODO: Return a new hash instead.
|
25
31
|
def alter(attributes, serialization_method)
|
26
|
-
# Don't serialize before values before inserting into columns of type
|
32
|
+
# Don't serialize non-encrypted before values before inserting into columns of type
|
27
33
|
# `JSON` on `PostgreSQL` databases.
|
28
|
-
|
34
|
+
attributes_to_serialize =
|
35
|
+
object_col_is_json? ? attributes.slice(*@encrypted_attributes) : attributes
|
36
|
+
return attributes if attributes_to_serialize.blank?
|
29
37
|
|
30
38
|
serializer = CastAttributeSerializer.new(@model_class)
|
31
|
-
|
39
|
+
attributes_to_serialize.each do |key, value|
|
32
40
|
attributes[key] = serializer.send(serialization_method, key, value)
|
33
41
|
end
|
42
|
+
|
43
|
+
attributes
|
34
44
|
end
|
35
45
|
|
36
46
|
def object_col_is_json?
|
@@ -8,6 +8,12 @@ module PaperTrail
|
|
8
8
|
class ObjectChangesAttribute
|
9
9
|
def initialize(item_class)
|
10
10
|
@item_class = item_class
|
11
|
+
|
12
|
+
# ActiveRecord since 7.0 has a built-in encryption mechanism
|
13
|
+
@encrypted_attributes =
|
14
|
+
if PaperTrail.active_record_gte_7_0?
|
15
|
+
@item_class.encrypted_attributes&.map(&:to_s)
|
16
|
+
end
|
11
17
|
end
|
12
18
|
|
13
19
|
def serialize(changes)
|
@@ -23,17 +29,21 @@ module PaperTrail
|
|
23
29
|
# Modifies `changes` in place.
|
24
30
|
# TODO: Return a new hash instead.
|
25
31
|
def alter(changes, serialization_method)
|
26
|
-
# Don't serialize before values before inserting into columns of type
|
32
|
+
# Don't serialize non-encrypted before values before inserting into columns of type
|
27
33
|
# `JSON` on `PostgreSQL` databases.
|
28
|
-
|
34
|
+
changes_to_serialize =
|
35
|
+
object_changes_col_is_json? ? changes.slice(*@encrypted_attributes) : changes.clone
|
36
|
+
return changes if changes_to_serialize.blank?
|
29
37
|
|
30
38
|
serializer = CastAttributeSerializer.new(@item_class)
|
31
|
-
|
39
|
+
changes_to_serialize.each do |key, change|
|
32
40
|
# `change` is an Array with two elements, representing before and after.
|
33
41
|
changes[key] = Array(change).map do |value|
|
34
42
|
serializer.send(serialization_method, key, value)
|
35
43
|
end
|
36
44
|
end
|
45
|
+
|
46
|
+
changes
|
37
47
|
end
|
38
48
|
|
39
49
|
def object_changes_col_is_json?
|
@@ -17,8 +17,8 @@ module PaperTrail
|
|
17
17
|
# newer rails versions. Most PT users should avoid incompatible rails
|
18
18
|
# versions.
|
19
19
|
module Compatibility
|
20
|
-
ACTIVERECORD_GTE = ">=
|
21
|
-
ACTIVERECORD_LT = "<
|
20
|
+
ACTIVERECORD_GTE = ">= 6.1" # enforced in gemspec
|
21
|
+
ACTIVERECORD_LT = "< 8.1" # not enforced in gemspec
|
22
22
|
|
23
23
|
E_INCOMPATIBLE_AR = <<-EOS
|
24
24
|
PaperTrail %s is not compatible with ActiveRecord %s. We allow PT
|
data/lib/paper_trail/config.rb
CHANGED
@@ -14,7 +14,8 @@ module PaperTrail
|
|
14
14
|
:object_changes_adapter,
|
15
15
|
:serializer,
|
16
16
|
:version_limit,
|
17
|
-
:has_paper_trail_defaults
|
17
|
+
:has_paper_trail_defaults,
|
18
|
+
:version_error_behavior
|
18
19
|
)
|
19
20
|
|
20
21
|
def initialize
|
@@ -25,6 +26,7 @@ module PaperTrail
|
|
25
26
|
# Variables which affect all threads, whose access is *not* synchronized.
|
26
27
|
@serializer = PaperTrail::Serializers::YAML
|
27
28
|
@has_paper_trail_defaults = {}
|
29
|
+
@version_error_behavior = :legacy
|
28
30
|
end
|
29
31
|
|
30
32
|
# Indicates whether PaperTrail is on or off. Default: true.
|
@@ -22,6 +22,19 @@ module PaperTrail
|
|
22
22
|
#
|
23
23
|
# @api private
|
24
24
|
class Base
|
25
|
+
E_FORBIDDEN_METADATA_KEY = <<-EOS.squish
|
26
|
+
Forbidden metadata key: %s. As of PT 14, the following metadata keys are
|
27
|
+
forbidden: %s
|
28
|
+
EOS
|
29
|
+
FORBIDDEN_METADATA_KEYS = %i[
|
30
|
+
created_at
|
31
|
+
id
|
32
|
+
item_id
|
33
|
+
item_subtype
|
34
|
+
item_type
|
35
|
+
updated_at
|
36
|
+
].freeze
|
37
|
+
|
25
38
|
# @api private
|
26
39
|
def initialize(record, in_after_callback)
|
27
40
|
@record = record
|
@@ -44,6 +57,13 @@ module PaperTrail
|
|
44
57
|
|
45
58
|
private
|
46
59
|
|
60
|
+
# @api private
|
61
|
+
def assert_metadatum_key_is_permitted(key)
|
62
|
+
return unless FORBIDDEN_METADATA_KEYS.include?(key.to_sym)
|
63
|
+
raise PaperTrail::InvalidOption,
|
64
|
+
format(E_FORBIDDEN_METADATA_KEY, key, FORBIDDEN_METADATA_KEYS)
|
65
|
+
end
|
66
|
+
|
47
67
|
# Rails 5.1 changed the API of `ActiveRecord::Dirty`. See
|
48
68
|
# https://github.com/paper-trail-gem/paper_trail/pull/899
|
49
69
|
#
|
@@ -175,7 +195,9 @@ module PaperTrail
|
|
175
195
|
#
|
176
196
|
# @api private
|
177
197
|
def merge_metadata_from_controller_into(data)
|
178
|
-
|
198
|
+
metadata = PaperTrail.request.controller_info || {}
|
199
|
+
metadata.keys.each { |k| assert_metadatum_key_is_permitted(k) }
|
200
|
+
data.merge(metadata)
|
179
201
|
end
|
180
202
|
|
181
203
|
# Updates `data` from the model's `meta` option.
|
@@ -183,6 +205,7 @@ module PaperTrail
|
|
183
205
|
# @api private
|
184
206
|
def merge_metadata_from_model_into(data)
|
185
207
|
@record.paper_trail_options[:meta].each do |k, v|
|
208
|
+
assert_metadatum_key_is_permitted(k)
|
186
209
|
data[k] = model_metadatum(v, data[:event])
|
187
210
|
end
|
188
211
|
end
|
@@ -221,7 +244,7 @@ module PaperTrail
|
|
221
244
|
# @api private
|
222
245
|
def notable_changes
|
223
246
|
changes_in_latest_version.delete_if { |k, _v|
|
224
|
-
|
247
|
+
notably_changed.exclude?(k)
|
225
248
|
}
|
226
249
|
end
|
227
250
|
|
@@ -29,9 +29,6 @@ module PaperTrail
|
|
29
29
|
event: @record.paper_trail_event || "update",
|
30
30
|
whodunnit: PaperTrail.request.whodunnit
|
31
31
|
}
|
32
|
-
if @record.respond_to?(:updated_at)
|
33
|
-
data[:created_at] = @record.updated_at
|
34
|
-
end
|
35
32
|
if record_object?
|
36
33
|
data[:object] = recordable_object(@is_touch)
|
37
34
|
end
|
@@ -10,7 +10,7 @@ module PaperTrail
|
|
10
10
|
# We specify `before: "load_config_initializers"` to ensure that the PT
|
11
11
|
# initializer happens before "app initializers" (those defined in
|
12
12
|
# the app's `config/initalizers`).
|
13
|
-
initializer "paper_trail", before: "load_config_initializers" do
|
13
|
+
initializer "paper_trail", before: "load_config_initializers" do |app|
|
14
14
|
# `on_load` is a "lazy load hook". It "declares a block that will be
|
15
15
|
# executed when a Rails component is fully loaded". (See
|
16
16
|
# `active_support/lazy_load_hooks.rb`)
|
@@ -25,6 +25,10 @@ module PaperTrail
|
|
25
25
|
ActiveSupport.on_load(:active_record) do
|
26
26
|
require "paper_trail/frameworks/active_record"
|
27
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
|
28
32
|
end
|
29
33
|
end
|
30
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.
|
@@ -64,6 +68,8 @@ module PaperTrail
|
|
64
68
|
#
|
65
69
|
# @api public
|
66
70
|
def has_paper_trail(options = {})
|
71
|
+
raise Error, "has_paper_trail must be called only once" if self < InstanceMethods
|
72
|
+
|
67
73
|
defaults = PaperTrail.config.has_paper_trail_defaults
|
68
74
|
paper_trail.setup(defaults.merge(options))
|
69
75
|
end
|
@@ -82,8 +82,9 @@ module PaperTrail
|
|
82
82
|
|
83
83
|
# Adds a callback that records a version after a "touch" event.
|
84
84
|
#
|
85
|
-
# Rails < 6.0
|
86
|
-
# a `touch`.
|
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:
|
87
88
|
# https://github.com/paper-trail-gem/paper_trail/issues/1121
|
88
89
|
# https://github.com/paper-trail-gem/paper_trail/issues/1161
|
89
90
|
# https://github.com/paper-trail-gem/paper_trail/pull/1285
|
@@ -93,7 +94,7 @@ module PaperTrail
|
|
93
94
|
@model_class.after_touch { |r|
|
94
95
|
if r.paper_trail.save_version?
|
95
96
|
r.paper_trail.record_update(
|
96
|
-
force:
|
97
|
+
force: false,
|
97
98
|
in_after_callback: true,
|
98
99
|
is_touch: true
|
99
100
|
)
|
@@ -121,9 +122,6 @@ module PaperTrail
|
|
121
122
|
|
122
123
|
private
|
123
124
|
|
124
|
-
RAILS_LT_6_0 = ::ActiveRecord.gem_version < ::Gem::Version.new("6.0.0")
|
125
|
-
private_constant :RAILS_LT_6_0
|
126
|
-
|
127
125
|
# @api private
|
128
126
|
def append_option_uniquely(option, value)
|
129
127
|
collection = @model_class.paper_trail_options.fetch(option)
|
@@ -158,7 +156,7 @@ module PaperTrail
|
|
158
156
|
# @api private - `version_class_name`
|
159
157
|
@model_class.class_attribute :version_class_name
|
160
158
|
if options[:class_name]
|
161
|
-
|
159
|
+
PaperTrail.deprecator.warn(
|
162
160
|
format(
|
163
161
|
DPR_CLASS_NAME_OPTION,
|
164
162
|
class_name: options[:class_name].inspect
|
@@ -194,7 +192,7 @@ module PaperTrail
|
|
194
192
|
def ensure_versions_option_is_hash(options)
|
195
193
|
unless options[:versions].is_a?(Hash)
|
196
194
|
if options[:versions]
|
197
|
-
|
195
|
+
PaperTrail.deprecator.warn(
|
198
196
|
format(
|
199
197
|
DPR_PASSING_ASSOC_NAME_DIRECTLY_TO_VERSIONS_OPTION,
|
200
198
|
versions_name: options[:versions].inspect
|
@@ -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
|
@@ -25,14 +25,6 @@ module PaperTrail
|
|
25
25
|
@record.send("#{@record.class.version_association_name}=", nil)
|
26
26
|
end
|
27
27
|
|
28
|
-
# Is PT enabled for this particular record?
|
29
|
-
# @api private
|
30
|
-
def enabled?
|
31
|
-
PaperTrail.enabled? &&
|
32
|
-
PaperTrail.request.enabled? &&
|
33
|
-
PaperTrail.request.enabled_for_model?(@record.class)
|
34
|
-
end
|
35
|
-
|
36
28
|
# Returns true if this instance is the current, live one;
|
37
29
|
# returns false if this instance came from a previous version.
|
38
30
|
def live?
|
@@ -72,16 +64,11 @@ module PaperTrail
|
|
72
64
|
# of versions_assoc.build?, the association cache is unaware. So, we
|
73
65
|
# invalidate the `versions` association cache with `reset`.
|
74
66
|
versions.reset
|
67
|
+
rescue StandardError => e
|
68
|
+
handle_version_errors e, version, :create
|
75
69
|
end
|
76
70
|
end
|
77
71
|
|
78
|
-
# PT-AT extends this method to add its transaction id.
|
79
|
-
#
|
80
|
-
# @api private
|
81
|
-
def data_for_create
|
82
|
-
{}
|
83
|
-
end
|
84
|
-
|
85
72
|
# `recording_order` is "after" or "before". See ModelConfig#on_destroy.
|
86
73
|
#
|
87
74
|
# @api private
|
@@ -96,23 +83,22 @@ module PaperTrail
|
|
96
83
|
# `data_for_destroy` but PT-AT still does.
|
97
84
|
data = event.data.merge(data_for_destroy)
|
98
85
|
|
99
|
-
version = @record.class.paper_trail.version_class.
|
100
|
-
|
101
|
-
|
102
|
-
else
|
86
|
+
version = @record.class.paper_trail.version_class.new(data)
|
87
|
+
begin
|
88
|
+
version.save!
|
103
89
|
assign_and_reset_version_association(version)
|
104
90
|
version
|
91
|
+
rescue StandardError => e
|
92
|
+
handle_version_errors e, version, :destroy
|
105
93
|
end
|
106
94
|
end
|
107
95
|
|
108
|
-
# PT-AT extends this method to add its transaction id.
|
109
|
-
#
|
110
|
-
# @api private
|
111
|
-
def data_for_destroy
|
112
|
-
{}
|
113
|
-
end
|
114
|
-
|
115
96
|
# @api private
|
97
|
+
# @param force [boolean] Insert a `Version` even if `@record` has not
|
98
|
+
# `changed_notably?`.
|
99
|
+
# @param in_after_callback [boolean] True when called from an `after_update`
|
100
|
+
# or `after_touch` callback.
|
101
|
+
# @param is_touch [boolean] True when called from an `after_touch` callback.
|
116
102
|
# @return - The created version object, so that plugins can use it, e.g.
|
117
103
|
# paper_trail-association_tracking
|
118
104
|
def record_update(force:, in_after_callback:, is_touch:)
|
@@ -125,51 +111,18 @@ module PaperTrail
|
|
125
111
|
)
|
126
112
|
return unless version
|
127
113
|
|
128
|
-
|
114
|
+
begin
|
115
|
+
version.save!
|
129
116
|
# Because the version object was created using version_class.new instead
|
130
117
|
# of versions_assoc.build?, the association cache is unaware. So, we
|
131
118
|
# invalidate the `versions` association cache with `reset`.
|
132
119
|
versions.reset
|
133
120
|
version
|
134
|
-
|
135
|
-
|
136
|
-
end
|
137
|
-
end
|
138
|
-
|
139
|
-
# PT-AT extends this method to add its transaction id.
|
140
|
-
#
|
141
|
-
# @api private
|
142
|
-
def data_for_update
|
143
|
-
{}
|
144
|
-
end
|
145
|
-
|
146
|
-
# @api private
|
147
|
-
# @return - The created version object, so that plugins can use it, e.g.
|
148
|
-
# paper_trail-association_tracking
|
149
|
-
def record_update_columns(changes)
|
150
|
-
return unless enabled?
|
151
|
-
event = Events::Update.new(@record, false, false, changes)
|
152
|
-
|
153
|
-
# Merge data from `Event` with data from PT-AT. We no longer use
|
154
|
-
# `data_for_update_columns` but PT-AT still does.
|
155
|
-
data = event.data.merge(data_for_update_columns)
|
156
|
-
|
157
|
-
versions_assoc = @record.send(@record.class.versions_association_name)
|
158
|
-
version = versions_assoc.create(data)
|
159
|
-
if version.errors.any?
|
160
|
-
log_version_errors(version, :update)
|
161
|
-
else
|
162
|
-
version
|
121
|
+
rescue StandardError => e
|
122
|
+
handle_version_errors e, version, :update
|
163
123
|
end
|
164
124
|
end
|
165
125
|
|
166
|
-
# PT-AT extends this method to add its transaction id.
|
167
|
-
#
|
168
|
-
# @api private
|
169
|
-
def data_for_update_columns
|
170
|
-
{}
|
171
|
-
end
|
172
|
-
|
173
126
|
# Invoked via callback when a user attempts to persist a reified
|
174
127
|
# `Version`.
|
175
128
|
def reset_timestamp_attrs_for_update_if_needed
|
@@ -269,11 +222,23 @@ module PaperTrail
|
|
269
222
|
def build_version_on_update(force:, in_after_callback:, is_touch:)
|
270
223
|
event = Events::Update.new(@record, in_after_callback, is_touch, nil)
|
271
224
|
return unless force || event.changed_notably?
|
225
|
+
data = event.data
|
226
|
+
|
227
|
+
# Copy the (recently set) `updated_at` from the record to the `created_at`
|
228
|
+
# of the `Version`. Without this feature, these two timestamps would
|
229
|
+
# differ by a few milliseconds. To some people, it seems a little
|
230
|
+
# unnatural to tamper with creation timestamps in this way. But, this
|
231
|
+
# feature has existed for a long time, almost a decade now, and some users
|
232
|
+
# may rely on it now.
|
233
|
+
if @record.respond_to?(:updated_at) &&
|
234
|
+
@record.paper_trail_options[:synchronize_version_creation_timestamp] != false
|
235
|
+
data[:created_at] = @record.updated_at
|
236
|
+
end
|
272
237
|
|
273
238
|
# Merge data from `Event` with data from PT-AT. We no longer use
|
274
239
|
# `data_for_update` but PT-AT still does. To save memory, we use `merge!`
|
275
240
|
# instead of `merge`.
|
276
|
-
data
|
241
|
+
data.merge!(data_for_update)
|
277
242
|
|
278
243
|
# Using `version_class.new` reduces memory usage compared to
|
279
244
|
# `versions_assoc.build`. It's a trade-off though. We have to clear
|
@@ -282,6 +247,42 @@ module PaperTrail
|
|
282
247
|
@record.class.paper_trail.version_class.new(data)
|
283
248
|
end
|
284
249
|
|
250
|
+
# PT-AT extends this method to add its transaction id.
|
251
|
+
#
|
252
|
+
# @api public
|
253
|
+
def data_for_create
|
254
|
+
{}
|
255
|
+
end
|
256
|
+
|
257
|
+
# PT-AT extends this method to add its transaction id.
|
258
|
+
#
|
259
|
+
# @api public
|
260
|
+
def data_for_destroy
|
261
|
+
{}
|
262
|
+
end
|
263
|
+
|
264
|
+
# PT-AT extends this method to add its transaction id.
|
265
|
+
#
|
266
|
+
# @api public
|
267
|
+
def data_for_update
|
268
|
+
{}
|
269
|
+
end
|
270
|
+
|
271
|
+
# PT-AT extends this method to add its transaction id.
|
272
|
+
#
|
273
|
+
# @api public
|
274
|
+
def data_for_update_columns
|
275
|
+
{}
|
276
|
+
end
|
277
|
+
|
278
|
+
# Is PT enabled for this particular record?
|
279
|
+
# @api private
|
280
|
+
def enabled?
|
281
|
+
PaperTrail.enabled? &&
|
282
|
+
PaperTrail.request.enabled? &&
|
283
|
+
PaperTrail.request.enabled_for_model?(@record.class)
|
284
|
+
end
|
285
|
+
|
285
286
|
def log_version_errors(version, action)
|
286
287
|
version.logger&.warn(
|
287
288
|
"Unable to create version for #{action} of #{@record.class.name}" \
|
@@ -289,6 +290,47 @@ module PaperTrail
|
|
289
290
|
)
|
290
291
|
end
|
291
292
|
|
293
|
+
# Centralized handler for version errors
|
294
|
+
# @api private
|
295
|
+
def handle_version_errors(e, version, action)
|
296
|
+
case PaperTrail.config.version_error_behavior
|
297
|
+
when :legacy
|
298
|
+
# legacy behavior was to raise on create and log on update/delete
|
299
|
+
if action == :create
|
300
|
+
raise e
|
301
|
+
else
|
302
|
+
log_version_errors(version, action)
|
303
|
+
end
|
304
|
+
when :log
|
305
|
+
log_version_errors(version, action)
|
306
|
+
when :exception
|
307
|
+
raise e
|
308
|
+
when :silent
|
309
|
+
# noop
|
310
|
+
end
|
311
|
+
end
|
312
|
+
|
313
|
+
# @api private
|
314
|
+
# @return - The created version object, so that plugins can use it, e.g.
|
315
|
+
# paper_trail-association_tracking
|
316
|
+
def record_update_columns(changes)
|
317
|
+
return unless enabled?
|
318
|
+
data = Events::Update.new(@record, false, false, changes).data
|
319
|
+
|
320
|
+
# Merge data from `Event` with data from PT-AT. We no longer use
|
321
|
+
# `data_for_update_columns` but PT-AT still does.
|
322
|
+
data.merge!(data_for_update_columns)
|
323
|
+
|
324
|
+
versions_assoc = @record.send(@record.class.versions_association_name)
|
325
|
+
version = versions_assoc.new(data)
|
326
|
+
begin
|
327
|
+
version.save!
|
328
|
+
version
|
329
|
+
rescue StandardError => e
|
330
|
+
handle_version_errors e, version, :update
|
331
|
+
end
|
332
|
+
end
|
333
|
+
|
292
334
|
def version
|
293
335
|
@record.public_send(@record.class.version_association_name)
|
294
336
|
end
|
data/lib/paper_trail/request.rb
CHANGED
@@ -75,28 +75,6 @@ module PaperTrail
|
|
75
75
|
!!store.fetch(:"enabled_for_#{model}", true)
|
76
76
|
end
|
77
77
|
|
78
|
-
# @api private
|
79
|
-
def merge(options)
|
80
|
-
options.to_h.each do |k, v|
|
81
|
-
store[k] = v
|
82
|
-
end
|
83
|
-
end
|
84
|
-
|
85
|
-
# @api private
|
86
|
-
def set(options)
|
87
|
-
store.clear
|
88
|
-
merge(options)
|
89
|
-
end
|
90
|
-
|
91
|
-
# Returns a deep copy of the internal hash from our RequestStore. Keys are
|
92
|
-
# all symbols. Values are mostly primitives, but whodunnit can be a Proc.
|
93
|
-
# We cannot use Marshal.dump here because it doesn't support Proc. It is
|
94
|
-
# unclear exactly how `deep_dup` handles a Proc, but it doesn't complain.
|
95
|
-
# @api private
|
96
|
-
def to_h
|
97
|
-
store.deep_dup
|
98
|
-
end
|
99
|
-
|
100
78
|
# Temporarily set `options` and execute a block.
|
101
79
|
# @api private
|
102
80
|
def with(options)
|
@@ -133,6 +111,19 @@ module PaperTrail
|
|
133
111
|
|
134
112
|
private
|
135
113
|
|
114
|
+
# @api private
|
115
|
+
def merge(options)
|
116
|
+
options.to_h.each do |k, v|
|
117
|
+
store[k] = v
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
# @api private
|
122
|
+
def set(options)
|
123
|
+
store.clear
|
124
|
+
merge(options)
|
125
|
+
end
|
126
|
+
|
136
127
|
# Returns a Hash, initializing with default values if necessary.
|
137
128
|
# @api private
|
138
129
|
def store
|
@@ -141,6 +132,15 @@ module PaperTrail
|
|
141
132
|
}
|
142
133
|
end
|
143
134
|
|
135
|
+
# Returns a deep copy of the internal hash from our RequestStore. Keys are
|
136
|
+
# all symbols. Values are mostly primitives, but whodunnit can be a Proc.
|
137
|
+
# We cannot use Marshal.dump here because it doesn't support Proc. It is
|
138
|
+
# unclear exactly how `deep_dup` handles a Proc, but it doesn't complain.
|
139
|
+
# @api private
|
140
|
+
def to_h
|
141
|
+
store.deep_dup
|
142
|
+
end
|
143
|
+
|
144
144
|
# Provide a helpful error message if someone has a typo in one of their
|
145
145
|
# option keys. We don't validate option values here. That's traditionally
|
146
146
|
# been handled with casting (`to_s`, `!!`) in the accessor method.
|
@@ -12,7 +12,7 @@ module PaperTrail
|
|
12
12
|
if use_safe_load?
|
13
13
|
::YAML.safe_load(
|
14
14
|
string,
|
15
|
-
permitted_classes:
|
15
|
+
permitted_classes: yaml_column_permitted_classes,
|
16
16
|
aliases: true
|
17
17
|
)
|
18
18
|
elsif ::YAML.respond_to?(:unsafe_load)
|
@@ -39,10 +39,29 @@ module PaperTrail
|
|
39
39
|
|
40
40
|
private
|
41
41
|
|
42
|
-
# `use_yaml_unsafe_load` was added in 7.0.3.1, will be removed in 7.1.0?
|
43
42
|
def use_safe_load?
|
44
|
-
|
45
|
-
|
43
|
+
if ::ActiveRecord.gem_version >= Gem::Version.new("7.0.3.1")
|
44
|
+
# `use_yaml_unsafe_load` may be removed in the future, at which point
|
45
|
+
# safe loading will be the default.
|
46
|
+
!defined?(ActiveRecord.use_yaml_unsafe_load) || !ActiveRecord.use_yaml_unsafe_load
|
47
|
+
elsif defined?(ActiveRecord::Base.use_yaml_unsafe_load)
|
48
|
+
# Rails 5.2.8.1, 6.0.5.1, 6.1.6.1
|
49
|
+
!ActiveRecord::Base.use_yaml_unsafe_load
|
50
|
+
else
|
51
|
+
false
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def yaml_column_permitted_classes
|
56
|
+
if defined?(ActiveRecord.yaml_column_permitted_classes)
|
57
|
+
# Rails >= 7.0.3.1
|
58
|
+
ActiveRecord.yaml_column_permitted_classes
|
59
|
+
elsif defined?(ActiveRecord::Base.yaml_column_permitted_classes)
|
60
|
+
# Rails 5.2.8.1, 6.0.5.1, 6.1.6.1
|
61
|
+
ActiveRecord::Base.yaml_column_permitted_classes
|
62
|
+
else
|
63
|
+
[]
|
64
|
+
end
|
46
65
|
end
|
47
66
|
end
|
48
67
|
end
|
data/lib/paper_trail.rb
CHANGED
@@ -8,6 +8,12 @@
|
|
8
8
|
# can revisit this decision.
|
9
9
|
require "active_support/all"
|
10
10
|
|
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
|
+
|
11
17
|
require "paper_trail/errors"
|
12
18
|
require "paper_trail/cleaner"
|
13
19
|
require "paper_trail/compatibility"
|
@@ -26,8 +32,6 @@ module PaperTrail
|
|
26
32
|
named created_at.
|
27
33
|
EOS
|
28
34
|
|
29
|
-
RAILS_GTE_7_0 = ::ActiveRecord.gem_version >= ::Gem::Version.new("7.0.0")
|
30
|
-
|
31
35
|
extend PaperTrail::Cleaner
|
32
36
|
|
33
37
|
class << self
|
@@ -98,7 +102,7 @@ module PaperTrail
|
|
98
102
|
|
99
103
|
# Returns PaperTrail's global configuration object, a singleton. These
|
100
104
|
# settings affect all threads.
|
101
|
-
# @api
|
105
|
+
# @api public
|
102
106
|
def config
|
103
107
|
@config ||= PaperTrail::Config.instance
|
104
108
|
yield @config if block_given?
|
@@ -106,9 +110,18 @@ module PaperTrail
|
|
106
110
|
end
|
107
111
|
alias configure config
|
108
112
|
|
113
|
+
# @api public
|
109
114
|
def version
|
110
115
|
VERSION::STRING
|
111
116
|
end
|
117
|
+
|
118
|
+
def active_record_gte_7_0?
|
119
|
+
@active_record_gte_7_0 ||= ::ActiveRecord.gem_version >= ::Gem::Version.new("7.0.0")
|
120
|
+
end
|
121
|
+
|
122
|
+
def deprecator
|
123
|
+
@deprecator ||= ActiveSupport::Deprecation.new("16.0", "PaperTrail")
|
124
|
+
end
|
112
125
|
end
|
113
126
|
end
|
114
127
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: paper_trail
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 16.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andy Stewart
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date:
|
13
|
+
date: 2024-11-08 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: activerecord
|
@@ -18,42 +18,42 @@ dependencies:
|
|
18
18
|
requirements:
|
19
19
|
- - ">="
|
20
20
|
- !ruby/object:Gem::Version
|
21
|
-
version: '
|
21
|
+
version: '6.1'
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
24
|
version_requirements: !ruby/object:Gem::Requirement
|
25
25
|
requirements:
|
26
26
|
- - ">="
|
27
27
|
- !ruby/object:Gem::Version
|
28
|
-
version: '
|
28
|
+
version: '6.1'
|
29
29
|
- !ruby/object:Gem::Dependency
|
30
30
|
name: request_store
|
31
31
|
requirement: !ruby/object:Gem::Requirement
|
32
32
|
requirements:
|
33
33
|
- - "~>"
|
34
34
|
- !ruby/object:Gem::Version
|
35
|
-
version: '1.
|
35
|
+
version: '1.4'
|
36
36
|
type: :runtime
|
37
37
|
prerelease: false
|
38
38
|
version_requirements: !ruby/object:Gem::Requirement
|
39
39
|
requirements:
|
40
40
|
- - "~>"
|
41
41
|
- !ruby/object:Gem::Version
|
42
|
-
version: '1.
|
42
|
+
version: '1.4'
|
43
43
|
- !ruby/object:Gem::Dependency
|
44
44
|
name: appraisal
|
45
45
|
requirement: !ruby/object:Gem::Requirement
|
46
46
|
requirements:
|
47
47
|
- - "~>"
|
48
48
|
- !ruby/object:Gem::Version
|
49
|
-
version: 2.
|
49
|
+
version: '2.5'
|
50
50
|
type: :development
|
51
51
|
prerelease: false
|
52
52
|
version_requirements: !ruby/object:Gem::Requirement
|
53
53
|
requirements:
|
54
54
|
- - "~>"
|
55
55
|
- !ruby/object:Gem::Version
|
56
|
-
version: 2.
|
56
|
+
version: '2.5'
|
57
57
|
- !ruby/object:Gem::Dependency
|
58
58
|
name: byebug
|
59
59
|
requirement: !ruby/object:Gem::Requirement
|
@@ -116,14 +116,14 @@ dependencies:
|
|
116
116
|
requirements:
|
117
117
|
- - ">="
|
118
118
|
- !ruby/object:Gem::Version
|
119
|
-
version: '
|
119
|
+
version: '6.1'
|
120
120
|
type: :development
|
121
121
|
prerelease: false
|
122
122
|
version_requirements: !ruby/object:Gem::Requirement
|
123
123
|
requirements:
|
124
124
|
- - ">="
|
125
125
|
- !ruby/object:Gem::Version
|
126
|
-
version: '
|
126
|
+
version: '6.1'
|
127
127
|
- !ruby/object:Gem::Dependency
|
128
128
|
name: rake
|
129
129
|
requirement: !ruby/object:Gem::Requirement
|
@@ -144,14 +144,14 @@ dependencies:
|
|
144
144
|
requirements:
|
145
145
|
- - "~>"
|
146
146
|
- !ruby/object:Gem::Version
|
147
|
-
version:
|
147
|
+
version: 6.0.3
|
148
148
|
type: :development
|
149
149
|
prerelease: false
|
150
150
|
version_requirements: !ruby/object:Gem::Requirement
|
151
151
|
requirements:
|
152
152
|
- - "~>"
|
153
153
|
- !ruby/object:Gem::Version
|
154
|
-
version:
|
154
|
+
version: 6.0.3
|
155
155
|
- !ruby/object:Gem::Dependency
|
156
156
|
name: rubocop
|
157
157
|
requirement: !ruby/object:Gem::Requirement
|
@@ -351,7 +351,8 @@ files:
|
|
351
351
|
homepage: https://github.com/paper-trail-gem/paper_trail
|
352
352
|
licenses:
|
353
353
|
- MIT
|
354
|
-
metadata:
|
354
|
+
metadata:
|
355
|
+
changelog_uri: https://github.com/paper-trail-gem/paper_trail/blob/master/CHANGELOG.md
|
355
356
|
post_install_message:
|
356
357
|
rdoc_options: []
|
357
358
|
require_paths:
|
@@ -360,14 +361,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
360
361
|
requirements:
|
361
362
|
- - ">="
|
362
363
|
- !ruby/object:Gem::Version
|
363
|
-
version:
|
364
|
+
version: 3.0.0
|
364
365
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
365
366
|
requirements:
|
366
367
|
- - ">="
|
367
368
|
- !ruby/object:Gem::Version
|
368
369
|
version: 1.3.6
|
369
370
|
requirements: []
|
370
|
-
rubygems_version: 3.
|
371
|
+
rubygems_version: 3.4.19
|
371
372
|
signing_key:
|
372
373
|
specification_version: 4
|
373
374
|
summary: Track changes to your models.
|