The ability to track whether or not your active record objects have been modified or not becomes a lot easier with the new dirty object functionality of edge rails. It’s dead simple, and pretty slick, to use:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
article = Article.find(:first) article.changed? #=> false # Track changes to individual attributes with # attr_name_changed? accessor article.title #=> "Title" article.title = "New Title" article.title_changed? #=> true # Access previous value with attr_name_was accessor article.title_was #=> "Title" # See both previous and current value with attr_name_change accessor article.title_change #=> ["Title", "New Title"] |
Beyond the clever attribute based accessor methods, you can also query to object directly for its list of all changed attributes. (Continuing from the previous example):
1 2 3 4 5 |
# Get a list of changed attributes article.changed #=> ['title'] # Get the hash of changed attributes and their previous and current values article.changes #=> { 'title' => ["Title", "New Title"] } |
Note: Once you save a dirty object it clears out its changed state tracking and is once again considered unchanged.
1 2 3 |
article.changed? #=> true article.save #=> true article.changed? #=> false |
If you’re going to be modifying an attribute outside of the attr= writer, you can use attr_name_will_change! to tell the object to be aware of the change:
1 2 3 4 |
article = Article.find(:first) article.title_will_change! article.title.upcase! article.title_change #=> ['Title', 'TITLE'] |
And coming down the pipe is a feature that will make the most of this functionality – partial SQL updates that will only update attributes that have changed…
tags: ruby, rubyonrails

Partial updates are already in edge :)
What is the benefit of partial updates?
Jason: just imagine a model with large text content attributes, or god forbid, a blob.
I’m surprised that it took this long. Partial updates are much faster, cleaner, and easier to understand (let alone the most efficient way to update a table and avoid some locking mechanisms). Wether it is digging through logs, checking if you updated something that needs to flush the cache, or any other reason, this is has been a long time coming. Glad to see some much needed progress in this area finally committed.
That’s cool, though your last example is a little scary. That’s going to potentially be really hard to remember to have to flag attributes as dirty in those cases. Not that it happens a lot, but that will just make it all the more frustrating to debug…
great! :)
I think Don is right! Will I have to flag any change I made to field without the field= method?! That would cause really strange and undebuggable errors when I miss it.
On the other hand this is a cool feature. Maybe I just missed a point or it needs a little bit more tweaking? And: What happens when I do this:
article = Article.find(:first) article.title #=> “Test” article.title = “Test”
What would:
article.title_changed?
return?
AAAARG! ;)
article = Article.find(:first)
article.title #=> “Test”
article.title = “Test”
Pratik: Yes, I know partial updates are already here – I was trying to setup a little teaser for my readers whilst I labored on that article :)
Thorben: This functionality is smart enough to only mark a field as changed when its values are different. So your example will return false:
Get this functionality in your 2.0 app.
http://code.bitsweat.net/svn/dirty
But what about this?
After looking at the code in the plugin Bryan posted a link to, it’s pretty obvious that my example would return false, which is why the `will_change!` flag is necessary. Why not have the `changed?` methods and `save` methods check to see if the value is different, rather than have the setters keep track of things that changed? Then you don’t have to worry about the flag, if the value is different, no matter how it changed, `changed?` returns the right value.
Paul: You would then have to store all the original attributes twice in memory. Don’t know if that’s the reason, but it would be rather ugly.
article.title.changed? and article.title.was would have been much cleaner syntax than underscore accessor.
“would have been much cleaner syntax”
What makes it cleaner? There isn’t anything wrong with underscores. You’re asking the parent object if attribute x has changed, not asking the attribute itself if it’s changed, so your suggested syntax isn’t semantically correct.
You may now use Article.first instead of Article.find(:first) ! :)
Daniel: Yeah, I guess that’s the tradoff. I consider having to set the flag pretty ugly as well.
What would happend with partial updates at cases like Paul’s example?
@Luke… okay, I see your point, that is technically true, but Mike’s approach seems much more readable to me too, since dots are definitely always separators, while with underscores you have to stop and think about it. Perhaps just because this is new… maybe when I’m more used to it I’ll come around to seeing it your way.
That’s great, but can I selectively turn it off? What if I have an attribute which contains a large file blob? I don’t want to have it in memory twice.
I think this breaks acts_as_versioned
Agreed, this seems to break acts_as_versioned.
NoMethodError: You have a nil object when you didn’t expect it! You might have expected an instance of Array. The error occurred while evaluating nil.include? from /www/trunk/vendor/rails/activerecord/lib/active_record/dirty.rb:118:in `write_attribute’ from /www/trunk/vendor/rails/activerecord/lib/active_record/attribute_methods.rb:200:in `challenge_id=’ from /www/trunk/vendor/rails/activerecord/lib/active_record/attribute_methods.rb:229:in `send’ from /www/trunk/vendor/rails/activerecord/lib/active_record/attribute_methods.rb:229:in `method_missing’ from (irb):2
Looks great. Can’t wait to use this feature. Doing a quick threat-check for acts_as_versioned in our code and did find ... if const.respond_to? :versions #test for acts_as_versioned which I hope suggests there is a means to identify and work around acts_as_versioned issues