paper_trail 5.2.3 → 11.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 +5 -5
- data/lib/generators/paper_trail/install/USAGE +3 -0
- data/lib/generators/paper_trail/install/install_generator.rb +75 -0
- data/lib/generators/paper_trail/{templates/add_object_changes_to_versions.rb → install/templates/add_object_changes_to_versions.rb.erb} +1 -1
- data/lib/generators/paper_trail/install/templates/create_versions.rb.erb +36 -0
- data/lib/generators/paper_trail/migration_generator.rb +38 -0
- data/lib/generators/paper_trail/update_item_subtype/USAGE +4 -0
- data/lib/generators/paper_trail/update_item_subtype/templates/update_versions_for_item_subtype.rb.erb +85 -0
- data/lib/generators/paper_trail/update_item_subtype/update_item_subtype_generator.rb +17 -0
- data/lib/paper_trail.rb +82 -130
- data/lib/paper_trail/attribute_serializers/attribute_serializer_factory.rb +27 -0
- data/lib/paper_trail/attribute_serializers/cast_attribute_serializer.rb +15 -44
- data/lib/paper_trail/attribute_serializers/object_attribute.rb +2 -0
- data/lib/paper_trail/attribute_serializers/object_changes_attribute.rb +2 -0
- data/lib/paper_trail/cleaner.rb +3 -1
- data/lib/paper_trail/compatibility.rb +51 -0
- data/lib/paper_trail/config.rb +11 -49
- data/lib/paper_trail/events/base.rb +323 -0
- data/lib/paper_trail/events/create.rb +32 -0
- data/lib/paper_trail/events/destroy.rb +42 -0
- data/lib/paper_trail/events/update.rb +60 -0
- data/lib/paper_trail/frameworks/active_record.rb +2 -1
- data/lib/paper_trail/frameworks/active_record/models/paper_trail/version.rb +8 -3
- data/lib/paper_trail/frameworks/cucumber.rb +5 -3
- data/lib/paper_trail/frameworks/rails.rb +2 -0
- data/lib/paper_trail/frameworks/rails/controller.rb +33 -43
- data/lib/paper_trail/frameworks/rails/engine.rb +34 -1
- data/lib/paper_trail/frameworks/rspec.rb +17 -4
- data/lib/paper_trail/frameworks/rspec/helpers.rb +2 -0
- data/lib/paper_trail/has_paper_trail.rb +22 -310
- data/lib/paper_trail/model_config.rb +157 -109
- data/lib/paper_trail/queries/versions/where_object.rb +65 -0
- data/lib/paper_trail/queries/versions/where_object_changes.rb +75 -0
- data/lib/paper_trail/record_history.rb +3 -9
- data/lib/paper_trail/record_trail.rb +169 -319
- data/lib/paper_trail/reifier.rb +53 -374
- data/lib/paper_trail/request.rb +166 -0
- data/lib/paper_trail/serializers/json.rb +9 -10
- data/lib/paper_trail/serializers/yaml.rb +15 -28
- data/lib/paper_trail/type_serializers/postgres_array_serializer.rb +48 -0
- data/lib/paper_trail/version_concern.rb +160 -155
- data/lib/paper_trail/version_number.rb +12 -4
- metadata +77 -372
- data/.github/CONTRIBUTING.md +0 -109
- data/.github/ISSUE_TEMPLATE.md +0 -13
- data/.gitignore +0 -23
- data/.rspec +0 -2
- data/.rubocop.yml +0 -99
- data/.rubocop_todo.yml +0 -22
- data/.travis.yml +0 -41
- data/Appraisals +0 -38
- data/CHANGELOG.md +0 -560
- data/Gemfile +0 -2
- data/MIT-LICENSE +0 -20
- data/README.md +0 -1654
- data/Rakefile +0 -30
- data/doc/bug_report_template.rb +0 -69
- data/doc/warning_about_not_setting_whodunnit.md +0 -32
- data/gemfiles/ar3.gemfile +0 -19
- data/gemfiles/ar4.gemfile +0 -8
- data/gemfiles/ar5.gemfile +0 -9
- data/lib/generators/paper_trail/USAGE +0 -2
- data/lib/generators/paper_trail/default_initializer.rb +0 -0
- data/lib/generators/paper_trail/install_generator.rb +0 -57
- data/lib/generators/paper_trail/templates/add_transaction_id_column_to_versions.rb +0 -13
- data/lib/generators/paper_trail/templates/create_version_associations.rb +0 -22
- data/lib/generators/paper_trail/templates/create_versions.rb +0 -80
- data/lib/paper_trail/attribute_serializers/legacy_active_record_shim.rb +0 -48
- data/lib/paper_trail/frameworks/active_record/models/paper_trail/version_association.rb +0 -11
- data/lib/paper_trail/frameworks/sinatra.rb +0 -40
- data/lib/paper_trail/version_association_concern.rb +0 -17
- data/paper_trail.gemspec +0 -56
- data/spec/generators/install_generator_spec.rb +0 -66
- data/spec/generators/paper_trail/templates/create_versions_spec.rb +0 -51
- data/spec/models/animal_spec.rb +0 -36
- data/spec/models/boolit_spec.rb +0 -48
- data/spec/models/callback_modifier_spec.rb +0 -96
- data/spec/models/car_spec.rb +0 -13
- data/spec/models/custom_primary_key_record_spec.rb +0 -18
- data/spec/models/fluxor_spec.rb +0 -17
- data/spec/models/gadget_spec.rb +0 -68
- data/spec/models/joined_version_spec.rb +0 -47
- data/spec/models/json_version_spec.rb +0 -102
- data/spec/models/kitchen/banana_spec.rb +0 -14
- data/spec/models/not_on_update_spec.rb +0 -22
- data/spec/models/post_with_status_spec.rb +0 -50
- data/spec/models/skipper_spec.rb +0 -46
- data/spec/models/thing_spec.rb +0 -11
- data/spec/models/truck_spec.rb +0 -5
- data/spec/models/vehicle_spec.rb +0 -5
- data/spec/models/version_spec.rb +0 -272
- data/spec/models/widget_spec.rb +0 -343
- data/spec/modules/paper_trail_spec.rb +0 -27
- data/spec/modules/version_concern_spec.rb +0 -31
- data/spec/modules/version_number_spec.rb +0 -43
- data/spec/paper_trail/config_spec.rb +0 -33
- data/spec/paper_trail_spec.rb +0 -79
- data/spec/rails_helper.rb +0 -34
- data/spec/requests/articles_spec.rb +0 -34
- data/spec/spec_helper.rb +0 -114
- data/spec/support/alt_db_init.rb +0 -54
- data/test/custom_json_serializer.rb +0 -13
- data/test/dummy/Rakefile +0 -7
- data/test/dummy/app/controllers/application_controller.rb +0 -33
- data/test/dummy/app/controllers/articles_controller.rb +0 -20
- data/test/dummy/app/controllers/test_controller.rb +0 -5
- data/test/dummy/app/controllers/widgets_controller.rb +0 -32
- data/test/dummy/app/helpers/application_helper.rb +0 -2
- data/test/dummy/app/models/animal.rb +0 -6
- data/test/dummy/app/models/article.rb +0 -24
- data/test/dummy/app/models/authorship.rb +0 -5
- data/test/dummy/app/models/bar_habtm.rb +0 -4
- data/test/dummy/app/models/book.rb +0 -9
- data/test/dummy/app/models/boolit.rb +0 -4
- data/test/dummy/app/models/callback_modifier.rb +0 -45
- data/test/dummy/app/models/car.rb +0 -3
- data/test/dummy/app/models/cat.rb +0 -2
- data/test/dummy/app/models/chapter.rb +0 -9
- data/test/dummy/app/models/citation.rb +0 -5
- data/test/dummy/app/models/custom_primary_key_record.rb +0 -13
- data/test/dummy/app/models/customer.rb +0 -4
- data/test/dummy/app/models/document.rb +0 -4
- data/test/dummy/app/models/dog.rb +0 -2
- data/test/dummy/app/models/editor.rb +0 -4
- data/test/dummy/app/models/editorship.rb +0 -5
- data/test/dummy/app/models/elephant.rb +0 -3
- data/test/dummy/app/models/fluxor.rb +0 -3
- data/test/dummy/app/models/foo_habtm.rb +0 -5
- data/test/dummy/app/models/foo_widget.rb +0 -2
- data/test/dummy/app/models/fruit.rb +0 -5
- data/test/dummy/app/models/gadget.rb +0 -3
- data/test/dummy/app/models/kitchen/banana.rb +0 -5
- data/test/dummy/app/models/legacy_widget.rb +0 -4
- data/test/dummy/app/models/line_item.rb +0 -4
- data/test/dummy/app/models/not_on_update.rb +0 -4
- data/test/dummy/app/models/order.rb +0 -5
- data/test/dummy/app/models/paragraph.rb +0 -5
- data/test/dummy/app/models/person.rb +0 -38
- data/test/dummy/app/models/post.rb +0 -3
- data/test/dummy/app/models/post_with_status.rb +0 -8
- data/test/dummy/app/models/protected_widget.rb +0 -3
- data/test/dummy/app/models/quotation.rb +0 -5
- data/test/dummy/app/models/section.rb +0 -6
- data/test/dummy/app/models/skipper.rb +0 -6
- data/test/dummy/app/models/song.rb +0 -41
- data/test/dummy/app/models/thing.rb +0 -3
- data/test/dummy/app/models/translation.rb +0 -4
- data/test/dummy/app/models/truck.rb +0 -4
- data/test/dummy/app/models/vehicle.rb +0 -4
- data/test/dummy/app/models/whatchamajigger.rb +0 -4
- data/test/dummy/app/models/widget.rb +0 -16
- data/test/dummy/app/models/wotsit.rb +0 -8
- data/test/dummy/app/versions/custom_primary_key_record_version.rb +0 -3
- data/test/dummy/app/versions/joined_version.rb +0 -6
- data/test/dummy/app/versions/json_version.rb +0 -3
- data/test/dummy/app/versions/kitchen/banana_version.rb +0 -5
- data/test/dummy/app/versions/post_version.rb +0 -3
- data/test/dummy/app/views/layouts/application.html.erb +0 -14
- data/test/dummy/config.ru +0 -4
- data/test/dummy/config/application.rb +0 -80
- data/test/dummy/config/boot.rb +0 -10
- data/test/dummy/config/database.mysql.yml +0 -19
- data/test/dummy/config/database.postgres.yml +0 -15
- data/test/dummy/config/database.sqlite.yml +0 -15
- data/test/dummy/config/environment.rb +0 -5
- data/test/dummy/config/environments/development.rb +0 -41
- data/test/dummy/config/environments/production.rb +0 -74
- data/test/dummy/config/environments/test.rb +0 -51
- data/test/dummy/config/initializers/backtrace_silencers.rb +0 -9
- data/test/dummy/config/initializers/inflections.rb +0 -10
- data/test/dummy/config/initializers/mime_types.rb +0 -5
- data/test/dummy/config/initializers/paper_trail.rb +0 -9
- data/test/dummy/config/initializers/secret_token.rb +0 -9
- data/test/dummy/config/initializers/session_store.rb +0 -8
- data/test/dummy/config/locales/en.yml +0 -5
- data/test/dummy/config/routes.rb +0 -4
- data/test/dummy/db/migrate/20110208155312_set_up_test_tables.rb +0 -361
- data/test/dummy/db/schema.rb +0 -288
- data/test/dummy/script/rails +0 -8
- data/test/functional/controller_test.rb +0 -90
- data/test/functional/enabled_for_controller_test.rb +0 -28
- data/test/functional/modular_sinatra_test.rb +0 -46
- data/test/functional/sinatra_test.rb +0 -51
- data/test/functional/thread_safety_test.rb +0 -46
- data/test/test_helper.rb +0 -127
- data/test/time_travel_helper.rb +0 -1
- data/test/unit/associations_test.rb +0 -1016
- data/test/unit/cleaner_test.rb +0 -188
- data/test/unit/inheritance_column_test.rb +0 -43
- data/test/unit/model_test.rb +0 -1489
- data/test/unit/protected_attrs_test.rb +0 -52
- data/test/unit/serializer_test.rb +0 -119
- data/test/unit/serializers/json_test.rb +0 -95
- data/test/unit/serializers/mixin_json_test.rb +0 -37
- data/test/unit/serializers/mixin_yaml_test.rb +0 -53
- data/test/unit/serializers/yaml_test.rb +0 -54
- data/test/unit/timestamp_test.rb +0 -41
- data/test/unit/version_test.rb +0 -119
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module PaperTrail
|
2
4
|
# Represents the history of a single record.
|
3
5
|
# @api private
|
@@ -26,7 +28,7 @@ module PaperTrail
|
|
26
28
|
@versions.select(primary_key).order(primary_key.asc)
|
27
29
|
else
|
28
30
|
@versions.
|
29
|
-
select([
|
31
|
+
select([table[:created_at], primary_key]).
|
30
32
|
order(@version_class.timestamp_sort_order)
|
31
33
|
end
|
32
34
|
end
|
@@ -45,13 +47,5 @@ module PaperTrail
|
|
45
47
|
def table
|
46
48
|
@version_class.arel_table
|
47
49
|
end
|
48
|
-
|
49
|
-
# @return - Arel::Attribute - Attribute representing the timestamp column
|
50
|
-
# of the version table, usually named `created_at` (the rails convention)
|
51
|
-
# but not always.
|
52
|
-
# @api private
|
53
|
-
def timestamp
|
54
|
-
table[PaperTrail.timestamp_field]
|
55
|
-
end
|
56
50
|
end
|
57
51
|
end
|
@@ -1,42 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "paper_trail/events/create"
|
4
|
+
require "paper_trail/events/destroy"
|
5
|
+
require "paper_trail/events/update"
|
6
|
+
|
1
7
|
module PaperTrail
|
2
8
|
# Represents the "paper trail" for a single record.
|
3
9
|
class RecordTrail
|
10
|
+
RAILS_GTE_5_1 = ::ActiveRecord.gem_version >= ::Gem::Version.new("5.1.0.beta1")
|
11
|
+
|
4
12
|
def initialize(record)
|
5
13
|
@record = record
|
6
14
|
end
|
7
15
|
|
8
|
-
# Utility method for reifying. Anything executed inside the block will
|
9
|
-
# appear like a new record.
|
10
|
-
def appear_as_new_record
|
11
|
-
@record.instance_eval {
|
12
|
-
alias :old_new_record? :new_record?
|
13
|
-
alias :new_record? :present?
|
14
|
-
}
|
15
|
-
yield
|
16
|
-
@record.instance_eval { alias :new_record? :old_new_record? }
|
17
|
-
end
|
18
|
-
|
19
|
-
def attributes_before_change
|
20
|
-
changed = @record.changed_attributes.select { |k, _v|
|
21
|
-
@record.class.column_names.include?(k)
|
22
|
-
}
|
23
|
-
@record.attributes.merge(changed)
|
24
|
-
end
|
25
|
-
|
26
|
-
def changed_and_not_ignored
|
27
|
-
ignore = @record.paper_trail_options[:ignore].dup
|
28
|
-
# Remove Hash arguments and then evaluate whether the attributes (the
|
29
|
-
# keys of the hash) should also get pushed into the collection.
|
30
|
-
ignore.delete_if do |obj|
|
31
|
-
obj.is_a?(Hash) &&
|
32
|
-
obj.each { |attr, condition|
|
33
|
-
ignore << attr if condition.respond_to?(:call) && condition.call(@record)
|
34
|
-
}
|
35
|
-
end
|
36
|
-
skip = @record.paper_trail_options[:skip]
|
37
|
-
@record.changed - ignore - skip
|
38
|
-
end
|
39
|
-
|
40
16
|
# Invoked after rollbacks to ensure versions records are not created for
|
41
17
|
# changes that never actually took place. Optimization: Use lazy `reset`
|
42
18
|
# instead of eager `reload` because, in many use cases, the association will
|
@@ -51,43 +27,12 @@ module PaperTrail
|
|
51
27
|
@record.send("#{@record.class.version_association_name}=", nil)
|
52
28
|
end
|
53
29
|
|
54
|
-
#
|
55
|
-
# instance. A timestamp-only update (e.g. only `updated_at` changed) is
|
56
|
-
# considered notable unless an ignored attribute was also changed.
|
57
|
-
def changed_notably?
|
58
|
-
if ignored_attr_has_changed?
|
59
|
-
timestamps = @record.send(:timestamp_attributes_for_update_in_model).map(&:to_s)
|
60
|
-
(notably_changed - timestamps).any?
|
61
|
-
else
|
62
|
-
notably_changed.any?
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
30
|
+
# Is PT enabled for this particular record?
|
66
31
|
# @api private
|
67
|
-
def changes
|
68
|
-
notable_changes = @record.changes.delete_if { |k, _v|
|
69
|
-
!notably_changed.include?(k)
|
70
|
-
}
|
71
|
-
AttributeSerializers::ObjectChangesAttribute.
|
72
|
-
new(@record.class).
|
73
|
-
serialize(notable_changes)
|
74
|
-
notable_changes.to_hash
|
75
|
-
end
|
76
|
-
|
77
32
|
def enabled?
|
78
|
-
PaperTrail.enabled? &&
|
79
|
-
|
80
|
-
|
81
|
-
def enabled_for_model?
|
82
|
-
@record.class.paper_trail.enabled?
|
83
|
-
end
|
84
|
-
|
85
|
-
# An attributed is "ignored" if it is listed in the `:ignore` option
|
86
|
-
# and/or the `:skip` option. Returns true if an ignored attribute has
|
87
|
-
# changed.
|
88
|
-
def ignored_attr_has_changed?
|
89
|
-
ignored = @record.paper_trail_options[:ignore] + @record.paper_trail_options[:skip]
|
90
|
-
ignored.any? && (@record.changed & ignored).any?
|
33
|
+
PaperTrail.enabled? &&
|
34
|
+
PaperTrail.request.enabled? &&
|
35
|
+
PaperTrail.request.enabled_for_model?(@record.class)
|
91
36
|
end
|
92
37
|
|
93
38
|
# Returns true if this instance is the current, live one;
|
@@ -96,237 +41,143 @@ module PaperTrail
|
|
96
41
|
source_version.nil?
|
97
42
|
end
|
98
43
|
|
99
|
-
# @api private
|
100
|
-
def merge_metadata(data)
|
101
|
-
# First we merge the model-level metadata in `meta`.
|
102
|
-
@record.paper_trail_options[:meta].each do |k, v|
|
103
|
-
data[k] =
|
104
|
-
if v.respond_to?(:call)
|
105
|
-
v.call(@record)
|
106
|
-
elsif v.is_a?(Symbol) && @record.respond_to?(v, true)
|
107
|
-
# If it is an attribute that is changing in an existing object,
|
108
|
-
# be sure to grab the current version.
|
109
|
-
if @record.has_attribute?(v) &&
|
110
|
-
@record.send("#{v}_changed?".to_sym) &&
|
111
|
-
data[:event] != "create"
|
112
|
-
@record.send("#{v}_was".to_sym)
|
113
|
-
else
|
114
|
-
@record.send(v)
|
115
|
-
end
|
116
|
-
else
|
117
|
-
v
|
118
|
-
end
|
119
|
-
end
|
120
|
-
|
121
|
-
# Second we merge any extra data from the controller (if available).
|
122
|
-
data.merge(PaperTrail.controller_info || {})
|
123
|
-
end
|
124
|
-
|
125
44
|
# Returns the object (not a Version) as it became next.
|
126
45
|
# NOTE: if self (the item) was not reified from a version, i.e. it is the
|
127
46
|
# "live" item, we return nil. Perhaps we should return self instead?
|
128
47
|
def next_version
|
129
48
|
subsequent_version = source_version.next
|
130
49
|
subsequent_version ? subsequent_version.reify : @record.class.find(@record.id)
|
131
|
-
rescue # TODO: Rescue something more specific
|
50
|
+
rescue StandardError # TODO: Rescue something more specific
|
132
51
|
nil
|
133
52
|
end
|
134
53
|
|
135
|
-
def notably_changed
|
136
|
-
only = @record.paper_trail_options[:only].dup
|
137
|
-
# Remove Hash arguments and then evaluate whether the attributes (the
|
138
|
-
# keys of the hash) should also get pushed into the collection.
|
139
|
-
only.delete_if do |obj|
|
140
|
-
obj.is_a?(Hash) &&
|
141
|
-
obj.each { |attr, condition|
|
142
|
-
only << attr if condition.respond_to?(:call) && condition.call(@record)
|
143
|
-
}
|
144
|
-
end
|
145
|
-
only.empty? ? changed_and_not_ignored : (changed_and_not_ignored & only)
|
146
|
-
end
|
147
|
-
|
148
|
-
# Returns hash of attributes (with appropriate attributes serialized),
|
149
|
-
# omitting attributes to be skipped.
|
150
|
-
def object_attrs_for_paper_trail
|
151
|
-
attrs = attributes_before_change.except(*@record.paper_trail_options[:skip])
|
152
|
-
AttributeSerializers::ObjectAttribute.new(@record.class).serialize(attrs)
|
153
|
-
attrs
|
154
|
-
end
|
155
|
-
|
156
54
|
# Returns who put `@record` into its current state.
|
55
|
+
#
|
56
|
+
# @api public
|
157
57
|
def originator
|
158
58
|
(source_version || versions.last).try(:whodunnit)
|
159
59
|
end
|
160
60
|
|
161
61
|
# Returns the object (not a Version) as it was most recently.
|
62
|
+
#
|
63
|
+
# @api public
|
162
64
|
def previous_version
|
163
65
|
(source_version ? source_version.previous : versions.last).try(:reify)
|
164
66
|
end
|
165
67
|
|
166
68
|
def record_create
|
167
69
|
return unless enabled?
|
168
|
-
data = {
|
169
|
-
event: @record.paper_trail_event || "create",
|
170
|
-
whodunnit: PaperTrail.whodunnit
|
171
|
-
}
|
172
|
-
if @record.respond_to?(:updated_at)
|
173
|
-
data[PaperTrail.timestamp_field] = @record.updated_at
|
174
|
-
end
|
175
|
-
if record_object_changes? && changed_notably?
|
176
|
-
data[:object_changes] = recordable_object_changes
|
177
|
-
end
|
178
|
-
add_transaction_id_to(data)
|
179
|
-
versions_assoc = @record.send(@record.class.versions_association_name)
|
180
|
-
version = versions_assoc.create! merge_metadata(data)
|
181
|
-
update_transaction_id(version)
|
182
|
-
save_associations(version)
|
183
|
-
end
|
184
70
|
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
object: recordable_object,
|
192
|
-
whodunnit: PaperTrail.whodunnit
|
193
|
-
}
|
194
|
-
add_transaction_id_to(data)
|
195
|
-
version = @record.class.paper_trail.version_class.create(merge_metadata(data))
|
196
|
-
if version.errors.any?
|
197
|
-
log_version_errors(version, :destroy)
|
198
|
-
else
|
199
|
-
@record.send("#{@record.class.version_association_name}=", version)
|
200
|
-
@record.send(@record.class.versions_association_name).reset
|
201
|
-
update_transaction_id(version)
|
202
|
-
save_associations(version)
|
203
|
-
end
|
71
|
+
build_version_on_create(in_after_callback: true).tap do |version|
|
72
|
+
version.save!
|
73
|
+
# Because the version object was created using version_class.new instead
|
74
|
+
# of versions_assoc.build?, the association cache is unaware. So, we
|
75
|
+
# invalidate the `versions` association cache with `reset`.
|
76
|
+
versions.reset
|
204
77
|
end
|
205
78
|
end
|
206
79
|
|
207
|
-
#
|
208
|
-
#
|
80
|
+
# PT-AT extends this method to add its transaction id.
|
81
|
+
#
|
209
82
|
# @api private
|
210
|
-
def
|
211
|
-
|
212
|
-
@record.class.paper_trail.version_class.column_names.include?("object_changes")
|
83
|
+
def data_for_create
|
84
|
+
{}
|
213
85
|
end
|
214
86
|
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
save_associations(version)
|
236
|
-
end
|
87
|
+
# `recording_order` is "after" or "before". See ModelConfig#on_destroy.
|
88
|
+
#
|
89
|
+
# @api private
|
90
|
+
# @return - The created version object, so that plugins can use it, e.g.
|
91
|
+
# paper_trail-association_tracking
|
92
|
+
def record_destroy(recording_order)
|
93
|
+
return unless enabled? && !@record.new_record?
|
94
|
+
in_after_callback = recording_order == "after"
|
95
|
+
event = Events::Destroy.new(@record, in_after_callback)
|
96
|
+
|
97
|
+
# Merge data from `Event` with data from PT-AT. We no longer use
|
98
|
+
# `data_for_destroy` but PT-AT still does.
|
99
|
+
data = event.data.merge(data_for_destroy)
|
100
|
+
|
101
|
+
version = @record.class.paper_trail.version_class.create(data)
|
102
|
+
if version.errors.any?
|
103
|
+
log_version_errors(version, :destroy)
|
104
|
+
else
|
105
|
+
assign_and_reset_version_association(version)
|
106
|
+
version
|
237
107
|
end
|
238
108
|
end
|
239
109
|
|
240
|
-
#
|
241
|
-
#
|
242
|
-
# column, then a hash can be used in the assignment, otherwise the column
|
243
|
-
# is a `text` column, and we must perform the serialization here, using
|
244
|
-
# `PaperTrail.serializer`.
|
110
|
+
# PT-AT extends this method to add its transaction id.
|
111
|
+
#
|
245
112
|
# @api private
|
246
|
-
def
|
247
|
-
|
248
|
-
|
113
|
+
def data_for_destroy
|
114
|
+
{}
|
115
|
+
end
|
116
|
+
|
117
|
+
# @api private
|
118
|
+
# @return - The created version object, so that plugins can use it, e.g.
|
119
|
+
# paper_trail-association_tracking
|
120
|
+
def record_update(force:, in_after_callback:, is_touch:)
|
121
|
+
return unless enabled?
|
122
|
+
|
123
|
+
version = build_version_on_update(
|
124
|
+
force: force,
|
125
|
+
in_after_callback: in_after_callback,
|
126
|
+
is_touch: is_touch
|
127
|
+
)
|
128
|
+
return unless version
|
129
|
+
|
130
|
+
if version.save
|
131
|
+
# Because the version object was created using version_class.new instead
|
132
|
+
# of versions_assoc.build?, the association cache is unaware. So, we
|
133
|
+
# invalidate the `versions` association cache with `reset`.
|
134
|
+
versions.reset
|
135
|
+
version
|
249
136
|
else
|
250
|
-
|
137
|
+
log_version_errors(version, :update)
|
251
138
|
end
|
252
139
|
end
|
253
140
|
|
254
|
-
#
|
255
|
-
#
|
256
|
-
# a postgres `json` column, then a hash can be used in the assignment,
|
257
|
-
# otherwise the column is a `text` column, and we must perform the
|
258
|
-
# serialization here, using `PaperTrail.serializer`.
|
141
|
+
# PT-AT extends this method to add its transaction id.
|
142
|
+
#
|
259
143
|
# @api private
|
260
|
-
def
|
261
|
-
|
262
|
-
|
144
|
+
def data_for_update
|
145
|
+
{}
|
146
|
+
end
|
147
|
+
|
148
|
+
# @api private
|
149
|
+
# @return - The created version object, so that plugins can use it, e.g.
|
150
|
+
# paper_trail-association_tracking
|
151
|
+
def record_update_columns(changes)
|
152
|
+
return unless enabled?
|
153
|
+
event = Events::Update.new(@record, false, false, changes)
|
154
|
+
|
155
|
+
# Merge data from `Event` with data from PT-AT. We no longer use
|
156
|
+
# `data_for_update_columns` but PT-AT still does.
|
157
|
+
data = event.data.merge(data_for_update_columns)
|
158
|
+
|
159
|
+
versions_assoc = @record.send(@record.class.versions_association_name)
|
160
|
+
version = versions_assoc.create(data)
|
161
|
+
if version.errors.any?
|
162
|
+
log_version_errors(version, :update)
|
263
163
|
else
|
264
|
-
|
164
|
+
version
|
265
165
|
end
|
266
166
|
end
|
267
167
|
|
168
|
+
# PT-AT extends this method to add its transaction id.
|
169
|
+
#
|
170
|
+
# @api private
|
171
|
+
def data_for_update_columns
|
172
|
+
{}
|
173
|
+
end
|
174
|
+
|
268
175
|
# Invoked via callback when a user attempts to persist a reified
|
269
176
|
# `Version`.
|
270
177
|
def reset_timestamp_attrs_for_update_if_needed
|
271
178
|
return if live?
|
272
179
|
@record.send(:timestamp_attributes_for_update_in_model).each do |column|
|
273
|
-
#
|
274
|
-
# `restore_column!`.
|
275
|
-
if @record.respond_to?("restore_#{column}!")
|
276
|
-
@record.send("restore_#{column}!")
|
277
|
-
else
|
278
|
-
@record.send("reset_#{column}!")
|
279
|
-
end
|
280
|
-
end
|
281
|
-
end
|
282
|
-
|
283
|
-
# Saves associations if the join table for `VersionAssociation` exists.
|
284
|
-
def save_associations(version)
|
285
|
-
return unless PaperTrail.config.track_associations?
|
286
|
-
save_associations_belongs_to(version)
|
287
|
-
save_associations_habtm(version)
|
288
|
-
end
|
289
|
-
|
290
|
-
def save_associations_belongs_to(version)
|
291
|
-
@record.class.reflect_on_all_associations(:belongs_to).each do |assoc|
|
292
|
-
assoc_version_args = {
|
293
|
-
version_id: version.id,
|
294
|
-
foreign_key_name: assoc.foreign_key
|
295
|
-
}
|
296
|
-
|
297
|
-
if assoc.options[:polymorphic]
|
298
|
-
associated_record = @record.send(assoc.name) if @record.send(assoc.foreign_type)
|
299
|
-
if associated_record && associated_record.class.paper_trail.enabled?
|
300
|
-
assoc_version_args[:foreign_key_id] = associated_record.id
|
301
|
-
end
|
302
|
-
elsif assoc.klass.paper_trail.enabled?
|
303
|
-
assoc_version_args[:foreign_key_id] = @record.send(assoc.foreign_key)
|
304
|
-
end
|
305
|
-
|
306
|
-
if assoc_version_args.key?(:foreign_key_id)
|
307
|
-
PaperTrail::VersionAssociation.create(assoc_version_args)
|
308
|
-
end
|
309
|
-
end
|
310
|
-
end
|
311
|
-
|
312
|
-
def save_associations_habtm(version)
|
313
|
-
# Use the :added and :removed keys to extrapolate the HABTM associations
|
314
|
-
# to before any changes were made
|
315
|
-
@record.class.reflect_on_all_associations(:has_and_belongs_to_many).each do |a|
|
316
|
-
next unless
|
317
|
-
@record.class.paper_trail_save_join_tables.include?(a.name) ||
|
318
|
-
a.klass.paper_trail.enabled?
|
319
|
-
assoc_version_args = {
|
320
|
-
version_id: version.transaction_id,
|
321
|
-
foreign_key_name: a.name
|
322
|
-
}
|
323
|
-
assoc_ids =
|
324
|
-
@record.send(a.name).to_a.map(&:id) +
|
325
|
-
(@record.paper_trail_habtm.try(:[], a.name).try(:[], :removed) || []) -
|
326
|
-
(@record.paper_trail_habtm.try(:[], a.name).try(:[], :added) || [])
|
327
|
-
assoc_ids.each do |id|
|
328
|
-
PaperTrail::VersionAssociation.create(assoc_version_args.merge(foreign_key_id: id))
|
329
|
-
end
|
180
|
+
@record.send("restore_#{column}!")
|
330
181
|
end
|
331
182
|
end
|
332
183
|
|
@@ -342,26 +193,41 @@ module PaperTrail
|
|
342
193
|
version
|
343
194
|
end
|
344
195
|
|
345
|
-
#
|
346
|
-
#
|
347
|
-
# `:on`, `:if`, or `:unless`.
|
196
|
+
# Save, and create a version record regardless of options such as `:on`,
|
197
|
+
# `:if`, or `:unless`.
|
348
198
|
#
|
349
|
-
#
|
350
|
-
#
|
351
|
-
#
|
352
|
-
#
|
353
|
-
def
|
354
|
-
|
355
|
-
|
199
|
+
# Arguments are passed to `save`.
|
200
|
+
#
|
201
|
+
# This is an "update" event. That is, we record the same data we would in
|
202
|
+
# the case of a normal AR `update`.
|
203
|
+
def save_with_version(*args)
|
204
|
+
::PaperTrail.request(enabled: false) do
|
205
|
+
@record.save(*args)
|
206
|
+
end
|
207
|
+
record_update(force: true, in_after_callback: false, is_touch: false)
|
208
|
+
end
|
209
|
+
|
210
|
+
# Like the `update_column` method from `ActiveRecord::Persistence`, but also
|
211
|
+
# creates a version to record those changes.
|
212
|
+
# @api public
|
213
|
+
def update_column(name, value)
|
214
|
+
update_columns(name => value)
|
215
|
+
end
|
216
|
+
|
217
|
+
# Like the `update_columns` method from `ActiveRecord::Persistence`, but also
|
218
|
+
# creates a version to record those changes.
|
219
|
+
# @api public
|
220
|
+
def update_columns(attributes)
|
221
|
+
# `@record.update_columns` skips dirty-tracking, so we can't just use
|
222
|
+
# `@record.changes` or @record.saved_changes` from `ActiveModel::Dirty`.
|
223
|
+
# We need to build our own hash with the changes that will be made
|
224
|
+
# directly to the database.
|
225
|
+
changes = {}
|
226
|
+
attributes.each do |k, v|
|
227
|
+
changes[k] = [@record[k], v]
|
356
228
|
end
|
357
|
-
|
358
|
-
|
359
|
-
current_time = @record.send :current_time_from_proper_timezone
|
360
|
-
attributes.each { |column|
|
361
|
-
@record.send(:write_attribute, column, current_time)
|
362
|
-
}
|
363
|
-
@record.record_update(true) unless will_record_after_update?
|
364
|
-
@record.save!(validate: false)
|
229
|
+
@record.update_columns(attributes)
|
230
|
+
record_update_columns(changes)
|
365
231
|
end
|
366
232
|
|
367
233
|
# Returns the object (not a Version) as it was at the given timestamp.
|
@@ -376,69 +242,53 @@ module PaperTrail
|
|
376
242
|
# Returns the objects (not Versions) as they were between the given times.
|
377
243
|
def versions_between(start_time, end_time)
|
378
244
|
versions = send(@record.class.versions_association_name).between(start_time, end_time)
|
379
|
-
versions.collect { |version|
|
380
|
-
version_at(version.send(PaperTrail.timestamp_field))
|
381
|
-
}
|
245
|
+
versions.collect { |version| version_at(version.created_at) }
|
382
246
|
end
|
383
247
|
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
public_send(method)
|
391
|
-
else
|
392
|
-
@record.send(method)
|
393
|
-
end
|
394
|
-
else
|
395
|
-
yield @record
|
396
|
-
end
|
397
|
-
ensure
|
398
|
-
@record.class.paper_trail.enable if paper_trail_was_enabled
|
248
|
+
private
|
249
|
+
|
250
|
+
# @api private
|
251
|
+
def assign_and_reset_version_association(version)
|
252
|
+
@record.send("#{@record.class.version_association_name}=", version)
|
253
|
+
@record.send(@record.class.versions_association_name).reset
|
399
254
|
end
|
400
255
|
|
401
|
-
#
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
256
|
+
# @api private
|
257
|
+
def build_version_on_create(in_after_callback:)
|
258
|
+
event = Events::Create.new(@record, in_after_callback)
|
259
|
+
|
260
|
+
# Merge data from `Event` with data from PT-AT. We no longer use
|
261
|
+
# `data_for_create` but PT-AT still does.
|
262
|
+
data = event.data.merge!(data_for_create)
|
263
|
+
|
264
|
+
# Pure `version_class.new` reduces memory usage compared to `versions_assoc.build`
|
265
|
+
@record.class.paper_trail.version_class.new(data)
|
410
266
|
end
|
411
267
|
|
412
|
-
private
|
268
|
+
# @api private
|
269
|
+
def build_version_on_update(force:, in_after_callback:, is_touch:)
|
270
|
+
event = Events::Update.new(@record, in_after_callback, is_touch, nil)
|
271
|
+
return unless force || event.changed_notably?
|
272
|
+
|
273
|
+
# Merge data from `Event` with data from PT-AT. We no longer use
|
274
|
+
# `data_for_update` but PT-AT still does. To save memory, we use `merge!`
|
275
|
+
# instead of `merge`.
|
276
|
+
data = event.data.merge!(data_for_update)
|
413
277
|
|
414
|
-
|
415
|
-
|
416
|
-
|
278
|
+
# Using `version_class.new` reduces memory usage compared to
|
279
|
+
# `versions_assoc.build`. It's a trade-off though. We have to clear
|
280
|
+
# the association cache (see `versions.reset`) and that could cause an
|
281
|
+
# additional query in certain applications.
|
282
|
+
@record.class.paper_trail.version_class.new(data)
|
417
283
|
end
|
418
284
|
|
419
285
|
def log_version_errors(version, action)
|
420
|
-
version.logger
|
421
|
-
"Unable to create version for #{action} of #{@record.class.name}"
|
286
|
+
version.logger&.warn(
|
287
|
+
"Unable to create version for #{action} of #{@record.class.name}" \
|
422
288
|
"##{@record.id}: " + version.errors.full_messages.join(", ")
|
423
289
|
)
|
424
290
|
end
|
425
291
|
|
426
|
-
# Returns true if `save` will cause `record_update`
|
427
|
-
# to be called via the `after_update` callback.
|
428
|
-
def will_record_after_update?
|
429
|
-
on = @record.paper_trail_options[:on]
|
430
|
-
on.nil? || on.include?(:update)
|
431
|
-
end
|
432
|
-
|
433
|
-
def update_transaction_id(version)
|
434
|
-
return unless @record.class.paper_trail.version_class.column_names.include?("transaction_id")
|
435
|
-
if PaperTrail.transaction? && PaperTrail.transaction_id.nil?
|
436
|
-
PaperTrail.transaction_id = version.id
|
437
|
-
version.transaction_id = version.id
|
438
|
-
version.save
|
439
|
-
end
|
440
|
-
end
|
441
|
-
|
442
292
|
def version
|
443
293
|
@record.public_send(@record.class.version_association_name)
|
444
294
|
end
|