Define Custom Callbacks for ActiveRecord and 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

# call for creates, updates, and deletes
after_commit :all_callback

# call for creates and updates
after_commit :my_callback, :if => :persisted?

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.