提交 bbf738f2 编写于 作者: J Jeremy Kemper

Track changes to unsaved attributes


git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@9127 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
上级 3704f4ba
*SVN*
* Track changes to unsaved attributes. [Jeremy Kemper]
* Switched to UTC-timebased version numbers for migrations and the schema. This will as good as eliminate the problem of multiple migrations getting the same version assigned in different branches. Also added rake db:migrate:up/down to apply individual migrations that may need to be run when you merge branches #11458 [jbarnette]
* Fixed that has_many :through would ignore the hash conditions #11447 [miloops]
......
......@@ -55,6 +55,7 @@
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 @@
include ActiveRecord::Calculations
include ActiveRecord::Serialization
include ActiveRecord::AttributeMethods
include ActiveRecord::Dirty
end
require 'active_record/connection_adapters/abstract_adapter'
......
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
require 'cases/helper'
# Stub out an AR-alike.
class DirtyTestSubject
def self.table_name; 'people' end
def self.primary_key; 'id' end
def self.attribute_method_suffix(*suffixes) suffixes end
def initialize(attrs = {}) @attributes = attrs end
def save
changed_attributes.clear
end
alias_method :save!, :save
def name; read_attribute('name') end
def name=(value); write_attribute('name', value) end
def name_was; attribute_was('name') end
def name_change; attribute_change('name') end
def name_changed?; attribute_changed?('name') end
private
def define_read_methods; nil end
def read_attribute(attr)
@attributes[attr]
end
def write_attribute(attr, value)
@attributes[attr] = value
end
end
# Include the module after the class is all set up.
DirtyTestSubject.module_eval { include ActiveRecord::Dirty }
class DirtyTest < Test::Unit::TestCase
def test_attribute_changes
# New record - no changes.
person = DirtyTestSubject.new
assert !person.name_changed?
assert_nil person.name_change
# Change name.
person.name = 'a'
assert person.name_changed?
assert_nil person.name_was
assert_equal [nil, 'a'], person.name_change
# Saved - no changes.
person.save!
assert !person.name_changed?
assert_nil person.name_change
# Same value - no changes.
person.name = 'a'
assert !person.name_changed?
assert_nil person.name_change
end
def test_object_should_be_changed_if_any_attribute_is_changed
person = DirtyTestSubject.new
assert !person.changed?
assert_equal [], person.changed
assert_equal Hash.new, person.changes
person.name = 'a'
assert person.changed?
assert_nil person.name_was
assert_equal %w(name), person.changed
assert_equal({'name' => [nil, 'a']}, person.changes)
person.save
assert !person.changed?
assert_equal [], person.changed
assert_equal({}, person.changes)
end
end
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册