Rails has multiple methods to update a record,
each one reserved for a unique purpose.
While the traditional update
is used to update records by executing validations,
callbacks
and
track changes, there might be a few instances where one would like to skip validations.
Using update_attribute
does just that.
Before
To skip validations on the update,
use update_attribute
that ensures:
- Validations are skipped
- Callbacks are invoked
- updated_at / updated_on column is updated (if that column is available)
- Tracks changes via ActiveModel::Dirty
Let’s look at an example!
class Blog < ApplicationRecord
validates_presence_of :text
before_save :check_if_title_contains_special_characters
def check_if_title_contains_special_characters
throw(:abort) if title.index( /[^[:alnum:]]/ ).present?
end
end
Now let’s compare the differences between update
, update!
and update_attribute
,
> blog = Blog.last
=> #<Blog id: 1, title: "Ruby on Rails Tutorial: Learn Web Development with...", text: nil ... >
> blog.update text: nil
=> false
> blog.update! text: nil
Traceback (most recent call last):
1: from (irb):4
ActiveRecord::RecordInvalid (Validation failed: Text can't be blank)
> blog.update_attribute :text, nil
=> true
As we can see, validations are skipped when using update_attribute
but what is interesting is the behavior of update
and update!
.
While update
silently fails,
calling update!
throws an ActiveRecord::RecordInvalid
error.
Let’s compare callbacks now.
> blog.update title: "Hel@lo"
=> false
> blog.update! title: "Hel@lo"
Traceback (most recent call last):
1: from (irb):10
ActiveRecord::RecordNotSaved (Failed to save the record)
> blog.update_attribute :title, "Hel@lo"
=> false
Now update
and update_attribute
silently fail,
while update!
gives us a better understanding.
Unfortunately, there is no equivalent of update!
for update_attribute
that can
give us insights into record updates.
After
Fortunately, Rails introduces update_attribute! which can be used to prevent “silent” updates.
> blog.update title: "Hel@lo"
=> false
> blog.update! title: "Hel@lo"
Traceback (most recent call last):
1: from (irb):10
ActiveRecord::RecordNotSaved (Failed to save the record)
> blog.update_attribute :title, "Hel@lo"
=> false
> blog.update_attribute! :title, "Hel@lo"
Traceback (most recent call last):
1: from (irb):12
ActiveRecord::RecordNotSaved (Failed to save the record)
This change adds a new method,
ActiveRecord::Persistence#update_attribute!
which calls save!
instead of save
.
The update_attribute!
method will now raise an ActiveRecord::RecordNotSaved
error should any before_*
callbacks throw :abort
.