Saving without callbacks
Occasionally, you need to do something to a model without invoking callbacks. This is especially necessary for instances where you need to modify the record you just saved in an after_save callback. For example, say you want to create a hash based on the record’s ID. You won’t have the ID until after it’s been saved, and if you call self.save in an after_save callback, you’re going to end up in an endless loop.
It turns out that ActiveRecord has two private methods.
create_without_callbacks
and
update_without_callbacks
Remember that private in Ruby is less about keeping you from calling the methods and more about letting you know that you shouldn’t depend on them. With Ruby’s send method, using either of these in your application is easy.
Note that in Ruby 1.9, send doesn’t allow calling private methods, but send! does.
class Project < ActiveRecord::Base
after_save :create_hash_from_name_and_id
private
def create_hash_from_name_and_id
self.hash = Digest::SHA1.hexdigest("#{self.id}#{Time.now.to_s}#{self.name}")
self.send :update_without_callbacks
end
end
It's my opinion that these methods should not be private. If I can skip validations with save(false), then I should be able to skip callbacks as well.
There are other patterns you can use to skip callbacks.
class Project < ActiveRecord::Base
attr_accessor :skip_callbacks
with_options :unless => :skip_callbacks do |project|
project.after_save :create_hash_from_name_and_id
project.after_save :do_more_stuff
end
private
def create_hash_from_name_and_id
self.skip_callbacks = true
self.hash = Digest::SHA1.hexdigest("#{self.id}#{Time.now.to_s}#{self.name}")
self.save
end
end
So there you go. Go forth and skip callbacks at will.
What about keeping your after_save callback, but in the callback method, use update_all, which does not trigger callbacks, so you avoid the endless loop scenario.
(http://api.rubyonrails.org/classes/ActiveRecord/Base.html#M002273)
I think that’s an acceptable approach, but what I don’t like about it is it’s not as declarative, and it’s inconsistent with how update_attribute and update work. At least you know with my example by looking that I’m not triggering callbacks.
Thanks for bringing that up though!