Module: ActiveModel::Dirty
Relationships & Source Files | |
Extension / Inclusion / Inheritance Descendants | |
Included In:
| |
Super Chains via Extension / Inclusion / Inheritance | |
Class Chain:
self,
::ActiveSupport::Concern
|
|
Instance Chain:
self,
AttributeMethods
|
|
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
*_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 *_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 *_will_change!
on Active Record models.
person.name_will_change!
person.name_change # => ["Bill", "Bill"]
person.name << 'y'
person.name_change # => ["Bill", "Billy"]
Methods can be invoked as name_changed?
or by passing an argument to the generic method attribute_changed?("name")
.
Constant Summary
AttributeMethods
- Attributes & Methods
- .attribute_aliases rw
- #attribute_aliases readonly
- .attribute_aliases? ⇒ Boolean rw
- #attribute_aliases? ⇒ Boolean readonly
- .attribute_method_patterns rw
- #attribute_method_patterns readonly
- .attribute_method_patterns? ⇒ Boolean rw
- #attribute_method_patterns? ⇒ Boolean readonly
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. |
append_features, prepend_features |
Instance Attribute Summary
-
#changed? ⇒ Boolean
readonly
Returns
true
if any of the attributes has unsaved changes,false
otherwise.
Instance Method Summary
-
#attribute_changed?(attr_name, **options) ⇒ Boolean
Dispatch target for
*_changed?
attribute methods. -
#attribute_previously_changed?(attr_name, **options) ⇒ Boolean
Dispatch target for
*_previously_changed?
attribute methods. -
#attribute_previously_was(attr_name)
Dispatch target for
*_previously_was
attribute methods. -
#attribute_was(attr_name)
Dispatch target for
*_was
attribute methods. -
#changed
readonly
Returns an array with the name of the attributes with unsaved changes.
-
#changed_attributes
Returns a hash of the attributes with unsaved changes indicating their original values like
attr => original value
. -
#changes
Returns a hash of changed attributes indicating their original and new values like
attr => [original value, new value]
. -
#changes_applied
Clears dirty data and moves #changes to #previous_changes and #mutations_from_database to #mutations_before_last_save respectively.
- #clear_attribute_changes(attr_names)
-
#clear_changes_information
Clears all dirty data: current changes and previous changes.
-
#previous_changes
Returns a hash of attributes that were changed before the model was saved.
-
#restore_attributes(attr_names = changed)
Restore all previous data of the provided attributes.
-
#attribute_change(attr_name)
private
Dispatch target for
*_change
attribute methods. -
#attribute_previous_change(attr_name)
private
Dispatch target for
*_previous_change
attribute methods. -
#attribute_will_change!(attr_name)
private
Dispatch target for
*_will_change!
attribute methods. - #clear_attribute_change(attr_name) private
- #forget_attribute_assignments private
- #init_internals private
- #mutations_before_last_save private
- #mutations_from_database private
-
#restore_attribute!(attr_name)
private
Dispatch target for
restore_*!
attribute methods. - #as_json(options = {}) Internal use only
- #attribute_changed_in_place?(attr_name) ⇒ Boolean Internal use only
- #init_attributes(other) Internal use only
- #initialize_dup(other) Internal use only
AttributeMethods
- Included
#attribute_missing |
|
#method_missing | Allows access to the object attributes, which are held in the hash returned by |
#respond_to?, | |
#respond_to_without_attributes? | A |
#_read_attribute, #attribute_method?, | |
#matched_attribute_method | Returns a struct representing the matching attribute method. |
#missing_attribute |
Dynamic Method Handling
This class handles dynamic methods through the method_missing method in the class ActiveModel::AttributeMethods
DSL Calls
included
[ GitHub ]127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246
# File 'activemodel/lib/active_model/dirty.rb', line 127
included do ## # :method: *_previously_changed? # # :call-seq: *_previously_changed?(**options) # # This method is generated for each attribute. # # Returns true if the attribute previously had unsaved changes. # # person = Person.new # person.name = 'Britanny' # person.save # person.name_previously_changed? # => true # person.name_previously_changed?(from: nil, to: 'Britanny') # => true ## # :method: *_changed? # # This method is generated for each attribute. # # Returns true if the attribute has unsaved changes. # # person = Person.new # person.name = 'Andrew' # person.name_changed? # => true ## # :method: *_change # # This method is generated for each attribute. # # Returns the old and the new value of the attribute. # # person = Person.new # person.name = 'Nick' # person.name_change # => [nil, 'Nick'] ## # :method: *_will_change! # # This method is generated for each attribute. # # If an attribute is modified in-place then make use of # <tt>*_will_change!</tt> 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 <tt>*_will_change!</tt> on Active Record # models. # # person = Person.new('Sandy') # person.name_will_change! # person.name_change # => ['Sandy', 'Sandy'] ## # :method: *_was # # This method is generated for each attribute. # # Returns the old value of the attribute. # # person = Person.new(name: 'Steph') # person.name = 'Stephanie' # person.name_was # => 'Steph' ## # :method: *_previous_change # # This method is generated for each attribute. # # Returns the old and the new value of the attribute before the last save. # # person = Person.new # person.name = 'Emmanuel' # person.save # person.name_previous_change # => [nil, 'Emmanuel'] ## # :method: *_previously_was # # This method is generated for each attribute. # # Returns the old value of the attribute before the last save. # # person = Person.new # person.name = 'Sage' # person.save # person.name_previously_was # => nil ## # :method: restore_*! # # This method is generated for each attribute. # # Restores the attribute to the old value. # # person = Person.new # person.name = 'Amanda' # person.restore_name! # person.name # => nil ## # :method: clear_*_change # # This method is generated for each attribute. # # Clears all dirty data of the attribute: current changes and previous changes. # # person = Person.new(name: 'Chris') # person.name = 'Jason' # person.name_change # => ['Chris', 'Jason'] # person.clear_name_change # person.name_change # => nil attribute_method_suffix "_previously_changed?", "_changed?", parameters: "**options" attribute_method_suffix "_change", "_will_change!", "_was", parameters: false attribute_method_suffix "_previous_change", "_previously_was", parameters: false attribute_method_affix prefix: "restore_", suffix: "!", parameters: false attribute_method_affix prefix: "clear_", suffix: "_change", parameters: false end
Class Attribute Details
.attribute_aliases (rw)
[ GitHub ]# File 'activemodel/lib/active_model/attribute_methods.rb', line 71
class_attribute :attribute_aliases, instance_writer: false, default: {}
.attribute_aliases? ⇒ Boolean
(rw)
[ GitHub ]
# File 'activemodel/lib/active_model/attribute_methods.rb', line 71
class_attribute :attribute_aliases, instance_writer: false, default: {}
.attribute_method_patterns (rw)
[ GitHub ]# File 'activemodel/lib/active_model/attribute_methods.rb', line 72
class_attribute :attribute_method_patterns, instance_writer: false, default: [ ClassMethods::AttributeMethodPattern.new ]
.attribute_method_patterns? ⇒ Boolean
(rw)
[ GitHub ]
# File 'activemodel/lib/active_model/attribute_methods.rb', line 72
class_attribute :attribute_method_patterns, instance_writer: false, default: [ ClassMethods::AttributeMethodPattern.new ]
Instance Attribute Details
#attribute_aliases (readonly)
[ GitHub ]# File 'activemodel/lib/active_model/attribute_methods.rb', line 71
class_attribute :attribute_aliases, instance_writer: false, default: {}
#attribute_aliases? ⇒ Boolean
(readonly)
[ GitHub ]
# File 'activemodel/lib/active_model/attribute_methods.rb', line 71
class_attribute :attribute_aliases, instance_writer: false, default: {}
#attribute_method_patterns (readonly)
[ GitHub ]# File 'activemodel/lib/active_model/attribute_methods.rb', line 72
class_attribute :attribute_method_patterns, instance_writer: false, default: [ ClassMethods::AttributeMethodPattern.new ]
#attribute_method_patterns? ⇒ Boolean
(readonly)
[ GitHub ]
# File 'activemodel/lib/active_model/attribute_methods.rb', line 72
class_attribute :attribute_method_patterns, instance_writer: false, default: [ ClassMethods::AttributeMethodPattern.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
# File 'activemodel/lib/active_model/dirty.rb', line 286
def changed? mutations_from_database.any_changes? end
Instance Method Details
#as_json(options = {})
# File 'activemodel/lib/active_model/dirty.rb', line 264
def as_json( = {}) # :nodoc: except = [* [:except], "mutations_from_database", "mutations_before_last_save"] = .merge except: except super( ) end
#attribute_change(attr_name) (private)
Dispatch target for *_change
attribute methods.
# File 'activemodel/lib/active_model/dirty.rb', line 399
def attribute_change(attr_name) mutations_from_database.change_to_attribute(attr_name.to_s) end
#attribute_changed?(attr_name, **options) ⇒ Boolean
Dispatch target for *_changed?
attribute methods.
# File 'activemodel/lib/active_model/dirty.rb', line 300
def attribute_changed?(attr_name, ** ) mutations_from_database.changed?(attr_name.to_s, ** ) end
#attribute_changed_in_place?(attr_name) ⇒ Boolean
# File 'activemodel/lib/active_model/dirty.rb', line 367
def attribute_changed_in_place?(attr_name) # :nodoc: mutations_from_database.changed_in_place?(attr_name.to_s) end
#attribute_previous_change(attr_name) (private)
Dispatch target for *_previous_change
attribute methods.
# File 'activemodel/lib/active_model/dirty.rb', line 404
def attribute_previous_change(attr_name) mutations_before_last_save.change_to_attribute(attr_name.to_s) end
#attribute_previously_changed?(attr_name, **options) ⇒ Boolean
Dispatch target for *_previously_changed?
attribute methods.
# File 'activemodel/lib/active_model/dirty.rb', line 310
def attribute_previously_changed?(attr_name, ** ) mutations_before_last_save.changed?(attr_name.to_s, ** ) end
#attribute_previously_was(attr_name)
Dispatch target for *_previously_was
attribute methods.
# File 'activemodel/lib/active_model/dirty.rb', line 315
def attribute_previously_was(attr_name) mutations_before_last_save.original_value(attr_name.to_s) end
#attribute_was(attr_name)
Dispatch target for *_was
attribute methods.
# File 'activemodel/lib/active_model/dirty.rb', line 305
def attribute_was(attr_name) mutations_from_database.original_value(attr_name.to_s) end
#attribute_will_change!(attr_name) (private)
Dispatch target for *_will_change!
attribute methods.
# File 'activemodel/lib/active_model/dirty.rb', line 409
def attribute_will_change!(attr_name) mutations_from_database.force_change(attr_name.to_s) end
#changed (readonly)
Returns an array with the name of the attributes with unsaved changes.
person.changed # => []
person.name = 'bob'
person.changed # => ["name"]
# File 'activemodel/lib/active_model/dirty.rb', line 295
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"}
# File 'activemodel/lib/active_model/dirty.rb', line 343
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"] }
# File 'activemodel/lib/active_model/dirty.rb', line 353
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.
# File 'activemodel/lib/active_model/dirty.rb', line 272
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_change(attr_name) (private)
[ GitHub ]# File 'activemodel/lib/active_model/dirty.rb', line 378
def clear_attribute_change(attr_name) mutations_from_database.forget_change(attr_name.to_s) end
#clear_attribute_changes(attr_names)
[ GitHub ]# File 'activemodel/lib/active_model/dirty.rb', line 331
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.
# File 'activemodel/lib/active_model/dirty.rb', line 325
def clear_changes_information @mutations_before_last_save = nil forget_attribute_assignments @mutations_from_database = nil end
#forget_attribute_assignments (private)
[ GitHub ]# File 'activemodel/lib/active_model/dirty.rb', line 390
def forget_attribute_assignments @attributes = @attributes.map(&:forgetting_assignment) if defined?(@attributes) end
#init_attributes(other)
# File 'activemodel/lib/active_model/dirty.rb', line 253
def init_attributes(other) # :nodoc: attrs = super if other.persisted? && self.class.respond_to?(:_default_attributes) self.class._default_attributes.map do |attr| attr.with_value_from_user(attrs.fetch_value(attr.name)) end else attrs end end
#init_internals (private)
[ GitHub ]# File 'activemodel/lib/active_model/dirty.rb', line 372
def init_internals super @mutations_before_last_save = nil @mutations_from_database = nil end
#initialize_dup(other)
# File 'activemodel/lib/active_model/dirty.rb', line 248
def initialize_dup(other) # :nodoc: super @mutations_from_database = nil end
#mutations_before_last_save (private)
[ GitHub ]# File 'activemodel/lib/active_model/dirty.rb', line 394
def mutations_before_last_save @mutations_before_last_save ||= ActiveModel::NullMutationTracker.instance end
#mutations_from_database (private)
[ GitHub ]# File 'activemodel/lib/active_model/dirty.rb', line 382
def mutations_from_database @mutations_from_database ||= if defined?(@attributes) ActiveModel::AttributeMutationTracker.new(@attributes) else ActiveModel::ForcedMutationTracker.new(self) end 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"]}
# File 'activemodel/lib/active_model/dirty.rb', line 363
def previous_changes mutations_before_last_save.changes end
#restore_attribute!(attr_name) (private)
Dispatch target for restore_*!
attribute methods.
# File 'activemodel/lib/active_model/dirty.rb', line 414
def restore_attribute!(attr_name) attr_name = attr_name.to_s if attribute_changed?(attr_name) __send__("#{attr_name}=", attribute_was(attr_name)) clear_attribute_change(attr_name) end end
#restore_attributes(attr_names = changed)
Restore all previous data of the provided attributes.
# File 'activemodel/lib/active_model/dirty.rb', line 320
def restore_attributes(attr_names = changed) attr_names.each { |attr_name| restore_attribute!(attr_name) } end