123456789_123456789_123456789_123456789_123456789_

Module: ActiveModel::Dirty

Relationships & Source Files
Extension / Inclusion / Inheritance Descendants
Included In:
::ActiveRecord::AttributeMethods::Dirty, ::ActiveRecord::Base, ActiveRecord::InternalMetadata, ActiveRecord::SchemaMigration
Super Chains via Extension / Inclusion / Inheritance
Class Chain:
Instance Chain:
Defined in: activemodel/lib/active_model/dirty.rb

Overview

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

The requirements for implementing 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 initialize
    @name = nil
  end

  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: nil, to: "Bob") # => true
person.name_was       # => nil
person.name_change    # => [nil, "Bob"]
person.name = 'Bill'
person.name_change    # => [nil, "Bill"]

Save the changes:

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

Reset the changes:

person.previous_changes         # => {"name" => [nil, "Bill"]}
person.name_previously_changed? # => true
person.name_previously_changed?(from: nil, to: "Bill") # => true
person.name_previous_change     # => [nil, "Bill"]
person.name_previously_was      # => nil
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"]

Constant Summary

AttributeMethods - Included

CALL_COMPILABLE_REGEXP, NAME_COMPILABLE_REGEXP

AttributeMethods - Attributes & Methods

Class Method Summary

::ActiveSupport::Concern - Extended

class_methods

Define class methods from given block.

included

Evaluate given block in context of base class, so that you can write class macros here.

prepended

Evaluate given block in context of base class, so that you can write class macros here.

Instance Attribute Summary

Instance Method Summary

AttributeMethods - Included

#attribute_missing

attribute_missing is like method_missing, but for attributes.

#method_missing

Allows access to the object attributes, which are held in the hash returned by attributes, as though they were first-class methods.

#respond_to?,
#respond_to_without_attributes?

A Person instance with a name attribute can ask person.respond_to?(:name), person.respond_to?(:name=), and person.respond_to?(:name?) which will all return true.

Dynamic Method Handling

This class handles dynamic methods through the method_missing method in the class ActiveModel::AttributeMethods

DSL Calls

included

[ GitHub ]


125
126
127
128
129
130
# File 'activemodel/lib/active_model/dirty.rb', line 125

included do
  attribute_method_suffix "_changed?", "_change", "_will_change!", "_was"
  attribute_method_suffix "_previously_changed?", "_previous_change", "_previously_was"
  attribute_method_affix prefix: "restore_", suffix: "!"
  attribute_method_affix prefix: "clear_", suffix: "_change"
end

Class Attribute Details

.attribute_aliases (rw)

[ GitHub ]

  
# File 'activemodel/lib/active_model/attribute_methods.rb', line 72

class_attribute :attribute_aliases, instance_writer: false, default: {}

.attribute_aliases?Boolean (rw)

[ GitHub ]

  
# File 'activemodel/lib/active_model/attribute_methods.rb', line 72

class_attribute :attribute_aliases, instance_writer: false, default: {}

.attribute_method_matchers (rw)

[ GitHub ]

  
# File 'activemodel/lib/active_model/attribute_methods.rb', line 73

class_attribute :attribute_method_matchers, instance_writer: false, default: [ ClassMethods::AttributeMethodMatcher.new ]

.attribute_method_matchers?Boolean (rw)

[ GitHub ]

  
# File 'activemodel/lib/active_model/attribute_methods.rb', line 73

class_attribute :attribute_method_matchers, instance_writer: false, default: [ ClassMethods::AttributeMethodMatcher.new ]

Instance Attribute Details

#attribute_aliases (readonly)

[ GitHub ]

  
# File 'activemodel/lib/active_model/attribute_methods.rb', line 72

class_attribute :attribute_aliases, instance_writer: false, default: {}

#attribute_aliases?Boolean (readonly)

[ GitHub ]

  
# File 'activemodel/lib/active_model/attribute_methods.rb', line 72

class_attribute :attribute_aliases, instance_writer: false, default: {}

#attribute_method_matchers (readonly)

[ GitHub ]

  
# File 'activemodel/lib/active_model/attribute_methods.rb', line 73

class_attribute :attribute_method_matchers, instance_writer: false, default: [ ClassMethods::AttributeMethodMatcher.new ]

#attribute_method_matchers?Boolean (readonly)

[ GitHub ]

  
# File 'activemodel/lib/active_model/attribute_methods.rb', line 73

class_attribute :attribute_method_matchers, instance_writer: false, default: [ ClassMethods::AttributeMethodMatcher.new ]

#changed?Boolean (readonly)

Returns true if any of the attributes has unsaved changes, false otherwise.

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

  
# File 'activemodel/lib/active_model/dirty.rb', line 163

def changed?
  mutations_from_database.any_changes?
end

Instance Method Details

#changed (readonly)

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

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

  
# File 'activemodel/lib/active_model/dirty.rb', line 172

def changed
  mutations_from_database.changed_attribute_names
end

#changed_attributes

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"}
[ GitHub ]

  
# File 'activemodel/lib/active_model/dirty.rb', line 220

def changed_attributes
  mutations_from_database.changed_values
end

#changes

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"] }
[ GitHub ]

  
# File 'activemodel/lib/active_model/dirty.rb', line 230

def changes
  mutations_from_database.changes
end

#changes_applied

Clears dirty data and moves #changes to #previous_changes and #mutations_from_database to #mutations_before_last_save respectively.

[ GitHub ]

  
# File 'activemodel/lib/active_model/dirty.rb', line 149

def changes_applied
  unless defined?(@attributes)
    mutations_from_database.finalize_changes
  end
  @mutations_before_last_save = mutations_from_database
  forget_attribute_assignments
  @mutations_from_database = nil
end

#clear_attribute_changes(attr_names)

[ GitHub ]

  
# File 'activemodel/lib/active_model/dirty.rb', line 208

def clear_attribute_changes(attr_names)
  attr_names.each do |attr_name|
    clear_attribute_change(attr_name)
  end
end

#clear_changes_information

Clears all dirty data: current changes and previous changes.

[ GitHub ]

  
# File 'activemodel/lib/active_model/dirty.rb', line 202

def clear_changes_information
  @mutations_before_last_save = nil
  forget_attribute_assignments
  @mutations_from_database = nil
end

#previous_changes

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"]}
[ GitHub ]

  
# File 'activemodel/lib/active_model/dirty.rb', line 240

def previous_changes
  mutations_before_last_save.changes
end

#restore_attributes(attr_names = changed)

Restore all previous data of the provided attributes.

[ GitHub ]

  
# File 'activemodel/lib/active_model/dirty.rb', line 197

def restore_attributes(attr_names = changed)
  attr_names.each { |attr_name| restore_attribute!(attr_name) }
end