What's New in Edge Rails: Dirty Objects

Posted by ryan
at 7:38 PM on Monday, March 31, 2008



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

Comments

Leave a response

  1. PratikMarch 31, 2008 @ 08:43 PM

    Partial updates are already in edge :)

  2. jasonMarch 31, 2008 @ 09:05 PM

    What is the benefit of partial updates?

  3. Jason WatkinsMarch 31, 2008 @ 09:17 PM

    Jason: just imagine a model with large text content attributes, or god forbid, a blob.

  4. Josh MartinMarch 31, 2008 @ 09:45 PM

    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.

  5. DonApril 01, 2008 @ 01:42 AM

    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…

  6. SebastianApril 01, 2008 @ 03:30 AM

    great! :)

  7. ThorbenApril 01, 2008 @ 04:34 AM

    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?

  8. ThorbenApril 01, 2008 @ 04:35 AM

    AAAARG! ;)

    article = Article.find(:first)

    article.title #=> “Test”

    article.title = “Test”

  9. RyanApril 01, 2008 @ 07:21 AM

    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:

    
    article = Article.find(:first)
    article.title #=> "Title" 
    article.title = "Title" 
    article.title_changed? #=> false
    
  10. bryanlApril 01, 2008 @ 07:49 AM

    Get this functionality in your 2.0 app.

    http://code.bitsweat.net/svn/dirty

  11. Paul BarryApril 01, 2008 @ 08:18 AM

    But what about this?

    article = Article.find(:first)
    article.title.upcase! #=> "TITLE" 
    article.title_changed? #=> ?
  12. Paul BarryApril 01, 2008 @ 08:49 AM

    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.

  13. DanielApril 01, 2008 @ 09:48 AM

    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.

  14. MikeApril 01, 2008 @ 08:21 PM

    article.title.changed? and article.title.was would have been much cleaner syntax than underscore accessor.

  15. Luke RedpathApril 02, 2008 @ 06:10 AM

    “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.

  16. jeromeApril 02, 2008 @ 01:00 PM

    You may now use Article.first instead of Article.find(:first) ! :)

  17. Paul BarryApril 03, 2008 @ 11:00 AM

    Daniel: Yeah, I guess that’s the tradoff. I consider having to set the flag pretty ugly as well.

  18. PabloApril 03, 2008 @ 05:42 PM

    What would happend with partial updates at cases like Paul’s example?

  19. BrandonApril 03, 2008 @ 07:26 PM

    @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.

  20. Mark ThomasApril 05, 2008 @ 03:25 PM

    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.

  21. Sam GranieriApril 13, 2008 @ 03:42 PM

    I think this breaks acts_as_versioned

  22. Andy WattsApril 14, 2008 @ 01:52 PM

    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

  23. Kevin KohrtApril 16, 2008 @ 09:36 AM

    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

  24. JohnnyApril 25, 2008 @ 04:15 PM

    Some of this stuff required some ugly interceptor work in Hibernate! Especially those times when you have requirements to the effect of needing to do calculations between a user’s input value and a previous one.

  25. Adult ÜhlerMay 28, 2008 @ 08:18 AM

    Those objects look a lot cleaner than some of the objects I’ve seen (PHP). You rails guys seem to be a lot cleaner than PHP developers.

  26. rihadJune 02, 2008 @ 12:45 AM

    I really don’t like the will_change! kludge. It seems to go against the principles of DRY.

    article.title.changed? and article.title.was would have been much cleaner syntax Ditto. Imaging a title_changed column (“title_changed_changed”).

    Partial updates are already in edge Do they require a prior fetch of all data simply for storing the data that has been modified, or do something smarter than that involving data caching? The performance gains wouldn’t be so impressive in the former case, I guess.

  27. alexvJune 02, 2008 @ 12:16 PM

    (Repost for better formatting)

    rihad +1. title_changed_changed is awkward. Luke Redpath -1. “Changed” is flag that belong to attribute(column) here, not to whole object (record). So (IMHO) article.title.changed? is absolutely right semantic.

    About will_change! – it is bad. Think about why it is there? just to cover alternative way to modify attribute. What if tomorrow it will be ANOTHER way to modify attribute, something like AOP or else. In environment, where setter is NOT in full control of attribute there is no way to reliable implement “changed?” without caching original value. (Getting it from database per demand would be another way, but it is slightly different – for example it returns changed if database was modified by another transaction).

    Thus, I would be looking for the following setup switch:

    Mode-1 Old-way: no original value caching – good for memory, easy to code, consistent, hard on db connection because of full save. “changed?” is optional, not reliable, not used during “save”

    Mode-2 Consistent dirty check. Full caching of original values – hard on memory. “Changed?” is reliable and used for partial save – good for DB connection performance.

    Mode-3 Inconsistent dirty check with flags – as implemented today. Good for memory, good for performance. Unreliable behaviour unless specially coded (“will_change!”). Unreliable behaviour in AOP future (my guess).

    Sorry, I did not checked yet if there is possibilities to switch between modes. Mode-3 is not my type of doing business, but if somebody will use it with FULL understanding of its limitations – good luck.

  28. Brandon ZylstraJune 03, 2008 @ 02:45 AM

    @Josh Martin

    This took so long because of the following problem: Say you have a model, which requires that field_a and field_b cannot both be true (although they can both be false, so they cannot be conflated into one single boolean). Suppose one update changes field_a to true, and another independent update changes field_b to true.

    As I understand it, validations would not have caught this.

    I’m not sure how they solved this for 2.1.

  29. vinayJune 04, 2008 @ 12:30 PM

    My App is on 1.2.6, Cannot change it to 2.1 yet. What is the best way to have similar functionality on my current version. I need to implement only ‘changed?’ method. Only solution that comes to my mind is to re-read the record in changed? method and compare. Is that is how it is implemented in 2.1 or there is any other smarter way?

  30. RyanJune 05, 2008 @ 07:46 AM

    @vinay – check out the dirty plugin. Not sure it can be used on Rails v1.2.x but it might be an easy tweak for you?

  31. tcm visaJune 06, 2008 @ 01:21 PM

    Nice Site! http://google.com

  32. Roderick van DomburgJune 12, 2008 @ 05:17 PM

    Wonderful, the power of simplicity. We had used the dirty plugin before, but that has the nasty habit of freezing objects at times.

  33. NSJune 18, 2008 @ 11:36 AM

    Thing to note.

    If you do article.title = ‘NEW TITLE’, it is considered as changed.

    If you do article.title.upcase, it is considered not changed.

    This is a great addition!

  34. DerrekJune 25, 2008 @ 05:44 AM

    Hi!

    We have a legacy DB with a column named “changed”. How to read this cilumn with rails 2.1? Any suggestions?