Ruby on Rails | Screencasts | Download | Documentation | Weblog | Community | Source

root/trunk/activerecord/lib/active_record/dirty.rb

Revision 9196, 4.2 kB (checked in by bitsweat, 1 year ago)

Partial updates off by default

Line 
1 module ActiveRecord
2   # Track unsaved attribute changes.
3   #
4   # A newly instantiated object is unchanged:
5   #   person = Person.find_by_name('uncle bob')
6   #   person.changed?       # => false
7   #
8   # Change the name:
9   #   person.name = 'Bob'
10   #   person.changed?       # => true
11   #   person.name_changed?  # => true
12   #   person.name_was       # => 'uncle bob'
13   #   person.name_change    # => ['uncle bob', 'Bob']
14   #   person.name = 'Bill'
15   #   person.name_change    # => ['uncle bob', 'Bill']
16   #
17   # Save the changes:
18   #   person.save
19   #   person.changed?       # => false
20   #   person.name_changed?  # => false
21   #
22   # Assigning the same value leaves the attribute unchanged:
23   #   person.name = 'Bill'
24   #   person.name_changed?  # => false
25   #   person.name_change    # => nil
26   #
27   # Which attributes have changed?
28   #   person.name = 'bob'
29   #   person.changed        # => ['name']
30   #   person.changes        # => { 'name' => ['Bill', 'bob'] }
31   #
32   # Before modifying an attribute in-place:
33   #   person.name_will_change!
34   #   person.name << 'by'
35   #   person.name_change    # => ['uncle bob', 'uncle bobby']
36   module Dirty
37     def self.included(base)
38       base.attribute_method_suffix '_changed?', '_change', '_will_change!', '_was'
39       base.alias_method_chain :write_attribute, :dirty
40       base.alias_method_chain :save,            :dirty
41       base.alias_method_chain :save!,           :dirty
42       base.alias_method_chain :update,          :dirty
43
44       base.superclass_delegating_accessor :partial_updates
45       base.partial_updates = false
46     end
47
48     # Do any attributes have unsaved changes?
49     #   person.changed? # => false
50     #   person.name = 'bob'
51     #   person.changed? # => true
52     def changed?
53       !changed_attributes.empty?
54     end
55
56     # List of attributes with unsaved changes.
57     #   person.changed # => []
58     #   person.name = 'bob'
59     #   person.changed # => ['name']
60     def changed
61       changed_attributes.keys
62     end
63
64     # Map of changed attrs => [original value, new value]
65     #   person.changes # => {}
66     #   person.name = 'bob'
67     #   person.changes # => { 'name' => ['bill', 'bob'] }
68     def changes
69       changed.inject({}) { |h, attr| h[attr] = attribute_change(attr); h }
70     end
71
72
73     # Clear changed attributes after they are saved.
74     def save_with_dirty(*args) #:nodoc:
75       save_without_dirty(*args)
76     ensure
77       changed_attributes.clear
78     end
79
80     # Clear changed attributes after they are saved.
81     def save_with_dirty!(*args) #:nodoc:
82       save_without_dirty!(*args)
83     ensure
84       changed_attributes.clear
85     end
86
87     private
88       # Map of change attr => original value.
89       def changed_attributes
90         @changed_attributes ||= {}
91       end
92
93       # Handle *_changed? for method_missing.
94       def attribute_changed?(attr)
95         changed_attributes.include?(attr)
96       end
97
98       # Handle *_change for method_missing.
99       def attribute_change(attr)
100         [changed_attributes[attr], __send__(attr)] if attribute_changed?(attr)
101       end
102
103       # Handle *_was for method_missing.
104       def attribute_was(attr)
105         attribute_changed?(attr) ? changed_attributes[attr] : __send__(attr)
106       end
107
108       # Handle *_will_change! for method_missing.
109       def attribute_will_change!(attr)
110         changed_attributes[attr] = clone_attribute_value(:read_attribute, attr)
111       end
112
113       # Wrap write_attribute to remember original attribute value.
114       def write_attribute_with_dirty(attr, value)
115         attr = attr.to_s
116
117         # The attribute already has an unsaved change.
118         unless changed_attributes.include?(attr)
119           old = clone_attribute_value(:read_attribute, attr)
120
121           # Remember the original value if it's different.
122           typecasted = if column = column_for_attribute(attr)
123                          column.type_cast(value)
124                        else
125                          value
126                        end
127           changed_attributes[attr] = old unless old == typecasted
128         end
129
130         # Carry on.
131         write_attribute_without_dirty(attr, value)
132       end
133
134       def update_with_dirty
135         if partial_updates?
136           update_without_dirty(changed)
137         else
138           update_without_dirty
139         end
140       end
141   end
142 end
Note: See TracBrowser for help on using the browser.