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:
- Understanding Active Record Associations
- Understanding the Asset Pipeline in Ruby on Rails
- Understanding Active Record Migrations
- Understanding Ruby on Rails ActiveRecord Validations
- Understanding the Ruby on Rails CLI
or more from our Ruby on Rails collection
 

 
    
Top comments (0)