From 791caac8f69883e8fb7c950ee124af33d0418095 Mon Sep 17 00:00:00 2001 From: Scott Barr Date: Fri, 15 May 2009 12:18:23 +0800 Subject: [PATCH 1/2] Added pending_changes to ActiveRecord::Dirty I added this feature so that a Map of changed fields could be retrieved after a model had been saved. This is useful in the after_save callback when you need to know what fields changed. At present there is no way to do this other than have code in the before_save callback that takes a copy of the changes Map, which I thought was a bit messy. Example. person = Person.find_by_name('bob') person.name = 'robert' person.changes # => {'name' => ['bob, 'robert']} person.save person.changes # => {} person.previous_changes # => {'name' => ['bob, 'robert']} person.reload person.previous_changes # => {} --- activerecord/lib/active_record/dirty.rb | 17 ++++++++++++ activerecord/test/cases/dirty_test.rb | 42 ++++++++++++++++++++++++++++--- 2 files changed, 55 insertions(+), 4 deletions(-) diff --git a/activerecord/lib/active_record/dirty.rb b/activerecord/lib/active_record/dirty.rb index 4a2510a..5e717cc 100644 --- a/activerecord/lib/active_record/dirty.rb +++ b/activerecord/lib/active_record/dirty.rb @@ -66,6 +66,15 @@ module ActiveRecord changed_attributes.keys end + # Map of attributes that were changed when the model was saved. + # person.name # => 'bob' + # person.name = 'robert' + # person.save + # person.previous_changes # => {'name' => ['bob, 'robert']} + def previous_changes + previously_changed_attributes + end + # Map of changed attrs => [original value, new value]. # person.changes # => {} # person.name = 'bob' @@ -77,6 +86,7 @@ module ActiveRecord # Attempts to +save+ the record and clears changed attributes if successful. def save_with_dirty(*args) #:nodoc: if status = save_without_dirty(*args) + @previously_changed = changes changed_attributes.clear end status @@ -85,6 +95,7 @@ module ActiveRecord # Attempts to save! the record and clears changed attributes if successful. def save_with_dirty!(*args) #:nodoc: status = save_without_dirty!(*args) + @previously_changed = changes changed_attributes.clear status end @@ -92,6 +103,7 @@ module ActiveRecord # reload the record and clears changed attributes. def reload_with_dirty(*args) #:nodoc: record = reload_without_dirty(*args) + previously_changed_attributes.clear changed_attributes.clear record end @@ -101,6 +113,11 @@ module ActiveRecord def changed_attributes @changed_attributes ||= {} end + + # Map of fields that were changed when the model was saved + def previously_changed_attributes + @previously_changed ||= {} + end # Handle *_changed? for +method_missing+. def attribute_changed?(attr) diff --git a/activerecord/test/cases/dirty_test.rb b/activerecord/test/cases/dirty_test.rb index ac95bac..3a7383e 100644 --- a/activerecord/test/cases/dirty_test.rb +++ b/activerecord/test/cases/dirty_test.rb @@ -31,24 +31,24 @@ class DirtyTest < ActiveRecord::TestCase pirate = Pirate.new assert !pirate.catchphrase_changed? assert_nil pirate.catchphrase_change - + # Change catchphrase. pirate.catchphrase = 'arrr' assert pirate.catchphrase_changed? assert_nil pirate.catchphrase_was assert_equal [nil, 'arrr'], pirate.catchphrase_change - + # Saved - no changes. pirate.save! assert !pirate.catchphrase_changed? assert_nil pirate.catchphrase_change - + # Same value - no changes. pirate.catchphrase = 'arrr' assert !pirate.catchphrase_changed? assert_nil pirate.catchphrase_change end - + def test_aliased_attribute_changes # the actual attribute here is name, title is an # alias setup via alias_attribute @@ -288,6 +288,40 @@ class DirtyTest < ActiveRecord::TestCase end end + def test_previous_changes + # original values should be in previous_changes + pirate = Pirate.new + + assert_equal Hash.new, pirate.previous_changes + pirate.catchphrase = "arrr" + pirate.save! + + assert_equal 4, pirate.previous_changes.size + assert_equal [nil, "arrr"], pirate.previous_changes['catchphrase'] + assert_equal [nil, pirate.id], pirate.previous_changes['id'] + assert_nil pirate.previous_changes['updated_on'][0] + assert_not_nil pirate.previous_changes['updated_on'][1] + assert_nil pirate.previous_changes['created_on'][0] + assert_not_nil pirate.previous_changes['created_on'][1] + + # original values should be in previous_changes + pirate = Pirate.new + + assert_equal Hash.new, pirate.previous_changes + pirate.catchphrase = "arrr" + pirate.save + + assert_equal 4, pirate.previous_changes.size + assert_equal [nil, "arrr"], pirate.previous_changes['catchphrase'] + assert_equal [nil, pirate.id], pirate.previous_changes['id'] + assert pirate.previous_changes.include?('updated_on') + assert pirate.previous_changes.include?('created_on') + + pirate.catchphrase = "Yar!!" + pirate.reload + assert_equal Hash.new, pirate.previous_changes + end + private def with_partial_updates(klass, on = true) old = klass.partial_updates? -- 1.6.3.3 From 87015fd810d916e9506e0ade0a5412b740708ba9 Mon Sep 17 00:00:00 2001 From: Greg Date: Sat, 8 Aug 2009 12:20:19 -0400 Subject: [PATCH 2/2] Improve text coverage for previous_changes --- activerecord/test/cases/dirty_test.rb | 44 +++++++++++++++++++++++++++++++++ 1 files changed, 44 insertions(+), 0 deletions(-) diff --git a/activerecord/test/cases/dirty_test.rb b/activerecord/test/cases/dirty_test.rb index 3a7383e..5d20c38 100644 --- a/activerecord/test/cases/dirty_test.rb +++ b/activerecord/test/cases/dirty_test.rb @@ -303,6 +303,7 @@ class DirtyTest < ActiveRecord::TestCase assert_not_nil pirate.previous_changes['updated_on'][1] assert_nil pirate.previous_changes['created_on'][0] assert_not_nil pirate.previous_changes['created_on'][1] + assert !pirate.previous_changes.key?('parrot_id') # original values should be in previous_changes pirate = Pirate.new @@ -316,10 +317,53 @@ class DirtyTest < ActiveRecord::TestCase assert_equal [nil, pirate.id], pirate.previous_changes['id'] assert pirate.previous_changes.include?('updated_on') assert pirate.previous_changes.include?('created_on') + assert !pirate.previous_changes.key?('parrot_id') pirate.catchphrase = "Yar!!" pirate.reload assert_equal Hash.new, pirate.previous_changes + + pirate = Pirate.find_by_catchphrase("arrr") + pirate.catchphrase = "Me Maties!" + pirate.save! + + assert_equal 2, pirate.previous_changes.size + assert_equal ["arrr", "Me Maties!"], pirate.previous_changes['catchphrase'] + assert_not_nil pirate.previous_changes['updated_on'][0] + assert_not_nil pirate.previous_changes['updated_on'][1] + assert !pirate.previous_changes.key?('parrot_id') + assert !pirate.previous_changes.key?('created_on') + + pirate = Pirate.find_by_catchphrase("Me Maties!") + pirate.catchphrase = "Thar She Blows!" + pirate.save + + assert_equal 2, pirate.previous_changes.size + assert_equal ["Me Maties!", "Thar She Blows!"], pirate.previous_changes['catchphrase'] + assert_not_nil pirate.previous_changes['updated_on'][0] + assert_not_nil pirate.previous_changes['updated_on'][1] + assert !pirate.previous_changes.key?('parrot_id') + assert !pirate.previous_changes.key?('created_on') + + pirate = Pirate.find_by_catchphrase("Thar She Blows!") + pirate.update_attributes(:catchphrase => "Ahoy!") + + assert_equal 2, pirate.previous_changes.size + assert_equal ["Thar She Blows!", "Ahoy!"], pirate.previous_changes['catchphrase'] + assert_not_nil pirate.previous_changes['updated_on'][0] + assert_not_nil pirate.previous_changes['updated_on'][1] + assert !pirate.previous_changes.key?('parrot_id') + assert !pirate.previous_changes.key?('created_on') + + pirate = Pirate.find_by_catchphrase("Ahoy!") + pirate.update_attribute(:catchphrase, "Ninjas suck!") + + assert_equal 2, pirate.previous_changes.size + assert_equal ["Ahoy!", "Ninjas suck!"], pirate.previous_changes['catchphrase'] + assert_not_nil pirate.previous_changes['updated_on'][0] + assert_not_nil pirate.previous_changes['updated_on'][1] + assert !pirate.previous_changes.key?('parrot_id') + assert !pirate.previous_changes.key?('created_on') end private -- 1.6.3.3