paper_trail 10.3.1 → 12.1.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/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 +8 -46
- data/lib/paper_trail/compatibility.rb +2 -2
- data/lib/paper_trail/config.rb +0 -33
- data/lib/paper_trail/errors.rb +33 -0
- data/lib/paper_trail/events/base.rb +35 -50
- 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/controller.rb +1 -9
- data/lib/paper_trail/frameworks/rails/railtie.rb +30 -0
- data/lib/paper_trail/frameworks/rails.rb +1 -2
- data/lib/paper_trail/has_paper_trail.rb +1 -1
- data/lib/paper_trail/model_config.rb +25 -30
- 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 +8 -13
- 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 +3 -5
- data/lib/paper_trail/reifier.rb +41 -26
- data/lib/paper_trail/request.rb +0 -3
- data/lib/paper_trail/serializers/json.rb +0 -10
- data/lib/paper_trail/serializers/yaml.rb +0 -12
- data/lib/paper_trail/type_serializers/postgres_array_serializer.rb +1 -14
- data/lib/paper_trail/version_concern.rb +75 -40
- data/lib/paper_trail/version_number.rb +3 -3
- data/lib/paper_trail.rb +15 -40
- metadata +113 -40
- 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
|
@@ -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.
|
@@ -53,13 +48,7 @@ module PaperTrail
|
|
53
48
|
#
|
54
49
|
# @api public
|
55
50
|
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
|
51
|
+
assert_valid_recording_order_for_on_destroy(recording_order)
|
63
52
|
|
64
53
|
@model_class.send(
|
65
54
|
"#{recording_order}_destroy",
|
@@ -97,11 +86,18 @@ module PaperTrail
|
|
97
86
|
end
|
98
87
|
|
99
88
|
# Adds a callback that records a version after a "touch" event.
|
89
|
+
#
|
90
|
+
# Rails < 6.0 has a bug where dirty-tracking does not occur during
|
91
|
+
# a `touch`. (https://github.com/rails/rails/issues/33429) See also:
|
92
|
+
# https://github.com/paper-trail-gem/paper_trail/issues/1121
|
93
|
+
# https://github.com/paper-trail-gem/paper_trail/issues/1161
|
94
|
+
# https://github.com/paper-trail-gem/paper_trail/pull/1285
|
95
|
+
#
|
100
96
|
# @api public
|
101
97
|
def on_touch
|
102
98
|
@model_class.after_touch { |r|
|
103
99
|
r.paper_trail.record_update(
|
104
|
-
force:
|
100
|
+
force: RAILS_LT_6_0,
|
105
101
|
in_after_callback: true,
|
106
102
|
is_touch: true
|
107
103
|
)
|
@@ -117,42 +113,41 @@ module PaperTrail
|
|
117
113
|
@model_class.send :include, ::PaperTrail::Model::InstanceMethods
|
118
114
|
setup_options(options)
|
119
115
|
setup_associations(options)
|
120
|
-
check_presence_of_item_subtype_column(options)
|
121
116
|
@model_class.after_rollback { paper_trail.clear_rolled_back_versions }
|
122
117
|
setup_callbacks_from_options options[:on]
|
123
118
|
end
|
124
119
|
|
120
|
+
# @api private
|
125
121
|
def version_class
|
126
|
-
@
|
122
|
+
@version_class ||= @model_class.version_class_name.constantize
|
127
123
|
end
|
128
124
|
|
129
125
|
private
|
130
126
|
|
131
|
-
|
132
|
-
|
133
|
-
end
|
127
|
+
RAILS_LT_6_0 = ::ActiveRecord.gem_version < ::Gem::Version.new("6.0.0")
|
128
|
+
private_constant :RAILS_LT_6_0
|
134
129
|
|
135
130
|
# Raises an error if the provided class is an `abstract_class`.
|
136
131
|
# @api private
|
137
132
|
def assert_concrete_activerecord_class(class_name)
|
138
133
|
if class_name.constantize.abstract_class?
|
139
|
-
raise format(E_HPT_ABSTRACT_CLASS, @model_class, class_name)
|
134
|
+
raise Error, format(E_HPT_ABSTRACT_CLASS, @model_class, class_name)
|
140
135
|
end
|
141
136
|
end
|
142
137
|
|
143
|
-
|
144
|
-
|
145
|
-
|
138
|
+
# @api private
|
139
|
+
def assert_valid_recording_order_for_on_destroy(recording_order)
|
140
|
+
unless %w[after before].include?(recording_order.to_s)
|
141
|
+
raise ArgumentError, 'recording order can only be "after" or "before"'
|
142
|
+
end
|
143
|
+
|
144
|
+
if recording_order.to_s == "after" && cannot_record_after_destroy?
|
145
|
+
raise Error, E_CANNOT_RECORD_AFTER_DESTROY
|
146
|
+
end
|
146
147
|
end
|
147
148
|
|
148
|
-
|
149
|
-
|
150
|
-
#
|
151
|
-
# @api private
|
152
|
-
def check_presence_of_item_subtype_column(options)
|
153
|
-
return unless options.key?(:limit)
|
154
|
-
return if version_class.item_subtype_column_present?
|
155
|
-
raise format(E_MODEL_LIMIT_REQUIRES_ITEM_SUBTYPE, @model_class.name)
|
149
|
+
def cannot_record_after_destroy?
|
150
|
+
::ActiveRecord::Base.belongs_to_required_by_default
|
156
151
|
end
|
157
152
|
|
158
153
|
def check_version_class_name(options)
|
@@ -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
|
@@ -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
|
@@ -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
|
@@ -285,7 +283,7 @@ module PaperTrail
|
|
285
283
|
def log_version_errors(version, action)
|
286
284
|
version.logger&.warn(
|
287
285
|
"Unable to create version for #{action} of #{@record.class.name}" \
|
288
|
-
|
286
|
+
"##{@record.id}: " + version.errors.full_messages.join(", ")
|
289
287
|
)
|
290
288
|
end
|
291
289
|
|