DEV Community

Andy Leverenz
Andy Leverenz

Posted on • Originally published at web-crunch.com on

Understanding Active Record Callbacks

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 
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

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> 
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

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

Top comments (0)