module ActiveModel::Dirty - activemodel-4.2.3 Documentation

# module ActiveModel::Dirty

## Active Model Dirty¶ ↑

Provides a way to track changes in your object in the same way as Active Record does.

The requirements for implementing ActiveModel::Dirty are:

- `include ActiveModel::Dirty` in your object.

- Call `define_attribute_methods` passing each method you want to track.

- Call `attr_name_will_change!` before each change to the tracked attribute.

- Call `changes_applied` after the changes are persisted.

- Call `clear_changes_information` when you want to reset the changes information.

- Call `restore_attributes` when you want to restore previous data.

A minimal implementation could be:

    class Person
      include ActiveModel::Dirty

      define_attribute_methods :name

      def name
        @name
      end

      def name=(val)
        name_will_change! unless val == @name
        @name = val
      end

      def save
        # do persistence work

        changes_applied
      end

      def reload!
        # get the values from the persistence layer

        clear_changes_information
      end

      def rollback!
        restore_attributes
      end
    end

A newly instantiated `Person` object is unchanged:

    person = Person.new
    person.changed? # => false

Change the name:

    person.name = 'Bob'
    person.changed?       # => true
    person.name_changed?  # => true
    person.name_changed?(from: "Uncle Bob", to: "Bob") # => true
    person.name_was       # => "Uncle Bob"
    person.name_change    # => ["Uncle Bob", "Bob"]
    person.name = 'Bill'
    person.name_change    # => ["Uncle Bob", "Bill"]

Save the changes:

    person.save
    person.changed?      # => false
    person.name_changed? # => false

Reset the changes:

    person.previous_changes # => {"name" => ["Uncle Bob", "Bill"]}
    person.reload!
    person.previous_changes # => {}

Rollback the changes:

    person.name = "Uncle Bob"
    person.rollback!
    person.name          # => "Bill"
    person.name_changed? # => false

Assigning the same value leaves the attribute unchanged:

    person.name = 'Bill'
    person.name_changed? # => false
    person.name_change   # => nil

Which attributes have changed?

    person.name = 'Bob'
    person.changed # => ["name"]
    person.changes # => {"name" => ["Bill", "Bob"]}

If an attribute is modified in-place then make use of +[attribute_name]_will_change!+ to mark that the attribute is changing. Otherwise Active Model can't track changes to in-place attributes. Note that Active Record can detect in-place modifications automatically. You do not need to call +[attribute_name]_will_change!+ on Active Record models.

    person.name_will_change!
    person.name_change # => ["Bill", "Bill"]
    person.name << 'y'
    person.name_change # => ["Bill", "Billy"]

### Public Instance Methods

changed() click to toggle source

Returns an array with the name of the attributes with unsaved changes.

    person.changed # => []
    person.name = 'bob'
    person.changed # => ["name"]

    # File lib/active_model/dirty.rb, line 137
    def changed
      changed_attributes.keys
    end

changed?() click to toggle source

Returns `true` if any attribute have unsaved changes, `false` otherwise.

    person.changed? # => false
    person.name = 'bob'
    person.changed? # => true

    # File lib/active_model/dirty.rb, line 128
    def changed?
      changed_attributes.present?
    end

changed_attributes() click to toggle source

Returns a hash of the attributes with unsaved changes indicating their original values like `attr => original value`.

    person.name # => "bob"
    person.name = 'robert'
    person.changed_attributes # => {"name" => "bob"}

    # File lib/active_model/dirty.rb, line 167
    def changed_attributes
      @changed_attributes ||= ActiveSupport::HashWithIndifferentAccess.new
    end

Also aliased as: attributes_changed_by_setter
changes() click to toggle source

Returns a hash of changed attributes indicating their original and new values like `attr => [original value, new value]`.

    person.changes # => {}
    person.name = 'bob'
    person.changes # => { "name" => ["bill", "bob"] }

    # File lib/active_model/dirty.rb, line 147
    def changes
      ActiveSupport::HashWithIndifferentAccess[changed.map { |attr| [attr, attribute_change(attr)] }]
    end

previous_changes() click to toggle source

Returns a hash of attributes that were changed before the model was saved.

    person.name # => "bob"
    person.name = 'robert'
    person.save
    person.previous_changes # => {"name" => ["bob", "robert"]}

    # File lib/active_model/dirty.rb, line 157
    def previous_changes
      @previously_changed ||= ActiveSupport::HashWithIndifferentAccess.new
    end

restore_attributes(attributes = changed) click to toggle source

Restore all previous data of the provided attributes.

    # File lib/active_model/dirty.rb, line 185
    def restore_attributes(attributes = changed)
      attributes.each { |attr| restore_attribute! attr }
    end

### Private Instance Methods

attribute_change(attr) click to toggle source

Handle `*_change` for `method_missing`.

    # File lib/active_model/dirty.rb, line 218
    def attribute_change(attr)
      [changed_attributes[attr], __send__(attr)] if attribute_changed?(attr)
    end

attribute_changed_by_setter?(attr_name)
Alias for: changes_include?
attribute_will_change!(attr) click to toggle source

Handle `*_will_change!` for `method_missing`.

    # File lib/active_model/dirty.rb, line 223
    def attribute_will_change!(attr)
      return if attribute_changed?(attr)

      begin
        value = __send__(attr)
        value = value.duplicable? ? value.clone : value
      rescue TypeError, NoMethodError
      end

      set_attribute_was(attr, value)
    end

attributes_changed_by_setter()

This is necessary because `changed_attributes` might be overridden in other implemntations (e.g. in `ActiveRecord`)

Alias for: changed_attributes
changes_applied() click to toggle source

Removes current changes and makes them accessible through `previous_changes`.

    # File lib/active_model/dirty.rb, line 197
    def changes_applied # :doc:
      @previously_changed = changes
      @changed_attributes = ActiveSupport::HashWithIndifferentAccess.new
    end

changes_include?(attr_name) click to toggle source

    # File lib/active_model/dirty.rb, line 191
    def changes_include?(attr_name)
      attributes_changed_by_setter.include?(attr_name)
    end

Also aliased as: attribute_changed_by_setter?
clear_attribute_changes(attributes) click to toggle source

Remove changes information for the provided attributes.

    # File lib/active_model/dirty.rb, line 263
    def clear_attribute_changes(attributes) # :doc:
      attributes_changed_by_setter.except!(*attributes)
    end

clear_changes_information() click to toggle source

Clear all dirty data: current changes and previous changes.

    # File lib/active_model/dirty.rb, line 203
    def clear_changes_information # :doc:
      @previously_changed = ActiveSupport::HashWithIndifferentAccess.new
      @changed_attributes = ActiveSupport::HashWithIndifferentAccess.new
    end

reset_attribute!(attr) click to toggle source

Handle `reset_*!` for `method_missing`.

    # File lib/active_model/dirty.rb, line 236
          def reset_attribute!(attr)
            ActiveSupport::Deprecation.warn("          `#reset_#{attr}!` is deprecated and will be removed on Rails 5.
              Please use `#restore_#{attr}!` instead.
    ".squish)

            restore_attribute!(attr)
          end

reset_changes() click to toggle source

    # File lib/active_model/dirty.rb, line 208
          def reset_changes
            ActiveSupport::Deprecation.warn("          `#reset_changes` is deprecated and will be removed on Rails 5.
              Please use `#clear_changes_information` instead.
    ".squish)

            clear_changes_information
          end

restore_attribute!(attr) click to toggle source

Handle `restore_*!` for `method_missing`.

    # File lib/active_model/dirty.rb, line 246
    def restore_attribute!(attr)
      if attribute_changed?(attr)
        __send__("#{attr}=", changed_attributes[attr])
        clear_attribute_changes([attr])
      end
    end

set_attribute_was(attr, old_value) click to toggle source

Force an attribute to have a particular “before” value

    # File lib/active_model/dirty.rb, line 258
    def set_attribute_was(attr, old_value)
      attributes_changed_by_setter[attr] = old_value
    end