#109
May 21, 2008

Tracking Attribute Changes

Rails 2.1 keeps track of the changes you make to a model's attributes. It also allows you to see what the previous value was. But watch out for the gotcha! See this episode for details.
Download (8.1 MB, 7:26)
alternative download for iPod & Apple TV (7.9 MB, 7:26)

Update: Partial updates are no longer enabled by default in Rails 2.1 as you can see in this commit. Now you can set them in your environment config if you want.

Resources

# config/initializers/active_record_settings.rb
ActiveRecord::Base.partial_updates = true

# change tracking methods
p.changed?
p.changed
p.changes
p.name_changed?
p.name_was
p.name_change
p.name_will_change!

RSS Feed for Episode Comments 24 comments

1. Rymaï May 21, 2008 at 05:13

Cool feature! Very useful!

But use it with care : http://www.dcmanges.com/blog/rails-21-partial-updates-may-create-invalid-records


2. DBA May 21, 2008 at 06:05

Another useful webcast, as usual.

Thanks Ryan.


3. zul May 21, 2008 at 06:31

Thank u so much for useful casts :)


4. Tom Harrison May 21, 2008 at 08:13

These new features rock, as does your RailsCast.

I am a little dubious as to the wisdom of having partial updates as a default, however. Sure, when a record contains BLOBs or many large text fields it may make sense. But in most cases I'll bet the benefit is negligible.

@Rymai's link already points out the new risk of data consistency issues (it's worth reading!).

It seems to me that model-level syntax to turn partial updates off or on conditionally would be smarter. For example:

partial_update :unless => { :termination_date.changed? || :eligibility.changed? }

or

full_update :if => {
:termination_date.changed? || :eligibility.changed? }

Anyway, it's worth thinking about the consistency issue. The case may be unlikely, but it introduces a data-level issue in the name of dubious claims of performance increase.


5. Ryan Bates May 21, 2008 at 09:22

@Rymaï, thanks, I added the link to the show notes.

@Tom, I'm a little surprised partial updates are on by default in new 2.1 projects as well. I could see it causing some tricky bugs.

Your idea of being able to change this setting on a case by case basis makes sense. I look at it as more of a performance improvement which can be applied if you have a model with BLOBs or index columns. I recommend making a post on the rails core mailing list about this if there isn't one already.


6. Jeremy May 21, 2008 at 11:06

@Tom:

It is amazing to me how cool the rails community is. I have wondered over and over how Rails can have so many useful features... and your comment proved why: because upon seeing a problem, instead of accepting it, we try to solve it.

Sweet.

Please do submit a ticket or update to rails too see a feature like that be added to future releases.

@Ryan: Thanks for introducing another feature in such a concise manner.


7. Pascal May 21, 2008 at 12:36

Hi, I have this error on server startup:

/Library/Ruby/Gems/1.8/gems/activerecord-2.0.2/lib/active_record/base.rb:1532:in `method_missing': undefined method `partial_updates=' for ActiveRecord::Base:Class (NoMethodError)

I'm using Rails 2.0.2.

Any ideal?

Thx


8. Pascal May 21, 2008 at 13:02

@Pascal (me):

Forget it... I was running my project on Rails 2.0.2. I have to migrate my projet to 2.1 doh.


9. Alex May 21, 2008 at 15:17

Thanks for the rapid-fire onslaught of episodes lately. The recent Rails 2.1 episodes have been great. Keep on chuggin.


10. Peter Theill May 22, 2008 at 07:40

Since tracking of attribute changes have been introduced, I'm wondering if it's possible (through a configuration setting or similar) to implement a lost update check by extending the where clause, i.e. instead of Rails generating:

UPDATE users SET age = 30 WHERE id = ?

it should generate

UPDATE users SET age = 30 WHERE id = ? AND age = 29

if "age" attribute changed from 29 to being 30.


11. QuBiT May 22, 2008 at 09:43

@Peter Theill

go back and watch episode 59,
that's what you are looking for :)

http://railscasts.com/episodes/59


12. Ryan Bates May 22, 2008 at 10:50

@Peter, that's an interesting idea to only update if the existing record hasn't changed. It has some differences from optimistic locking in the fact that it only checks the columns it's updating. It doesn't consider changes to other columns a conflict.

This still suffers from the invalid record possibility problem. I would also hesitate adding it before fully exploring other potential problems. But it's worth investigating.


13. Virginian May 22, 2008 at 20:47

 Another great Railscasts, Thanks Ryan.

One question, when using partial updates rails is still validating all the attributes not just the ones changed. Is there a way to restrict it to only run validations on the changed attributes?


14. HappyCoder May 22, 2008 at 23:41

@Peter Theill
I don't understand. Why do you want that feature? The PK provides record's uniqueness. Isn't it enough? Besides, it has a performance issue. I mean, setting an index for every column in a table will not do any good.

[quote]
it should generate

UPDATE users SET age = 30 WHERE id = ? AND age = 29

if "age" attribute changed from 29 to being 30."
[/quote]


15. Ryan Bates May 22, 2008 at 23:43

@Virginian, not that I know of. Validations may not necessarily be tied to one attribute. It's possible a validation may rely on multiple attributes.

However, you can add this behavior manually when you specify the validations. To do this, add a condition to see if the attribute has changed before running the validation.


16. HappyCoder May 22, 2008 at 23:44

I was probably wrong about "performance issues" :-) I don't know how databases work in this situation. Does it search all records with such id (which is 1) and then finds from them just one with age=29?


17. Ryan Bates May 22, 2008 at 23:49

@HappyCoder, I haven't done any benchmarking so I can't say for sure if that would degrade performance. My guess is that it would be minimal.

As for your original question. The reason you may want to do this is to ensure the record hasn't been changed since you last fetched it. This way you don't mistakenly overwrite another change on the save.


18. kino May 23, 2008 at 01:51

The fact is evident that noematic descriptions, in respect of this evidence, denote the universal primal phenomena of, by a freely actualizable return to noetic acts, the phenomenological epoche.


19. Andrés May 30, 2008 at 16:56

Helo Ryan, can you tell me what is what you do to show the SQL statements executed by Rails at the bottom of the screen?

Thanks.


20. Virginian Jun 04, 2008 at 12:22

Hi Ryan-

I can do something like

  validates_presence_of :p1_new_first_name, :p1_new_last_name, :if => :p1_new_first_name_changed?

  validates_presence_of :p1_first_name, :p1_last_name, :if => :p1_first_name_changed?

And it almost works, but the flaw is obvious. If the attribute is left blank, it wont check be changed. I can run validations using this on almost anything except for presence_of is there any way around this?
Thanks for your help.


21. Pavel Jun 06, 2008 at 04:44

thank you very much! I have successfully used it. This is a very important thing for me :)


22. Jesse Jul 01, 2008 at 22:06

Ryan - Quick question, do you know why these don't work for virtual attributes that I've created using getter/setter methods? Thanks!


23. Matt Aug 08, 2008 at 14:13

Another thing to look out for with partial updates is that changes to a serialized attribute do not mark the attribute as changed, and as a result, will not get saved. So if you have a model called EmailQueue and a serialized attribute called "email" that stores a TMail object, and you try this:

c = EmailQueue.first
c.email.body = "new email body text"
c.save

You will find that the change was not saved. Perhaps there is an easy way to add a :before_save callback that marks the serialized attribute as dirty? But then I guess it's probably just as easy to turn off partial updates for the whole model.


24. Elaine Aug 21, 2008 at 08:24

I'd like to use this to replace the example in the #59 Optimistic Locking railscast, but I'm not having any luck in getting it to work. Of the fields in conflict, I'd like to show the field name that changed, what the value of the field was and what the user changed it to. Any help would be appreciated. Thanks.

Add your comment:

(SKIP THIS ONE)

(required)

(not shown)


(use pastie or gist for code)

sponsored by:
if you want to help:
required:
Get Quicktime Player