diff --git a/lib/vestal_versions/changes.rb b/lib/vestal_versions/changes.rb index 6ffa49b8..93557845 100644 --- a/lib/vestal_versions/changes.rb +++ b/lib/vestal_versions/changes.rb @@ -11,50 +11,48 @@ module Changes # Methods available to versioned ActiveRecord::Base instances in order to manage changes used # for version creation. - module InstanceMethods - # Collects an array of changes from a record's versions between the given range and compiles - # them into one summary hash of changes. The +from+ and +to+ arguments can each be either a - # version number, a symbol representing an association proxy method, a string representing a - # version tag or a version object itself. - def changes_between(from, to) - from_number, to_number = versions.number_at(from), versions.number_at(to) - return {} if from_number == to_number - chain = versions.between(from_number, to_number).reject(&:initial?) - return {} if chain.empty? + # Collects an array of changes from a record's versions between the given range and compiles + # them into one summary hash of changes. The +from+ and +to+ arguments can each be either a + # version number, a symbol representing an association proxy method, a string representing a + # version tag or a version object itself. + def changes_between(from, to) + from_number, to_number = versions.number_at(from), versions.number_at(to) + return {} if from_number == to_number + chain = versions.between(from_number, to_number).reject(&:initial?) + return {} if chain.empty? - backward = from_number > to_number - backward ? chain.pop : chain.shift unless from_number == 1 || to_number == 1 + backward = from_number > to_number + backward ? chain.pop : chain.shift unless from_number == 1 || to_number == 1 - chain.inject({}) do |changes, version| - changes.append_changes!(backward ? version.changes.reverse_changes : version.changes) - end + chain.inject({}) do |changes, version| + changes.append_changes!(backward ? version.changes.reverse_changes : version.changes) end + end - private - # Before a new version is created, the newly-changed attributes are appended onto a hash - # of previously-changed attributes. Typically the previous changes will be empty, except in - # the case that a control block is used where versions are to be merged. See - # VestalVersions::Control for more information. - def merge_version_changes - version_changes.append_changes!(incremental_version_changes) - end + private + # Before a new version is created, the newly-changed attributes are appended onto a hash + # of previously-changed attributes. Typically the previous changes will be empty, except in + # the case that a control block is used where versions are to be merged. See + # VestalVersions::Control for more information. + def merge_version_changes + version_changes.append_changes!(incremental_version_changes) + end - # Stores the cumulative changes that are eventually used for version creation. - def version_changes - @version_changes ||= {} - end + # Stores the cumulative changes that are eventually used for version creation. + def version_changes + @version_changes ||= {} + end - # Stores the incremental changes that are appended to the cumulative changes before version - # creation. Incremental changes are reset when the record is saved because they represent - # a subset of the dirty attribute changes, which are reset upon save. - def incremental_version_changes - changes.slice(*versioned_columns) - end + # Stores the incremental changes that are appended to the cumulative changes before version + # creation. Incremental changes are reset when the record is saved because they represent + # a subset of the dirty attribute changes, which are reset upon save. + def incremental_version_changes + changes.slice(*versioned_columns) + end - # Simply resets the cumulative changes after version creation. - def reset_version_changes - @version_changes = nil - end + # Simply resets the cumulative changes after version creation. + def reset_version_changes + @version_changes = nil end # Instance methods included into Hash for dealing with manipulation of hashes in the specific @@ -84,7 +82,7 @@ module HashMethods def append_changes(changes) changes.inject(self) do |new_changes, (attribute, change)| new_change = [new_changes.fetch(attribute, change).first, change.last] - new_changes.merge(attribute => new_change) + new_changes.merge(attribute => new_change) end.reject do |attribute, change| change.first == change.last end diff --git a/lib/vestal_versions/conditions.rb b/lib/vestal_versions/conditions.rb index 46431a1b..469af83c 100644 --- a/lib/vestal_versions/conditions.rb +++ b/lib/vestal_versions/conditions.rb @@ -25,33 +25,31 @@ def prepare_versioned_options(options) # Instance methods that determine based on the :if and :unless conditions, # whether a version is to be create or updated. - module InstanceMethods - private - # After first determining whether the :if and :unless conditions are - # satisfied, the original, unaliased +create_version?+ method is called to determine - # whether a new version should be created upon update of the ActiveRecord::Base instance. - def create_version? - version_conditions_met? && super - end + private + # After first determining whether the :if and :unless conditions are + # satisfied, the original, unaliased +create_version?+ method is called to determine + # whether a new version should be created upon update of the ActiveRecord::Base instance. + def create_version? + version_conditions_met? && super + end - # After first determining whether the :if and :unless conditions are - # satisfied, the original, unaliased +update_version?+ method is called to determine - # whther the last version should be updated to include changes merged from the current - # ActiveRecord::Base instance update. - # - # The overridden +update_version?+ method simply returns false, effectively delegating - # the decision to whether the :if and :unless conditions are met. - def update_version? - version_conditions_met? && super - end + # After first determining whether the :if and :unless conditions are + # satisfied, the original, unaliased +update_version?+ method is called to determine + # whther the last version should be updated to include changes merged from the current + # ActiveRecord::Base instance update. + # + # The overridden +update_version?+ method simply returns false, effectively delegating + # the decision to whether the :if and :unless conditions are met. + def update_version? + version_conditions_met? && super + end - # Simply checks whether the :if and :unless conditions given in the - # +versioned+ options are met: meaning that all procs in the :if array must - # evaluate to a non-false, non-nil value and that all procs in the :unless array - # must all evaluate to either false or nil. - def version_conditions_met? - vestal_versions_options[:if].all?{|p| p.call(self) } && !vestal_versions_options[:unless].any?{|p| p.call(self) } - end + # Simply checks whether the :if and :unless conditions given in the + # +versioned+ options are met: meaning that all procs in the :if array must + # evaluate to a non-false, non-nil value and that all procs in the :unless array + # must all evaluate to either false or nil. + def version_conditions_met? + vestal_versions_options[:if].all?{|p| p.call(self) } && !vestal_versions_options[:unless].any?{|p| p.call(self) } end end end diff --git a/lib/vestal_versions/control.rb b/lib/vestal_versions/control.rb index 0a9ca895..b76732d9 100644 --- a/lib/vestal_versions/control.rb +++ b/lib/vestal_versions/control.rb @@ -8,193 +8,190 @@ module Control class_attribute :_skip_version, :instance_writer => false end - # Control blocks are called on ActiveRecord::Base instances as to not cause any conflict with # other instances of the versioned class whose behavior could be inadvertently altered within # a control block. - module InstanceMethods - # The +skip_version+ block simply allows for updates to be made to an instance of a versioned - # ActiveRecord model while ignoring all new version creation. The :if and - # :unless conditions (if given) will not be evaulated inside a +skip_version+ block. - # - # When the block closes, the instance is automatically saved, so explicitly saving the - # object within the block is unnecessary. - # - # == Example - # - # user = User.find_by_first_name("Steve") - # user.version # => 1 - # user.skip_version do - # user.first_name = "Stephen" - # end - # user.version # => 1 - def skip_version - _with_version_flag(:_skip_version) do - yield if block_given? - save - end - end - - # Behaving almost identically to the +skip_version+ block, the only difference with the - # +skip_version!+ block is that the save automatically performed at the close of the block - # is a +save!+, meaning that an exception will be raised if the object cannot be saved. - def skip_version! - _with_version_flag(:_skip_version) do - yield if block_given? - save! - end - end - - # Merging versions with the +merge_version+ block will take all of the versions that would - # be created within the block and merge them into one version and pushing that single version - # onto the ActiveRecord::Base instance's version history. A new version will be created and - # the instance's version number will be incremented. - # - # == Example - # - # user = User.find_by_first_name("Steve") - # user.version # => 1 - # user.merge_version do - # user.update_attributes(:first_name => "Steven", :last_name => "Tyler") - # user.update_attribute(:first_name, "Stephen") - # user.update_attribute(:last_name, "Richert") - # end - # user.version # => 2 - # user.versions.last.changes - # # => {"first_name" => ["Steve", "Stephen"], "last_name" => ["Jobs", "Richert"]} - # - # See VestalVersions::Changes for an explanation on how changes are appended. - def merge_version - _with_version_flag(:merge_version) do - yield if block_given? - end + # The +skip_version+ block simply allows for updates to be made to an instance of a versioned + # ActiveRecord model while ignoring all new version creation. The :if and + # :unless conditions (if given) will not be evaulated inside a +skip_version+ block. + # + # When the block closes, the instance is automatically saved, so explicitly saving the + # object within the block is unnecessary. + # + # == Example + # + # user = User.find_by_first_name("Steve") + # user.version # => 1 + # user.skip_version do + # user.first_name = "Stephen" + # end + # user.version # => 1 + def skip_version + _with_version_flag(:_skip_version) do + yield if block_given? save end + end - # Behaving almost identically to the +merge_version+ block, the only difference with the - # +merge_version!+ block is that the save automatically performed at the close of the block - # is a +save!+, meaning that an exception will be raised if the object cannot be saved. - def merge_version! - _with_version_flag(:merge_version) do - yield if block_given? - end + # Behaving almost identically to the +skip_version+ block, the only difference with the + # +skip_version!+ block is that the save automatically performed at the close of the block + # is a +save!+, meaning that an exception will be raised if the object cannot be saved. + def skip_version! + _with_version_flag(:_skip_version) do + yield if block_given? save! end + end - # A convenience method for determining whether a versioned instance is set to merge its next - # versions into one before version creation. - def merge_version? - !!@merge_version + # Merging versions with the +merge_version+ block will take all of the versions that would + # be created within the block and merge them into one version and pushing that single version + # onto the ActiveRecord::Base instance's version history. A new version will be created and + # the instance's version number will be incremented. + # + # == Example + # + # user = User.find_by_first_name("Steve") + # user.version # => 1 + # user.merge_version do + # user.update_attributes(:first_name => "Steven", :last_name => "Tyler") + # user.update_attribute(:first_name, "Stephen") + # user.update_attribute(:last_name, "Richert") + # end + # user.version # => 2 + # user.versions.last.changes + # # => {"first_name" => ["Steve", "Stephen"], "last_name" => ["Jobs", "Richert"]} + # + # See VestalVersions::Changes for an explanation on how changes are appended. + def merge_version + _with_version_flag(:merge_version) do + yield if block_given? end + save + end - # Appending versions with the +append_version+ block acts similarly to the +merge_version+ - # block in that all would-be version creations within the block are defered until the block - # closes. The major difference is that with +append_version+, a new version is not created. - # Rather, the cumulative changes are appended to the serialized changes of the instance's - # last version. A new version is not created, so the version number is not incremented. - # - # == Example - # - # user = User.find_by_first_name("Steve") - # user.version # => 2 - # user.versions.last.changes - # # => {"first_name" => ["Stephen", "Steve"]} - # user.append_version do - # user.last_name = "Jobs" - # end - # user.versions.last.changes - # # => {"first_name" => ["Stephen", "Steve"], "last_name" => ["Richert", "Jobs"]} - # user.version # => 2 - # - # See VestalVersions::Changes for an explanation on how changes are appended. - def append_version - _with_version_flag(:merge_version) do - yield if block_given? - end - - _with_version_flag(:append_version) do - save - end + # Behaving almost identically to the +merge_version+ block, the only difference with the + # +merge_version!+ block is that the save automatically performed at the close of the block + # is a +save!+, meaning that an exception will be raised if the object cannot be saved. + def merge_version! + _with_version_flag(:merge_version) do + yield if block_given? end + save! + end - # Behaving almost identically to the +append_version+ block, the only difference with the - # +append_version!+ block is that the save automatically performed at the close of the block - # is a +save!+, meaning that an exception will be raised if the object cannot be saved. - def append_version! - _with_version_flag(:merge_version) do - yield if block_given? - end - - _with_version_flag(:append_version) do - save! - end - end + # A convenience method for determining whether a versioned instance is set to merge its next + # versions into one before version creation. + def merge_version? + !!@merge_version + end - # A convenience method for determining whether a versioned instance is set to append its next - # version's changes into the last version changes. - def append_version? - !!@append_version + # Appending versions with the +append_version+ block acts similarly to the +merge_version+ + # block in that all would-be version creations within the block are defered until the block + # closes. The major difference is that with +append_version+, a new version is not created. + # Rather, the cumulative changes are appended to the serialized changes of the instance's + # last version. A new version is not created, so the version number is not incremented. + # + # == Example + # + # user = User.find_by_first_name("Steve") + # user.version # => 2 + # user.versions.last.changes + # # => {"first_name" => ["Stephen", "Steve"]} + # user.append_version do + # user.last_name = "Jobs" + # end + # user.versions.last.changes + # # => {"first_name" => ["Stephen", "Steve"], "last_name" => ["Richert", "Jobs"]} + # user.version # => 2 + # + # See VestalVersions::Changes for an explanation on how changes are appended. + def append_version + _with_version_flag(:merge_version) do + yield if block_given? end - # Used for each control block, the +_with_version_flag+ method sets a given variable to - # true and then executes the given block, ensuring that the variable is returned to a nil - # value before returning. This is useful to be certain that one of the control flag - # instance variables isn't inadvertently left in the "on" position by execution within the - # block raising an exception. - def _with_version_flag(flag) - instance_variable_set("@#{flag}", true) - yield - ensure - remove_instance_variable("@#{flag}") + _with_version_flag(:append_version) do + save end + end - # Overrides the basal +create_version?+ method to make sure that new versions are not - # created when inside any of the control blocks (until the block terminates). - def create_version? - !_skip_version? && !merge_version? && !append_version? && super + # Behaving almost identically to the +append_version+ block, the only difference with the + # +append_version!+ block is that the save automatically performed at the close of the block + # is a +save!+, meaning that an exception will be raised if the object cannot be saved. + def append_version! + _with_version_flag(:merge_version) do + yield if block_given? end - # Overrides the basal +update_version?+ method to allow the last version of an versioned - # ActiveRecord::Base instance to be updated at the end of an +append_version+ block. - def update_version? - append_version? + _with_version_flag(:append_version) do + save! end + end + # A convenience method for determining whether a versioned instance is set to append its next + # version's changes into the last version changes. + def append_version? + !!@append_version + end + + # Used for each control block, the +_with_version_flag+ method sets a given variable to + # true and then executes the given block, ensuring that the variable is returned to a nil + # value before returning. This is useful to be certain that one of the control flag + # instance variables isn't inadvertently left in the "on" position by execution within the + # block raising an exception. + def _with_version_flag(flag) + instance_variable_set("@#{flag}", true) + yield + ensure + remove_instance_variable("@#{flag}") + end + + # Overrides the basal +create_version?+ method to make sure that new versions are not + # created when inside any of the control blocks (until the block terminates). + def create_version? + !_skip_version? && !merge_version? && !append_version? && super + end + + # Overrides the basal +update_version?+ method to allow the last version of an versioned + # ActiveRecord::Base instance to be updated at the end of an +append_version+ block. + def update_version? + append_version? end - module ClassMethods - # The +skip_version+ block simply allows for updates to be made to an instance of a versioned - # ActiveRecord model while ignoring all new version creation. The :if and - # :unless conditions (if given) will not be evaulated inside a +skip_version+ block. - # - # When the block closes, the instance is automatically saved, so explicitly saving the - # object within the block is unnecessary. - # - # == Example - # - # user = User.find_by_first_name("Steve") - # user.version # => 1 - # user.skip_version do - # user.first_name = "Stephen" - # end - # user.version # => 1 - def skip_version - _with_version_flag(:_skip_version) do - yield if block_given? - end - end - # Used for each control block, the +with_version_flag+ method sets a given variable to - # true and then executes the given block, ensuring that the variable is returned to a nil - # value before returning. This is useful to be certain that one of the control flag - # instance variables isn't inadvertently left in the "on" position by execution within the - # block raising an exception. - def _with_version_flag(flag) - self.send("#{flag}=", true) - yield - ensure - self.send("#{flag}=", nil) + end + module ClassMethods + # The +skip_version+ block simply allows for updates to be made to an instance of a versioned + # ActiveRecord model while ignoring all new version creation. The :if and + # :unless conditions (if given) will not be evaulated inside a +skip_version+ block. + # + # When the block closes, the instance is automatically saved, so explicitly saving the + # object within the block is unnecessary. + # + # == Example + # + # user = User.find_by_first_name("Steve") + # user.version # => 1 + # user.skip_version do + # user.first_name = "Stephen" + # end + # user.version # => 1 + def skip_version + _with_version_flag(:_skip_version) do + yield if block_given? end + end + # Used for each control block, the +with_version_flag+ method sets a given variable to + # true and then executes the given block, ensuring that the variable is returned to a nil + # value before returning. This is useful to be certain that one of the control flag + # instance variables isn't inadvertently left in the "on" position by execution within the + # block raising an exception. + def _with_version_flag(flag) + self.send("#{flag}=", true) + yield + ensure + self.send("#{flag}=", nil) end + end end diff --git a/lib/vestal_versions/creation.rb b/lib/vestal_versions/creation.rb index 362bb469..aebbb4ab 100644 --- a/lib/vestal_versions/creation.rb +++ b/lib/vestal_versions/creation.rb @@ -21,73 +21,71 @@ def prepare_versioned_options(options) self.vestal_versions_options[:only] = Array(options.delete(:only)).map(&:to_s).uniq if options[:only] self.vestal_versions_options[:except] = Array(options.delete(:except)).map(&:to_s).uniq if options[:except] self.vestal_versions_options[:initial_version] = options.delete(:initial_version) - + result end end # Instance methods that determine whether to save a version and actually perform the save. - module InstanceMethods - private - # Returns whether an initial version should be created upon creation of the parent record. - def create_initial_version? - vestal_versions_options[:initial_version] == true - end + private + # Returns whether an initial version should be created upon creation of the parent record. + def create_initial_version? + vestal_versions_options[:initial_version] == true + end - # Creates an initial version upon creation of the parent record. - def create_initial_version - versions.create(version_attributes.merge(:number => 1)) - reset_version_changes - reset_version - end - - # Returns whether a new version should be created upon updating the parent record. - def create_version? - !version_changes.blank? - end + # Creates an initial version upon creation of the parent record. + def create_initial_version + versions.create(version_attributes.merge(:number => 1)) + reset_version_changes + reset_version + end - # Creates a new version upon updating the parent record. - def create_version(attributes = nil) - versions.create(attributes || version_attributes) - reset_version_changes - reset_version - end + # Returns whether a new version should be created upon updating the parent record. + def create_version? + !version_changes.blank? + end - # Returns whether the last version should be updated upon updating the parent record. - # This method is overridden in VestalVersions::Control to account for a control block that - # merges changes onto the previous version. - def update_version? - false - end + # Creates a new version upon updating the parent record. + def create_version(attributes = nil) + versions.create(attributes || version_attributes) + reset_version_changes + reset_version + end - # Updates the last version's changes by appending the current version changes. - def update_version - return create_version unless v = versions.last - v.modifications_will_change! - v.update_attribute(:modifications, v.changes.append_changes(version_changes)) - reset_version_changes - reset_version - end + # Returns whether the last version should be updated upon updating the parent record. + # This method is overridden in VestalVersions::Control to account for a control block that + # merges changes onto the previous version. + def update_version? + false + end - # Returns an array of column names that should be included in the changes of created - # versions. If vestal_versions_options[:only] is specified, only those columns - # will be versioned. Otherwise, if vestal_versions_options[:except] is specified, - # all columns will be versioned other than those specified. Without either option, the - # default is to version all columns. At any rate, the four "automagic" timestamp columns - # maintained by Rails are never versioned. - def versioned_columns - case - when vestal_versions_options[:only] then self.class.column_names & vestal_versions_options[:only] - when vestal_versions_options[:except] then self.class.column_names - vestal_versions_options[:except] - else self.class.column_names - end - %w(created_at created_on updated_at updated_on) - end + # Updates the last version's changes by appending the current version changes. + def update_version + return create_version unless v = versions.last + v.modifications_will_change! + v.update_attribute(:modifications, v.changes.append_changes(version_changes)) + reset_version_changes + reset_version + end - # Specifies the attributes used during version creation. This is separated into its own - # method so that it can be overridden by the VestalVersions::Users feature. - def version_attributes - {:modifications => version_changes, :number => last_version + 1} - end - end + # Returns an array of column names that should be included in the changes of created + # versions. If vestal_versions_options[:only] is specified, only those columns + # will be versioned. Otherwise, if vestal_versions_options[:except] is specified, + # all columns will be versioned other than those specified. Without either option, the + # default is to version all columns. At any rate, the four "automagic" timestamp columns + # maintained by Rails are never versioned. + def versioned_columns + case + when vestal_versions_options[:only] then self.class.column_names & vestal_versions_options[:only] + when vestal_versions_options[:except] then self.class.column_names - vestal_versions_options[:except] + else self.class.column_names + end - %w(created_at created_on updated_at updated_on) + end + + # Specifies the attributes used during version creation. This is separated into its own + # method so that it can be overridden by the VestalVersions::Users feature. + def version_attributes + {:modifications => version_changes, :number => last_version + 1} + end end end diff --git a/lib/vestal_versions/deletion.rb b/lib/vestal_versions/deletion.rb index b9ecec34..2e0ffab1 100644 --- a/lib/vestal_versions/deletion.rb +++ b/lib/vestal_versions/deletion.rb @@ -23,17 +23,15 @@ def prepare_versioned_options(options) end end - module InstanceMethods - private + private - def delete_version? - vestal_versions_options[:track_destroy] - end - - def create_destroyed_version - create_version({:modifications => attributes, :number => last_version + 1, :tag => 'deleted'}) - end + def delete_version? + vestal_versions_options[:track_destroy] + end + def create_destroyed_version + create_version({:modifications => attributes, :number => last_version + 1, :tag => 'deleted'}) end + end end diff --git a/lib/vestal_versions/reload.rb b/lib/vestal_versions/reload.rb index 458f2fed..429e7d79 100644 --- a/lib/vestal_versions/reload.rb +++ b/lib/vestal_versions/reload.rb @@ -5,13 +5,11 @@ module Reload extend ActiveSupport::Concern # Adds instance methods into ActiveRecord::Base to tap into the +reload+ method. - module InstanceMethods - # Overrides ActiveRecord::Base#reload, resetting the instance-variable-cached version number - # before performing the original +reload+ method. - def reload(*args) - reset_version - super - end + # Overrides ActiveRecord::Base#reload, resetting the instance-variable-cached version number + # before performing the original +reload+ method. + def reload(*args) + reset_version + super end end end diff --git a/lib/vestal_versions/reset.rb b/lib/vestal_versions/reset.rb index 7af2da26..41e20fe8 100644 --- a/lib/vestal_versions/reset.rb +++ b/lib/vestal_versions/reset.rb @@ -4,21 +4,19 @@ module Reset extend ActiveSupport::Concern # Adds the instance methods required to reset an object to a previous version. - module InstanceMethods - # Similar to +revert_to!+, the +reset_to!+ method reverts an object to a previous version, - # only instead of creating a new record in the version history, +reset_to!+ deletes all of - # the version history that occurs after the version reverted to. - # - # The action taken on each version record after the point of reversion is determined by the - # :dependent option given to the +versioned+ method. See the +versioned+ method - # documentation for more details. - def reset_to!(value) - if saved = skip_version{ revert_to!(value) } - association(:versions).send(:delete_records, versions.after(value), self.class.reflect_on_association(:versions).options[:dependent]) - reset_version - end - saved + # Similar to +revert_to!+, the +reset_to!+ method reverts an object to a previous version, + # only instead of creating a new record in the version history, +reset_to!+ deletes all of + # the version history that occurs after the version reverted to. + # + # The action taken on each version record after the point of reversion is determined by the + # :dependent option given to the +versioned+ method. See the +versioned+ method + # documentation for more details. + def reset_to!(value) + if saved = skip_version{ revert_to!(value) } + association(:versions).send(:delete_records, versions.after(value), self.class.reflect_on_association(:versions).options[:dependent]) + reset_version end + saved end end end diff --git a/lib/vestal_versions/reversion.rb b/lib/vestal_versions/reversion.rb index 4aae830f..bf22ec80 100644 --- a/lib/vestal_versions/reversion.rb +++ b/lib/vestal_versions/reversion.rb @@ -4,79 +4,77 @@ module Reversion extend ActiveSupport::Concern # Provides the base instance methods required to revert a versioned instance. - module InstanceMethods - # Returns the current version number for the versioned object. - def version - @version ||= last_version - end - - # Accepts a value corresponding to a specific version record, builds a history of changes - # between that version and the current version, and then iterates over that history updating - # the object's attributes until the it's reverted to its prior state. - # - # The single argument should adhere to one of the formats as documented in the +at+ method of - # VestalVersions::Versions. - # - # After the object is reverted to the target version, it is not saved. In order to save the - # object after the reversion, use the +revert_to!+ method. - # - # The version number of the object will reflect whatever version has been reverted to, and - # the return value of the +revert_to+ method is also the target version number. - def revert_to(value) - to_number = versions.number_at(value) + # Returns the current version number for the versioned object. + def version + @version ||= last_version + end - changes_between(version, to_number).each do |attribute, change| - write_attribute(attribute, change.last) - end + # Accepts a value corresponding to a specific version record, builds a history of changes + # between that version and the current version, and then iterates over that history updating + # the object's attributes until the it's reverted to its prior state. + # + # The single argument should adhere to one of the formats as documented in the +at+ method of + # VestalVersions::Versions. + # + # After the object is reverted to the target version, it is not saved. In order to save the + # object after the reversion, use the +revert_to!+ method. + # + # The version number of the object will reflect whatever version has been reverted to, and + # the return value of the +revert_to+ method is also the target version number. + def revert_to(value) + to_number = versions.number_at(value) - reset_version(to_number) + changes_between(version, to_number).each do |attribute, change| + write_attribute(attribute, change.last) end - # Behaves similarly to the +revert_to+ method except that it automatically saves the record - # after the reversion. The return value is the success of the save. - def revert_to!(value) - revert_to(value) - reset_version if saved = save - saved - end + reset_version(to_number) + end - # Returns a boolean specifying whether the object has been reverted to a previous version or - # if the object represents the latest version in the version history. - def reverted? - version != last_version - end + # Behaves similarly to the +revert_to+ method except that it automatically saves the record + # after the reversion. The return value is the success of the save. + def revert_to!(value) + revert_to(value) + reset_version if saved = save + saved + end - private + # Returns a boolean specifying whether the object has been reverted to a previous version or + # if the object represents the latest version in the version history. + def reverted? + version != last_version + end + + private - # Mixes in the reverted_from value if it is currently within a revert - def version_attributes - attributes = super + # Mixes in the reverted_from value if it is currently within a revert + def version_attributes + attributes = super - if @reverted_from.nil? - attributes - else - attributes.merge(:reverted_from => @reverted_from) - end - end + if @reverted_from.nil? + attributes + else + attributes.merge(:reverted_from => @reverted_from) + end + end - # Returns the number of the last created version in the object's version history. - # - # If no associated versions exist, the object is considered at version 1. - def last_version - @last_version ||= versions.maximum(:number) || 1 - end + # Returns the number of the last created version in the object's version history. + # + # If no associated versions exist, the object is considered at version 1. + def last_version + @last_version ||= versions.maximum(:number) || 1 + end - # Clears the cached version number instance variables so that they can be recalculated. - # Useful after a new version is created. - def reset_version(version = nil) - if version.nil? - @last_version = nil - @reverted_from = nil - else - @reverted_from = version - end - @version = version - end + # Clears the cached version number instance variables so that they can be recalculated. + # Useful after a new version is created. + def reset_version(version = nil) + if version.nil? + @last_version = nil + @reverted_from = nil + else + @reverted_from = version + end + @version = version end end end diff --git a/lib/vestal_versions/users.rb b/lib/vestal_versions/users.rb index a817b605..856bca2f 100644 --- a/lib/vestal_versions/users.rb +++ b/lib/vestal_versions/users.rb @@ -11,14 +11,12 @@ module Users # Methods added to versioned ActiveRecord::Base instances to enable versioning with additional # user information. - module InstanceMethods - private - # Overrides the +version_attributes+ method to include user information passed into the - # parent object, by way of a +updated_by+ attr_accessor. - def version_attributes - super.merge(:user => updated_by) - end + private + # Overrides the +version_attributes+ method to include user information passed into the + # parent object, by way of a +updated_by+ attr_accessor. + def version_attributes + super.merge(:user => updated_by) end # Instance methods added to VestalVersions::Version to accomodate incoming user information. diff --git a/lib/vestal_versions/version_tagging.rb b/lib/vestal_versions/version_tagging.rb index 04f64a7e..e7f4f41b 100644 --- a/lib/vestal_versions/version_tagging.rb +++ b/lib/vestal_versions/version_tagging.rb @@ -5,19 +5,17 @@ module VersionTagging extend ActiveSupport::Concern # Adds an instance method which allows version tagging through the parent object. - module InstanceMethods - # Accepts a single string argument which is attached to the version record associated with - # the current version number of the parent object. - # - # Returns the given tag if successful, nil if not. Tags must be unique within the scope of - # the parent object. Tag creation will fail if non-unique. - # - # Version records corresponding to version number 1 are not typically created, but one will - # be built to house the given tag if the parent object's current version number is 1. - def tag_version(tag) - v = versions.at(version) || versions.build(:number => 1) - v.tag!(tag) - end + # Accepts a single string argument which is attached to the version record associated with + # the current version number of the parent object. + # + # Returns the given tag if successful, nil if not. Tags must be unique within the scope of + # the parent object. Tag creation will fail if non-unique. + # + # Version records corresponding to version number 1 are not typically created, but one will + # be built to house the given tag if the parent object's current version number is 1. + def tag_version(tag) + v = versions.at(version) || versions.build(:number => 1) + v.tag!(tag) end # Instance methods included into VestalVersions::Version to enable version tagging.