loading...

Understanding Active Record Callbacks

justalever profile image Andy Leverenz Originally published at web-crunch.com on ・4 min read

Callbacks are methods that get called at certain moments of an object's life cycle. We can use callbacks as a means to manipulate objects further with built-in functionality that Ruby on Rails ships with.

This guide is an overview of Active Record Callbacks and how they can enhance your Ruby on Rails app with seemingly little effort.

To learn even more about Active Record Callbacks I recommend you visit and review the documentation around it.

The available callbacks

Rails ships with a number of callbacks baked in. We can use these methods as a means to perform more logic during, after, before, or around the manipulation of active record objects.

Creating an Object

  • before_validation
  • after_validation
  • before_save
  • around_save
  • before_create
  • around_create
  • after_create
  • after_save
  • after_commit/after_rollback

Updating an Object

  • before_validation
  • after_validation
  • before_save
  • around_save
  • before_update
  • around_update
  • after_update
  • after_save
  • after_commit/after_rollback

Destroying an Object

  • before_destroy
  • around_destroy
  • after_destroy
  • after_commit/after_rollback

Registering a callback

A basic example of a callback is as follows. These take place in your models in a given Ruby on Rails application.

Here we register the callback before_validation and reference a Symbol (another method) that will run when a user tries to log in.

class User < ApplicationRecord
  validates :login, :email, presence: true

  before_validation :ensure_login_has_value

  private

    def ensure_login_has_a_value
      if login.nil?
        self.login = email unless email.blank?
      end
    end
end

Passing a block

Sometimes your code is short enough to be a one-liner. You can optionally pass a block in the callback instead of referencing another method.

class User < ApplicationRecord
  validates :login, :email, presence: true

  before_create do
    self.name = login.capitalize if name.blank?
  end
end

Dictate when the callback fires

Extending callbacks with on: allows you to pass one or more lifecycle events dictating when a callback executes. This is really handy!

class User < ApplicationRecord
  before_validation :normalize_name, on: :create

  # :on takes an array as well
  after_validation :set_location, on: [:create, :update]

  private
    def normalize_name
      self.name = name.downcase.titleize
    end

    def set_location
      self.location = LocationService.query(self)
    end
end

When callbacks get fired

Active Record already gives us a number of methods to transform data via objects. When we run these, callbacks can actually be called.

Some examples

User.destroy_all

# The following methods trigger callbacks:

  # create
  # create!
  # destroy
  # destroy!
  # destroy_all
  # save
  # save!
  # save(validate: false)
  # toggle!
  # touch
  # update_attribute
  # update
  # update!
  # valid?

## Additionally, the after_find callback is triggered by the following finder methods:

User.all

  # all
  # first
  # find
  # find_by
  # find_by_*
  # find_by_*!
  # find_by_sql
  # last

Relational Callbacks

Much like Active Record relations, callbacks can be performed when related objects are changed. A common example of this might be if you destroy a record and have dependent: :destroy enabled. This proceeds to destroy and orphaned data associated with the parent class. What's great is that you can additionally use a callback to perform more operations if necessary.

class User < ApplicationRecord
  has_many :articles, dependent: :destroy
end

class Article < ApplicationRecord
  after_destroy :log_destroy_action

  def log_destroy_action
    puts 'Article destroyed'
  end
end

>> user = User.first
=> #<User id: 1>
>> user.articles.create!
=> #<Article id: 1, user_id: 1>
>> user.destroy
Article destroyed
=> #<User id: 1>

Here, a User has many articles. If a user account is deleted, and dependent: :destroy is in place, you can see the after_destroy callback fire.

Conditional callbacks

Often times you might need to conditionally render logic much as you would elsewhere in a Rails app. Luckily, Active Record Callbacks make this quite easy.

You can accomplish conditional callbacks in a few ways. The first is with :if or :unless as a Symbol. This approach makes for quite legible code.

# Using :if and :unless with a Symbol
class Order < ApplicationRecord
  before_save :normalize_card_number, if: :paid_with_card?

  paid_with_card?
   # ...
  end
end

If you have multiple conditions or callbacks you can opt for more logic but the readability gets more complicated.

class Comment < ApplicationRecord
  after_create :send_email_to_author, if: :author_wants_emails?,
    unless: Proc.new { |comment| comment.article.ignore_comments? }
end

If there's a bunch of logic to account for it might make sense to extract it to a new Ruby class.

class PictureFileCallbacks
  def self.after_destroy(picture_file)
    if File.exist?(picture_file.filepath)
      File.delete(picture_file.filepath)
    end
  end
end

class PictureFile < ApplicationRecord
  after_destroy PictureFileCallbacks
end

Hopefully, this guide opened your eyes a bit to how Active Record Callbacks can enhance your toolkit. I find myself using many of these with conditional logic around background jobs, authentication, and more. The possibilities are endless here. I'd aim to use callbacks sparingly where possible. Having too much logic transpiring when modifying objects can get tedious and sometimes slow down your app.

If you enjoyed this guide you might also like:

or more from our Ruby on Rails collection

Posted on by:

justalever profile

Andy Leverenz

@justalever

Product Designer @memberful/@patreon, Blogger/Vlogger @webcrunchblog, Guitarist 🎸 and YouTuber. I enjoy whiskey, metal, and people watching. Most recent launch: Hello Rails (https://hellorails.io)

Discussion

markdown guide