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)