Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Newer
Older
100644 267 lines (241 sloc) 8.658 kb
c3cea9b @jeremy Fix unstated dep on HWIA
jeremy authored
1 require 'active_support/hash_with_indifferent_access'
3b1c69d @fxn adds a few requires in active_model/dirty.rb
fxn authored
2 require 'active_support/core_ext/object/duplicable'
c3cea9b @jeremy Fix unstated dep on HWIA
jeremy authored
3
f97dae5 @josh Extract common dirty tracking methods in AMo
josh authored
4 module ActiveModel
c9a88a2 @frodsan minor edits in AM documentation [ci skip]
frodsan authored
5 # == Active \Model \Dirty
716c243 @rizwanreza Minor changes to active_model/callbacks.rb and dirty.rb
rizwanreza authored
6 #
b451de0 @spastorino Deletes trailing whitespaces (over text files only find * -type f -exec ...
spastorino authored
7 # Provides a way to track changes in your object in the same way as
716c243 @rizwanreza Minor changes to active_model/callbacks.rb and dirty.rb
rizwanreza authored
8 # Active Record does.
b451de0 @spastorino Deletes trailing whitespaces (over text files only find * -type f -exec ...
spastorino authored
9 #
a43abfa Rewording
Oge Nnadi authored
10 # The requirements for implementing ActiveModel::Dirty are:
dba196c @lifo Merge docrails
lifo authored
11 #
f87820d @frodsan update ActiveModel::Dirty documentation
frodsan authored
12 # * <tt>include ActiveModel::Dirty</tt> in your object.
b451de0 @spastorino Deletes trailing whitespaces (over text files only find * -type f -exec ...
spastorino authored
13 # * Call <tt>define_attribute_methods</tt> passing each method you want to
f87820d @frodsan update ActiveModel::Dirty documentation
frodsan authored
14 # track.
a4b02be @tiii add brackets around attribute_name
tiii authored
15 # * Call <tt>[attr_name]_will_change!</tt> before each change to the tracked
f87820d @frodsan update ActiveModel::Dirty documentation
frodsan authored
16 # attribute.
9aa1a3d @rafaelfranca Merge pull request #10816 from bogdan/less-dirty-dirty
rafaelfranca authored
17 # * Call <tt>changes_applied</tt> after the changes are persisted.
66d0a01 @rafaelfranca Deprecate ActiveModel::Dirty#reset_changes in favor of #clear_changes_in...
rafaelfranca authored
18 # * Call <tt>clear_changes_information</tt> when you want to reset the changes
089e1b6 @rafaelfranca Document reset_changes since it is part of public API
rafaelfranca authored
19 # information.
41fb06f @rafaelfranca Deprecate `reset_#{attribute}` in favor of `restore_#{attribute}`.
rafaelfranca authored
20 # * Call <tt>restore_attributes</tt> when you want to restore previous data.
b451de0 @spastorino Deletes trailing whitespaces (over text files only find * -type f -exec ...
spastorino authored
21 #
dba196c @lifo Merge docrails
lifo authored
22 # A minimal implementation could be:
b451de0 @spastorino Deletes trailing whitespaces (over text files only find * -type f -exec ...
spastorino authored
23 #
dba196c @lifo Merge docrails
lifo authored
24 # class Person
25 # include ActiveModel::Dirty
b451de0 @spastorino Deletes trailing whitespaces (over text files only find * -type f -exec ...
spastorino authored
26 #
00c94d7 @frodsan updating define_attribute_methods documentation
frodsan authored
27 # define_attribute_methods :name
b451de0 @spastorino Deletes trailing whitespaces (over text files only find * -type f -exec ...
spastorino authored
28 #
dba196c @lifo Merge docrails
lifo authored
29 # def name
30 # @name
31 # end
b451de0 @spastorino Deletes trailing whitespaces (over text files only find * -type f -exec ...
spastorino authored
32 #
dba196c @lifo Merge docrails
lifo authored
33 # def name=(val)
2c8a4a5 @toretore Remove or fix non-working examples and add a few tests to Dirty [#5185 s...
toretore authored
34 # name_will_change! unless val == @name
dba196c @lifo Merge docrails
lifo authored
35 # @name = val
36 # end
b451de0 @spastorino Deletes trailing whitespaces (over text files only find * -type f -exec ...
spastorino authored
37 #
dba196c @lifo Merge docrails
lifo authored
38 # def save
9aa1a3d @rafaelfranca Merge pull request #10816 from bogdan/less-dirty-dirty
rafaelfranca authored
39 # # do persistence work
66d0a01 @rafaelfranca Deprecate ActiveModel::Dirty#reset_changes in favor of #clear_changes_in...
rafaelfranca authored
40 #
9aa1a3d @rafaelfranca Merge pull request #10816 from bogdan/less-dirty-dirty
rafaelfranca authored
41 # changes_applied
dba196c @lifo Merge docrails
lifo authored
42 # end
089e1b6 @rafaelfranca Document reset_changes since it is part of public API
rafaelfranca authored
43 #
44 # def reload!
66d0a01 @rafaelfranca Deprecate ActiveModel::Dirty#reset_changes in favor of #clear_changes_in...
rafaelfranca authored
45 # # get the values from the persistence layer
46 #
47 # clear_changes_information
089e1b6 @rafaelfranca Document reset_changes since it is part of public API
rafaelfranca authored
48 # end
552d4d8 @igor04 Added rollback method to ActiveModel::Dirty
igor04 authored
49 #
50 # def rollback!
41fb06f @rafaelfranca Deprecate `reset_#{attribute}` in favor of `restore_#{attribute}`.
rafaelfranca authored
51 # restore_attributes
552d4d8 @igor04 Added rollback method to ActiveModel::Dirty
igor04 authored
52 # end
dba196c @lifo Merge docrails
lifo authored
53 # end
b451de0 @spastorino Deletes trailing whitespaces (over text files only find * -type f -exec ...
spastorino authored
54 #
6f8d9bd @fxn revises AM:Dirty example [Godfrey Chan & Xavier Noria]
fxn authored
55 # A newly instantiated +Person+ object is unchanged:
f87820d @frodsan update ActiveModel::Dirty documentation
frodsan authored
56 #
6f8d9bd @fxn revises AM:Dirty example [Godfrey Chan & Xavier Noria]
fxn authored
57 # person = Person.new
4af4cea @fxn applies guidelines to dirty.rb [ci skip]
fxn authored
58 # person.changed? # => false
f97dae5 @josh Extract common dirty tracking methods in AMo
josh authored
59 #
60 # Change the name:
f87820d @frodsan update ActiveModel::Dirty documentation
frodsan authored
61 #
f97dae5 @josh Extract common dirty tracking methods in AMo
josh authored
62 # person.name = 'Bob'
63 # person.changed? # => true
64 # person.name_changed? # => true
da2b05b @gja Allows you to check if an attribute has changed to a particular value
gja authored
65 # person.name_changed?(from: "Uncle Bob", to: "Bob") # => true
f87820d @frodsan update ActiveModel::Dirty documentation
frodsan authored
66 # person.name_was # => "Uncle Bob"
67 # person.name_change # => ["Uncle Bob", "Bob"]
f97dae5 @josh Extract common dirty tracking methods in AMo
josh authored
68 # person.name = 'Bill'
f87820d @frodsan update ActiveModel::Dirty documentation
frodsan authored
69 # person.name_change # => ["Uncle Bob", "Bill"]
f97dae5 @josh Extract common dirty tracking methods in AMo
josh authored
70 #
71 # Save the changes:
f87820d @frodsan update ActiveModel::Dirty documentation
frodsan authored
72 #
f97dae5 @josh Extract common dirty tracking methods in AMo
josh authored
73 # person.save
4af4cea @fxn applies guidelines to dirty.rb [ci skip]
fxn authored
74 # person.changed? # => false
75 # person.name_changed? # => false
f97dae5 @josh Extract common dirty tracking methods in AMo
josh authored
76 #
089e1b6 @rafaelfranca Document reset_changes since it is part of public API
rafaelfranca authored
77 # Reset the changes:
78 #
f072db8 @fertapric Add `ActiveModel::Dirty#[attr_name]_previously_changed?` and
fertapric authored
79 # person.previous_changes # => {"name" => ["Uncle Bob", "Bill"]}
80 # person.name_previously_changed? # => true
81 # person.name_previous_change # => ["Uncle Bob", "Bill"]
ed0b080 @rafaelfranca Fix the documentation method.
rafaelfranca authored
82 # person.reload!
f072db8 @fertapric Add `ActiveModel::Dirty#[attr_name]_previously_changed?` and
fertapric authored
83 # person.previous_changes # => {}
089e1b6 @rafaelfranca Document reset_changes since it is part of public API
rafaelfranca authored
84 #
552d4d8 @igor04 Added rollback method to ActiveModel::Dirty
igor04 authored
85 # Rollback the changes:
86 #
87 # person.name = "Uncle Bob"
88 # person.rollback!
4af4cea @fxn applies guidelines to dirty.rb [ci skip]
fxn authored
89 # person.name # => "Bill"
90 # person.name_changed? # => false
552d4d8 @igor04 Added rollback method to ActiveModel::Dirty
igor04 authored
91 #
f97dae5 @josh Extract common dirty tracking methods in AMo
josh authored
92 # Assigning the same value leaves the attribute unchanged:
f87820d @frodsan update ActiveModel::Dirty documentation
frodsan authored
93 #
f97dae5 @josh Extract common dirty tracking methods in AMo
josh authored
94 # person.name = 'Bill'
4af4cea @fxn applies guidelines to dirty.rb [ci skip]
fxn authored
95 # person.name_changed? # => false
96 # person.name_change # => nil
f97dae5 @josh Extract common dirty tracking methods in AMo
josh authored
97 #
98 # Which attributes have changed?
f87820d @frodsan update ActiveModel::Dirty documentation
frodsan authored
99 #
f97dae5 @josh Extract common dirty tracking methods in AMo
josh authored
100 # person.name = 'Bob'
4af4cea @fxn applies guidelines to dirty.rb [ci skip]
fxn authored
101 # person.changed # => ["name"]
102 # person.changes # => {"name" => ["Bill", "Bob"]}
f97dae5 @josh Extract common dirty tracking methods in AMo
josh authored
103 #
e04c4c0 @sgrif Note that `_will_change!` is no longer needed for AR instances
sgrif authored
104 # If an attribute is modified in-place then make use of
105 # +[attribute_name]_will_change!+ to mark that the attribute is changing.
7cc145e @claudiob Use Active Model, not ActiveModel in plain English
claudiob authored
106 # Otherwise \Active \Model can't track changes to in-place attributes. Note
36a3897 @sgrif We are talking about the libraries, not the constants
sgrif authored
107 # that Active Record can detect in-place modifications automatically. You do
108 # not need to call +[attribute_name]_will_change!+ on Active Record models.
1a6d762 @neerajdotname expanding on the will_change! method in documentation
neerajdotname authored
109 #
f97dae5 @josh Extract common dirty tracking methods in AMo
josh authored
110 # person.name_will_change!
4af4cea @fxn applies guidelines to dirty.rb [ci skip]
fxn authored
111 # person.name_change # => ["Bill", "Bill"]
f97dae5 @josh Extract common dirty tracking methods in AMo
josh authored
112 # person.name << 'y'
4af4cea @fxn applies guidelines to dirty.rb [ci skip]
fxn authored
113 # person.name_change # => ["Bill", "Billy"]
f97dae5 @josh Extract common dirty tracking methods in AMo
josh authored
114 module Dirty
115 extend ActiveSupport::Concern
116 include ActiveModel::AttributeMethods
117
118 included do
008f3da @chancancode Don't expose these new APIs yet (added in 877ea78 / #16189)
chancancode authored
119 attribute_method_suffix '_changed?', '_change', '_will_change!', '_was'
f072db8 @fertapric Add `ActiveModel::Dirty#[attr_name]_previously_changed?` and
fertapric authored
120 attribute_method_suffix '_previously_changed?', '_previous_change'
41fb06f @rafaelfranca Deprecate `reset_#{attribute}` in favor of `restore_#{attribute}`.
rafaelfranca authored
121 attribute_method_affix prefix: 'restore_', suffix: '!'
f97dae5 @josh Extract common dirty tracking methods in AMo
josh authored
122 end
123
0f67f00 @vipulnsward AM#Dirty doc fixes
vipulnsward authored
124 # Returns +true+ if any of the attributes have unsaved changes, +false+ otherwise.
f87820d @frodsan update ActiveModel::Dirty documentation
frodsan authored
125 #
f97dae5 @josh Extract common dirty tracking methods in AMo
josh authored
126 # person.changed? # => false
127 # person.name = 'bob'
128 # person.changed? # => true
129 def changed?
5e70522 Changing active model dirty module helper method to more appropriate met...
Prasath Venkatraman authored
130 changed_attributes.present?
f97dae5 @josh Extract common dirty tracking methods in AMo
josh authored
131 end
132
f87820d @frodsan update ActiveModel::Dirty documentation
frodsan authored
133 # Returns an array with the name of the attributes with unsaved changes.
134 #
f97dae5 @josh Extract common dirty tracking methods in AMo
josh authored
135 # person.changed # => []
136 # person.name = 'bob'
f87820d @frodsan update ActiveModel::Dirty documentation
frodsan authored
137 # person.changed # => ["name"]
f97dae5 @josh Extract common dirty tracking methods in AMo
josh authored
138 def changed
3adaef8 @spohlenz Restore changed_attributes method in ActiveModel::Dirty and loosen expec...
spohlenz authored
139 changed_attributes.keys
f97dae5 @josh Extract common dirty tracking methods in AMo
josh authored
140 end
141
f87820d @frodsan update ActiveModel::Dirty documentation
frodsan authored
142 # Returns a hash of changed attributes indicating their original
bc818e4 @frodsan update ActiveModel::Errors documentation and minor fixes [ci skip]
frodsan authored
143 # and new values like <tt>attr => [original value, new value]</tt>.
f87820d @frodsan update ActiveModel::Dirty documentation
frodsan authored
144 #
f97dae5 @josh Extract common dirty tracking methods in AMo
josh authored
145 # person.changes # => {}
146 # person.name = 'bob'
f87820d @frodsan update ActiveModel::Dirty documentation
frodsan authored
147 # person.changes # => { "name" => ["bill", "bob"] }
f97dae5 @josh Extract common dirty tracking methods in AMo
josh authored
148 def changes
c8e632b @amatsuda Namespace HashWithIndifferentAccess
amatsuda authored
149 ActiveSupport::HashWithIndifferentAccess[changed.map { |attr| [attr, attribute_change(attr)] }]
f97dae5 @josh Extract common dirty tracking methods in AMo
josh authored
150 end
151
f87820d @frodsan update ActiveModel::Dirty documentation
frodsan authored
152 # Returns a hash of attributes that were changed before the model was saved.
153 #
154 # person.name # => "bob"
8098943 @crankharder I added this feature so that a Map of changed fields could be retrieved
crankharder authored
155 # person.name = 'robert'
156 # person.save
f87820d @frodsan update ActiveModel::Dirty documentation
frodsan authored
157 # person.previous_changes # => {"name" => ["bob", "robert"]}
8098943 @crankharder I added this feature so that a Map of changed fields could be retrieved
crankharder authored
158 def previous_changes
b8302bc @rafaelfranca Forgot to push this change in the parent commit
rafaelfranca authored
159 @previously_changed ||= ActiveSupport::HashWithIndifferentAccess.new
8098943 @crankharder I added this feature so that a Map of changed fields could be retrieved
crankharder authored
160 end
161
f87820d @frodsan update ActiveModel::Dirty documentation
frodsan authored
162 # Returns a hash of the attributes with unsaved changes indicating their original
163 # values like <tt>attr => original value</tt>.
164 #
165 # person.name # => "bob"
166 # person.name = 'robert'
167 # person.changed_attributes # => {"name" => "bob"}
6b4e0cc @fbrubacher a cloned object no longer mimics changed flags from creator , plus a tes...
fbrubacher authored
168 def changed_attributes
0e65587 @griffinmyers DirtyModel uses a hash to keep track of any changes made to attributes
griffinmyers authored
169 @changed_attributes ||= ActiveSupport::HashWithIndifferentAccess.new
6b4e0cc @fbrubacher a cloned object no longer mimics changed flags from creator , plus a tes...
fbrubacher authored
170 end
171
0f67f00 @vipulnsward AM#Dirty doc fixes
vipulnsward authored
172 # Handles <tt>*_changed?</tt> for +method_missing+.
da2b05b @gja Allows you to check if an attribute has changed to a particular value
gja authored
173 def attribute_changed?(attr, options = {}) #:nodoc:
18ae065 @sgrif Don't calculate all in-place changes to determine if attribute_changed?
sgrif authored
174 result = changes_include?(attr)
da2b05b @gja Allows you to check if an attribute has changed to a particular value
gja authored
175 result &&= options[:to] == __send__(attr) if options.key?(:to)
176 result &&= options[:from] == changed_attributes[attr] if options.key?(:from)
177 result
47617ec @tenderlove expose a few attribute changed methods
tenderlove authored
178 end
3adaef8 @spohlenz Restore changed_attributes method in ActiveModel::Dirty and loosen expec...
spohlenz authored
179
0f67f00 @vipulnsward AM#Dirty doc fixes
vipulnsward authored
180 # Handles <tt>*_was</tt> for +method_missing+.
f650981 @chancancode Added :nodoc: for `attribute_changed?` and `attribute_was` [ci skip]
chancancode authored
181 def attribute_was(attr) # :nodoc:
47617ec @tenderlove expose a few attribute changed methods
tenderlove authored
182 attribute_changed?(attr) ? changed_attributes[attr] : __send__(attr)
183 end
184
f072db8 @fertapric Add `ActiveModel::Dirty#[attr_name]_previously_changed?` and
fertapric authored
185 # Handles <tt>*_previously_changed?</tt> for +method_missing+.
186 def attribute_previously_changed?(attr, options = {}) #:nodoc:
187 previous_changes_include?(attr)
188 end
189
1a300b6 @rafaelfranca Make restore_attributes public
rafaelfranca authored
190 # Restore all previous data of the provided attributes.
191 def restore_attributes(attributes = changed)
192 attributes.each { |attr| restore_attribute! attr }
193 end
194
47617ec @tenderlove expose a few attribute changed methods
tenderlove authored
195 private
f97dae5 @josh Extract common dirty tracking methods in AMo
josh authored
196
0f67f00 @vipulnsward AM#Dirty doc fixes
vipulnsward authored
197 # Returns +true+ if attr_name is changed, +false+ otherwise.
18ae065 @sgrif Don't calculate all in-place changes to determine if attribute_changed?
sgrif authored
198 def changes_include?(attr_name)
199 attributes_changed_by_setter.include?(attr_name)
200 end
ea721d7 @sgrif Don't calculate in-place changes on attribute assignment
sgrif authored
201 alias attribute_changed_by_setter? changes_include?
18ae065 @sgrif Don't calculate all in-place changes to determine if attribute_changed?
sgrif authored
202
f072db8 @fertapric Add `ActiveModel::Dirty#[attr_name]_previously_changed?` and
fertapric authored
203 # Returns +true+ if attr_name were changed before the model was saved,
204 # +false+ otherwise.
205 def previous_changes_include?(attr_name)
206 @previously_changed.include?(attr_name)
207 end
208
9aa1a3d @rafaelfranca Merge pull request #10816 from bogdan/less-dirty-dirty
rafaelfranca authored
209 # Removes current changes and makes them accessible through +previous_changes+.
b34f7c1 @rafaelfranca Add CHANGELOG entry for #14861 and document private methods on the API
rafaelfranca authored
210 def changes_applied # :doc:
9aa1a3d @rafaelfranca Merge pull request #10816 from bogdan/less-dirty-dirty
rafaelfranca authored
211 @previously_changed = changes
06a0003 @rafaelfranca When applying changes or reseting changes create the right class
rafaelfranca authored
212 @changed_attributes = ActiveSupport::HashWithIndifferentAccess.new
9aa1a3d @rafaelfranca Merge pull request #10816 from bogdan/less-dirty-dirty
rafaelfranca authored
213 end
214
0f67f00 @vipulnsward AM#Dirty doc fixes
vipulnsward authored
215 # Clears all dirty data: current changes and previous changes.
66d0a01 @rafaelfranca Deprecate ActiveModel::Dirty#reset_changes in favor of #clear_changes_in...
rafaelfranca authored
216 def clear_changes_information # :doc:
06a0003 @rafaelfranca When applying changes or reseting changes create the right class
rafaelfranca authored
217 @previously_changed = ActiveSupport::HashWithIndifferentAccess.new
218 @changed_attributes = ActiveSupport::HashWithIndifferentAccess.new
9aa1a3d @rafaelfranca Merge pull request #10816 from bogdan/less-dirty-dirty
rafaelfranca authored
219 end
220
0f67f00 @vipulnsward AM#Dirty doc fixes
vipulnsward authored
221 # Handles <tt>*_change</tt> for +method_missing+.
f97dae5 @josh Extract common dirty tracking methods in AMo
josh authored
222 def attribute_change(attr)
3adaef8 @spohlenz Restore changed_attributes method in ActiveModel::Dirty and loosen expec...
spohlenz authored
223 [changed_attributes[attr], __send__(attr)] if attribute_changed?(attr)
f97dae5 @josh Extract common dirty tracking methods in AMo
josh authored
224 end
225
f072db8 @fertapric Add `ActiveModel::Dirty#[attr_name]_previously_changed?` and
fertapric authored
226 # Handles <tt>*_previous_change</tt> for +method_missing+.
227 def attribute_previous_change(attr)
228 @previously_changed[attr] if attribute_previously_changed?(attr)
229 end
230
0f67f00 @vipulnsward AM#Dirty doc fixes
vipulnsward authored
231 # Handles <tt>*_will_change!</tt> for +method_missing+.
f97dae5 @josh Extract common dirty tracking methods in AMo
josh authored
232 def attribute_will_change!(attr)
9d1f6ed @carlosantoniodasilva Return earlier if attribute already changed in *_will_change! methods
carlosantoniodasilva authored
233 return if attribute_changed?(attr)
234
f97dae5 @josh Extract common dirty tracking methods in AMo
josh authored
235 begin
236 value = __send__(attr)
237 value = value.duplicable? ? value.clone : value
238 rescue TypeError, NoMethodError
239 end
240
877ea78 @sgrif Implement `_was` and `changes` for in-place mutations of AR attributes
sgrif authored
241 set_attribute_was(attr, value)
f97dae5 @josh Extract common dirty tracking methods in AMo
josh authored
242 end
243
0f67f00 @vipulnsward AM#Dirty doc fixes
vipulnsward authored
244 # Handles <tt>restore_*!</tt> for +method_missing+.
41fb06f @rafaelfranca Deprecate `reset_#{attribute}` in favor of `restore_#{attribute}`.
rafaelfranca authored
245 def restore_attribute!(attr)
cf7ab60 @rmascarenhas Reset attributes should not report changes.
rmascarenhas authored
246 if attribute_changed?(attr)
247 __send__("#{attr}=", changed_attributes[attr])
877ea78 @sgrif Implement `_was` and `changes` for in-place mutations of AR attributes
sgrif authored
248 clear_attribute_changes([attr])
cf7ab60 @rmascarenhas Reset attributes should not report changes.
rmascarenhas authored
249 end
f97dae5 @josh Extract common dirty tracking methods in AMo
josh authored
250 end
008f3da @chancancode Don't expose these new APIs yet (added in 877ea78 / #16189)
chancancode authored
251
252 # This is necessary because `changed_attributes` might be overridden in
0f67f00 @vipulnsward AM#Dirty doc fixes
vipulnsward authored
253 # other implementations (e.g. in `ActiveRecord`)
008f3da @chancancode Don't expose these new APIs yet (added in 877ea78 / #16189)
chancancode authored
254 alias_method :attributes_changed_by_setter, :changed_attributes # :nodoc:
255
256 # Force an attribute to have a particular "before" value
257 def set_attribute_was(attr, old_value)
258 attributes_changed_by_setter[attr] = old_value
259 end
260
261 # Remove changes information for the provided attributes.
eb05774 @marzapower [Enh] Changed the visibility of the ActiveModel::Dirty#clear_attribute_c...
marzapower authored
262 def clear_attribute_changes(attributes) # :doc:
008f3da @chancancode Don't expose these new APIs yet (added in 877ea78 / #16189)
chancancode authored
263 attributes_changed_by_setter.except!(*attributes)
264 end
f97dae5 @josh Extract common dirty tracking methods in AMo
josh authored
265 end
266 end
Something went wrong with that request. Please try again.