Skip to main content

Mobomo webinars-now on demand! | learn more.

Rails ActiveRecord models have a lifecycle that developers are allowed to hook into. But while most of us know about before_save and after_update, there are a few lesser unknown callbacks that are good to know about before you reinvent them. In this post, I'll cover all of the available ActiveRecord lifecycle callbacks, and also show how you can define custom callbacks for normal ruby objects.

Meet the Callbacks

The Rails guide for ActiveRecord Validations and Callbacks is a good starting point for an introduction of the available callbacks and what they do. Most developers will be familiar with the validation and persistence callbacks, so let's start with these

:before_validation, :after_validation :before_save, :after_save :before_create, :after_create :before_update, :after_update :before_destroy, :after_destroy 

The callbacks above are self explanatory and commonly used, but if you're unfamiliar with them, or need a refresher, check out the Rails guide on the topic.

Around Callbacks

For save, create, update, and destroy, Rails also gives extra helper methods for defining both a before and after save callback at the same time.

For example, suppose you wanted to trigger your own custom callback while a model was being destroyed. You can do so by defining and triggering your own callback as follows:

class SomeModel < ActiveRecord::Base   define_callbacks :custom_callback    around_destroy :around_callback    def around_callback     run_callbacks :custom_callback do       yield  # runs the actual destroy here     end   end end 

Custom Callbacks without ActiveRecord

Most of the time, your Rails models will be using ActiveModel, but sometimes it makes sense to use a plain old ruby object. Wouldn't it be nice if we could define callbacks in the same way? Fortunately, the callback system is neatly abstracted into ActiveSupport::Callbacks so it's easy to mix into any ruby class.

# Look Ma, I'm just a normal ruby class! class Group   include ActiveSupport::Callbacks   define_callbacks :user_added    def initialize(opts = {})     @users = []   end    # Whenever we add a new user to our array, we wrap the code   # with `run_callbacks`. This will run any defined callbacks   # in order.   def add_user(u)     run_callbacks :user_added do       @users << u     end   end end 

For a fully documented and runnable example, check out this github project. It'll also give some extra explanation about call order and inheritance.

Other Useful Callbacks

  • :after_initialize is called right after an object has been unmarshalled from the database. This allows you to do any other custom initialization you want. Instead of defining an initialize method on a model, use this instead.
  • :after_find hasn't been useful in my experience. I haven't run into a case where I wanted to manipulate documents after a find action. It could potentially be useful for metrics and profiling.
  • :after_touch. ActiveRecord allows you to touch a record or its association to refresh its updated_at attribute. I've found this callback useful to triggering notifications to users after a model has been marked as updated, but not actually changed.
  • :after_commit is an interesting and tricky callback. Whenever ActiveRecord wants to make a change to a record (create, update, destroy), it wraps it around a transaction. after_commit is called after you're positive that something has been written out to the database. Because it is also called for destroys, it makes sense to scope the callback if you intend to use it only for saves. Be warned that after_commit can be tricky to use if you're using nested transactions. That'll probably be the topic of another post though.
# call for creates, updates, and deletes after_commit :all_callback  # call for creates and updates after_commit :my_callback, :if => :persisted? 
  • :after_rollback is the complement to after_commit. I haven't used it yet, but I can see it as being useful for doing manual cleanup after a failed transaction.

Go Forth and Callback!

While many of our models will be backed with ActiveRecord, or some ActiveModel compatitible datastore, it's nice to see how easy it is to follow a similar pattern in normal ruby without having to depend on Rails.

Categories
Author

ActiveRecord callbacks can be super-handy, but every once in a while, they get in the way.

I was recently working on a client project and I had to create a rake task to import a large set of data from a spreadsheet.  One of the models that was being imported had an after_save callback that sent out an email notification.  I didn't really want 3500 emails to be sent out whenever this rake task was ran, so I needed to disable the callback while the import task was running.

Fortunately, this is easy to do.

Say you've got a model like so...

class Thing < ActiveRecord::Base    before_save :do_stuff      def do_stuff      raise "This thing is doing stuff..."    end  end  

In the config/initializers directory, I created a file called extensions.rb.  You can call it whatever you like.  I chose 'extensions' because I use the same file for any minor Ruby or Rails class extensions.  In it, I put this...

class ActiveRecord::Base    def self.without_callback(callback, &block)      method = self.send(:instance_method, callback)      self.send(:remove_method, callback)      self.send(:define_method, callback) {true}      yield      self.send(:remove_method, callback)      self.send(:define_method, callback, method)    end  end   

What this does is grab the callback and store it in the method variable.  Remember, this code only circumvents the 'do_stuff' method, not anything declared as a before_save callback.  It then redefines the method to merely return true, yields to the block, and then redifines the the method with the contents of the method variable. 

Yielding to the block allows you to not have to worry about maintaining the method contents, or restoring it once you're done.  Also, if you were so inclined, you could easily modify this code to accept an array of method names and disable all of them

Once the extension is in place, you can do this...

Thing.without_callback(:do_stuff) do    thing = Thing.new    thing.save  end

...without 'do_stuff' ever being called.

Some people will probably argue that if you need to disable your callback, then your model should be defined differently, but I disagree.  I think this is perfectly acceptable in many cases.  Granted you probably wouldn't want to do this all throughout your application code, but I see no problem with using this technique in a test, or data migration, or in my case, a rake task.

Categories
Author
1
Subscribe to Callbacks