paper_trail 4.0.0 → 5.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/CONTRIBUTING.md +105 -0
- data/.github/ISSUE_TEMPLATE.md +13 -0
- data/.gitignore +2 -0
- data/.rubocop.yml +100 -0
- data/.rubocop_todo.yml +14 -0
- data/.travis.yml +11 -10
- data/Appraisals +37 -0
- data/CHANGELOG.md +173 -8
- data/Gemfile +1 -1
- data/README.md +641 -470
- data/Rakefile +19 -19
- data/doc/bug_report_template.rb +71 -0
- data/doc/warning_about_not_setting_whodunnit.md +32 -0
- data/gemfiles/ar3.gemfile +18 -0
- data/gemfiles/ar4.gemfile +7 -0
- data/gemfiles/ar5.gemfile +13 -0
- data/lib/generators/paper_trail/install_generator.rb +26 -18
- data/lib/generators/paper_trail/templates/add_object_changes_to_versions.rb +3 -1
- data/lib/generators/paper_trail/templates/add_transaction_id_column_to_versions.rb +2 -0
- data/lib/generators/paper_trail/templates/create_version_associations.rb +9 -4
- data/lib/generators/paper_trail/templates/create_versions.rb +53 -5
- data/lib/paper_trail/attribute_serializers/README.md +10 -0
- data/lib/paper_trail/attribute_serializers/cast_attribute_serializer.rb +58 -0
- data/lib/paper_trail/attribute_serializers/legacy_active_record_shim.rb +48 -0
- data/lib/paper_trail/attribute_serializers/object_attribute.rb +39 -0
- data/lib/paper_trail/attribute_serializers/object_changes_attribute.rb +42 -0
- data/lib/paper_trail/cleaner.rb +41 -18
- data/lib/paper_trail/config.rb +42 -26
- data/lib/paper_trail/frameworks/active_record/models/paper_trail/version.rb +5 -1
- data/lib/paper_trail/frameworks/active_record/models/paper_trail/version_association.rb +6 -2
- data/lib/paper_trail/frameworks/active_record.rb +2 -2
- data/lib/paper_trail/frameworks/cucumber.rb +1 -0
- data/lib/paper_trail/frameworks/rails/controller.rb +50 -14
- data/lib/paper_trail/frameworks/rails/engine.rb +6 -1
- data/lib/paper_trail/frameworks/rails.rb +2 -7
- data/lib/paper_trail/frameworks/rspec/helpers.rb +3 -1
- data/lib/paper_trail/frameworks/rspec.rb +5 -5
- data/lib/paper_trail/frameworks/sinatra.rb +8 -5
- data/lib/paper_trail/has_paper_trail.rb +381 -221
- data/lib/paper_trail/record_history.rb +57 -0
- data/lib/paper_trail/reifier.rb +450 -0
- data/lib/paper_trail/serializers/json.rb +7 -7
- data/lib/paper_trail/serializers/yaml.rb +31 -12
- data/lib/paper_trail/version_association_concern.rb +6 -2
- data/lib/paper_trail/version_concern.rb +200 -287
- data/lib/paper_trail/version_number.rb +6 -9
- data/lib/paper_trail.rb +169 -137
- data/paper_trail.gemspec +41 -43
- data/spec/generators/install_generator_spec.rb +24 -25
- data/spec/generators/paper_trail/templates/create_versions_spec.rb +51 -0
- data/spec/models/animal_spec.rb +23 -6
- data/spec/models/boolit_spec.rb +8 -8
- data/spec/models/callback_modifier_spec.rb +96 -0
- data/spec/models/car_spec.rb +13 -0
- data/spec/models/fluxor_spec.rb +3 -3
- data/spec/models/gadget_spec.rb +19 -19
- data/spec/models/joined_version_spec.rb +3 -3
- data/spec/models/json_version_spec.rb +50 -28
- data/spec/models/kitchen/banana_spec.rb +3 -3
- data/spec/models/not_on_update_spec.rb +7 -4
- data/spec/models/post_with_status_spec.rb +13 -3
- data/spec/models/skipper_spec.rb +40 -11
- data/spec/models/thing_spec.rb +4 -4
- data/spec/models/truck_spec.rb +5 -0
- data/spec/models/vehicle_spec.rb +5 -0
- data/spec/models/version_spec.rb +103 -59
- data/spec/models/widget_spec.rb +86 -55
- data/spec/modules/paper_trail_spec.rb +2 -2
- data/spec/modules/version_concern_spec.rb +11 -12
- data/spec/modules/version_number_spec.rb +3 -4
- data/spec/paper_trail/config_spec.rb +33 -0
- data/spec/paper_trail_spec.rb +16 -14
- data/spec/rails_helper.rb +10 -9
- data/spec/requests/articles_spec.rb +11 -7
- data/spec/spec_helper.rb +42 -17
- data/spec/support/alt_db_init.rb +8 -13
- data/test/custom_json_serializer.rb +3 -3
- data/test/dummy/Rakefile +2 -2
- data/test/dummy/app/controllers/application_controller.rb +21 -8
- data/test/dummy/app/controllers/articles_controller.rb +11 -8
- data/test/dummy/app/controllers/widgets_controller.rb +13 -12
- data/test/dummy/app/models/animal.rb +1 -1
- data/test/dummy/app/models/article.rb +19 -11
- data/test/dummy/app/models/authorship.rb +1 -1
- data/test/dummy/app/models/bar_habtm.rb +4 -0
- data/test/dummy/app/models/book.rb +4 -4
- data/test/dummy/app/models/boolit.rb +1 -1
- data/test/dummy/app/models/callback_modifier.rb +45 -0
- data/test/dummy/app/models/car.rb +3 -0
- data/test/dummy/app/models/chapter.rb +9 -0
- data/test/dummy/app/models/citation.rb +5 -0
- data/test/dummy/app/models/customer.rb +1 -1
- data/test/dummy/app/models/document.rb +2 -2
- data/test/dummy/app/models/editor.rb +1 -1
- data/test/dummy/app/models/foo_habtm.rb +5 -0
- data/test/dummy/app/models/fruit.rb +2 -2
- data/test/dummy/app/models/gadget.rb +1 -1
- data/test/dummy/app/models/kitchen/banana.rb +1 -1
- data/test/dummy/app/models/legacy_widget.rb +2 -2
- data/test/dummy/app/models/line_item.rb +1 -1
- data/test/dummy/app/models/not_on_update.rb +1 -1
- data/test/dummy/app/models/paragraph.rb +5 -0
- data/test/dummy/app/models/person.rb +6 -6
- data/test/dummy/app/models/post.rb +1 -1
- data/test/dummy/app/models/post_with_status.rb +1 -1
- data/test/dummy/app/models/quotation.rb +5 -0
- data/test/dummy/app/models/section.rb +6 -0
- data/test/dummy/app/models/skipper.rb +2 -2
- data/test/dummy/app/models/song.rb +13 -4
- data/test/dummy/app/models/thing.rb +2 -2
- data/test/dummy/app/models/translation.rb +2 -2
- data/test/dummy/app/models/truck.rb +4 -0
- data/test/dummy/app/models/vehicle.rb +4 -0
- data/test/dummy/app/models/whatchamajigger.rb +1 -1
- data/test/dummy/app/models/widget.rb +7 -6
- data/test/dummy/app/versions/joined_version.rb +4 -3
- data/test/dummy/app/versions/json_version.rb +1 -1
- data/test/dummy/app/versions/kitchen/banana_version.rb +1 -1
- data/test/dummy/app/versions/post_version.rb +2 -2
- data/test/dummy/config/application.rb +20 -9
- data/test/dummy/config/boot.rb +5 -5
- data/test/dummy/config/database.postgres.yml +1 -1
- data/test/dummy/config/environment.rb +1 -1
- data/test/dummy/config/environments/development.rb +4 -3
- data/test/dummy/config/environments/production.rb +3 -2
- data/test/dummy/config/environments/test.rb +15 -5
- data/test/dummy/config/initializers/backtrace_silencers.rb +4 -2
- data/test/dummy/config/initializers/paper_trail.rb +4 -3
- data/test/dummy/config/initializers/secret_token.rb +3 -1
- data/test/dummy/config/initializers/session_store.rb +1 -1
- data/test/dummy/config/routes.rb +2 -2
- data/test/dummy/config.ru +1 -1
- data/test/dummy/db/migrate/20110208155312_set_up_test_tables.rb +148 -68
- data/test/dummy/db/schema.rb +119 -31
- data/test/dummy/script/rails +6 -4
- data/test/functional/controller_test.rb +34 -35
- data/test/functional/enabled_for_controller_test.rb +6 -7
- data/test/functional/modular_sinatra_test.rb +43 -38
- data/test/functional/sinatra_test.rb +49 -40
- data/test/functional/thread_safety_test.rb +4 -6
- data/test/paper_trail_test.rb +15 -14
- data/test/test_helper.rb +78 -18
- data/test/time_travel_helper.rb +1 -15
- data/test/unit/associations_test.rb +1016 -0
- data/test/unit/cleaner_test.rb +66 -60
- data/test/unit/inheritance_column_test.rb +19 -19
- data/test/unit/model_test.rb +646 -1071
- data/test/unit/protected_attrs_test.rb +19 -14
- data/test/unit/serializer_test.rb +44 -43
- data/test/unit/serializers/json_test.rb +28 -21
- data/test/unit/serializers/mixin_json_test.rb +15 -14
- data/test/unit/serializers/mixin_yaml_test.rb +20 -16
- data/test/unit/serializers/yaml_test.rb +16 -14
- data/test/unit/timestamp_test.rb +10 -12
- data/test/unit/version_test.rb +88 -70
- metadata +166 -72
- data/gemfiles/3.0.gemfile +0 -52
@@ -1,169 +1,236 @@
|
|
1
|
-
require
|
1
|
+
require "active_support/core_ext/object" # provides the `try` method
|
2
|
+
require "paper_trail/attribute_serializers/legacy_active_record_shim"
|
3
|
+
require "paper_trail/attribute_serializers/object_attribute"
|
4
|
+
require "paper_trail/attribute_serializers/object_changes_attribute"
|
2
5
|
|
3
6
|
module PaperTrail
|
7
|
+
# Extensions to `ActiveRecord::Base`. See `frameworks/active_record.rb`.
|
4
8
|
module Model
|
5
|
-
|
6
9
|
def self.included(base)
|
7
10
|
base.send :extend, ClassMethods
|
11
|
+
base.send :attr_accessor, :paper_trail_habtm
|
8
12
|
end
|
9
13
|
|
14
|
+
# :nodoc:
|
10
15
|
module ClassMethods
|
11
|
-
# Declare this in your model to track every create, update, and destroy.
|
12
|
-
# the model is available in the `versions` association.
|
16
|
+
# Declare this in your model to track every create, update, and destroy.
|
17
|
+
# Each version of the model is available in the `versions` association.
|
13
18
|
#
|
14
19
|
# Options:
|
15
|
-
# :on the events to track (optional; defaults to all of them). Set to an array of
|
16
|
-
# `:create`, `:update`, `:destroy` as desired.
|
17
|
-
# :class_name the name of a custom Version class. This class should inherit from `PaperTrail::Version`.
|
18
|
-
# :ignore an array of attributes for which a new `Version` will not be created if only they change.
|
19
|
-
# it can also aceept a Hash as an argument where the key is the attribute to ignore (a `String` or `Symbol`),
|
20
|
-
# which will only be ignored if the value is a `Proc` which returns truthily.
|
21
|
-
# :if, :unless Procs that allow to specify conditions when to save versions for an object
|
22
|
-
# :only inverse of `ignore` - a new `Version` will be created only for these attributes if supplied
|
23
|
-
# it can also aceept a Hash as an argument where the key is the attribute to track (a `String` or `Symbol`),
|
24
|
-
# which will only be counted if the value is a `Proc` which returns truthily.
|
25
|
-
# :skip fields to ignore completely. As with `ignore`, updates to these fields will not create
|
26
|
-
# a new `Version`. In addition, these fields will not be included in the serialized versions
|
27
|
-
# of the object whenever a new `Version` is created.
|
28
|
-
# :meta a hash of extra data to store. You must add a column to the `versions` table for each key.
|
29
|
-
# Values are objects or procs (which are called with `self`, i.e. the model with the paper
|
30
|
-
# trail). See `PaperTrail::Controller.info_for_paper_trail` for how to store data from
|
31
|
-
# the controller.
|
32
|
-
# :versions the name to use for the versions association. Default is `:versions`.
|
33
|
-
# :version the name to use for the method which returns the version the instance was reified from.
|
34
|
-
# Default is `:version`.
|
35
|
-
# :save_changes whether or not to save changes to the object_changes column if it exists. Default is true
|
36
20
|
#
|
21
|
+
# - :on - The events to track (optional; defaults to all of them). Set
|
22
|
+
# to an array of `:create`, `:update`, `:destroy` as desired.
|
23
|
+
# - :class_name - The name of a custom Version class. This class should
|
24
|
+
# inherit from `PaperTrail::Version`.
|
25
|
+
# - :ignore - An array of attributes for which a new `Version` will not be
|
26
|
+
# created if only they change. It can also aceept a Hash as an
|
27
|
+
# argument where the key is the attribute to ignore (a `String` or
|
28
|
+
# `Symbol`), which will only be ignored if the value is a `Proc` which
|
29
|
+
# returns truthily.
|
30
|
+
# - :if, :unless - Procs that allow to specify conditions when to save
|
31
|
+
# versions for an object.
|
32
|
+
# - :only - Inverse of `ignore`. A new `Version` will be created only
|
33
|
+
# for these attributes if supplied it can also aceept a Hash as an
|
34
|
+
# argument where the key is the attribute to track (a `String` or
|
35
|
+
# `Symbol`), which will only be counted if the value is a `Proc` which
|
36
|
+
# returns truthily.
|
37
|
+
# - :skip - Fields to ignore completely. As with `ignore`, updates to
|
38
|
+
# these fields will not create a new `Version`. In addition, these
|
39
|
+
# fields will not be included in the serialized versions of the object
|
40
|
+
# whenever a new `Version` is created.
|
41
|
+
# - :meta - A hash of extra data to store. You must add a column to the
|
42
|
+
# `versions` table for each key. Values are objects or procs (which
|
43
|
+
# are called with `self`, i.e. the model with the paper trail). See
|
44
|
+
# `PaperTrail::Controller.info_for_paper_trail` for how to store data
|
45
|
+
# from the controller.
|
46
|
+
# - :versions - The name to use for the versions association. Default
|
47
|
+
# is `:versions`.
|
48
|
+
# - :version - The name to use for the method which returns the version
|
49
|
+
# the instance was reified from. Default is `:version`.
|
50
|
+
# - :save_changes - Whether or not to save changes to the object_changes
|
51
|
+
# column if it exists. Default is true
|
52
|
+
# - :join_tables - If the model has a has_and_belongs_to_many relation
|
53
|
+
# with an unpapertrailed model, passing the name of the association to
|
54
|
+
# the join_tables option will paper trail the join table but not save
|
55
|
+
# the other model, allowing reification of the association but with the
|
56
|
+
# other models latest state (if the other model is paper trailed, this
|
57
|
+
# option does nothing)
|
58
|
+
#
|
59
|
+
# @api public
|
37
60
|
def has_paper_trail(options = {})
|
61
|
+
options[:on] ||= [:create, :update, :destroy]
|
62
|
+
|
63
|
+
# Wrap the :on option in an array if necessary. This allows a single
|
64
|
+
# symbol to be passed in.
|
65
|
+
options[:on] = Array(options[:on])
|
66
|
+
|
67
|
+
setup_model_for_paper_trail(options)
|
68
|
+
|
69
|
+
setup_callbacks_from_options options[:on]
|
70
|
+
|
71
|
+
setup_callbacks_for_habtm options[:join_tables]
|
72
|
+
end
|
73
|
+
|
74
|
+
def update_for_callback(name, callback, model, assoc)
|
75
|
+
model.paper_trail_habtm ||= {}
|
76
|
+
model.paper_trail_habtm.reverse_merge!(name => { removed: [], added: [] })
|
77
|
+
case callback
|
78
|
+
when :before_add
|
79
|
+
model.paper_trail_habtm[name][:added] |= [assoc.id]
|
80
|
+
model.paper_trail_habtm[name][:removed] -= [assoc.id]
|
81
|
+
when :before_remove
|
82
|
+
model.paper_trail_habtm[name][:removed] |= [assoc.id]
|
83
|
+
model.paper_trail_habtm[name][:added] -= [assoc.id]
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
attr_reader :paper_trail_save_join_tables
|
88
|
+
|
89
|
+
def setup_callbacks_for_habtm(join_tables)
|
90
|
+
@paper_trail_save_join_tables = Array.wrap(join_tables)
|
91
|
+
# Adds callbacks to record changes to habtm associations such that on
|
92
|
+
# save the previous version of the association (if changed) can be
|
93
|
+
# interpreted
|
94
|
+
reflect_on_all_associations(:has_and_belongs_to_many).
|
95
|
+
reject { |a| paper_trail_options[:skip].include?(a.name.to_s) }.
|
96
|
+
each do |a|
|
97
|
+
added_callback = lambda do |*args|
|
98
|
+
update_for_callback(a.name, :before_add, args[-2], args.last)
|
99
|
+
end
|
100
|
+
removed_callback = lambda do |*args|
|
101
|
+
update_for_callback(a.name, :before_remove, args[-2], args.last)
|
102
|
+
end
|
103
|
+
send(:"before_add_for_#{a.name}").send(:<<, added_callback)
|
104
|
+
send(:"before_remove_for_#{a.name}").send(:<<, removed_callback)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
# Installs callbacks, associations, "class attributes", and more.
|
109
|
+
# For details of how "class attributes" work, see the activesupport docs.
|
110
|
+
# @api private
|
111
|
+
def setup_model_for_paper_trail(options = {})
|
38
112
|
# Lazily include the instance methods so we don't clutter up
|
39
113
|
# any more ActiveRecord models than we have to.
|
40
114
|
send :include, InstanceMethods
|
41
115
|
|
116
|
+
if ::ActiveRecord::VERSION::STRING < "4.2"
|
117
|
+
send :extend, AttributeSerializers::LegacyActiveRecordShim
|
118
|
+
end
|
119
|
+
|
42
120
|
class_attribute :version_association_name
|
43
121
|
self.version_association_name = options[:version] || :version
|
44
122
|
|
45
123
|
# The version this instance was reified from.
|
46
|
-
attr_accessor
|
124
|
+
attr_accessor version_association_name
|
47
125
|
|
48
126
|
class_attribute :version_class_name
|
49
|
-
self.version_class_name = options[:class_name] ||
|
127
|
+
self.version_class_name = options[:class_name] || "PaperTrail::Version"
|
50
128
|
|
51
|
-
|
52
|
-
self.paper_trail_options = options.dup
|
53
|
-
|
54
|
-
[:ignore, :skip, :only].each do |k|
|
55
|
-
paper_trail_options[k] =
|
56
|
-
[paper_trail_options[k]].flatten.compact.map { |attr| attr.is_a?(Hash) ? attr.stringify_keys : attr.to_s }
|
57
|
-
end
|
58
|
-
|
59
|
-
paper_trail_options[:meta] ||= {}
|
60
|
-
paper_trail_options[:save_changes] = true if paper_trail_options[:save_changes].nil?
|
129
|
+
setup_paper_trail_options(options)
|
61
130
|
|
62
131
|
class_attribute :versions_association_name
|
63
132
|
self.versions_association_name = options[:versions] || :versions
|
64
133
|
|
65
134
|
attr_accessor :paper_trail_event
|
66
135
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
136
|
+
# `has_many` syntax for specifying order uses a lambda in Rails 4
|
137
|
+
if ::ActiveRecord::VERSION::MAJOR >= 4
|
138
|
+
has_many versions_association_name,
|
139
|
+
-> { order(model.timestamp_sort_order) },
|
140
|
+
class_name: version_class_name, as: :item
|
71
141
|
else
|
72
|
-
has_many
|
73
|
-
:
|
74
|
-
:
|
75
|
-
:
|
76
|
-
end
|
77
|
-
|
78
|
-
options[:on] ||= [:create, :update, :destroy]
|
79
|
-
options_on = Array(options[:on]) # so that a single symbol can be passed in without wrapping it in an `Array`
|
80
|
-
after_create :record_create, :if => :save_version? if options_on.include?(:create)
|
81
|
-
if options_on.include?(:update)
|
82
|
-
before_save :reset_timestamp_attrs_for_update_if_needed!, :on => :update
|
83
|
-
after_update :record_update, :if => :save_version?
|
84
|
-
after_update :clear_version_instance!
|
142
|
+
has_many versions_association_name,
|
143
|
+
class_name: version_class_name,
|
144
|
+
as: :item,
|
145
|
+
order: paper_trail_version_class.timestamp_sort_order
|
85
146
|
end
|
86
|
-
after_destroy :record_destroy, :if => :save_version? if options_on.include?(:destroy)
|
87
147
|
|
88
|
-
# Reset the transaction id when the transaction is closed
|
148
|
+
# Reset the transaction id when the transaction is closed.
|
89
149
|
after_commit :reset_transaction_id
|
90
150
|
after_rollback :reset_transaction_id
|
91
151
|
after_rollback :clear_rolled_back_versions
|
92
152
|
end
|
93
153
|
|
94
|
-
#
|
95
|
-
|
96
|
-
|
154
|
+
# Given `options`, populates `paper_trail_options`.
|
155
|
+
# @api private
|
156
|
+
def setup_paper_trail_options(options)
|
157
|
+
class_attribute :paper_trail_options
|
158
|
+
self.paper_trail_options = options.dup
|
159
|
+
[:ignore, :skip, :only].each do |k|
|
160
|
+
paper_trail_options[k] = [paper_trail_options[k]].flatten.compact.map { |attr|
|
161
|
+
attr.is_a?(Hash) ? attr.stringify_keys : attr.to_s
|
162
|
+
}
|
163
|
+
end
|
164
|
+
paper_trail_options[:meta] ||= {}
|
165
|
+
if paper_trail_options[:save_changes].nil?
|
166
|
+
paper_trail_options[:save_changes] = true
|
167
|
+
end
|
97
168
|
end
|
98
169
|
|
99
|
-
|
100
|
-
|
101
|
-
|
170
|
+
def setup_callbacks_from_options(options_on = [])
|
171
|
+
options_on.each do |option|
|
172
|
+
send "paper_trail_on_#{option}"
|
173
|
+
end
|
102
174
|
end
|
103
175
|
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
176
|
+
# Record version before or after "destroy" event
|
177
|
+
def paper_trail_on_destroy(recording_order = "before")
|
178
|
+
unless %w(after before).include?(recording_order.to_s)
|
179
|
+
raise ArgumentError, 'recording order can only be "after" or "before"'
|
180
|
+
end
|
108
181
|
|
109
|
-
|
110
|
-
|
182
|
+
if recording_order == "after" &&
|
183
|
+
Gem::Version.new(ActiveRecord::VERSION::STRING) >= Gem::Version.new("5")
|
184
|
+
if ::ActiveRecord::Base.belongs_to_required_by_default
|
185
|
+
::ActiveSupport::Deprecation.warn(
|
186
|
+
"paper_trail_on_destroy(:after) is incompatible with ActiveRecord " +
|
187
|
+
"belongs_to_required_by_default and has no effect. Please use :before " +
|
188
|
+
"or disable belongs_to_required_by_default."
|
189
|
+
)
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
send "#{recording_order}_destroy", :record_destroy, if: :save_version?
|
194
|
+
|
195
|
+
return if paper_trail_options[:on].include?(:destroy)
|
196
|
+
paper_trail_options[:on] << :destroy
|
111
197
|
end
|
112
198
|
|
113
|
-
#
|
114
|
-
def
|
115
|
-
|
116
|
-
|
199
|
+
# Record version after "update" event
|
200
|
+
def paper_trail_on_update
|
201
|
+
before_save :reset_timestamp_attrs_for_update_if_needed!, on: :update
|
202
|
+
after_update :record_update, if: :save_version?
|
203
|
+
after_update :clear_version_instance!
|
117
204
|
|
118
|
-
|
119
|
-
|
120
|
-
# Fall back to current serializer if `coder` has no `dump` method
|
121
|
-
coder = PaperTrail.serializer unless coder.respond_to?(:dump)
|
122
|
-
attributes[key] = coder.dump(attributes[key])
|
123
|
-
end
|
124
|
-
end
|
205
|
+
return if paper_trail_options[:on].include?(:update)
|
206
|
+
paper_trail_options[:on] << :update
|
125
207
|
end
|
126
208
|
|
127
|
-
|
128
|
-
|
129
|
-
|
209
|
+
# Record version after "create" event
|
210
|
+
def paper_trail_on_create
|
211
|
+
after_create :record_create, if: :save_version?
|
130
212
|
|
131
|
-
|
132
|
-
|
133
|
-
coder = PaperTrail.serializer unless coder.respond_to?(:dump)
|
134
|
-
attributes[key] = coder.load(attributes[key])
|
135
|
-
end
|
136
|
-
end
|
213
|
+
return if paper_trail_options[:on].include?(:create)
|
214
|
+
paper_trail_options[:on] << :create
|
137
215
|
end
|
138
216
|
|
139
|
-
#
|
140
|
-
def
|
141
|
-
|
142
|
-
|
217
|
+
# Switches PaperTrail off for this class.
|
218
|
+
def paper_trail_off!
|
219
|
+
PaperTrail.enabled_for_model(self, false)
|
220
|
+
end
|
143
221
|
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
coder = PaperTrail.serializer unless coder.respond_to?(:dump)
|
148
|
-
old_value, new_value = changes[key]
|
149
|
-
changes[key] = [coder.dump(old_value),
|
150
|
-
coder.dump(new_value)]
|
151
|
-
end
|
152
|
-
end
|
222
|
+
# Switches PaperTrail on for this class.
|
223
|
+
def paper_trail_on!
|
224
|
+
PaperTrail.enabled_for_model(self, true)
|
153
225
|
end
|
154
226
|
|
155
|
-
def
|
156
|
-
|
157
|
-
|
227
|
+
def paper_trail_enabled_for_model?
|
228
|
+
return false unless include?(PaperTrail::Model::InstanceMethods)
|
229
|
+
PaperTrail.enabled_for_model?(self)
|
230
|
+
end
|
158
231
|
|
159
|
-
|
160
|
-
|
161
|
-
coder = PaperTrail.serializer unless coder.respond_to?(:dump)
|
162
|
-
old_value, new_value = changes[key]
|
163
|
-
changes[key] = [coder.load(old_value),
|
164
|
-
coder.load(new_value)]
|
165
|
-
end
|
166
|
-
end
|
232
|
+
def paper_trail_version_class
|
233
|
+
@paper_trail_version_class ||= version_class_name.constantize
|
167
234
|
end
|
168
235
|
end
|
169
236
|
|
@@ -183,34 +250,43 @@ module PaperTrail
|
|
183
250
|
|
184
251
|
def originator
|
185
252
|
::ActiveSupport::Deprecation.warn "Use paper_trail_originator instead of originator."
|
186
|
-
|
253
|
+
paper_trail_originator
|
187
254
|
end
|
188
255
|
|
189
256
|
# Invoked after rollbacks to ensure versions records are not created
|
190
|
-
# for changes that never actually took place
|
257
|
+
# for changes that never actually took place.
|
258
|
+
# Optimization: Use lazy `reset` instead of eager `reload` because, in
|
259
|
+
# many use cases, the association will not be used.
|
191
260
|
def clear_rolled_back_versions
|
192
|
-
send(self.class.versions_association_name).
|
261
|
+
send(self.class.versions_association_name).reset
|
193
262
|
end
|
194
263
|
|
195
264
|
# Returns the object (not a Version) as it was at the given timestamp.
|
196
|
-
def version_at(timestamp, reify_options={})
|
265
|
+
def version_at(timestamp, reify_options = {})
|
197
266
|
# Because a version stores how its object looked *before* the change,
|
198
267
|
# we need to look for the first version created *after* the timestamp.
|
199
268
|
v = send(self.class.versions_association_name).subsequent(timestamp, true).first
|
200
269
|
return v.reify(reify_options) if v
|
201
|
-
self unless
|
270
|
+
self unless destroyed?
|
202
271
|
end
|
203
272
|
|
204
273
|
# Returns the objects (not Versions) as they were between the given times.
|
205
|
-
|
274
|
+
# TODO: Either add support for the third argument, `_reify_options`, or
|
275
|
+
# add a deprecation warning if someone tries to use it.
|
276
|
+
def versions_between(start_time, end_time, _reify_options = {})
|
206
277
|
versions = send(self.class.versions_association_name).between(start_time, end_time)
|
207
|
-
versions.collect { |version| version_at(version.send
|
278
|
+
versions.collect { |version| version_at(version.send(PaperTrail.timestamp_field)) }
|
208
279
|
end
|
209
280
|
|
210
281
|
# Returns the object (not a Version) as it was most recently.
|
211
282
|
def previous_version
|
212
|
-
|
213
|
-
|
283
|
+
previous =
|
284
|
+
if source_version
|
285
|
+
source_version.previous
|
286
|
+
else
|
287
|
+
send(self.class.versions_association_name).last
|
288
|
+
end
|
289
|
+
previous.try(:reify)
|
214
290
|
end
|
215
291
|
|
216
292
|
# Returns the object (not a Version) as it became next.
|
@@ -218,7 +294,7 @@ module PaperTrail
|
|
218
294
|
# "live" item, we return nil. Perhaps we should return self instead?
|
219
295
|
def next_version
|
220
296
|
subsequent_version = source_version.next
|
221
|
-
subsequent_version ? subsequent_version.reify : self.class.find(
|
297
|
+
subsequent_version ? subsequent_version.reify : self.class.find(id)
|
222
298
|
rescue
|
223
299
|
nil
|
224
300
|
end
|
@@ -229,14 +305,15 @@ module PaperTrail
|
|
229
305
|
|
230
306
|
# Executes the given method or block without creating a new version.
|
231
307
|
def without_versioning(method = nil)
|
232
|
-
paper_trail_was_enabled =
|
308
|
+
paper_trail_was_enabled = paper_trail_enabled_for_model?
|
233
309
|
self.class.paper_trail_off!
|
234
310
|
method ? method.to_proc.call(self) : yield(self)
|
235
311
|
ensure
|
236
312
|
self.class.paper_trail_on! if paper_trail_was_enabled
|
237
313
|
end
|
238
314
|
|
239
|
-
# Utility method for reifying. Anything executed inside the block will
|
315
|
+
# Utility method for reifying. Anything executed inside the block will
|
316
|
+
# appear like a new record.
|
240
317
|
def appear_as_new_record
|
241
318
|
instance_eval {
|
242
319
|
alias :old_new_record? :new_record?
|
@@ -246,9 +323,10 @@ module PaperTrail
|
|
246
323
|
instance_eval { alias :new_record? :old_new_record? }
|
247
324
|
end
|
248
325
|
|
249
|
-
# Temporarily overwrites the value of whodunnit and then executes the
|
326
|
+
# Temporarily overwrites the value of whodunnit and then executes the
|
327
|
+
# provided block.
|
250
328
|
def whodunnit(value)
|
251
|
-
raise ArgumentError,
|
329
|
+
raise ArgumentError, "expected to receive a block" unless block_given?
|
252
330
|
current_whodunnit = PaperTrail.whodunnit
|
253
331
|
PaperTrail.whodunnit = value
|
254
332
|
yield self
|
@@ -261,7 +339,7 @@ module PaperTrail
|
|
261
339
|
# `:on`, `:if`, or `:unless`.
|
262
340
|
#
|
263
341
|
# TODO: look into leveraging the `after_touch` callback from
|
264
|
-
# `ActiveRecord` to allow the regular `touch` method
|
342
|
+
# `ActiveRecord` to allow the regular `touch` method to generate a version
|
265
343
|
# as normal. May make sense to switch the `record_update` method to
|
266
344
|
# leverage an `after_update` callback anyways (likely for v4.0.0)
|
267
345
|
def touch_with_version(name = nil)
|
@@ -274,7 +352,7 @@ module PaperTrail
|
|
274
352
|
attributes.each { |column| write_attribute(column, current_time) }
|
275
353
|
|
276
354
|
record_update(true) unless will_record_after_update?
|
277
|
-
save!(:
|
355
|
+
save!(validate: false)
|
278
356
|
end
|
279
357
|
|
280
358
|
private
|
@@ -293,66 +371,103 @@ module PaperTrail
|
|
293
371
|
def record_create
|
294
372
|
if paper_trail_switched_on?
|
295
373
|
data = {
|
296
|
-
:
|
297
|
-
:
|
374
|
+
event: paper_trail_event || "create",
|
375
|
+
whodunnit: PaperTrail.whodunnit
|
298
376
|
}
|
299
|
-
if respond_to?(:
|
300
|
-
data[PaperTrail.timestamp_field] =
|
301
|
-
end
|
302
|
-
if paper_trail_options[:save_changes] && changed_notably? && self.class.paper_trail_version_class.column_names.include?('object_changes')
|
303
|
-
data[:object_changes] = self.class.paper_trail_version_class.object_changes_col_is_json? ? changes_for_paper_trail :
|
304
|
-
PaperTrail.serializer.dump(changes_for_paper_trail)
|
377
|
+
if respond_to?(:updated_at)
|
378
|
+
data[PaperTrail.timestamp_field] = updated_at
|
305
379
|
end
|
306
|
-
if
|
307
|
-
data[:
|
380
|
+
if pt_record_object_changes? && changed_notably?
|
381
|
+
data[:object_changes] = pt_recordable_object_changes
|
308
382
|
end
|
383
|
+
add_transaction_id_to(data)
|
309
384
|
version = send(self.class.versions_association_name).create! merge_metadata(data)
|
310
|
-
|
385
|
+
update_transaction_id(version)
|
311
386
|
save_associations(version)
|
312
387
|
end
|
313
388
|
end
|
314
389
|
|
315
390
|
def record_update(force = nil)
|
316
391
|
if paper_trail_switched_on? && (force || changed_notably?)
|
317
|
-
object_attrs = object_attrs_for_paper_trail(attributes_before_change)
|
318
392
|
data = {
|
319
|
-
:
|
320
|
-
|
321
|
-
:
|
393
|
+
event: paper_trail_event || "update",
|
394
|
+
object: pt_recordable_object,
|
395
|
+
whodunnit: PaperTrail.whodunnit
|
322
396
|
}
|
323
397
|
if respond_to?(:updated_at)
|
324
398
|
data[PaperTrail.timestamp_field] = updated_at
|
325
399
|
end
|
326
|
-
if
|
327
|
-
data[:object_changes] =
|
328
|
-
PaperTrail.serializer.dump(changes_for_paper_trail)
|
329
|
-
end
|
330
|
-
if self.class.paper_trail_version_class.column_names.include?('transaction_id')
|
331
|
-
data[:transaction_id] = PaperTrail.transaction_id
|
400
|
+
if pt_record_object_changes?
|
401
|
+
data[:object_changes] = pt_recordable_object_changes
|
332
402
|
end
|
403
|
+
add_transaction_id_to(data)
|
333
404
|
version = send(self.class.versions_association_name).create merge_metadata(data)
|
334
|
-
|
335
|
-
|
405
|
+
if version.errors.any?
|
406
|
+
log_version_errors(version, :update)
|
407
|
+
else
|
408
|
+
update_transaction_id(version)
|
409
|
+
save_associations(version)
|
410
|
+
end
|
336
411
|
end
|
337
412
|
end
|
338
413
|
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
414
|
+
# Returns a boolean indicating whether to store serialized version diffs
|
415
|
+
# in the `object_changes` column of the version record.
|
416
|
+
# @api private
|
417
|
+
def pt_record_object_changes?
|
418
|
+
paper_trail_options[:save_changes] &&
|
419
|
+
self.class.paper_trail_version_class.column_names.include?("object_changes")
|
420
|
+
end
|
421
|
+
|
422
|
+
# Returns an object which can be assigned to the `object` attribute of a
|
423
|
+
# nascent version record. If the `object` column is a postgres `json`
|
424
|
+
# column, then a hash can be used in the assignment, otherwise the column
|
425
|
+
# is a `text` column, and we must perform the serialization here, using
|
426
|
+
# `PaperTrail.serializer`.
|
427
|
+
# @api private
|
428
|
+
def pt_recordable_object
|
429
|
+
if self.class.paper_trail_version_class.object_col_is_json?
|
430
|
+
object_attrs_for_paper_trail
|
431
|
+
else
|
432
|
+
PaperTrail.serializer.dump(object_attrs_for_paper_trail)
|
343
433
|
end
|
344
|
-
_changes.to_hash
|
345
434
|
end
|
346
435
|
|
347
|
-
#
|
436
|
+
# Returns an object which can be assigned to the `object_changes`
|
437
|
+
# attribute of a nascent version record. If the `object_changes` column is
|
438
|
+
# a postgres `json` column, then a hash can be used in the assignment,
|
439
|
+
# otherwise the column is a `text` column, and we must perform the
|
440
|
+
# serialization here, using `PaperTrail.serializer`.
|
441
|
+
# @api private
|
442
|
+
def pt_recordable_object_changes
|
443
|
+
if self.class.paper_trail_version_class.object_changes_col_is_json?
|
444
|
+
changes_for_paper_trail
|
445
|
+
else
|
446
|
+
PaperTrail.serializer.dump(changes_for_paper_trail)
|
447
|
+
end
|
448
|
+
end
|
449
|
+
|
450
|
+
def changes_for_paper_trail
|
451
|
+
notable_changes = changes.delete_if { |k, _v| !notably_changed.include?(k) }
|
452
|
+
AttributeSerializers::ObjectChangesAttribute.
|
453
|
+
new(self.class).
|
454
|
+
serialize(notable_changes)
|
455
|
+
notable_changes.to_hash
|
456
|
+
end
|
457
|
+
|
458
|
+
# Invoked via`after_update` callback for when a previous version is
|
459
|
+
# reified and then saved.
|
348
460
|
def clear_version_instance!
|
349
461
|
send("#{self.class.version_association_name}=", nil)
|
350
462
|
end
|
351
463
|
|
464
|
+
# Invoked via callback when a user attempts to persist a reified
|
465
|
+
# `Version`.
|
352
466
|
def reset_timestamp_attrs_for_update_if_needed!
|
353
|
-
return if
|
467
|
+
return if live?
|
354
468
|
timestamp_attributes_for_update_in_model.each do |column|
|
355
|
-
# ActiveRecord 4.2 deprecated `reset_column!` in favor of
|
469
|
+
# ActiveRecord 4.2 deprecated `reset_column!` in favor of
|
470
|
+
# `restore_column!`.
|
356
471
|
if respond_to?("restore_#{column}!")
|
357
472
|
send("restore_#{column}!")
|
358
473
|
else
|
@@ -362,54 +477,74 @@ module PaperTrail
|
|
362
477
|
end
|
363
478
|
|
364
479
|
def record_destroy
|
365
|
-
if paper_trail_switched_on?
|
366
|
-
object_attrs = object_attrs_for_paper_trail(attributes_before_change)
|
480
|
+
if paper_trail_switched_on? && !new_record?
|
367
481
|
data = {
|
368
|
-
:
|
369
|
-
:
|
370
|
-
:
|
371
|
-
|
372
|
-
:
|
482
|
+
item_id: id,
|
483
|
+
item_type: self.class.base_class.name,
|
484
|
+
event: paper_trail_event || "destroy",
|
485
|
+
object: pt_recordable_object,
|
486
|
+
whodunnit: PaperTrail.whodunnit
|
373
487
|
}
|
374
|
-
|
375
|
-
data[:transaction_id] = PaperTrail.transaction_id
|
376
|
-
end
|
488
|
+
add_transaction_id_to(data)
|
377
489
|
version = self.class.paper_trail_version_class.create(merge_metadata(data))
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
490
|
+
if version.errors.any?
|
491
|
+
log_version_errors(version, :destroy)
|
492
|
+
else
|
493
|
+
send("#{self.class.version_association_name}=", version)
|
494
|
+
send(self.class.versions_association_name).reset
|
495
|
+
update_transaction_id(version)
|
496
|
+
save_associations(version)
|
497
|
+
end
|
382
498
|
end
|
383
499
|
end
|
384
500
|
|
385
|
-
#
|
501
|
+
# Saves associations if the join table for `VersionAssociation` exists.
|
386
502
|
def save_associations(version)
|
387
503
|
return unless PaperTrail.config.track_associations?
|
504
|
+
save_associations_belongs_to(version)
|
505
|
+
save_associations_has_and_belongs_to_many(version)
|
506
|
+
end
|
507
|
+
|
508
|
+
def save_associations_belongs_to(version)
|
388
509
|
self.class.reflect_on_all_associations(:belongs_to).each do |assoc|
|
389
510
|
assoc_version_args = {
|
390
|
-
|
391
|
-
|
511
|
+
version_id: version.id,
|
512
|
+
foreign_key_name: assoc.foreign_key
|
392
513
|
}
|
393
514
|
|
394
515
|
if assoc.options[:polymorphic]
|
395
516
|
associated_record = send(assoc.name) if send(assoc.foreign_type)
|
396
517
|
if associated_record && associated_record.class.paper_trail_enabled_for_model?
|
397
|
-
assoc_version_args
|
518
|
+
assoc_version_args[:foreign_key_id] = associated_record.id
|
398
519
|
end
|
399
520
|
elsif assoc.klass.paper_trail_enabled_for_model?
|
400
|
-
assoc_version_args
|
521
|
+
assoc_version_args[:foreign_key_id] = send(assoc.foreign_key)
|
401
522
|
end
|
402
523
|
|
403
|
-
|
524
|
+
if assoc_version_args.key?(:foreign_key_id)
|
525
|
+
PaperTrail::VersionAssociation.create(assoc_version_args)
|
526
|
+
end
|
404
527
|
end
|
405
528
|
end
|
406
529
|
|
407
|
-
def
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
530
|
+
def save_associations_has_and_belongs_to_many(version)
|
531
|
+
# Use the :added and :removed keys to extrapolate the HABTM associations
|
532
|
+
# to before any changes were made
|
533
|
+
self.class.reflect_on_all_associations(:has_and_belongs_to_many).each do |a|
|
534
|
+
next unless
|
535
|
+
self.class.paper_trail_save_join_tables.include?(a.name) ||
|
536
|
+
a.klass.paper_trail_enabled_for_model?
|
537
|
+
assoc_version_args = {
|
538
|
+
version_id: version.transaction_id,
|
539
|
+
foreign_key_name: a.name
|
540
|
+
}
|
541
|
+
assoc_ids =
|
542
|
+
send(a.name).to_a.map(&:id) +
|
543
|
+
(@paper_trail_habtm.try(:[], a.name).try(:[], :removed) || []) -
|
544
|
+
(@paper_trail_habtm.try(:[], a.name).try(:[], :added) || [])
|
545
|
+
assoc_ids.each do |id|
|
546
|
+
PaperTrail::VersionAssociation.create(assoc_version_args.merge(foreign_key_id: id))
|
547
|
+
end
|
413
548
|
end
|
414
549
|
end
|
415
550
|
|
@@ -419,14 +554,14 @@ module PaperTrail
|
|
419
554
|
|
420
555
|
def merge_metadata(data)
|
421
556
|
# First we merge the model-level metadata in `meta`.
|
422
|
-
paper_trail_options[:meta].each do |k,v|
|
557
|
+
paper_trail_options[:meta].each do |k, v|
|
423
558
|
data[k] =
|
424
559
|
if v.respond_to?(:call)
|
425
560
|
v.call(self)
|
426
|
-
elsif v.is_a?(Symbol) && respond_to?(v)
|
427
|
-
#
|
428
|
-
# be sure to grab the current version
|
429
|
-
if has_attribute?(v) && send("#{v}_changed?".to_sym) && data[:event] !=
|
561
|
+
elsif v.is_a?(Symbol) && respond_to?(v, true)
|
562
|
+
# If it is an attribute that is changing in an existing object,
|
563
|
+
# be sure to grab the current version.
|
564
|
+
if has_attribute?(v) && send("#{v}_changed?".to_sym) && data[:event] != "create"
|
430
565
|
send("#{v}_was".to_sym)
|
431
566
|
else
|
432
567
|
send(v)
|
@@ -435,34 +570,27 @@ module PaperTrail
|
|
435
570
|
v
|
436
571
|
end
|
437
572
|
end
|
573
|
+
|
438
574
|
# Second we merge any extra data from the controller (if available).
|
439
575
|
data.merge(PaperTrail.controller_info || {})
|
440
576
|
end
|
441
577
|
|
442
578
|
def attributes_before_change
|
443
|
-
|
444
|
-
|
445
|
-
changed_attributes.select { |k,v| self.class.column_names.include?(k) }.each do |attr, before|
|
446
|
-
before = enums[attr][before] if enums[attr]
|
447
|
-
prev[attr] = before
|
448
|
-
end
|
449
|
-
end
|
579
|
+
changed = changed_attributes.select { |k, _v| self.class.column_names.include?(k) }
|
580
|
+
attributes.merge(changed)
|
450
581
|
end
|
451
582
|
|
452
|
-
#
|
453
|
-
# ommitting attributes to be skipped
|
454
|
-
def object_attrs_for_paper_trail
|
455
|
-
attrs =
|
456
|
-
|
457
|
-
self.class.serialize_attributes_for_paper_trail!(attrs)
|
458
|
-
end
|
583
|
+
# Returns hash of attributes (with appropriate attributes serialized),
|
584
|
+
# ommitting attributes to be skipped.
|
585
|
+
def object_attrs_for_paper_trail
|
586
|
+
attrs = attributes_before_change.except(*paper_trail_options[:skip])
|
587
|
+
AttributeSerializers::ObjectAttribute.new(self.class).serialize(attrs)
|
459
588
|
attrs
|
460
589
|
end
|
461
590
|
|
462
|
-
#
|
463
|
-
#
|
464
|
-
#
|
465
|
-
# changed.
|
591
|
+
# Determines whether it is appropriate to generate a new version
|
592
|
+
# instance. A timestamp-only update (e.g. only `updated_at` changed) is
|
593
|
+
# considered notable unless an ignored attribute was also changed.
|
466
594
|
def changed_notably?
|
467
595
|
if ignored_attr_has_changed?
|
468
596
|
timestamps = timestamp_attributes_for_update_in_model.map(&:to_s)
|
@@ -473,41 +601,73 @@ module PaperTrail
|
|
473
601
|
end
|
474
602
|
|
475
603
|
# An attributed is "ignored" if it is listed in the `:ignore` option
|
476
|
-
# and/or the `:skip` option. Returns true if an ignored attribute
|
477
|
-
#
|
604
|
+
# and/or the `:skip` option. Returns true if an ignored attribute has
|
605
|
+
# changed.
|
478
606
|
def ignored_attr_has_changed?
|
479
|
-
ignored =
|
607
|
+
ignored = paper_trail_options[:ignore] + paper_trail_options[:skip]
|
480
608
|
ignored.any? && (changed & ignored).any?
|
481
609
|
end
|
482
610
|
|
483
611
|
def notably_changed
|
484
|
-
only =
|
485
|
-
#
|
612
|
+
only = paper_trail_options[:only].dup
|
613
|
+
# Remove Hash arguments and then evaluate whether the attributes (the
|
614
|
+
# keys of the hash) should also get pushed into the collection.
|
486
615
|
only.delete_if do |obj|
|
487
|
-
obj.is_a?(Hash) &&
|
616
|
+
obj.is_a?(Hash) &&
|
617
|
+
obj.each { |attr, condition|
|
618
|
+
only << attr if condition.respond_to?(:call) && condition.call(self)
|
619
|
+
}
|
488
620
|
end
|
489
621
|
only.empty? ? changed_and_not_ignored : (changed_and_not_ignored & only)
|
490
622
|
end
|
491
623
|
|
492
624
|
def changed_and_not_ignored
|
493
|
-
ignore =
|
494
|
-
|
625
|
+
ignore = paper_trail_options[:ignore].dup
|
626
|
+
# Remove Hash arguments and then evaluate whether the attributes (the
|
627
|
+
# keys of the hash) should also get pushed into the collection.
|
495
628
|
ignore.delete_if do |obj|
|
496
|
-
obj.is_a?(Hash) &&
|
629
|
+
obj.is_a?(Hash) &&
|
630
|
+
obj.each { |attr, condition|
|
631
|
+
ignore << attr if condition.respond_to?(:call) && condition.call(self)
|
632
|
+
}
|
497
633
|
end
|
498
|
-
skip =
|
634
|
+
skip = paper_trail_options[:skip]
|
499
635
|
changed - ignore - skip
|
500
636
|
end
|
501
637
|
|
502
638
|
def paper_trail_switched_on?
|
503
|
-
PaperTrail.enabled? &&
|
639
|
+
PaperTrail.enabled? &&
|
640
|
+
PaperTrail.enabled_for_controller? &&
|
641
|
+
paper_trail_enabled_for_model?
|
504
642
|
end
|
505
643
|
|
506
644
|
def save_version?
|
507
|
-
if_condition
|
508
|
-
unless_condition =
|
645
|
+
if_condition = paper_trail_options[:if]
|
646
|
+
unless_condition = paper_trail_options[:unless]
|
509
647
|
(if_condition.blank? || if_condition.call(self)) && !unless_condition.try(:call, self)
|
510
648
|
end
|
649
|
+
|
650
|
+
def add_transaction_id_to(data)
|
651
|
+
return unless self.class.paper_trail_version_class.column_names.include?("transaction_id")
|
652
|
+
data[:transaction_id] = PaperTrail.transaction_id
|
653
|
+
end
|
654
|
+
|
655
|
+
# @api private
|
656
|
+
def update_transaction_id(version)
|
657
|
+
return unless self.class.paper_trail_version_class.column_names.include?("transaction_id")
|
658
|
+
if PaperTrail.transaction? && PaperTrail.transaction_id.nil?
|
659
|
+
PaperTrail.transaction_id = version.id
|
660
|
+
version.transaction_id = version.id
|
661
|
+
version.save
|
662
|
+
end
|
663
|
+
end
|
664
|
+
|
665
|
+
def log_version_errors(version, action)
|
666
|
+
version.logger.warn(
|
667
|
+
"Unable to create version for #{action} of #{self.class.name}##{id}: " +
|
668
|
+
version.errors.full_messages.join(", ")
|
669
|
+
)
|
670
|
+
end
|
511
671
|
end
|
512
672
|
end
|
513
673
|
end
|