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]
291
def test_save_should_not_save_serialized_attribute_with_partial_updates_if_not_present
292
with_partial_updates(Topic) do
293
Topic.create!(:author_name => 'Bill', :content => {:a => "a"})
294
topic = Topic.first(:select => 'id, author_name')
295
topic.update_attribute :author_name, 'John'
297
assert_not_nil topic.content
302
def with_partial_updates(klass, on = true)
303
old = klass.partial_updates?
304
klass.partial_updates = on
307
klass.partial_updates = old
310
def check_pirate_after_save_failure(pirate)
311
assert pirate.changed?
312
assert pirate.parrot_id_changed?
313
assert_equal %w(parrot_id), pirate.changed
314
assert_nil pirate.parrot_id_was