How to make sure an attribute doesn't get updated
I have a Rails model in which I don’t want the title attribute to change. I implemented this with a before_update:
before_update :check_title_unchanged
protected
def check_title_unchanged
Post.find(id).title == title
end
This didn’t quite work because of an ActiveRecord bug I wrote about before.
When talking to Michael Koziarski at #rubyonrails about this bug, he made clear that I have at least one problem with my code and that I could have made a different implementation.
First to tackle the real problem in my code. The problem with my implementation is that it isn’t thread-safe, because the title could have been changed by another thread after we’ve fetched the object from the database. And hence our title wouldn’t have changed, but we still wouldn’t be able to save our model.
So the better way of doing it is by adding an after_find which is documented at the Rails Wiki. And also updating the before_update method accordingly.
before_update :check_title_unchanged
attr_accessor :original_title
protected
def after_find
@original_title = title
end
def check_title_unchanged
@original_title == title
end
Now to take a look at another approach to tackle this. We could also override the validate method. The validate method has the following documentation:
Overwrite this method for validation checks on all saves and use Errors.add(field, msg) for invalid attributes.
So we could now change our code to:
attr_accessor :original_title
protected
def after_find
@original_title = title
end
def validate
@original_title==title
end
And if we would want to make it really nice, we would use Errors.add to add a descriptive error message, which is then added to the list of errors that will be displayed by error_messages_for. This would be the only reason I would use this approach instead of using a before_update.
So we could do:
def validate
result = (@original_title==title)
if !result
Errors.add(:title, "Title cannot be changed.")
end
end
Conclusion: in my project I have no use for the error message, so I don’t really see the point in overriding the validate method. But now I at least know the available options and can choose one wisely. So if I just need to make sure the title cannot be updated by a programming mistake in a controller, I use the before_update. If the user needs to be presented with a nice error message why they cannot change the title, then I would choose for overriding the validate method.
Update: I tried to put this nice piece of code to work, but it breaks my CRUD tests. My CRUD tests might behave a little different from the actual application, but still, they shouldn’t break. I actually create an object, save it, update it and then save it again. This failed, because I did not do a find for the same object and hence original_title is not yet set. So I’ll leave my implementation as it is currently. The issue with thread-safety is not that shocking in my case, since the title really shouldn’t be changed anyway.