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 DirtyTest
< ActiveRecord
::TestCase
25 def test_attribute_changes
26 # New record - no changes.
28 assert
!pirate
.catchphrase_changed
?
29 assert_nil pirate
.catchphrase_change
32 pirate
.catchphrase
= 'arrr'
33 assert pirate
.catchphrase_changed
?
34 assert_nil pirate
.catchphrase_was
35 assert_equal
[nil, 'arrr'], pirate
.catchphrase_change
39 assert
!pirate
.catchphrase_changed
?
40 assert_nil pirate
.catchphrase_change
42 # Same value - no changes.
43 pirate
.catchphrase
= 'arrr'
44 assert
!pirate
.catchphrase_changed
?
45 assert_nil pirate
.catchphrase_change
48 def test_aliased_attribute_changes
49 # the actual attribute here is name, title is an
50 # alias setup via alias_attribute
52 assert
!parrot
.title_changed
?
53 assert_nil parrot
.title_change
56 assert parrot
.title_changed
?
57 assert_nil parrot
.title_was
58 assert_equal parrot
.name_change
, parrot
.title_change
61 def test_nullable_integer_not_marked_as_changed_if_new_value_is_blank
64 ["", nil].each
do |value
|
65 pirate
.parrot_id
= value
66 assert
!pirate
.parrot_id_changed
?
67 assert_nil pirate
.parrot_id_change
71 def test_zero_to_blank_marked_as_changed
73 pirate
.catchphrase
= "Yarrrr, me hearties"
77 # check the change from 1 to ''
78 pirate
= Pirate
.find_by_catchphrase("Yarrrr, me hearties")
80 assert pirate
.parrot_id_changed
?
81 assert_equal([1, nil], pirate
.parrot_id_change
)
84 # check the change from nil to 0
85 pirate
= Pirate
.find_by_catchphrase("Yarrrr, me hearties")
87 assert pirate
.parrot_id_changed
?
88 assert_equal([nil, 0], pirate
.parrot_id_change
)
91 # check the change from 0 to ''
92 pirate
= Pirate
.find_by_catchphrase("Yarrrr, me hearties")
94 assert pirate
.parrot_id_changed
?
95 assert_equal([0, nil], pirate
.parrot_id_change
)
98 def test_object_should_be_changed_if_any_attribute_is_changed
100 assert
!pirate
.changed
?
101 assert_equal
[], pirate
.changed
102 assert_equal Hash
.new
, pirate
.changes
104 pirate
.catchphrase
= 'arrr'
105 assert pirate
.changed
?
106 assert_nil pirate
.catchphrase_was
107 assert_equal
%w(catchphrase
), pirate
.changed
108 assert_equal({'catchphrase' => [nil, 'arrr']}, pirate
.changes
)
111 assert
!pirate
.changed
?
112 assert_equal
[], pirate
.changed
113 assert_equal Hash
.new
, pirate
.changes
116 def test_attribute_will_change
!
117 pirate
= Pirate
.create
!(:catchphrase => 'arr')
119 pirate
.catchphrase
<< ' matey'
120 assert
!pirate
.catchphrase_changed
?
122 assert pirate
.catchphrase_will_change
!
123 assert pirate
.catchphrase_changed
?
124 assert_equal
['arr matey', 'arr matey'], pirate
.catchphrase_change
126 pirate
.catchphrase
<< '!'
127 assert pirate
.catchphrase_changed
?
128 assert_equal
['arr matey', 'arr matey!'], pirate
.catchphrase_change
131 def test_association_assignment_changes_foreign_key
132 pirate
= Pirate
.create
!(:catchphrase => 'jarl')
133 pirate
.parrot
= Parrot
.create
!
134 assert pirate
.changed
?
135 assert_equal
%w(parrot_id
), pirate
.changed
138 def test_attribute_should_be_compared_with_type_cast
140 assert topic
.approved
?
141 assert
!topic
.approved_changed
?
143 # Coming from web form.
144 params
= {:topic => {:approved => 1}}
146 topic
.attributes
= params
[:topic]
147 assert topic
.approved
?
148 assert
!topic
.approved_changed
?
151 def test_partial_update
152 pirate
= Pirate
.new(:catchphrase => 'foo')
153 old_updated_on
= 1.hour
.ago
.beginning_of_day
155 with_partial_updates Pirate
, false do
156 assert_queries(2) { 2.times
{ pirate
.save
! } }
157 Pirate
.update_all({ :updated_on => old_updated_on
}, :id => pirate
.id
)
160 with_partial_updates Pirate
, true do
161 assert_queries(0) { 2.times
{ pirate
.save
! } }
162 assert_equal old_updated_on
, pirate
.reload
.updated_on
164 assert_queries(1) { pirate
.catchphrase
= 'bar'; pirate
.save
! }
165 assert_not_equal old_updated_on
, pirate
.reload
.updated_on
169 def test_partial_update_with_optimistic_locking
170 person
= Person
.new(:first_name => 'foo')
173 with_partial_updates Person
, false do
174 assert_queries(2) { 2.times
{ person
.save
! } }
175 Person
.update_all({ :first_name => 'baz' }, :id => person
.id
)
178 with_partial_updates Person
, true do
179 assert_queries(0) { 2.times
{ person
.save
! } }
180 assert_equal old_lock_version
, person
.reload
.lock_version
182 assert_queries(1) { person
.first_name
= 'bar'; person
.save
! }
183 assert_not_equal old_lock_version
, person
.reload
.lock_version
187 def test_changed_attributes_should_be_preserved_if_save_failure
191 check_pirate_after_save_failure(pirate
)
195 assert_raises(ActiveRecord
::RecordInvalid) { pirate
.save
! }
196 check_pirate_after_save_failure(pirate
)
199 def test_reload_should_clear_changed_attributes
200 pirate
= Pirate
.create
!(:catchphrase => "shiver me timbers")
201 pirate
.catchphrase
= "*hic*"
202 assert pirate
.changed
?
204 assert
!pirate
.changed
?
207 def test_reverted_changes_are_not_dirty
208 phrase
= "shiver me timbers"
209 pirate
= Pirate
.create
!(:catchphrase => phrase
)
210 pirate
.catchphrase
= "*hic*"
211 assert pirate
.changed
?
212 pirate
.catchphrase
= phrase
213 assert
!pirate
.changed
?
216 def test_reverted_changes_are_not_dirty_after_multiple_changes
217 phrase
= "shiver me timbers"
218 pirate
= Pirate
.create
!(:catchphrase => phrase
)
220 pirate
.catchphrase
= "*hic*" * i
221 assert pirate
.changed
?
223 assert pirate
.changed
?
224 pirate
.catchphrase
= phrase
225 assert
!pirate
.changed
?
229 def test_reverted_changes_are_not_dirty_going_from_nil_to_value_and_back
230 pirate
= Pirate
.create
!(:catchphrase => "Yar!")
233 assert pirate
.changed
?
234 assert pirate
.parrot_id_changed
?
235 assert
!pirate
.catchphrase_changed
?
237 pirate
.parrot_id
= nil
238 assert
!pirate
.changed
?
239 assert
!pirate
.parrot_id_changed
?
240 assert
!pirate
.catchphrase_changed
?
243 def test_save_should_store_serialized_attributes_even_with_partial_updates
244 with_partial_updates(Topic
) do
245 topic
= Topic
.create
!(:content => {:a => "a"})
246 topic
.content
[:b] = "b"
247 #assert topic.changed? # Known bug, will fail
249 assert_equal
"b", topic
.content
[:b]
251 assert_equal
"b", topic
.content
[:b]
256 def with_partial_updates(klass
, on
= true)
257 old
= klass
.partial_updates
?
258 klass
.partial_updates
= on
261 klass
.partial_updates
= old
264 def check_pirate_after_save_failure(pirate
)
265 assert pirate
.changed
?
266 assert pirate
.parrot_id_changed
?
267 assert_equal
%w(parrot_id
), pirate
.changed
268 assert_nil pirate
.parrot_id_was