2 require 'models/topic' # For booleans
3 require 'models/pirate' # For timestamps
4 require 'models/parrot'
5 require 'models/person' # For optimistic locking
7 class Pirate
# Just reopening it, not defining it
8 attr_accessor
:detected_changes_in_after_update # Boolean for if changes are detected
9 attr_accessor
:changes_detected_in_after_update # Actual changes
11 after_update
:check_changes
14 # after_save/update in sweepers, observers, and the model itself
15 # can end up checking dirty status and acting on the results
18 self.detected_changes_in_after_update
= true
19 self.changes_detected_in_after_update
= self.changes
24 class NumericData
< ActiveRecord
::Base
25 self.table_name
= 'numeric_data'
28 class DirtyTest
< ActiveRecord
::TestCase
29 def test_attribute_changes
30 # New record - no changes.
32 assert
!pirate
.catchphrase_changed
?
33 assert_nil pirate
.catchphrase_change
36 pirate
.catchphrase
= 'arrr'
37 assert pirate
.catchphrase_changed
?
38 assert_nil pirate
.catchphrase_was
39 assert_equal
[nil, 'arrr'], pirate
.catchphrase_change
43 assert
!pirate
.catchphrase_changed
?
44 assert_nil pirate
.catchphrase_change
46 # Same value - no changes.
47 pirate
.catchphrase
= 'arrr'
48 assert
!pirate
.catchphrase_changed
?
49 assert_nil pirate
.catchphrase_change
52 def test_aliased_attribute_changes
53 # the actual attribute here is name, title is an
54 # alias setup via alias_attribute
56 assert
!parrot
.title_changed
?
57 assert_nil parrot
.title_change
60 assert parrot
.title_changed
?
61 assert_nil parrot
.title_was
62 assert_equal parrot
.name_change
, parrot
.title_change
65 def test_nullable_number_not_marked_as_changed_if_new_value_is_blank
68 ["", nil].each
do |value
|
69 pirate
.parrot_id
= value
70 assert
!pirate
.parrot_id_changed
?
71 assert_nil pirate
.parrot_id_change
75 def test_nullable_decimal_not_marked_as_changed_if_new_value_is_blank
76 numeric_data
= NumericData
.new
78 ["", nil].each
do |value
|
79 numeric_data
.bank_balance
= value
80 assert
!numeric_data
.bank_balance_changed
?
81 assert_nil numeric_data
.bank_balance_change
85 def test_nullable_float_not_marked_as_changed_if_new_value_is_blank
86 numeric_data
= NumericData
.new
88 ["", nil].each
do |value
|
89 numeric_data
.temperature
= value
90 assert
!numeric_data
.temperature_changed
?
91 assert_nil numeric_data
.temperature_change
95 def test_nullable_integer_zero_to_string_zero_not_marked_as_changed
98 pirate
.catchphrase
= 'arrr'
101 assert
!pirate
.changed
?
103 pirate
.parrot_id
= '0'
104 assert
!pirate
.changed
?
107 def test_zero_to_blank_marked_as_changed
109 pirate
.catchphrase
= "Yarrrr, me hearties"
113 # check the change from 1 to ''
114 pirate
= Pirate
.find_by_catchphrase("Yarrrr, me hearties")
115 pirate
.parrot_id
= ''
116 assert pirate
.parrot_id_changed
?
117 assert_equal([1, nil], pirate
.parrot_id_change
)
120 # check the change from nil to 0
121 pirate
= Pirate
.find_by_catchphrase("Yarrrr, me hearties")
123 assert pirate
.parrot_id_changed
?
124 assert_equal([nil, 0], pirate
.parrot_id_change
)
127 # check the change from 0 to ''
128 pirate
= Pirate
.find_by_catchphrase("Yarrrr, me hearties")
129 pirate
.parrot_id
= ''
130 assert pirate
.parrot_id_changed
?
131 assert_equal([0, nil], pirate
.parrot_id_change
)
134 def test_object_should_be_changed_if_any_attribute_is_changed
136 assert
!pirate
.changed
?
137 assert_equal
[], pirate
.changed
138 assert_equal Hash
.new
, pirate
.changes
140 pirate
.catchphrase
= 'arrr'
141 assert pirate
.changed
?
142 assert_nil pirate
.catchphrase_was
143 assert_equal
%w(catchphrase
), pirate
.changed
144 assert_equal({'catchphrase' => [nil, 'arrr']}, pirate
.changes
)
147 assert
!pirate
.changed
?
148 assert_equal
[], pirate
.changed
149 assert_equal Hash
.new
, pirate
.changes
152 def test_attribute_will_change
!
153 pirate
= Pirate
.create
!(:catchphrase => 'arr')
155 pirate
.catchphrase
<< ' matey'
156 assert
!pirate
.catchphrase_changed
?
158 assert pirate
.catchphrase_will_change
!
159 assert pirate
.catchphrase_changed
?
160 assert_equal
['arr matey', 'arr matey'], pirate
.catchphrase_change
162 pirate
.catchphrase
<< '!'
163 assert pirate
.catchphrase_changed
?
164 assert_equal
['arr matey', 'arr matey!'], pirate
.catchphrase_change
167 def test_association_assignment_changes_foreign_key
168 pirate
= Pirate
.create
!(:catchphrase => 'jarl')
169 pirate
.parrot
= Parrot
.create
!(:name => 'Lorre')
170 assert pirate
.changed
?
171 assert_equal
%w(parrot_id
), pirate
.changed
174 def test_attribute_should_be_compared_with_type_cast
176 assert topic
.approved
?
177 assert
!topic
.approved_changed
?
179 # Coming from web form.
180 params
= {:topic => {:approved => 1}}
182 topic
.attributes
= params
[:topic]
183 assert topic
.approved
?
184 assert
!topic
.approved_changed
?
187 def test_partial_update
188 pirate
= Pirate
.new(:catchphrase => 'foo')
189 old_updated_on
= 1.hour
.ago
.beginning_of_day
191 with_partial_updates Pirate
, false do
192 assert_queries(2) { 2.times
{ pirate
.save
! } }
193 Pirate
.update_all({ :updated_on => old_updated_on
}, :id => pirate
.id
)
196 with_partial_updates Pirate
, true do
197 assert_queries(0) { 2.times
{ pirate
.save
! } }
198 assert_equal old_updated_on
, pirate
.reload
.updated_on
200 assert_queries(1) { pirate
.catchphrase
= 'bar'; pirate
.save
! }
201 assert_not_equal old_updated_on
, pirate
.reload
.updated_on
205 def test_partial_update_with_optimistic_locking
206 person
= Person
.new(:first_name => 'foo')
209 with_partial_updates Person
, false do
210 assert_queries(2) { 2.times
{ person
.save
! } }
211 Person
.update_all({ :first_name => 'baz' }, :id => person
.id
)
214 with_partial_updates Person
, true do
215 assert_queries(0) { 2.times
{ person
.save
! } }
216 assert_equal old_lock_version
, person
.reload
.lock_version
218 assert_queries(1) { person
.first_name
= 'bar'; person
.save
! }
219 assert_not_equal old_lock_version
, person
.reload
.lock_version
223 def test_changed_attributes_should_be_preserved_if_save_failure
227 check_pirate_after_save_failure(pirate
)
231 assert_raise(ActiveRecord
::RecordInvalid) { pirate
.save
! }
232 check_pirate_after_save_failure(pirate
)
235 def test_reload_should_clear_changed_attributes
236 pirate
= Pirate
.create
!(:catchphrase => "shiver me timbers")
237 pirate
.catchphrase
= "*hic*"
238 assert pirate
.changed
?
240 assert
!pirate
.changed
?
243 def test_reverted_changes_are_not_dirty
244 phrase
= "shiver me timbers"
245 pirate
= Pirate
.create
!(:catchphrase => phrase
)
246 pirate
.catchphrase
= "*hic*"
247 assert pirate
.changed
?
248 pirate
.catchphrase
= phrase
249 assert
!pirate
.changed
?
252 def test_reverted_changes_are_not_dirty_after_multiple_changes
253 phrase
= "shiver me timbers"
254 pirate
= Pirate
.create
!(:catchphrase => phrase
)
256 pirate
.catchphrase
= "*hic*" * i
257 assert pirate
.changed
?
259 assert pirate
.changed
?
260 pirate
.catchphrase
= phrase
261 assert
!pirate
.changed
?
265 def test_reverted_changes_are_not_dirty_going_from_nil_to_value_and_back
266 pirate
= Pirate
.create
!(:catchphrase => "Yar!")
269 assert pirate
.changed
?
270 assert pirate
.parrot_id_changed
?
271 assert
!pirate
.catchphrase_changed
?
273 pirate
.parrot_id
= nil
274 assert
!pirate
.changed
?
275 assert
!pirate
.parrot_id_changed
?
276 assert
!pirate
.catchphrase_changed
?
279 def test_save_should_store_serialized_attributes_even_with_partial_updates
280 with_partial_updates(Topic
) do
281 topic
= Topic
.create
!(:content => {:a => "a"})
282 topic
.content
[:b] = "b"
283 #assert topic.changed? # Known bug, will fail
285 assert_equal
"b", topic
.content
[:b]
287 assert_equal
"b", topic
.content
[:b]
292 def with_partial_updates(klass
, on
= true)
293 old
= klass
.partial_updates
?
294 klass
.partial_updates
= on
297 klass
.partial_updates
= old
300 def check_pirate_after_save_failure(pirate
)
301 assert pirate
.changed
?
302 assert pirate
.parrot_id_changed
?
303 assert_equal
%w(parrot_id
), pirate
.changed
304 assert_nil pirate
.parrot_id_was