Saving without callbacks

Posted by Brian in Howto, Rails, tips (August 19th, 2009)

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.

2 Responses to ' Saving without callbacks '

Subscribe to comments with RSS or TrackBack to ' Saving without callbacks '.

  1. Nick said,
    on August 19th, 2009 at 10:47 pm

    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)

  2. Brian said,
    on August 27th, 2009 at 1:52 pm

    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!

Leave a reply

:mrgreen: :neutral: :twisted: :shock: :smile: :???: :cool: :evil: :grin: :oops: :razz: :roll: :wink: :cry: :eek: :lol: :mad: :sad: