Skipping ActiveRecord Callback Methods
December 12th, 2007 » 6 Comments »
ActiveRecord provides a great way to tie into a model’s lifecycle through various callbacks. However, in certain cases you may want to skip specific callbacks you’ve defined on a model similar to skipping filters in ActionController. Rails doesn’t support this natively, but its very easy to extend ActiveRecord to add this feature. The solution we ended up using was inspired by this post on thatswhatimtalkingabout.org.
Frist, we’ll need to open up ActiveRecord and add the method that will allow us to skip the callback. There are a few ways you can go about doing this, but we use an initializer. So create a file in config/initializers called active_record_extensions.rb with the following code:
class ActiveRecord::Base
def self.skip_callback(callback, &block)
method = instance_method(callback)
remove_method(callback) if respond_to?(callback)
define_method(callback){ true }
yield
remove_method(callback)
define_method(callback, method)
end
end
All this code does is store off the callback method, yields the passed in block and then adds the callback back in.
So, lets say we have the following model…
class User < ActiveRecord::Base after_create :send_notification end
You may have some process that is creating Users where you don't want the notification to be sent after the user gets created. In order to do this, simply wrap your create or save in the skip_callback method like so:
User.skip_callback(:send_notification) do User.create(:name => 'Josh') end
Now you've successfully created a User without the send_notification callback getting executed.
Thank you for this tip! But: Your skip_callback method does not handle the result of the yielded block. If have fixed it with this code:
def self.skip_callback(callback, &block) method = instance_method(callback) remove_method(callback) if respond_to?(callback) define_method(callback){ true } result = yield remove_method(callback) define_method(callback, method) result endHi. This is really interesting post. Thank You! I have just subscribed to Your rss!
Best regards
Really usefull solution to me! I used this in a module and didn’t get it working on first until I read about the difference between including and extending a module. For all developers getting the same problem, I found a lot of usefull info on this url:
http://www.juixe.com/techknow/index.php/2006/06/15/mixins-in-ruby/
One more addition: Bad things happen if an exception is raised in the yielded block – the former callback is not restored. To avoid this I have changed the method like this:
def self.skip_callback(callback, &block) method = instance_method(callback) remove_method(callback) if respond_to?(callback) define_method(callback){ true } begin result = yield ensure # Always redefine the old callback, even if # there were exceptions raised in the yielded block remove_method(callback) define_method(callback, method) end result endWhat i find even more interesting is that it works even it the callback itself.
class Person < ActiveRecord::Base after_save :create_another def create_another Person.skip_callback("create_brother") do Person.create(:name => "peter") end end end Person.create :name => "ronald" This code will not explode, even though the method removes itself and then add itself back (gives me headache).Hm. Please close the pre tag behind me, thank you :)