Callbacks in Rails

WARNING. after_save is fired on both creation and update, but always after the more specific after_create and after_update callbacks, regardless of the order in which the macro calls are fired.

NOTE: The before_destroy callback must be placed before dependent: :destroy bindings (or use the prepend: true option) to ensure that they are executed before the entries are removed with dependent: :destroy.

after_initialize and after_find

The after_initialize callback is called whenever an Active Record object is instantiated, either directly by using new, or when a record is loaded from the database. It can be useful to avoid having to directly override the Active Record initialize method.

The after_find callback will be called whenever Active Record loads a record from the database. after_find is called before after_initialize if both are defined.

The after_initialize and after_find callbacks do not have a before_* pair, but they can be registered like other Active Record callbacks.

class User<ApplicationRecord

  after_initialize do |user|

    puts “You have initialized an object!”

  end

  after_find do |user|

    puts “You have found an object!”

  end

end

>> User.new

You have initialized an object!

=> #<User id: nil>

>> User.first

You have found an object!

You have initialized an object!

=> #<User id: 1>

after_touch

The after_touch callback will be called when touch is called on an Active Record object.

class User<ApplicationRecord

  after_touch do |user|

    puts “You have touched an object”

  end

end

>> u = User.create(name: ‘Kuldeep’)

=> #<User id: 1, name: “Kuldeep”, created_at: “2013-11-25 12:17:49”, updated_at: “2013-11-25 12:17:49”>

>>u.touch

You have touched an object

=> true

It can be used in conjunction with belongs_to:

class Employee < ApplicationRecord

  belongs_to :company, touch: true

  after_touch do

    puts ‘An Employee was touched’

  end

end

classCompany < ApplicationRecord

  has_many:employees

  after_touch :log_when_employees_or_company_touched

  private

  def log_when_employees_or_company_touched

    puts ‘Employee/Company was touched’

  end

end

>> @employee = Employee.last

=> #<Employee id: 1, company_id: 1, created_at: “2013-11-25 17:04:22”, updated_at: “2013-11-25 17:05:05”>

# calls @employee.company.touch

>> @employee.touch

Employee/Company was touched

An employee was touched

=> true

Running callbacks

The following methods fire callbacks:

create

create!

destroy

destroy!

destroy_all

save

save!

save(validate: false)

toggle!

update_attribute

update

update!

valid?

Additionally, the after_find callback is fired by the following find methods:

all

first

find

find_by

find_by_*

find_by_*!

find_by_sql

last

The after_initialize callback is fired whenever a new class object is initialized.

NOTE: The find_by_* and find_by_* methods! these are dynamic lookup methods generated automatically for each attribute. Learn more about them in the Dynamic Search section.

Skipping callbacks

Similar to validations, it is also possible to skip callbacks using the following methods.

decrement

decrement_counter

delete

delete_all

increment

increment_counter

toggle

touch

update_column

update_columns

update_all

update_counters

However, these methods must be used with care as important business rules and application logic can be contained in callbacks. Skipping them without understanding the possible consequences can lead to invalid data.

Interrupt execution

As soon as you have registered new callbacks in your models, they will be queued for execution. This queue includes all your model validations, registered callbacks, and database operations to perform.

The entire chain of callbacks is packaged into an operation. If any callback throws an exception, the running chain is aborted and ROLLBACK is started. To intentionally stop a chain, use:

throw :abort

WARNING. Throwing an arbitrary exception can break code that assumes that save and the like will not fail like this. The ActiveRecord::Rollback exception tells Active Record a bit more precisely that a rollback is in progress. It is picked up from within, but does not rethrow the exception.

WARNING. Any exception other than ActiveRecord::Rollback or ActiveRecord::RecordInvalid will be rethrown by Rails after the callback chain breaks. Throwing an exception other than ActiveRecord::Rollback or ActiveRecord::RecordInvalid can break code that doesn’t expect methods like save and update_attributes (which usually try to return true or false) to throw an exception.

Relationship Callbacks

Callbacks work with relationships between models, and can even be defined by them. Imagine an example where a user has many articles. The user’s articles must be destroyed if the user is destroyed. Let’s add an after_destroy callback to the User model through its relationship with the Article model.

Conditional callbacks

As with validations, it is possible to make a callback method call conditional depending on a given predicate. This is done using the :if and :unless options, which can take a character, a Proc, or an array. The :if option should be used to determine under what conditions the callback should be called. If you want to specify conditions under which the callback should not be called, use the :unless option.

Using :if and :unless with a symbol

The :if and :unless options can be bound to a symbol corresponding to the name of the predicate method that will be called just before the callback is called. When using the :if option, the callback will not be executed if the predicate method returns false; when using the :unless option, the callback will not be executed if the predicate method returns true. This is the most common option. When using this form of registration, it is also possible to register several different predicates that will be called to check if the callback should run.

classOrder < ApplicationRecord

  before_save :normalize_card_number, if: :paid_with_card?

end

Using :if and :unless with Proc

Finally, you can bind :if and :unless to the Proc object. This option is most suitable when writing short methods, usually one-liners.

classOrder < ApplicationRecord

  before_save :normalize_card_number,

    if: Proc.new { |order| order.paid_with_card? }

end

Compound conditions for callbacks

When writing conditional callbacks, it is possible to mix :if and :unless in the same callback declaration.

class Comment < ApplicationRecord

  after_create :send_email_to_author, if: :author_wants_emails?,

    unless: Proc.new { |comment| comment.article.ignore_comments? }

end

Callback classes

Sometimes the callback methods you write are useful enough to be reused in other models. Active Record makes it possible to create classes that include callback methods so that it becomes very easy to reuse them.

Here is an example where a class is created with an after_destroy callback for the PictureFile model:

class PictureFileCallbacks

  def after_destroy(picture_file)

    if File.exist?(picture_file.filepath)

      File.delete(picture_file.filepath)

    end

  end

end

When declared within a class, as above, the callback methods receive the model object as a parameter. Now we can use the callback class in the model:

class PictureFile < ApplicationRecord

  after_destroy PictureFileCallbacks.new

end

Note that we need to instantiate a new PictureFileCallbacks object after we have declared our callback as a separate method. This is especially useful if callbacks use the state of an object instance. Often, however, it is more appropriate to declare it as a class method.

class PictureFileCallbacks

  def self.after_destroy(picture_file)

    if File.exist?(picture_file.filepath)

      File.delete(picture_file.filepath)

    end

  end

end

If the callback method is declared this way, there is no need to instantiate the PictureFileCallbacks object.

class PictureFile < ApplicationRecord

  after_destroy PictureFileCallbacks

end

Inside your callback class, you can create as many callbacks as you like.