paper_trail 10.3.1 → 12.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
|