#109 Tracking Attribute Changes
May 21, 2008 | 7 minutes | Active Record, Rails 2.1
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.
Another useful webcast, as usual.
Thanks Ryan.
Thank u so much for useful casts :)
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.
@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.
@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.
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
@Pascal (me):
Forget it... I was running my project on Rails 2.0.2. I have to migrate my projet to 2.1 doh.
Thanks for the rapid-fire onslaught of episodes lately. The recent Rails 2.1 episodes have been great. Keep on chuggin.
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.
@Peter Theill
go back and watch episode 59,
that's what you are looking for :)
http://railscasts.com/episodes/59
@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.
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?
@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]
@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.
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?
@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.
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.
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.
thank you very much! I have successfully used it. This is a very important thing for me :)
Ryan - Quick question, do you know why these don't work for virtual attributes that I've created using getter/setter methods? Thanks!
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.
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.
@Ryan,
If you remember, there was an issue in episode 75 in which the Project.update_attributes was not calling the task_attributes= method (comments section Andrew). I dont know if it was ever resolved(I still have that problem and would love a solution) but i was just wondering if this was the reason. You mentioned that if records were not being saved properly for some reason, this could be it. Any thoughts?
Really appreciate your work Ryan. Thanks :)
Anyone know if this feature can be used for relational objects?
a has_many b
a.b_ids.changed?
This episode has been updated to Rails 5 as a blog post Tracking Attribute Changes in Rails 5