Skipping an ActiveRecord Callback Programatically

I am a massive fan of ActiveSupport callbacks and use them frequently. This allows me to chain behaviours together; essentially using data storage as an event based system to enforce business logic. An example of this, is to use after_create_commit callbacks to automatically trigger an email that needs to be sent; such as welcome user email, to automatically generate some accounting record, or an admin email notification.

This does have some drawbacks however. It means that you really need to have a good grasp of the domain logic; and means that it becomes critically important to choose the correct way to update the record (update_column vs update_attribute) lest you fail to trigger important business logic, or trigger them when you shouldn’t. But generally, when used appropriately I find them invaluable. But sometimes you might find yourself in a situation where you need to run some code (such as in a rake task) where you cannot influence the method used to update the database, but the callbacks must not be executed.

As I said, if you have direct control over the ActiveRecord relation, then its easy:

@object.update_column(:the_attribute, 'value')
@object.update_columns(attributes)

These will update the database, but skip validations and callbacks.

But if the updates are being triggered by another class/code outside of the scope or control of where you are, this wont work. Perhaps you care calling a method of a related class, and that method specifies .update_attribute and you cannot change it. What then?

Fortunately, there is a solution.

Lets say you have a class definition:

class User < ActiveRecord::Base
after_save :my_method
end

There are 2 ways you can save the object without triggering the callback.

Method #1:

User.send(:create_without_callbacks)
User.send(:update_without_callbacks)

But thats super gross! Using .send is a code smell if ever there was one.

Method #2:

User.skip_callback(:save, :after, :my_method)
User.create!

This is much more civilised. What’s more, after you’ve run your rspec, mock, rake task whatever: you can reset the callback with: User.set_callback(:save, :after, :my_method).

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.