# encoding: utf-8 module Mongoid #:nodoc: module Dirty #:nodoc: extend ActiveSupport::Concern # Get the changed attributes for the document. # # @example Get the changed attributes. # model.changed # # @return [ Array ] The changed attributes. # # @since 2.4.0 def changed changed_attributes.keys end # Has the document changed? # # @example Has the document changed? # model.changed? # # @return [ true, false ] If the document is changed. # # @since 2.4.0 def changed? changed_attributes.any? end # Get the attribute changes. # # @example Get the attribute changes. # model.changed_attributes # # @return [ Hash ] The attribute changes. # # @since 2.4.0 def changed_attributes @changed_attributes ||= {} end # Get all the changes for the document. # # @example Get all the changes. # model.changes # # @return [ Hash ] The changes. # # @since 2.4.0 def changes changed.inject({}.with_indifferent_access) do |changes, attr| changes.tap do |hash| hash[attr] = attribute_change(attr) end end end # Call this method after save, so the changes can be properly switched. # # This will unset the memoized children array, set new record to # false, set the document as validated, and move the dirty changes. # # @example Move the changes to previous. # person.move_changes # # @since 2.1.0 def move_changes @_children = nil @previous_changes = changes Atomic::UPDATES.each do |update| send(update).clear end changed_attributes.clear end # Get the previous changes on the document. # # @example Get the previous changes. # model.previous_changes # # @return [ Hash ] The previous changes. # # @since 2.4.0 def previous_changes @previous_changes end # Remove a change from the dirty attributes hash. Used by the single field # atomic updators. # # @example Remove a flagged change. # model.remove_change(:field) # # @param [ Symbol, String ] name The name of the field. # # @since 2.1.0 def remove_change(name) changed_attributes.delete(name.to_s) end # Gets all the new values for each of the changed fields, to be passed to # a MongoDB $set modifier. # # @todo: Durran: Refactor 3.0 # # @example Get the setters for the atomic updates. # person = Person.new(:title => "Sir") # person.title = "Madam" # person.setters # returns { "title" => "Madam" } # # @return [ Hash ] A +Hash+ of atomic setters. def setters {}.tap do |modifications| changes.each_pair do |name, changes| if changes old, new = changes field = fields[name] key = embedded? ? "#{atomic_position}.#{name}" : name if field && field.resizable? pushes, pulls = new - (old || []), (old || []) - new field.add_atomic_changes(self, key, modifications, pushes, pulls) else modifications[key] = new end end end end end private # Get the old and new value for the provided attribute. # # @example Get the attribute change. # model.attribute_change("name") # # @param [ String ] attr The name of the attribute. # # @return [ Array ] The old and new values. # # @since 2.1.0 def attribute_change(attr) [changed_attributes[attr], attributes[attr]] if attribute_changed?(attr) end # Determine if a specific attribute has changed. # # @example Has the attribute changed? # model.attribute_changed?("name") # # @param [ String ] attr The name of the attribute. # # @return [ true, false ] Whether the attribute has changed. # # @since 2.1.6 def attribute_changed?(attr) return false unless changed_attributes.has_key?(attr) changed_attributes[attr] != attributes[attr] end # Get the previous value for the attribute. # # @example Get the previous value. # model.attribute_was("name") # # @param [ String ] attr The attribute name. # # @since 2.4.0 def attribute_was(attr) attribute_changed?(attr) ? changed_attributes[attr] : attributes[attr] end # Flag an attribute as going to change. # # @example Flag the attribute. # model.attribute_will_change!("name") # # @param [ String ] attr The name of the attribute. # # @return [ Object ] The old value. # # @since 2.3.0 def attribute_will_change!(attr) unless changed_attributes.has_key?(attr) changed_attributes[attr] = read_attribute(attr)._deep_copy end end # Set the attribute back to it's old value. # # @example Reset the attribute. # model.reset_attribute!("name") # # @param [ String ] attr The name of the attribute. # # @return [ Object ] The old value. # # @since 2.4.0 def reset_attribute!(attr) attributes[attr] = changed_attributes[attr] if attribute_changed?(attr) end module ClassMethods #:nodoc: private # Generate all the dirty methods needed for the attribute. # # @example Generate the dirty methods. # Model.create_dirty_methods("name") # # @param [ String ] name The name of the attribute. # # @return [ Module ] The fields module. # # @since 2.4.0 def create_dirty_methods(name) generated_methods.module_eval do class_eval <<-EOM def #{name}_change attribute_change(#{name.inspect}) end def #{name}_changed? attribute_changed?(#{name.inspect}) end def #{name}_was attribute_was(#{name.inspect}) end def #{name}_will_change! attribute_will_change!(#{name.inspect}) end def reset_#{name}! reset_attribute!(#{name.inspect}) end EOM end end end end end