2016-03-05 22:07:32 +00:00
|
|
|
require "active_support/core_ext/object" # provides the `try` method
|
|
|
|
require "paper_trail/attributes_serialization"
|
2015-05-08 17:28:47 +00:00
|
|
|
|
2009-05-27 15:21:20 +00:00
|
|
|
module PaperTrail
|
2016-04-09 05:08:34 +00:00
|
|
|
# Extensions to `ActiveRecord::Base`. See `frameworks/active_record.rb`.
|
2010-03-19 17:21:16 +00:00
|
|
|
module Model
|
|
|
|
def self.included(base)
|
|
|
|
base.send :extend, ClassMethods
|
2016-04-05 09:37:24 +00:00
|
|
|
base.send :attr_accessor, :paper_trail_habtm
|
2010-03-19 17:21:16 +00:00
|
|
|
end
|
2009-05-27 15:21:20 +00:00
|
|
|
|
2016-04-09 05:08:34 +00:00
|
|
|
# :nodoc:
|
2010-03-19 17:21:16 +00:00
|
|
|
module ClassMethods
|
2015-08-03 20:45:42 +00:00
|
|
|
# Declare this in your model to track every create, update, and destroy.
|
|
|
|
# Each version of the model is available in the `versions` association.
|
2010-03-19 18:53:49 +00:00
|
|
|
#
|
2010-03-19 17:21:16 +00:00
|
|
|
# Options:
|
2015-08-03 20:45:42 +00:00
|
|
|
#
|
|
|
|
# - :on - The events to track (optional; defaults to all of them). Set
|
|
|
|
# to an array of `:create`, `:update`, `:destroy` as desired.
|
|
|
|
# - :class_name - The name of a custom Version class. This class should
|
|
|
|
# inherit from `PaperTrail::Version`.
|
|
|
|
# - :ignore - An array of attributes for which a new `Version` will not be
|
|
|
|
# created if only they change. It can also aceept a Hash as an
|
|
|
|
# argument where the key is the attribute to ignore (a `String` or
|
|
|
|
# `Symbol`), which will only be ignored if the value is a `Proc` which
|
|
|
|
# returns truthily.
|
|
|
|
# - :if, :unless - Procs that allow to specify conditions when to save
|
|
|
|
# versions for an object.
|
|
|
|
# - :only - Inverse of `ignore`. A new `Version` will be created only
|
|
|
|
# for these attributes if supplied it can also aceept a Hash as an
|
|
|
|
# argument where the key is the attribute to track (a `String` or
|
|
|
|
# `Symbol`), which will only be counted if the value is a `Proc` which
|
|
|
|
# returns truthily.
|
|
|
|
# - :skip - Fields to ignore completely. As with `ignore`, updates to
|
|
|
|
# these fields will not create a new `Version`. In addition, these
|
|
|
|
# fields will not be included in the serialized versions of the object
|
|
|
|
# whenever a new `Version` is created.
|
|
|
|
# - :meta - A hash of extra data to store. You must add a column to the
|
|
|
|
# `versions` table for each key. Values are objects or procs (which
|
|
|
|
# are called with `self`, i.e. the model with the paper trail). See
|
|
|
|
# `PaperTrail::Controller.info_for_paper_trail` for how to store data
|
|
|
|
# from the controller.
|
|
|
|
# - :versions - The name to use for the versions association. Default
|
|
|
|
# is `:versions`.
|
|
|
|
# - :version - The name to use for the method which returns the version
|
|
|
|
# the instance was reified from. Default is `:version`.
|
|
|
|
# - :save_changes - Whether or not to save changes to the object_changes
|
|
|
|
# column if it exists. Default is true
|
2016-04-05 09:37:24 +00:00
|
|
|
# - :join_tables - If the model has a has_and_belongs_to_many relation
|
|
|
|
# with an unpapertrailed model, passing the name of the association to
|
|
|
|
# the join_tables option will paper trail the join table but not save
|
|
|
|
# the other model, allowing reification of the association but with the
|
|
|
|
# other models latest state (if the other model is paper trailed, this
|
|
|
|
# option does nothing)
|
2015-03-23 12:10:35 +00:00
|
|
|
#
|
2010-03-19 17:21:16 +00:00
|
|
|
def has_paper_trail(options = {})
|
2015-09-10 15:40:59 +00:00
|
|
|
options[:on] ||= [:create, :update, :destroy]
|
|
|
|
|
|
|
|
# Wrap the :on option in an array if necessary. This allows a single
|
|
|
|
# symbol to be passed in.
|
2015-10-09 08:54:42 +00:00
|
|
|
options[:on] = Array(options[:on])
|
2015-09-10 15:40:59 +00:00
|
|
|
|
2015-09-15 13:06:14 +00:00
|
|
|
setup_model_for_paper_trail(options)
|
2015-09-23 15:45:45 +00:00
|
|
|
|
2015-10-09 08:54:42 +00:00
|
|
|
setup_callbacks_from_options options[:on]
|
2016-04-05 09:37:24 +00:00
|
|
|
|
|
|
|
setup_callbacks_for_habtm options[:join_tables]
|
|
|
|
end
|
|
|
|
|
|
|
|
def update_for_callback(name, callback, model, assoc)
|
|
|
|
model.paper_trail_habtm ||= {}
|
|
|
|
model.paper_trail_habtm.reverse_merge!(name => { removed: [], added: [] })
|
|
|
|
case callback
|
|
|
|
when :before_add
|
|
|
|
model.paper_trail_habtm[name][:added] |= [assoc.id]
|
|
|
|
model.paper_trail_habtm[name][:removed] -= [assoc.id]
|
|
|
|
when :before_remove
|
|
|
|
model.paper_trail_habtm[name][:removed] |= [assoc.id]
|
|
|
|
model.paper_trail_habtm[name][:added] -= [assoc.id]
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
attr_reader :paper_trail_save_join_tables
|
|
|
|
|
|
|
|
def setup_callbacks_for_habtm(join_tables)
|
|
|
|
@paper_trail_save_join_tables = Array.wrap(join_tables)
|
|
|
|
# Adds callbacks to record changes to habtm associations such that on
|
|
|
|
# save the previous version of the association (if changed) can be
|
|
|
|
# interpreted
|
|
|
|
reflect_on_all_associations(:has_and_belongs_to_many).
|
|
|
|
reject { |a| paper_trail_options[:skip].include?(a.name.to_s) }.
|
|
|
|
each do |a|
|
|
|
|
added_callback = lambda do |*args|
|
|
|
|
update_for_callback(a.name, :before_add, args[-2], args.last)
|
|
|
|
end
|
|
|
|
removed_callback = lambda do |*args|
|
|
|
|
update_for_callback(a.name, :before_remove, args[-2], args.last)
|
|
|
|
end
|
|
|
|
send(:"before_add_for_#{a.name}").send(:<<, added_callback)
|
|
|
|
send(:"before_remove_for_#{a.name}").send(:<<, removed_callback)
|
|
|
|
end
|
2015-09-10 15:40:59 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
def setup_model_for_paper_trail(options = {})
|
2010-04-06 16:27:15 +00:00
|
|
|
# Lazily include the instance methods so we don't clutter up
|
|
|
|
# any more ActiveRecord models than we have to.
|
2010-03-19 17:21:16 +00:00
|
|
|
send :include, InstanceMethods
|
2015-12-09 15:58:27 +00:00
|
|
|
send :extend, AttributesSerialization
|
2011-08-12 20:16:15 +00:00
|
|
|
|
2011-11-10 18:09:06 +00:00
|
|
|
class_attribute :version_association_name
|
|
|
|
self.version_association_name = options[:version] || :version
|
2011-09-07 19:36:37 +00:00
|
|
|
|
2011-02-07 22:36:06 +00:00
|
|
|
# The version this instance was reified from.
|
2016-03-05 22:17:23 +00:00
|
|
|
attr_accessor version_association_name
|
2011-03-31 16:11:21 +00:00
|
|
|
|
2011-08-31 08:15:32 +00:00
|
|
|
class_attribute :version_class_name
|
2016-03-05 22:07:32 +00:00
|
|
|
self.version_class_name = options[:class_name] || "PaperTrail::Version"
|
2011-02-07 22:36:06 +00:00
|
|
|
|
2015-09-23 15:45:45 +00:00
|
|
|
class_attribute :paper_trail_options
|
|
|
|
|
2012-05-07 20:13:21 +00:00
|
|
|
self.paper_trail_options = options.dup
|
2010-01-06 12:57:54 +00:00
|
|
|
|
2012-05-11 17:13:08 +00:00
|
|
|
[:ignore, :skip, :only].each do |k|
|
2015-11-28 04:37:29 +00:00
|
|
|
paper_trail_options[k] = [paper_trail_options[k]].flatten.compact.map { |attr|
|
|
|
|
attr.is_a?(Hash) ? attr.stringify_keys : attr.to_s
|
|
|
|
}
|
2012-05-07 20:13:21 +00:00
|
|
|
end
|
2011-03-16 15:32:28 +00:00
|
|
|
|
2012-05-07 20:13:21 +00:00
|
|
|
paper_trail_options[:meta] ||= {}
|
2015-03-23 12:10:35 +00:00
|
|
|
paper_trail_options[:save_changes] = true if paper_trail_options[:save_changes].nil?
|
2010-02-18 16:35:41 +00:00
|
|
|
|
2011-08-31 08:15:32 +00:00
|
|
|
class_attribute :versions_association_name
|
2011-07-23 15:57:45 +00:00
|
|
|
self.versions_association_name = options[:versions] || :versions
|
2011-07-18 15:07:46 +00:00
|
|
|
|
2013-05-13 15:38:53 +00:00
|
|
|
attr_accessor :paper_trail_event
|
2012-10-08 14:50:58 +00:00
|
|
|
|
2015-08-03 20:45:42 +00:00
|
|
|
# `has_many` syntax for specifying order uses a lambda in Rails 4
|
|
|
|
if ::ActiveRecord::VERSION::MAJOR >= 4
|
2016-03-05 22:17:23 +00:00
|
|
|
has_many versions_association_name,
|
2016-03-14 00:22:55 +00:00
|
|
|
-> { order(model.timestamp_sort_order) },
|
2016-03-05 22:17:23 +00:00
|
|
|
class_name: version_class_name, as: :item
|
2013-08-01 00:37:04 +00:00
|
|
|
else
|
2016-03-05 22:17:23 +00:00
|
|
|
has_many versions_association_name,
|
|
|
|
class_name: version_class_name,
|
2016-02-16 03:32:40 +00:00
|
|
|
as: :item,
|
2016-03-05 22:17:23 +00:00
|
|
|
order: paper_trail_version_class.timestamp_sort_order
|
2013-08-01 00:37:04 +00:00
|
|
|
end
|
2013-08-29 16:42:37 +00:00
|
|
|
|
2015-08-03 20:45:42 +00:00
|
|
|
# Reset the transaction id when the transaction is closed.
|
2014-02-25 14:02:36 +00:00
|
|
|
after_commit :reset_transaction_id
|
|
|
|
after_rollback :reset_transaction_id
|
2014-11-05 22:39:44 +00:00
|
|
|
after_rollback :clear_rolled_back_versions
|
2010-03-19 17:21:16 +00:00
|
|
|
end
|
2009-05-27 15:21:20 +00:00
|
|
|
|
2015-09-23 15:45:45 +00:00
|
|
|
def setup_callbacks_from_options(options_on = [])
|
|
|
|
options_on.each do |option|
|
|
|
|
send "paper_trail_on_#{option}"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
# Record version before or after "destroy" event
|
2016-03-05 22:07:32 +00:00
|
|
|
def paper_trail_on_destroy(recording_order = "before")
|
2016-03-05 22:22:29 +00:00
|
|
|
unless %w(after before).include?(recording_order.to_s)
|
2016-03-05 22:13:59 +00:00
|
|
|
raise ArgumentError, 'recording order can only be "after" or "before"'
|
2015-09-23 15:45:45 +00:00
|
|
|
end
|
|
|
|
|
2016-03-05 22:07:32 +00:00
|
|
|
if recording_order == "after" &&
|
2016-03-05 22:30:53 +00:00
|
|
|
Gem::Version.new(ActiveRecord::VERSION::STRING) >= Gem::Version.new("5")
|
2016-01-02 16:51:02 +00:00
|
|
|
if ::ActiveRecord::Base.belongs_to_required_by_default
|
|
|
|
::ActiveSupport::Deprecation.warn(
|
|
|
|
"paper_trail_on_destroy(:after) is incompatible with ActiveRecord " +
|
|
|
|
"belongs_to_required_by_default and has no effect. Please use :before " +
|
|
|
|
"or disable belongs_to_required_by_default."
|
|
|
|
)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-02-16 03:32:40 +00:00
|
|
|
send "#{recording_order}_destroy", :record_destroy, if: :save_version?
|
2015-10-09 08:54:42 +00:00
|
|
|
|
|
|
|
return if paper_trail_options[:on].include?(:destroy)
|
|
|
|
paper_trail_options[:on] << :destroy
|
2015-09-23 15:45:45 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
# Record version after "update" event
|
|
|
|
def paper_trail_on_update
|
2016-02-16 03:32:40 +00:00
|
|
|
before_save :reset_timestamp_attrs_for_update_if_needed!, on: :update
|
|
|
|
after_update :record_update, if: :save_version?
|
2015-09-23 15:45:45 +00:00
|
|
|
after_update :clear_version_instance!
|
2015-10-09 08:54:42 +00:00
|
|
|
|
|
|
|
return if paper_trail_options[:on].include?(:update)
|
|
|
|
paper_trail_options[:on] << :update
|
2015-09-23 15:45:45 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
# Record version after "create" event
|
|
|
|
def paper_trail_on_create
|
2016-02-16 03:32:40 +00:00
|
|
|
after_create :record_create, if: :save_version?
|
2015-10-09 08:54:42 +00:00
|
|
|
|
|
|
|
return if paper_trail_options[:on].include?(:create)
|
|
|
|
paper_trail_options[:on] << :create
|
2015-09-23 15:45:45 +00:00
|
|
|
end
|
|
|
|
|
2010-03-19 17:21:16 +00:00
|
|
|
# Switches PaperTrail off for this class.
|
2014-02-12 23:27:10 +00:00
|
|
|
def paper_trail_off!
|
2014-02-11 21:01:44 +00:00
|
|
|
PaperTrail.enabled_for_model(self, false)
|
2010-03-19 17:21:16 +00:00
|
|
|
end
|
2009-05-27 15:21:20 +00:00
|
|
|
|
2010-03-19 17:21:16 +00:00
|
|
|
# Switches PaperTrail on for this class.
|
2014-02-12 23:27:10 +00:00
|
|
|
def paper_trail_on!
|
2014-02-11 21:01:44 +00:00
|
|
|
PaperTrail.enabled_for_model(self, true)
|
2010-03-19 17:21:16 +00:00
|
|
|
end
|
2012-11-06 23:15:31 +00:00
|
|
|
|
2014-02-11 21:01:44 +00:00
|
|
|
def paper_trail_enabled_for_model?
|
2016-03-05 22:17:23 +00:00
|
|
|
return false unless include?(PaperTrail::Model::InstanceMethods)
|
2014-02-11 21:01:44 +00:00
|
|
|
PaperTrail.enabled_for_model?(self)
|
|
|
|
end
|
|
|
|
|
2013-10-18 02:55:11 +00:00
|
|
|
def paper_trail_version_class
|
|
|
|
@paper_trail_version_class ||= version_class_name.constantize
|
|
|
|
end
|
2009-05-27 15:21:20 +00:00
|
|
|
end
|
|
|
|
|
2010-04-06 16:27:15 +00:00
|
|
|
# Wrap the following methods in a module so we can include them only in the
|
|
|
|
# ActiveRecord models that declare `has_paper_trail`.
|
2010-03-19 17:21:16 +00:00
|
|
|
module InstanceMethods
|
2010-10-21 11:30:50 +00:00
|
|
|
# Returns true if this instance is the current, live one;
|
|
|
|
# returns false if this instance came from a previous version.
|
|
|
|
def live?
|
2014-04-16 16:44:21 +00:00
|
|
|
source_version.nil?
|
2010-10-21 11:30:50 +00:00
|
|
|
end
|
|
|
|
|
2010-06-28 13:03:35 +00:00
|
|
|
# Returns who put the object into its current state.
|
2015-05-08 17:28:47 +00:00
|
|
|
def paper_trail_originator
|
2014-06-17 21:37:30 +00:00
|
|
|
(source_version || send(self.class.versions_association_name).last).try(:whodunnit)
|
2010-06-28 13:03:35 +00:00
|
|
|
end
|
|
|
|
|
2015-05-08 17:28:47 +00:00
|
|
|
def originator
|
2015-07-13 17:36:34 +00:00
|
|
|
::ActiveSupport::Deprecation.warn "Use paper_trail_originator instead of originator."
|
2016-03-05 22:17:23 +00:00
|
|
|
paper_trail_originator
|
2015-05-08 17:28:47 +00:00
|
|
|
end
|
|
|
|
|
2014-11-11 00:12:50 +00:00
|
|
|
# Invoked after rollbacks to ensure versions records are not created
|
2016-03-06 19:05:21 +00:00
|
|
|
# for changes that never actually took place.
|
|
|
|
# Optimization: Use lazy `reset` instead of eager `reload` because, in
|
|
|
|
# many use cases, the association will not be used.
|
2014-11-05 22:39:44 +00:00
|
|
|
def clear_rolled_back_versions
|
2016-03-06 19:05:21 +00:00
|
|
|
send(self.class.versions_association_name).reset
|
2014-11-05 22:39:44 +00:00
|
|
|
end
|
|
|
|
|
2010-03-19 17:21:16 +00:00
|
|
|
# Returns the object (not a Version) as it was at the given timestamp.
|
2016-03-05 22:11:08 +00:00
|
|
|
def version_at(timestamp, reify_options = {})
|
2010-10-20 12:48:52 +00:00
|
|
|
# Because a version stores how its object looked *before* the change,
|
|
|
|
# we need to look for the first version created *after* the timestamp.
|
2014-04-01 17:38:53 +00:00
|
|
|
v = send(self.class.versions_association_name).subsequent(timestamp, true).first
|
2014-06-18 15:01:38 +00:00
|
|
|
return v.reify(reify_options) if v
|
2016-03-05 22:17:23 +00:00
|
|
|
self unless destroyed?
|
2010-03-19 17:21:16 +00:00
|
|
|
end
|
2009-05-27 15:21:20 +00:00
|
|
|
|
2012-03-12 13:01:18 +00:00
|
|
|
# Returns the objects (not Versions) as they were between the given times.
|
2016-02-16 01:31:10 +00:00
|
|
|
# TODO: Either add support for the third argument, `_reify_options`, or
|
|
|
|
# add a deprecation warning if someone tries to use it.
|
2016-03-05 22:11:08 +00:00
|
|
|
def versions_between(start_time, end_time, _reify_options = {})
|
2012-03-08 20:51:43 +00:00
|
|
|
versions = send(self.class.versions_association_name).between(start_time, end_time)
|
2016-03-13 23:57:17 +00:00
|
|
|
versions.collect { |version| version_at(version.send(PaperTrail.timestamp_field)) }
|
2012-03-08 20:51:43 +00:00
|
|
|
end
|
|
|
|
|
2010-06-22 13:41:26 +00:00
|
|
|
# Returns the object (not a Version) as it was most recently.
|
|
|
|
def previous_version
|
2016-04-01 05:42:54 +00:00
|
|
|
previous =
|
|
|
|
if source_version
|
|
|
|
source_version.previous
|
|
|
|
else
|
|
|
|
send(self.class.versions_association_name).last
|
|
|
|
end
|
|
|
|
previous.try(:reify)
|
2010-06-22 13:41:26 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
# Returns the object (not a Version) as it became next.
|
2013-02-05 18:45:59 +00:00
|
|
|
# NOTE: if self (the item) was not reified from a version, i.e. it is the
|
|
|
|
# "live" item, we return nil. Perhaps we should return self instead?
|
2010-06-22 13:41:26 +00:00
|
|
|
def next_version
|
2013-02-05 18:45:59 +00:00
|
|
|
subsequent_version = source_version.next
|
2016-03-05 22:17:23 +00:00
|
|
|
subsequent_version ? subsequent_version.reify : self.class.find(id)
|
2013-02-05 18:45:59 +00:00
|
|
|
rescue
|
|
|
|
nil
|
2010-06-22 13:41:26 +00:00
|
|
|
end
|
|
|
|
|
2014-02-20 16:40:52 +00:00
|
|
|
def paper_trail_enabled_for_model?
|
|
|
|
self.class.paper_trail_enabled_for_model?
|
|
|
|
end
|
|
|
|
|
2011-08-31 09:25:52 +00:00
|
|
|
# Executes the given method or block without creating a new version.
|
|
|
|
def without_versioning(method = nil)
|
2016-03-05 22:17:23 +00:00
|
|
|
paper_trail_was_enabled = paper_trail_enabled_for_model?
|
2014-02-12 23:27:10 +00:00
|
|
|
self.class.paper_trail_off!
|
2014-03-12 14:09:59 +00:00
|
|
|
method ? method.to_proc.call(self) : yield(self)
|
2011-08-31 09:25:52 +00:00
|
|
|
ensure
|
2014-02-12 23:27:10 +00:00
|
|
|
self.class.paper_trail_on! if paper_trail_was_enabled
|
2011-08-25 13:03:42 +00:00
|
|
|
end
|
|
|
|
|
2015-08-03 20:45:42 +00:00
|
|
|
# Utility method for reifying. Anything executed inside the block will
|
|
|
|
# appear like a new record.
|
2016-03-31 08:57:32 +00:00
|
|
|
# rubocop: disable Style/Alias
|
2014-02-25 14:02:36 +00:00
|
|
|
def appear_as_new_record
|
|
|
|
instance_eval {
|
2014-02-26 11:44:43 +00:00
|
|
|
alias :old_new_record? :new_record?
|
|
|
|
alias :new_record? :present?
|
2014-02-25 14:02:36 +00:00
|
|
|
}
|
|
|
|
yield
|
2014-02-26 11:44:43 +00:00
|
|
|
instance_eval { alias :new_record? :old_new_record? }
|
2014-02-25 14:02:36 +00:00
|
|
|
end
|
2016-03-31 08:57:32 +00:00
|
|
|
# rubocop: enable Style/Alias
|
2014-02-25 14:02:36 +00:00
|
|
|
|
2015-08-03 20:45:42 +00:00
|
|
|
# Temporarily overwrites the value of whodunnit and then executes the
|
|
|
|
# provided block.
|
2014-03-11 21:55:44 +00:00
|
|
|
def whodunnit(value)
|
2016-03-05 22:07:32 +00:00
|
|
|
raise ArgumentError, "expected to receive a block" unless block_given?
|
2014-03-11 21:55:44 +00:00
|
|
|
current_whodunnit = PaperTrail.whodunnit
|
|
|
|
PaperTrail.whodunnit = value
|
2014-03-12 14:09:59 +00:00
|
|
|
yield self
|
|
|
|
ensure
|
|
|
|
PaperTrail.whodunnit = current_whodunnit
|
2014-03-11 21:55:44 +00:00
|
|
|
end
|
|
|
|
|
2015-07-09 03:21:44 +00:00
|
|
|
# Mimics the `touch` method from `ActiveRecord::Persistence`, but also
|
|
|
|
# creates a version. A version is created regardless of options such as
|
|
|
|
# `:on`, `:if`, or `:unless`.
|
2014-02-21 23:20:40 +00:00
|
|
|
#
|
2015-07-02 15:29:11 +00:00
|
|
|
# TODO: look into leveraging the `after_touch` callback from
|
2015-11-26 12:54:11 +00:00
|
|
|
# `ActiveRecord` to allow the regular `touch` method to generate a version
|
2015-07-02 15:29:11 +00:00
|
|
|
# as normal. May make sense to switch the `record_update` method to
|
|
|
|
# leverage an `after_update` callback anyways (likely for v4.0.0)
|
2014-02-21 23:20:40 +00:00
|
|
|
def touch_with_version(name = nil)
|
|
|
|
raise ActiveRecordError, "can not touch on a new record object" unless persisted?
|
|
|
|
|
|
|
|
attributes = timestamp_attributes_for_update_in_model
|
|
|
|
attributes << name if name
|
|
|
|
current_time = current_time_from_proper_timezone
|
|
|
|
|
|
|
|
attributes.each { |column| write_attribute(column, current_time) }
|
2015-07-09 03:21:44 +00:00
|
|
|
|
|
|
|
record_update(true) unless will_record_after_update?
|
2016-02-16 03:32:40 +00:00
|
|
|
save!(validate: false)
|
2014-02-21 23:20:40 +00:00
|
|
|
end
|
|
|
|
|
2010-03-19 17:21:16 +00:00
|
|
|
private
|
2009-05-27 15:21:20 +00:00
|
|
|
|
2015-07-09 03:21:44 +00:00
|
|
|
# Returns true if `save` will cause `record_update`
|
|
|
|
# to be called via the `after_update` callback.
|
|
|
|
def will_record_after_update?
|
|
|
|
on = paper_trail_options[:on]
|
|
|
|
on.nil? || on.include?(:update)
|
|
|
|
end
|
|
|
|
|
2011-09-14 08:16:33 +00:00
|
|
|
def source_version
|
2011-11-10 18:09:06 +00:00
|
|
|
send self.class.version_association_name
|
2011-09-14 08:16:33 +00:00
|
|
|
end
|
|
|
|
|
2010-10-28 10:14:19 +00:00
|
|
|
def record_create
|
2013-08-20 17:34:42 +00:00
|
|
|
if paper_trail_switched_on?
|
2012-09-20 20:39:55 +00:00
|
|
|
data = {
|
2016-03-05 22:07:32 +00:00
|
|
|
event: paper_trail_event || "create",
|
2016-02-16 03:32:40 +00:00
|
|
|
whodunnit: PaperTrail.whodunnit
|
2012-09-20 20:39:55 +00:00
|
|
|
}
|
2015-08-11 16:58:03 +00:00
|
|
|
if respond_to?(:updated_at)
|
|
|
|
data[PaperTrail.timestamp_field] = updated_at
|
2014-05-30 21:38:23 +00:00
|
|
|
end
|
2015-11-28 04:08:02 +00:00
|
|
|
if pt_record_object_changes? && changed_notably?
|
2015-11-28 04:02:57 +00:00
|
|
|
data[:object_changes] = pt_recordable_object_changes
|
2012-09-20 20:39:55 +00:00
|
|
|
end
|
2016-03-05 22:07:32 +00:00
|
|
|
if self.class.paper_trail_version_class.column_names.include?("transaction_id")
|
2014-12-31 18:32:16 +00:00
|
|
|
data[:transaction_id] = PaperTrail.transaction_id
|
|
|
|
end
|
2014-02-25 14:02:36 +00:00
|
|
|
version = send(self.class.versions_association_name).create! merge_metadata(data)
|
2016-04-05 01:35:09 +00:00
|
|
|
update_transaction_id(version)
|
2014-02-25 14:02:36 +00:00
|
|
|
save_associations(version)
|
2010-10-28 10:14:19 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2015-03-18 21:40:24 +00:00
|
|
|
def record_update(force = nil)
|
|
|
|
if paper_trail_switched_on? && (force || changed_notably?)
|
2011-07-12 16:22:04 +00:00
|
|
|
data = {
|
2016-03-05 22:07:32 +00:00
|
|
|
event: paper_trail_event || "update",
|
2016-02-16 03:32:40 +00:00
|
|
|
object: pt_recordable_object,
|
|
|
|
whodunnit: PaperTrail.whodunnit
|
2011-07-12 16:22:04 +00:00
|
|
|
}
|
2014-05-30 21:38:23 +00:00
|
|
|
if respond_to?(:updated_at)
|
|
|
|
data[PaperTrail.timestamp_field] = updated_at
|
|
|
|
end
|
2015-11-28 04:08:02 +00:00
|
|
|
if pt_record_object_changes?
|
2015-11-28 04:02:57 +00:00
|
|
|
data[:object_changes] = pt_recordable_object_changes
|
2011-07-13 19:49:58 +00:00
|
|
|
end
|
2016-03-05 22:07:32 +00:00
|
|
|
if self.class.paper_trail_version_class.column_names.include?("transaction_id")
|
2014-12-31 18:32:16 +00:00
|
|
|
data[:transaction_id] = PaperTrail.transaction_id
|
|
|
|
end
|
2014-02-26 11:44:43 +00:00
|
|
|
version = send(self.class.versions_association_name).create merge_metadata(data)
|
2016-04-05 01:35:09 +00:00
|
|
|
update_transaction_id(version)
|
2014-02-25 14:02:36 +00:00
|
|
|
save_associations(version)
|
2010-10-28 10:14:19 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2015-11-28 04:08:02 +00:00
|
|
|
# Returns a boolean indicating whether to store serialized version diffs
|
|
|
|
# in the `object_changes` column of the version record.
|
|
|
|
# @api private
|
|
|
|
def pt_record_object_changes?
|
|
|
|
paper_trail_options[:save_changes] &&
|
2016-03-05 22:07:32 +00:00
|
|
|
self.class.paper_trail_version_class.column_names.include?("object_changes")
|
2015-11-28 04:08:02 +00:00
|
|
|
end
|
|
|
|
|
2015-11-28 03:49:45 +00:00
|
|
|
# Returns an object which can be assigned to the `object` attribute of a
|
|
|
|
# nascent version record. If the `object` column is a postgres `json`
|
|
|
|
# column, then a hash can be used in the assignment, otherwise the column
|
|
|
|
# is a `text` column, and we must perform the serialization here, using
|
|
|
|
# `PaperTrail.serializer`.
|
|
|
|
# @api private
|
|
|
|
def pt_recordable_object
|
|
|
|
if self.class.paper_trail_version_class.object_col_is_json?
|
Maps enums to database values before storing in `object_changes`
Keep consistency between versions with regard to `changes` and
`object_changes` and how enum columns store their values.
Before, `changes` would map the changed attributes enum columns to the database
values (integer values). This allows reifying that version to maintain the
integrity of the enum. It did not do so for `object_changes` and thus, `0`
for non-json columns, and the enum value for json columns would be stored instead.
For the non-json columns, it mapped any non-integer enum value to `0` because
during serialization that column is an `integer`. Now this is fixed,
so that `object_changes` stores the enum mapped value.
Here is an example:
```ruby
class PostWithStatus < ActiveRecord::Base
has_paper_trail
enum status: { draft: 0, published: 1, archived: 2 }
end
post = PostWithStatus.new(status: :draft)
post.published!
version = post.versions.last
# Before
version.changeset #> { 'status' => ['draft', 'draft'] } (stored as [0, 0])
# After
version.changeset #> { 'status' => ['draft', 'published'] } (stored as [0, 1])
```
2016-03-11 09:18:40 +00:00
|
|
|
object_attrs_for_paper_trail
|
2015-11-28 03:49:45 +00:00
|
|
|
else
|
Maps enums to database values before storing in `object_changes`
Keep consistency between versions with regard to `changes` and
`object_changes` and how enum columns store their values.
Before, `changes` would map the changed attributes enum columns to the database
values (integer values). This allows reifying that version to maintain the
integrity of the enum. It did not do so for `object_changes` and thus, `0`
for non-json columns, and the enum value for json columns would be stored instead.
For the non-json columns, it mapped any non-integer enum value to `0` because
during serialization that column is an `integer`. Now this is fixed,
so that `object_changes` stores the enum mapped value.
Here is an example:
```ruby
class PostWithStatus < ActiveRecord::Base
has_paper_trail
enum status: { draft: 0, published: 1, archived: 2 }
end
post = PostWithStatus.new(status: :draft)
post.published!
version = post.versions.last
# Before
version.changeset #> { 'status' => ['draft', 'draft'] } (stored as [0, 0])
# After
version.changeset #> { 'status' => ['draft', 'published'] } (stored as [0, 1])
```
2016-03-11 09:18:40 +00:00
|
|
|
PaperTrail.serializer.dump(object_attrs_for_paper_trail)
|
2015-11-28 03:49:45 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2015-11-28 04:02:57 +00:00
|
|
|
# Returns an object which can be assigned to the `object_changes`
|
|
|
|
# attribute of a nascent version record. If the `object_changes` column is
|
|
|
|
# a postgres `json` column, then a hash can be used in the assignment,
|
|
|
|
# otherwise the column is a `text` column, and we must perform the
|
|
|
|
# serialization here, using `PaperTrail.serializer`.
|
|
|
|
# @api private
|
|
|
|
def pt_recordable_object_changes
|
|
|
|
if self.class.paper_trail_version_class.object_changes_col_is_json?
|
|
|
|
changes_for_paper_trail
|
|
|
|
else
|
|
|
|
PaperTrail.serializer.dump(changes_for_paper_trail)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2012-11-05 19:09:06 +00:00
|
|
|
def changes_for_paper_trail
|
2016-02-16 01:25:11 +00:00
|
|
|
notable_changes = changes.delete_if { |k, _v| !notably_changed.include?(k) }
|
2016-02-16 01:09:59 +00:00
|
|
|
self.class.serialize_attribute_changes_for_paper_trail!(notable_changes)
|
|
|
|
notable_changes.to_hash
|
2012-11-05 19:09:06 +00:00
|
|
|
end
|
|
|
|
|
2015-08-03 20:45:42 +00:00
|
|
|
# Invoked via`after_update` callback for when a previous version is
|
|
|
|
# reified and then saved.
|
2014-04-16 20:33:10 +00:00
|
|
|
def clear_version_instance!
|
2014-04-16 16:44:21 +00:00
|
|
|
send("#{self.class.version_association_name}=", nil)
|
|
|
|
end
|
|
|
|
|
2015-08-03 20:45:42 +00:00
|
|
|
# Invoked via callback when a user attempts to persist a reified
|
|
|
|
# `Version`.
|
2014-04-16 20:33:10 +00:00
|
|
|
def reset_timestamp_attrs_for_update_if_needed!
|
2016-03-05 22:17:23 +00:00
|
|
|
return if live?
|
2014-12-30 21:55:19 +00:00
|
|
|
timestamp_attributes_for_update_in_model.each do |column|
|
2015-08-03 20:45:42 +00:00
|
|
|
# ActiveRecord 4.2 deprecated `reset_column!` in favor of
|
|
|
|
# `restore_column!`.
|
2014-12-30 21:55:19 +00:00
|
|
|
if respond_to?("restore_#{column}!")
|
|
|
|
send("restore_#{column}!")
|
|
|
|
else
|
|
|
|
send("reset_#{column}!")
|
|
|
|
end
|
|
|
|
end
|
2014-04-16 20:33:10 +00:00
|
|
|
end
|
|
|
|
|
2010-10-28 10:14:19 +00:00
|
|
|
def record_destroy
|
2016-02-16 02:33:36 +00:00
|
|
|
if paper_trail_switched_on? && !new_record?
|
2013-10-16 19:28:16 +00:00
|
|
|
data = {
|
2016-03-05 22:17:23 +00:00
|
|
|
item_id: id,
|
2016-02-16 03:32:40 +00:00
|
|
|
item_type: self.class.base_class.name,
|
2016-03-05 22:07:32 +00:00
|
|
|
event: paper_trail_event || "destroy",
|
2016-02-16 03:32:40 +00:00
|
|
|
object: pt_recordable_object,
|
|
|
|
whodunnit: PaperTrail.whodunnit
|
2013-10-16 19:28:16 +00:00
|
|
|
}
|
2016-03-05 22:07:32 +00:00
|
|
|
if self.class.paper_trail_version_class.column_names.include?("transaction_id")
|
2014-12-31 18:32:16 +00:00
|
|
|
data[:transaction_id] = PaperTrail.transaction_id
|
|
|
|
end
|
2014-10-20 17:04:51 +00:00
|
|
|
version = self.class.paper_trail_version_class.create(merge_metadata(data))
|
|
|
|
send("#{self.class.version_association_name}=", version)
|
2013-01-03 23:24:53 +00:00
|
|
|
send(self.class.versions_association_name).send :load_target
|
2016-04-05 01:35:09 +00:00
|
|
|
update_transaction_id(version)
|
2014-02-25 14:02:36 +00:00
|
|
|
save_associations(version)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2015-08-03 20:45:42 +00:00
|
|
|
# Saves associations if the join table for `VersionAssociation` exists.
|
2014-02-25 14:02:36 +00:00
|
|
|
def save_associations(version)
|
2015-03-25 23:17:22 +00:00
|
|
|
return unless PaperTrail.config.track_associations?
|
2016-04-05 09:37:24 +00:00
|
|
|
save_associations_belongs_to(version)
|
|
|
|
save_associations_has_and_belongs_to_many(version)
|
|
|
|
end
|
|
|
|
|
|
|
|
def save_associations_belongs_to(version)
|
2014-02-25 14:02:36 +00:00
|
|
|
self.class.reflect_on_all_associations(:belongs_to).each do |assoc|
|
2015-02-04 16:11:27 +00:00
|
|
|
assoc_version_args = {
|
2016-03-14 00:00:15 +00:00
|
|
|
version_id: version.id,
|
|
|
|
foreign_key_name: assoc.foreign_key
|
2015-02-04 16:11:27 +00:00
|
|
|
}
|
|
|
|
|
2015-02-04 16:15:02 +00:00
|
|
|
if assoc.options[:polymorphic]
|
2015-04-16 17:31:49 +00:00
|
|
|
associated_record = send(assoc.name) if send(assoc.foreign_type)
|
2015-03-20 21:17:44 +00:00
|
|
|
if associated_record && associated_record.class.paper_trail_enabled_for_model?
|
2016-02-16 02:23:11 +00:00
|
|
|
assoc_version_args[:foreign_key_id] = associated_record.id
|
2015-02-04 16:11:27 +00:00
|
|
|
end
|
|
|
|
elsif assoc.klass.paper_trail_enabled_for_model?
|
2016-02-16 02:23:11 +00:00
|
|
|
assoc_version_args[:foreign_key_id] = send(assoc.foreign_key)
|
2014-12-04 12:32:28 +00:00
|
|
|
end
|
2015-02-04 16:11:27 +00:00
|
|
|
|
2016-02-16 03:15:35 +00:00
|
|
|
if assoc_version_args.key?(:foreign_key_id)
|
2015-11-28 04:37:29 +00:00
|
|
|
PaperTrail::VersionAssociation.create(assoc_version_args)
|
|
|
|
end
|
2010-10-28 10:14:19 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-04-05 09:37:24 +00:00
|
|
|
def save_associations_has_and_belongs_to_many(version)
|
|
|
|
# Use the :added and :removed keys to extrapolate the HABTM associations
|
|
|
|
# to before any changes were made
|
|
|
|
self.class.reflect_on_all_associations(:has_and_belongs_to_many).each do |a|
|
|
|
|
next unless
|
|
|
|
self.class.paper_trail_save_join_tables.include?(a.name) ||
|
|
|
|
a.klass.paper_trail_enabled_for_model?
|
|
|
|
assoc_version_args = {
|
|
|
|
version_id: version.id,
|
|
|
|
foreign_key_name: a.name
|
|
|
|
}
|
|
|
|
assoc_ids =
|
|
|
|
send(a.name).to_a.map(&:id) +
|
|
|
|
(@paper_trail_habtm.try(:[], a.name).try(:[], :removed) || []) -
|
|
|
|
(@paper_trail_habtm.try(:[], a.name).try(:[], :added) || [])
|
|
|
|
assoc_ids.each do |id|
|
|
|
|
PaperTrail::VersionAssociation.create(assoc_version_args.merge(foreign_key_id: id))
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2014-02-25 14:02:36 +00:00
|
|
|
def reset_transaction_id
|
|
|
|
PaperTrail.transaction_id = nil
|
|
|
|
end
|
|
|
|
|
2010-03-19 17:21:16 +00:00
|
|
|
def merge_metadata(data)
|
2010-03-19 18:53:49 +00:00
|
|
|
# First we merge the model-level metadata in `meta`.
|
2016-02-16 01:25:11 +00:00
|
|
|
paper_trail_options[:meta].each do |k, v|
|
2011-03-16 15:32:28 +00:00
|
|
|
data[k] =
|
2011-02-06 08:32:35 +00:00
|
|
|
if v.respond_to?(:call)
|
|
|
|
v.call(self)
|
2016-04-07 10:52:07 +00:00
|
|
|
elsif v.is_a?(Symbol) && respond_to?(v, true)
|
2015-08-03 20:45:42 +00:00
|
|
|
# If it is an attribute that is changing in an existing object,
|
|
|
|
# be sure to grab the current version.
|
2016-03-05 22:07:32 +00:00
|
|
|
if has_attribute?(v) && send("#{v}_changed?".to_sym) && data[:event] != "create"
|
2012-10-11 15:37:24 +00:00
|
|
|
send("#{v}_was".to_sym)
|
2012-10-10 23:39:55 +00:00
|
|
|
else
|
|
|
|
send(v)
|
|
|
|
end
|
2011-02-06 08:32:35 +00:00
|
|
|
else
|
|
|
|
v
|
|
|
|
end
|
2010-03-19 17:21:16 +00:00
|
|
|
end
|
2015-08-03 20:45:42 +00:00
|
|
|
|
2010-03-19 18:53:49 +00:00
|
|
|
# Second we merge any extra data from the controller (if available).
|
|
|
|
data.merge(PaperTrail.controller_info || {})
|
2010-01-06 12:57:54 +00:00
|
|
|
end
|
|
|
|
|
2015-01-29 23:16:54 +00:00
|
|
|
def attributes_before_change
|
Maps enums to database values before storing in `object_changes`
Keep consistency between versions with regard to `changes` and
`object_changes` and how enum columns store their values.
Before, `changes` would map the changed attributes enum columns to the database
values (integer values). This allows reifying that version to maintain the
integrity of the enum. It did not do so for `object_changes` and thus, `0`
for non-json columns, and the enum value for json columns would be stored instead.
For the non-json columns, it mapped any non-integer enum value to `0` because
during serialization that column is an `integer`. Now this is fixed,
so that `object_changes` stores the enum mapped value.
Here is an example:
```ruby
class PostWithStatus < ActiveRecord::Base
has_paper_trail
enum status: { draft: 0, published: 1, archived: 2 }
end
post = PostWithStatus.new(status: :draft)
post.published!
version = post.versions.last
# Before
version.changeset #> { 'status' => ['draft', 'draft'] } (stored as [0, 0])
# After
version.changeset #> { 'status' => ['draft', 'published'] } (stored as [0, 1])
```
2016-03-11 09:18:40 +00:00
|
|
|
changed = changed_attributes.select { |k, _v| self.class.column_names.include?(k) }
|
|
|
|
attributes.merge(changed)
|
2009-05-27 15:21:20 +00:00
|
|
|
end
|
|
|
|
|
2015-08-03 20:45:42 +00:00
|
|
|
# Returns hash of attributes (with appropriate attributes serialized),
|
|
|
|
# ommitting attributes to be skipped.
|
Maps enums to database values before storing in `object_changes`
Keep consistency between versions with regard to `changes` and
`object_changes` and how enum columns store their values.
Before, `changes` would map the changed attributes enum columns to the database
values (integer values). This allows reifying that version to maintain the
integrity of the enum. It did not do so for `object_changes` and thus, `0`
for non-json columns, and the enum value for json columns would be stored instead.
For the non-json columns, it mapped any non-integer enum value to `0` because
during serialization that column is an `integer`. Now this is fixed,
so that `object_changes` stores the enum mapped value.
Here is an example:
```ruby
class PostWithStatus < ActiveRecord::Base
has_paper_trail
enum status: { draft: 0, published: 1, archived: 2 }
end
post = PostWithStatus.new(status: :draft)
post.published!
version = post.versions.last
# Before
version.changeset #> { 'status' => ['draft', 'draft'] } (stored as [0, 0])
# After
version.changeset #> { 'status' => ['draft', 'published'] } (stored as [0, 1])
```
2016-03-11 09:18:40 +00:00
|
|
|
def object_attrs_for_paper_trail
|
|
|
|
attrs = attributes_before_change.except(*paper_trail_options[:skip])
|
2015-12-09 15:58:27 +00:00
|
|
|
self.class.serialize_attributes_for_paper_trail!(attrs)
|
2014-12-29 19:38:32 +00:00
|
|
|
attrs
|
2010-03-19 17:21:16 +00:00
|
|
|
end
|
2009-10-28 13:12:36 +00:00
|
|
|
|
2015-08-03 20:45:42 +00:00
|
|
|
# Determines whether it is appropriate to generate a new version
|
|
|
|
# instance. A timestamp-only update (e.g. only `updated_at` changed) is
|
|
|
|
# considered notable unless an ignored attribute was also changed.
|
2011-02-06 07:46:06 +00:00
|
|
|
def changed_notably?
|
2015-07-16 01:28:07 +00:00
|
|
|
if ignored_attr_has_changed?
|
|
|
|
timestamps = timestamp_attributes_for_update_in_model.map(&:to_s)
|
|
|
|
(notably_changed - timestamps).any?
|
2014-10-06 23:13:39 +00:00
|
|
|
else
|
|
|
|
notably_changed.any?
|
|
|
|
end
|
2011-02-06 07:46:06 +00:00
|
|
|
end
|
|
|
|
|
2015-07-16 01:28:07 +00:00
|
|
|
# An attributed is "ignored" if it is listed in the `:ignore` option
|
2015-08-03 20:45:42 +00:00
|
|
|
# and/or the `:skip` option. Returns true if an ignored attribute has
|
|
|
|
# changed.
|
2015-07-16 01:28:07 +00:00
|
|
|
def ignored_attr_has_changed?
|
2016-03-05 22:17:23 +00:00
|
|
|
ignored = paper_trail_options[:ignore] + paper_trail_options[:skip]
|
2015-07-16 01:28:07 +00:00
|
|
|
ignored.any? && (changed & ignored).any?
|
|
|
|
end
|
|
|
|
|
2011-02-06 07:46:06 +00:00
|
|
|
def notably_changed
|
2016-03-05 22:17:23 +00:00
|
|
|
only = paper_trail_options[:only].dup
|
2015-08-03 20:45:42 +00:00
|
|
|
# Remove Hash arguments and then evaluate whether the attributes (the
|
|
|
|
# keys of the hash) should also get pushed into the collection.
|
2013-09-19 20:18:02 +00:00
|
|
|
only.delete_if do |obj|
|
2015-11-28 04:37:29 +00:00
|
|
|
obj.is_a?(Hash) &&
|
|
|
|
obj.each { |attr, condition|
|
|
|
|
only << attr if condition.respond_to?(:call) && condition.call(self)
|
|
|
|
}
|
2013-09-19 11:34:13 +00:00
|
|
|
end
|
2012-05-07 20:13:21 +00:00
|
|
|
only.empty? ? changed_and_not_ignored : (changed_and_not_ignored & only)
|
2011-02-25 14:36:32 +00:00
|
|
|
end
|
2011-03-16 15:32:28 +00:00
|
|
|
|
2011-02-25 14:36:32 +00:00
|
|
|
def changed_and_not_ignored
|
2016-03-05 22:17:23 +00:00
|
|
|
ignore = paper_trail_options[:ignore].dup
|
2015-08-03 20:45:42 +00:00
|
|
|
# Remove Hash arguments and then evaluate whether the attributes (the
|
|
|
|
# keys of the hash) should also get pushed into the collection.
|
2013-09-19 20:18:02 +00:00
|
|
|
ignore.delete_if do |obj|
|
2015-11-28 04:37:29 +00:00
|
|
|
obj.is_a?(Hash) &&
|
|
|
|
obj.each { |attr, condition|
|
|
|
|
ignore << attr if condition.respond_to?(:call) && condition.call(self)
|
|
|
|
}
|
2013-09-19 11:34:13 +00:00
|
|
|
end
|
2016-03-05 22:17:23 +00:00
|
|
|
skip = paper_trail_options[:skip]
|
2012-05-07 20:13:21 +00:00
|
|
|
changed - ignore - skip
|
2010-03-19 17:21:16 +00:00
|
|
|
end
|
2010-03-19 15:21:07 +00:00
|
|
|
|
2013-08-20 17:34:42 +00:00
|
|
|
def paper_trail_switched_on?
|
2015-11-28 04:37:29 +00:00
|
|
|
PaperTrail.enabled? &&
|
|
|
|
PaperTrail.enabled_for_controller? &&
|
2016-03-05 22:17:23 +00:00
|
|
|
paper_trail_enabled_for_model?
|
2010-03-19 17:21:16 +00:00
|
|
|
end
|
2009-05-27 15:21:20 +00:00
|
|
|
|
2012-01-17 16:35:24 +00:00
|
|
|
def save_version?
|
2016-04-09 04:31:44 +00:00
|
|
|
if_condition = paper_trail_options[:if]
|
2016-03-05 22:17:23 +00:00
|
|
|
unless_condition = paper_trail_options[:unless]
|
2012-01-17 16:35:24 +00:00
|
|
|
(if_condition.blank? || if_condition.call(self)) && !unless_condition.try(:call, self)
|
2012-01-16 16:17:57 +00:00
|
|
|
end
|
2016-04-05 01:35:09 +00:00
|
|
|
|
|
|
|
# @api private
|
|
|
|
def update_transaction_id(version)
|
|
|
|
return unless self.class.paper_trail_version_class.column_names.include?("transaction_id")
|
|
|
|
if PaperTrail.transaction? && PaperTrail.transaction_id.nil?
|
|
|
|
PaperTrail.transaction_id = version.id
|
|
|
|
version.transaction_id = version.id
|
|
|
|
version.save
|
|
|
|
end
|
|
|
|
end
|
2012-01-16 16:17:57 +00:00
|
|
|
end
|
2010-03-19 17:21:16 +00:00
|
|
|
end
|
2009-05-27 15:21:20 +00:00
|
|
|
end
|