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::Dirtyin your object.- Call
define_attribute_methodspassing 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
trueif any of the attributes has unsaved changes,falseotherwise.
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
*_changeattribute methods. -
#attribute_previous_change(attr_name)
private
Dispatch target for
*_previous_changeattribute 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? | A |
| #respond_to_without_attributes? | Checks whether the object responds to |
| #_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 ]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 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283
# File 'activemodel/lib/active_model/dirty.rb', line 146
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 # `*_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 = 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 the current changes of the attribute. # # ``` # 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 64
class_attribute :attribute_aliases, instance_writer: false, default: {}.freeze
.attribute_aliases? ⇒ Boolean (rw)
[ GitHub ]
# File 'activemodel/lib/active_model/attribute_methods.rb', line 64
class_attribute :attribute_aliases, instance_writer: false, default: {}.freeze
.attribute_method_patterns (rw)
[ GitHub ]# File 'activemodel/lib/active_model/attribute_methods.rb', line 66
class_attribute :attribute_method_patterns, instance_writer: false, default: ClassMethods::AttributeMethodPatternSet.new([ ClassMethods::AttributeMethodPattern::BASE ])
.attribute_method_patterns? ⇒ Boolean (rw)
[ GitHub ]
# File 'activemodel/lib/active_model/attribute_methods.rb', line 66
class_attribute :attribute_method_patterns, instance_writer: false, default: ClassMethods::AttributeMethodPatternSet.new([ ClassMethods::AttributeMethodPattern::BASE ])
Instance Attribute Details
#attribute_aliases (readonly)
[ GitHub ]# File 'activemodel/lib/active_model/attribute_methods.rb', line 64
class_attribute :attribute_aliases, instance_writer: false, default: {}.freeze
#attribute_aliases? ⇒ Boolean (readonly)
[ GitHub ]
# File 'activemodel/lib/active_model/attribute_methods.rb', line 64
class_attribute :attribute_aliases, instance_writer: false, default: {}.freeze
#attribute_method_patterns (readonly)
[ GitHub ]# File 'activemodel/lib/active_model/attribute_methods.rb', line 66
class_attribute :attribute_method_patterns, instance_writer: false, default: ClassMethods::AttributeMethodPatternSet.new([ ClassMethods::AttributeMethodPattern::BASE ])
#attribute_method_patterns? ⇒ Boolean (readonly)
[ GitHub ]
# File 'activemodel/lib/active_model/attribute_methods.rb', line 66
class_attribute :attribute_method_patterns, instance_writer: false, default: ClassMethods::AttributeMethodPatternSet.new([ ClassMethods::AttributeMethodPattern::BASE ])
#changed? ⇒ Boolean (readonly)
Returns true if any of the attributes has unsaved changes, false otherwise.
person.changed? # => false
person.name = 'bob'
person.changed? # => true
: () -> bool
# File 'activemodel/lib/active_model/dirty.rb', line 330
def changed? mutations_from_database.any_changes? end
Instance Method Details
#as_json(options = {})
# File 'activemodel/lib/active_model/dirty.rb', line 305
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 461
def attribute_change(attr_name) attr_name = attr_name.name if attr_name.is_a?(Symbol) 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 347
def attribute_changed?(attr_name, **) attr_name = attr_name.name if attr_name.is_a?(Symbol) mutations_from_database.changed?(attr_name.to_s, **) end
#attribute_changed_in_place?(attr_name) ⇒ Boolean
# File 'activemodel/lib/active_model/dirty.rb', line 427
def attribute_changed_in_place?(attr_name) # :nodoc: attr_name = attr_name.name if attr_name.is_a?(Symbol) 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 467
def attribute_previous_change(attr_name) attr_name = attr_name.name if attr_name.is_a?(Symbol) 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 359
def attribute_previously_changed?(attr_name, **) attr_name = attr_name.name if attr_name.is_a?(Symbol) 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 365
def attribute_previously_was(attr_name) attr_name = attr_name.name if attr_name.is_a?(Symbol) 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 353
def attribute_was(attr_name) attr_name = attr_name.name if attr_name.is_a?(Symbol) 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 473
def attribute_will_change!(attr_name) attr_name = attr_name.name if attr_name.is_a?(Symbol) 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"]
: () -> Array
# File 'activemodel/lib/active_model/dirty.rb', line 342
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 397
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 410
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 313
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 439
def clear_attribute_change(attr_name) attr_name = attr_name.name if attr_name.is_a?(Symbol) mutations_from_database.forget_change(attr_name.to_s) end
#clear_attribute_changes(attr_names)
[ GitHub ]# File 'activemodel/lib/active_model/dirty.rb', line 382
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 376
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 452
def forget_attribute_assignments @attributes = @attributes.map(&:forgetting_assignment) if defined?(@attributes) end
#init_attributes(other)
# File 'activemodel/lib/active_model/dirty.rb', line 290
def init_attributes(other) # :nodoc: attrs = super if self.class.respond_to?(:_default_attributes) da = self.class._default_attributes # mutate the copied @attributes object rather than calling `map` on # the default attributes. This way we can keep the "LazyAttributeSet" # type da.values.each do |attr| attrs[attr.name] = attr.with_value_from_user(attrs.fetch_value(attr.name)) end end attrs end
#init_internals (private)
[ GitHub ]# File 'activemodel/lib/active_model/dirty.rb', line 433
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 285
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 456
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 444
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"]}
: () -> (ActiveSupport::HashWithIndifferentAccess | Hash[String, [ untyped, untyped ]])
# File 'activemodel/lib/active_model/dirty.rb', line 423
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 479
def restore_attribute!(attr_name) attr_name = attr_name.is_a?(Symbol) ? attr_name.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 371
def restore_attributes(attr_names = changed) attr_names.each { |attr_name| restore_attribute!(attr_name) } end