lib/vestal_versions/control.rb in vestal_versions-1.0.2 vs lib/vestal_versions/control.rb in vestal_versions-2.0.0

- old
+ new

@@ -1,175 +1,199 @@ module VestalVersions # The control feature allows use of several code blocks that provide finer control over whether # a new version is created, or a previous version is updated. module Control - def self.included(base) # :nodoc: - base.class_eval do - include InstanceMethods + extend ActiveSupport::Concern - alias_method_chain :create_version?, :control - alias_method_chain :update_version?, :control - end + included do + 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 <tt>:if</tt> and - # <tt>:unless</tt> 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 + + # 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 <tt>:if</tt> and + # <tt>:unless</tt> 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 + # 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 skip its next - # version creation. - def skip_version? - !!@skip_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 - # 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 + # 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 + + # 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 + + # 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 + 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! + # 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 - # 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 + _with_version_flag(:append_version) do + save! end + 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. + # 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 <tt>:if</tt> and + # <tt>:unless</tt> 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 # => 2 - # user.versions.last.changes - # # => {"first_name" => ["Stephen", "Steve"]} - # user.append_version do - # user.last_name = "Jobs" + # user.version # => 1 + # user.skip_version do + # user.first_name = "Stephen" # 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 + # user.version # => 1 + def skip_version + _with_version_flag(:_skip_version) do yield if block_given? end - - with_version_flag(:append_version) do - save - end 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 + # 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 - # 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 - - private - # 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) - begin - instance_variable_set("@#{flag}", true) - yield - ensure - instance_variable_set("@#{flag}", nil) - 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_with_control? - !skip_version? && !merge_version? && !append_version? && create_version_without_control? - 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_with_control? - append_version? - end end end end