aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord/lib')
-rwxr-xr-xactiverecord/lib/active_record.rb2
-rw-r--r--activerecord/lib/active_record/dirty.rb117
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