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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: '0972b7805c1dea218a2eb15d46e68c76650fe4d8752e00a64080c38360544ede'
|
4
|
+
data.tar.gz: f242c006d46c1af35e67dcbf667458356372fba3d90238d56d29989f257f5f34
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 91e251f425f7389ffdd39e7793f6665f53dec8f1f967f0befbd00df0fd2aef423f7c074520b4f5f4410c3bfbca1f1c0beb2afe61c6d19825e1818209d36e8c45
|
7
|
+
data.tar.gz: f2eeac74231a4b51fd2e0b699c2591e0b3de1aad6f8d96bd8863375b37c78d96f6ca6161d7e552a856ee92821380d3f457f111322a686254a964117c908bf5b0
|
@@ -20,6 +20,12 @@ module PaperTrail
|
|
20
20
|
default: false,
|
21
21
|
desc: "Store changeset (diff) with each version"
|
22
22
|
)
|
23
|
+
class_option(
|
24
|
+
:uuid,
|
25
|
+
type: :boolean,
|
26
|
+
default: false,
|
27
|
+
desc: "Use uuid instead of bigint for item_id type (use only if tables use UUIDs)"
|
28
|
+
)
|
23
29
|
|
24
30
|
desc "Generates (but does not run) a migration to add a versions table." \
|
25
31
|
" See section 5.c. Generators in README.md for more information."
|
@@ -28,7 +34,9 @@ module PaperTrail
|
|
28
34
|
add_paper_trail_migration(
|
29
35
|
"create_versions",
|
30
36
|
item_type_options: item_type_options,
|
31
|
-
versions_table_options: versions_table_options
|
37
|
+
versions_table_options: versions_table_options,
|
38
|
+
item_id_type_options: item_id_type_options,
|
39
|
+
version_table_primary_key_type: version_table_primary_key_type
|
32
40
|
)
|
33
41
|
if options.with_changes?
|
34
42
|
add_paper_trail_migration("add_object_changes_to_versions")
|
@@ -37,12 +45,28 @@ module PaperTrail
|
|
37
45
|
|
38
46
|
private
|
39
47
|
|
48
|
+
# To use uuid instead of integer for primary key
|
49
|
+
def item_id_type_options
|
50
|
+
options.uuid? ? "string" : "bigint"
|
51
|
+
end
|
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
|
+
|
40
62
|
# MySQL 5.6 utf8mb4 limit is 191 chars for keys used in indexes.
|
41
63
|
# See https://github.com/paper-trail-gem/paper_trail/issues/651
|
42
64
|
def item_type_options
|
43
|
-
|
44
|
-
|
45
|
-
|
65
|
+
if mysql?
|
66
|
+
", null: false, limit: 191"
|
67
|
+
else
|
68
|
+
", null: false"
|
69
|
+
end
|
46
70
|
end
|
47
71
|
|
48
72
|
def mysql?
|
@@ -66,7 +90,7 @@ module PaperTrail
|
|
66
90
|
#
|
67
91
|
def versions_table_options
|
68
92
|
if mysql?
|
69
|
-
',
|
93
|
+
', options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci"'
|
70
94
|
else
|
71
95
|
""
|
72
96
|
end
|
@@ -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.bigint :
|
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
|
# -------------------------------------------------
|
@@ -29,8 +27,15 @@ class CreateVersions < ActiveRecord::Migration<%= migration_version %>
|
|
29
27
|
# version of ActiveRecord with support for fractional seconds in MySQL.
|
30
28
|
# (https://github.com/rails/rails/pull/14359)
|
31
29
|
#
|
30
|
+
# MySQL users should use the following line for `created_at`
|
31
|
+
# t.datetime :created_at, limit: 6
|
32
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
|
33
38
|
end
|
34
|
-
add_index :versions, %i
|
39
|
+
add_index :versions, %i[item_type item_id]
|
35
40
|
end
|
36
41
|
end
|
@@ -7,8 +7,10 @@ module PaperTrail
|
|
7
7
|
class UpdateItemSubtypeGenerator < MigrationGenerator
|
8
8
|
source_root File.expand_path("templates", __dir__)
|
9
9
|
|
10
|
-
desc
|
11
|
-
"
|
10
|
+
desc(
|
11
|
+
"Generates (but does not run) a migration to update item_subtype for "\
|
12
|
+
"STI entries in an existing versions table."
|
13
|
+
)
|
12
14
|
|
13
15
|
def create_migration_file
|
14
16
|
add_paper_trail_migration("update_versions_for_item_subtype", sti_type_options: options)
|
@@ -8,18 +8,32 @@ module PaperTrail
|
|
8
8
|
# not suited for writing JSON to a text column. This factory
|
9
9
|
# replaces certain default Active Record serializers
|
10
10
|
# with custom PaperTrail ones.
|
11
|
+
#
|
12
|
+
# @api private
|
11
13
|
module AttributeSerializerFactory
|
12
|
-
|
14
|
+
class << self
|
15
|
+
# @api private
|
16
|
+
def for(klass, attr)
|
17
|
+
active_record_serializer = klass.type_for_attribute(attr)
|
18
|
+
if ar_pg_array?(active_record_serializer)
|
19
|
+
TypeSerializers::PostgresArraySerializer.new(
|
20
|
+
active_record_serializer.subtype,
|
21
|
+
active_record_serializer.delimiter
|
22
|
+
)
|
23
|
+
else
|
24
|
+
active_record_serializer
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
13
29
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
else
|
22
|
-
active_record_serializer
|
30
|
+
# @api private
|
31
|
+
def ar_pg_array?(obj)
|
32
|
+
if defined?(::ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Array)
|
33
|
+
obj.instance_of?(::ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Array)
|
34
|
+
else
|
35
|
+
false
|
36
|
+
end
|
23
37
|
end
|
24
38
|
end
|
25
39
|
end
|
@@ -8,9 +8,6 @@ module PaperTrail
|
|
8
8
|
# The `CastAttributeSerializer` (de)serializes model attribute values. For
|
9
9
|
# example, the string "1.99" serializes into the integer `1` when assigned
|
10
10
|
# to an attribute of type `ActiveRecord::Type::Integer`.
|
11
|
-
#
|
12
|
-
# This implementation depends on the `type_for_attribute` method, which was
|
13
|
-
# introduced in rails 4.2. As of PT 8, we no longer support rails < 4.2.
|
14
11
|
class CastAttributeSerializer
|
15
12
|
def initialize(klass)
|
16
13
|
@klass = klass
|
@@ -30,22 +27,25 @@ module PaperTrail
|
|
30
27
|
def defined_enums
|
31
28
|
@defined_enums ||= (@klass.respond_to?(:defined_enums) ? @klass.defined_enums : {})
|
32
29
|
end
|
33
|
-
end
|
34
|
-
|
35
|
-
# Uses AR 5's `serialize` and `deserialize`.
|
36
|
-
class CastAttributeSerializer
|
37
|
-
def serialize(attr, val)
|
38
|
-
AttributeSerializerFactory.for(@klass, attr).serialize(val)
|
39
|
-
end
|
40
30
|
|
41
31
|
def deserialize(attr, val)
|
42
32
|
if defined_enums[attr] && val.is_a?(::String)
|
43
33
|
# Because PT 4 used to save the string version of enums to `object_changes`
|
44
34
|
val
|
35
|
+
elsif PaperTrail.active_record_gte_7_0? && val.is_a?(ActiveRecord::Type::Time::Value)
|
36
|
+
# Because Rails 7 time attribute throws a delegation error when you deserialize
|
37
|
+
# it with the factory.
|
38
|
+
# See ActiveRecord::Type::Time::Value crashes when loaded from YAML on rails 7.0
|
39
|
+
# https://github.com/rails/rails/issues/43966
|
40
|
+
val.instance_variable_get(:@time)
|
45
41
|
else
|
46
42
|
AttributeSerializerFactory.for(@klass, attr).deserialize(val)
|
47
43
|
end
|
48
44
|
end
|
45
|
+
|
46
|
+
def serialize(attr, val)
|
47
|
+
AttributeSerializerFactory.for(@klass, attr).serialize(val)
|
48
|
+
end
|
49
49
|
end
|
50
50
|
end
|
51
51
|
end
|
@@ -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?
|
@@ -8,7 +8,7 @@ module PaperTrail
|
|
8
8
|
#
|
9
9
|
# It is not safe to assume that a new version of rails will be compatible with
|
10
10
|
# PaperTrail. PT is only compatible with the versions of rails that it is
|
11
|
-
# tested against. See `.
|
11
|
+
# tested against. See `.github/workflows/test.yml`.
|
12
12
|
#
|
13
13
|
# However, as of
|
14
14
|
# [#1213](https://github.com/paper-trail-gem/paper_trail/pull/1213) our
|
@@ -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 = "< 7.3" # 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
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PaperTrail
|
4
|
+
# Generic PaperTrail exception.
|
5
|
+
# @api public
|
6
|
+
class Error < StandardError
|
7
|
+
end
|
8
|
+
|
9
|
+
# An unexpected option, perhaps a typo, was passed to a public API method.
|
10
|
+
# @api public
|
11
|
+
class InvalidOption < Error
|
12
|
+
end
|
13
|
+
|
14
|
+
# The application's database schema is not supported.
|
15
|
+
# @api public
|
16
|
+
class UnsupportedSchema < Error
|
17
|
+
end
|
18
|
+
|
19
|
+
# The application's database column type is not supported.
|
20
|
+
# @api public
|
21
|
+
class UnsupportedColumnType < UnsupportedSchema
|
22
|
+
def initialize(method:, expected:, actual:)
|
23
|
+
super(
|
24
|
+
format(
|
25
|
+
"%s expected %s column, got %s",
|
26
|
+
method,
|
27
|
+
expected,
|
28
|
+
actual
|
29
|
+
)
|
30
|
+
)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -22,7 +22,18 @@ module PaperTrail
|
|
22
22
|
#
|
23
23
|
# @api private
|
24
24
|
class Base
|
25
|
-
|
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
|
26
37
|
|
27
38
|
# @api private
|
28
39
|
def initialize(record, in_after_callback)
|
@@ -46,12 +57,19 @@ module PaperTrail
|
|
46
57
|
|
47
58
|
private
|
48
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
|
+
|
49
67
|
# Rails 5.1 changed the API of `ActiveRecord::Dirty`. See
|
50
68
|
# https://github.com/paper-trail-gem/paper_trail/pull/899
|
51
69
|
#
|
52
70
|
# @api private
|
53
71
|
def attribute_changed_in_latest_version?(attr_name)
|
54
|
-
if @in_after_callback
|
72
|
+
if @in_after_callback
|
55
73
|
@record.saved_change_to_attribute?(attr_name.to_s)
|
56
74
|
else
|
57
75
|
@record.attribute_changed?(attr_name.to_s)
|
@@ -60,30 +78,14 @@ module PaperTrail
|
|
60
78
|
|
61
79
|
# @api private
|
62
80
|
def nonskipped_attributes_before_change(is_touch)
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
if @record.class.column_names.include?(k)
|
68
|
-
record_attributes[k] = attribute_in_previous_version(k, is_touch)
|
69
|
-
end
|
81
|
+
record_attributes = @record.attributes.except(*@record.paper_trail_options[:skip])
|
82
|
+
record_attributes.each_key do |k|
|
83
|
+
if @record.class.column_names.include?(k)
|
84
|
+
record_attributes[k] = attribute_in_previous_version(k, is_touch)
|
70
85
|
end
|
71
86
|
end
|
72
87
|
end
|
73
88
|
|
74
|
-
# Rails 5.1 changed the API of `ActiveRecord::Dirty`.
|
75
|
-
# @api private
|
76
|
-
def cache_changed_attributes(&block)
|
77
|
-
if RAILS_GTE_5_1
|
78
|
-
# Everything works fine as it is
|
79
|
-
yield
|
80
|
-
else
|
81
|
-
# Any particular call to `changed_attributes` produces the huge memory allocation.
|
82
|
-
# Lets use the generic AR workaround for that.
|
83
|
-
@record.send(:cache_changed_attributes, &block)
|
84
|
-
end
|
85
|
-
end
|
86
|
-
|
87
89
|
# Rails 5.1 changed the API of `ActiveRecord::Dirty`. See
|
88
90
|
# https://github.com/paper-trail-gem/paper_trail/pull/899
|
89
91
|
#
|
@@ -91,18 +93,14 @@ module PaperTrail
|
|
91
93
|
#
|
92
94
|
# @api private
|
93
95
|
def attribute_in_previous_version(attr_name, is_touch)
|
94
|
-
if
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
@record.attribute_before_last_save(attr_name.to_s)
|
99
|
-
else
|
100
|
-
# We are either performing a `record_destroy` or a
|
101
|
-
# `record_update(is_touch: true)`.
|
102
|
-
@record.attribute_in_database(attr_name.to_s)
|
103
|
-
end
|
96
|
+
if @in_after_callback && !is_touch
|
97
|
+
# For most events, we want the original value of the attribute, before
|
98
|
+
# the last save.
|
99
|
+
@record.attribute_before_last_save(attr_name.to_s)
|
104
100
|
else
|
105
|
-
|
101
|
+
# We are either performing a `record_destroy` or a
|
102
|
+
# `record_update(is_touch: true)`.
|
103
|
+
@record.attribute_in_database(attr_name.to_s)
|
106
104
|
end
|
107
105
|
end
|
108
106
|
|
@@ -131,19 +129,25 @@ module PaperTrail
|
|
131
129
|
@changed_in_latest_version ||= changes_in_latest_version.keys
|
132
130
|
end
|
133
131
|
|
134
|
-
#
|
135
|
-
# https://github.com/paper-trail-gem/paper_trail/pull/899
|
132
|
+
# Memoized to reduce memory usage
|
136
133
|
#
|
137
134
|
# @api private
|
138
135
|
def changes_in_latest_version
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
136
|
+
@changes_in_latest_version ||= load_changes_in_latest_version
|
137
|
+
end
|
138
|
+
|
139
|
+
# @api private
|
140
|
+
def evaluate_only
|
141
|
+
only = @record.paper_trail_options[:only].dup
|
142
|
+
# Remove Hash arguments and then evaluate whether the attributes (the
|
143
|
+
# keys of the hash) should also get pushed into the collection.
|
144
|
+
only.delete_if do |obj|
|
145
|
+
obj.is_a?(Hash) &&
|
146
|
+
obj.each { |attr, condition|
|
147
|
+
only << attr if condition.respond_to?(:call) && condition.call(@record)
|
148
|
+
}
|
146
149
|
end
|
150
|
+
only
|
147
151
|
end
|
148
152
|
|
149
153
|
# An attributed is "ignored" if it is listed in the `:ignore` option
|
@@ -156,6 +160,18 @@ module PaperTrail
|
|
156
160
|
ignored.any? && (changed_in_latest_version & ignored).any?
|
157
161
|
end
|
158
162
|
|
163
|
+
# Rails 5.1 changed the API of `ActiveRecord::Dirty`. See
|
164
|
+
# https://github.com/paper-trail-gem/paper_trail/pull/899
|
165
|
+
#
|
166
|
+
# @api private
|
167
|
+
def load_changes_in_latest_version
|
168
|
+
if @in_after_callback
|
169
|
+
@record.saved_changes
|
170
|
+
else
|
171
|
+
@record.changes
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
159
175
|
# PT 10 has a new optional column, `item_subtype`
|
160
176
|
#
|
161
177
|
# @api private
|
@@ -179,7 +195,9 @@ module PaperTrail
|
|
179
195
|
#
|
180
196
|
# @api private
|
181
197
|
def merge_metadata_from_controller_into(data)
|
182
|
-
|
198
|
+
metadata = PaperTrail.request.controller_info || {}
|
199
|
+
metadata.keys.each { |k| assert_metadatum_key_is_permitted(k) }
|
200
|
+
data.merge(metadata)
|
183
201
|
end
|
184
202
|
|
185
203
|
# Updates `data` from the model's `meta` option.
|
@@ -187,6 +205,7 @@ module PaperTrail
|
|
187
205
|
# @api private
|
188
206
|
def merge_metadata_from_model_into(data)
|
189
207
|
@record.paper_trail_options[:meta].each do |k, v|
|
208
|
+
assert_metadatum_key_is_permitted(k)
|
190
209
|
data[k] = model_metadatum(v, data[:event])
|
191
210
|
end
|
192
211
|
end
|
@@ -200,24 +219,32 @@ module PaperTrail
|
|
200
219
|
if value.respond_to?(:call)
|
201
220
|
value.call(@record)
|
202
221
|
elsif value.is_a?(Symbol) && @record.respond_to?(value, true)
|
203
|
-
|
204
|
-
# be sure to grab the current version.
|
205
|
-
if event != "create" &&
|
206
|
-
@record.has_attribute?(value) &&
|
207
|
-
attribute_changed_in_latest_version?(value)
|
208
|
-
attribute_in_previous_version(value, false)
|
209
|
-
else
|
210
|
-
@record.send(value)
|
211
|
-
end
|
222
|
+
metadatum_from_model_method(event, value)
|
212
223
|
else
|
213
224
|
value
|
214
225
|
end
|
215
226
|
end
|
216
227
|
|
228
|
+
# The model method can either be an attribute or a non-attribute method.
|
229
|
+
#
|
230
|
+
# If it is an attribute that is changing in an existing object,
|
231
|
+
# be sure to grab the correct version.
|
232
|
+
#
|
233
|
+
# @api private
|
234
|
+
def metadatum_from_model_method(event, method)
|
235
|
+
if event != "create" &&
|
236
|
+
@record.has_attribute?(method) &&
|
237
|
+
attribute_changed_in_latest_version?(method)
|
238
|
+
attribute_in_previous_version(method, false)
|
239
|
+
else
|
240
|
+
@record.send(method)
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
217
244
|
# @api private
|
218
245
|
def notable_changes
|
219
246
|
changes_in_latest_version.delete_if { |k, _v|
|
220
|
-
|
247
|
+
notably_changed.exclude?(k)
|
221
248
|
}
|
222
249
|
end
|
223
250
|
|
@@ -225,16 +252,9 @@ module PaperTrail
|
|
225
252
|
def notably_changed
|
226
253
|
# Memoized to reduce memory usage
|
227
254
|
@notably_changed ||= begin
|
228
|
-
only =
|
229
|
-
|
230
|
-
|
231
|
-
only.delete_if do |obj|
|
232
|
-
obj.is_a?(Hash) &&
|
233
|
-
obj.each { |attr, condition|
|
234
|
-
only << attr if condition.respond_to?(:call) && condition.call(@record)
|
235
|
-
}
|
236
|
-
end
|
237
|
-
only.empty? ? changed_and_not_ignored : (changed_and_not_ignored & only)
|
255
|
+
only = evaluate_only
|
256
|
+
cani = changed_and_not_ignored
|
257
|
+
only.empty? ? cani : (cani & only)
|
238
258
|
end
|
239
259
|
end
|
240
260
|
|
@@ -263,7 +283,7 @@ module PaperTrail
|
|
263
283
|
# @api private
|
264
284
|
# @param changes HashWithIndifferentAccess
|
265
285
|
def recordable_object_changes(changes)
|
266
|
-
if PaperTrail.config.object_changes_adapter
|
286
|
+
if PaperTrail.config.object_changes_adapter.respond_to?(:diff)
|
267
287
|
# We'd like to avoid the `to_hash` here, because it increases memory
|
268
288
|
# usage, but that would be a breaking change because
|
269
289
|
# `object_changes_adapter` expects a plain `Hash`, not a
|
@@ -29,22 +29,38 @@ 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
|
38
|
-
|
39
|
-
changes = @force_changes.nil? ? notable_changes : @force_changes
|
40
|
-
data[:object_changes] = prepare_object_changes(changes)
|
41
|
-
end
|
35
|
+
merge_object_changes_into(data)
|
42
36
|
merge_item_subtype_into(data)
|
43
37
|
merge_metadata_into(data)
|
44
38
|
end
|
45
39
|
|
40
|
+
# If it is a touch event, and changed are empty, it is assumed to be
|
41
|
+
# implicit `touch` mutation, and will a version is created.
|
42
|
+
#
|
43
|
+
# See https://github.com/rails/rails/commit/dcb825902d79d0f6baba956f7c6ec5767611353e
|
44
|
+
#
|
45
|
+
# @api private
|
46
|
+
def changed_notably?
|
47
|
+
if @is_touch && changes_in_latest_version.empty?
|
48
|
+
true
|
49
|
+
else
|
50
|
+
super
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
46
54
|
private
|
47
55
|
|
56
|
+
# @api private
|
57
|
+
def merge_object_changes_into(data)
|
58
|
+
if record_object_changes?
|
59
|
+
changes = @force_changes.nil? ? notable_changes : @force_changes
|
60
|
+
data[:object_changes] = prepare_object_changes(changes)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
48
64
|
# `touch` cannot record `object_changes` because rails' `touch` does not
|
49
65
|
# perform dirty-tracking. Specifically, methods from `Dirty`, like
|
50
66
|
# `saved_changes`, return the same values before and after `touch`.
|
@@ -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
|