diff options
Diffstat (limited to 'activerecord/lib')
-rwxr-xr-x | activerecord/lib/active_record.rb | 2 | ||||
-rw-r--r-- | activerecord/lib/active_record/dirty.rb | 117 |
2 files changed, 119 insertions, 0 deletions
diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb index cedcd8b0a8..3fc40291ba 100755 --- a/activerecord/lib/active_record.rb +++ b/activerecord/lib/active_record.rb @@ -55,6 +55,7 @@ require 'active_record/schema' require 'active_record/calculations' require 'active_record/serialization' require 'active_record/attribute_methods' +require 'active_record/dirty' ActiveRecord::Base.class_eval do extend ActiveRecord::QueryCache @@ -73,6 +74,7 @@ ActiveRecord::Base.class_eval do include ActiveRecord::Calculations include ActiveRecord::Serialization include ActiveRecord::AttributeMethods + include ActiveRecord::Dirty end require 'active_record/connection_adapters/abstract_adapter' diff --git a/activerecord/lib/active_record/dirty.rb b/activerecord/lib/active_record/dirty.rb new file mode 100644 index 0000000000..a9ae2b148c --- /dev/null +++ b/activerecord/lib/active_record/dirty.rb @@ -0,0 +1,117 @@ +module ActiveRecord + # Track unsaved attribute changes. + # + # A newly instantiated object is unchanged: + # person = Person.find_by_name('uncle bob') + # person.changed? # => false + # + # Change the name: + # person.name = 'Bob' + # person.changed? # => true + # person.name_changed? # => 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 + # + # 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'] } + module Dirty + def self.included(base) + base.attribute_method_suffix '_changed?', '_change', '_was' + base.alias_method_chain :write_attribute, :dirty + base.alias_method_chain :save, :dirty + base.alias_method_chain :save!, :dirty + end + + # Do any attributes have unsaved changes? + # person.changed? # => false + # person.name = 'bob' + # person.changed? # => true + def changed? + !changed_attributes.empty? + end + + # List of attributes with unsaved changes. + # person.changed # => [] + # person.name = 'bob' + # person.changed # => ['name'] + def changed + changed_attributes.keys + end + + # Map of changed attrs => [original value, new value] + # person.changes # => {} + # person.name = 'bob' + # person.changes # => { 'name' => ['bill', 'bob'] } + def changes + changed.inject({}) { |h, attr| h[attr] = attribute_change(attr); h } + end + + + # Clear changed attributes after they are saved. + def save_with_dirty(*args) #:nodoc: + save_without_dirty(*args) + ensure + changed_attributes.clear + end + + # Clear changed attributes after they are saved. + def save_with_dirty!(*args) #:nodoc: + save_without_dirty!(*args) + ensure + changed_attributes.clear + end + + private + # Map of change attr => original value. + def changed_attributes + @changed_attributes ||= {} + end + + + # Wrap write_attribute to remember original attribute value. + def write_attribute_with_dirty(attr, value) + attr = attr.to_s + + # The attribute already has an unsaved change. + unless changed_attributes.include?(attr) + old = read_attribute(attr) + + # Remember the original value if it's different. + changed_attributes[attr] = old unless old == value + end + + # Carry on. + write_attribute_without_dirty(attr, value) + end + + + # Handle *_changed? for method_missing. + def attribute_changed?(attr) + changed_attributes.include?(attr) + end + + # Handle *_change for method_missing. + def attribute_change(attr) + [changed_attributes[attr], __send__(attr)] if attribute_changed?(attr) + end + + # Handle *_was for method_missing. + def attribute_was(attr) + attribute_changed?(attr) ? changed_attributes[attr] : __send__(attr) + end + end +end |