ruk·si

🛤️ Ruby on Rails
Callbacks

Updated at 2015-08-18 22:34

ActiveRecord have very good support for binding callbacks to different model events.

Callback registration. Callback methods should be defined as protected or private.

# Block syntax
class User < ActiveRecord::Base
  after_find do |user|
    puts "You have found an object!"
  end
end

# Method syntax
class User < ActiveRecord::Base
  validates :login, :email, presence: true

  before_validation :ensure_login_has_a_value

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

Register only to certain action context.

class User < ActiveRecord::Base
  before_validation :normalize_name, on: :create

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

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

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

List of available events:

# Creating an object
before_validation
after_validation
before_save
around_save
before_create
around_create
after_create
after_save
after_commit/after_rollback

# Modifying an object
before_validation
after_validation
before_save
around_save
before_update
around_update
after_update
after_save
after_commit/after_rollback

# Removing an object
before_destroy
around_destroy
after_destroy
after_commit/after_rollback

# Other callbacks
after_find
after_initialize
after_touch

Callbacks work with defined relations.

class User < ActiveRecord::Base
  has_many :articles, dependent: :destroy
end

class Article < ActiveRecord::Base
  after_destroy :log_destroy_action

  def log_destroy_action
    puts 'Article destroyed'
  end
end

user = User.new
user.articles.create!
user.destroy
# both the user and the article are destroyed

Conditional callbacks. Callback won't be called if method with the given name returns false.

# Symbol for the predicate method to call.
class Order < ActiveRecord::Base
  before_save :normalize_card_number, if: :paid_with_card?
end

# Bound inline block.
class Order < ActiveRecord::Base
  before_save :normalize_card_number, if: Proc.new { |o| o.paid_with_card? }
end

Use callback classes to reuse common callbacks.

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

class PictureFile < ActiveRecord::Base
  after_destroy PictureFileCallbacks
end

Callbacks can be used with transactions.

PictureFile.transaction do
  picture_file_1.destroy
  picture_file_2.save!
end

class PictureFile < ActiveRecord::Base
  after_commit :delete_picture_file_from_disk, on: [:destroy]

  def delete_picture_file_from_disk
    if File.exist?(filepath)
      File.delete(filepath)
    end
  end
end

Sources