v7.0.0
Veja mais em rubyonrails.org: Mais Ruby on Rails

Active Record Callbacks

Este guia ensina como se conectar ao ciclo de vida de seus objetos Active Record.

Depois de ler esse guia, você saberá:

1 O Ciclo de Vida do Objeto

Durante a operação normal de uma aplicação Rails, objetos podem ser criados, atualizados e destruídos. O Active Record fornece hooks para este ciclo de vida do objeto para que você possa controlar sua aplicação e seus dados.

Callbacks permitem você desencadear a lógica antes ou depois de uma alteração do estado de um objeto.

2 Visão geral de Callbacks

Callbacks são métodos que são chamados em certos momentos do ciclo de vida de um objeto. Com callbacks é possível escrever código que rodará sempre que um objeto Active Record é criado, salvo, atualizado, deletado, validado ou carregado de um banco de dados.

2.1 Registro de Callback

Para usar os callbacks disponíveis, você precisa registrá-los. Você pode implementar os callbacks como métodos comuns e usar o método macro-style de uma classe para registrá-los como callbacks:

class User < ApplicationRecord
  validates :login, :email, presence: true

  before_validation :ensure_login_has_a_value

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

Os métodos macro-style de uma classe podem também receber um bloco. Considere usar esse estilo se o código dentro do seu bloco é tão curto que cabe em uma linha:

class User < ApplicationRecord
  validates :login, :email, presence: true

  before_create do
    self.name = login.capitalize if name.blank?
  end
end

Callbacks também podem ser registrados para rodar apenas em certos eventos do ciclo de vida:

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

É considerado uma boa prática declarar métodos de callback como privados. Se deixados como públicos, podem ser chamados de fora do model e violar o princípio de encapsulamento de um objeto.

3 Callbacks Disponíveis

Aqui está uma lista com todos os Active Record callbacks, listados na mesma ordem na qual eles serão chamados durante as respectivas operações:

3.1 Criando um Objeto

3.2 Atualizando um Objeto

3.3 Destruindo um Objeto

after_save roda tanto na criação quanto na atualização, mas sempre depois de callbacks mais específicos after_create e after_update, não importa a ordem que uma chamada macro foi executada.

Evite atualizar ou salvar atributos em callbacks. Por exemplo, não chame update(attribute: "value") em um callback. Isso pode alterar o estado do model e resultar em efeitos colaterais inesperados durante a confirmação. Em vez disso, você pode atribuir valores diretamente com segurança (por exemplo, self.attribute = "value") em before_create / before_update ou callbacks anteriores.

Os callbacks before_destroy devem ser posicionados antes de associações dependent: :destroy (ou use a opção prepend: true), para garantir que executem antes dos registros serem deletados pelo dependent: :destroy.

3.4 after_initialize e after_find

O callback after_initialize será chamado sempre que um objeto Active Record for instanciado, usando diretamente new ou quando um registro é carregado do banco de dados. Isto pode ser útil para evitar a necessidade de substituir diretamente seu método initialize do Active Record.

O callback after_find será chamado sempre que o Active Record carregar um registro do banco de dados. after_find é chamado antes de after_initialize se ambos estiverem definidos.

Os callbacks after_initialize e after_find não possuem complementos before_*, mas podem ser registrados como os outros callbacks de Active Record.

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
irb> User.new
You have initialized an object!
=> #<User id: nil>

irb> User.first
You have found an object!
You have initialized an object!
=> #<User id: 1>

3.5 after_touch

O callback after_touch será chamado sempre que um objeto Active Record for alcançado.

class User < ApplicationRecord
  after_touch do |user|
    puts "You have touched an object"
  end
end
irb> 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">

irb> u.touch
You have touched an object
=> true

Pode ser usado junto de belongs_to:

class Employee < ApplicationRecord
  belongs_to :company, touch: true
  after_touch do
    puts 'An Employee was touched'
  end
end

class Company < 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
irb> @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">

irb> @employee.touch # também executa @employee.company.touch
An Employee was touched
Employee/Company was touched
=> true

4 Executando Callbacks

Os métodos a seguir acionam callbacks:

  • create
  • create!
  • destroy
  • destroy!
  • destroy_all
  • destroy_by
  • save
  • save!
  • save(validate: false)
  • toggle!
  • touch
  • update_attribute
  • update
  • update!
  • valid?

Adicionalmente, o callback after_find é acionado pelos seguintes métodos de localização:

  • all
  • first
  • find
  • find_by
  • find_by_*
  • find_by_*!
  • find_by_sql
  • last

O callback after_initialize é acionado toda vez que um novo objeto da classe é inicializado.

Os métodos find_by_* e find_by_*! são localizadores dinâmicos gerados automaticamente para cada atributo. Aprenda mais sobre eles na seção de Localizadores Dinâmicos

5 Ignorando Callbacks

Assim como nas validações, também é possível ignorar os callbacks usando os seguintes métodos:

  • decrement!
  • decrement_counter
  • delete
  • delete_all
  • delete_by
  • increment!
  • increment_counter
  • insert
  • insert!
  • insert_all
  • insert_all!
  • touch_all
  • update_column
  • update_columns
  • update_all
  • update_counters
  • upsert
  • upsert_all

Contudo, esses métodos devem ser usados com cautela, porque regras de negócio importantes e lógica da aplicação podem ser mantidos nos callbacks. Contorná-los sem entender as potenciais implicações pode levar a dados inválidos.

6 Interrompendo uma Execução

Quando você começar a registrar novos callbacks para seus models, eles serão enfileirados para a execução. Esta fila incluirá todas as validações do seu model, os callbacks registrados e a operação do banco de dados a ser executada.

Toda a cadeia do callback é empacotada em uma transação. Se algum callback lança uma exceção, a cadeia de execução é interrompida e um ROLLBACK é emitido. Para interromper intencionalmente uma cadeia, use:

throw :abort

Qualquer exceção que não seja ActiveRecord::Rollback ou ActiveRecord::RecordInvalid serão lançadas novamente pelo Rails após a cadeia do callback ser interrompida. Lançar uma outra exceção que não ActiveRecord::Rollback ou ActiveRecord::RecordInvalid pode quebrar um código que não espera por métodos como save ou update (os quais normalmente tentam retornar true ou false) para lançar uma exceção.

7 Callbacks Relacionais

Callbacks trabalham através de relacionamentos dos models e podem até ser definidos por eles. Suponha um exemplo onde um usuário tenha muitos artigos. Um artigo de um usuário deve ser apagado se o usuario for apagado. Vamos adicionar um callback after_destroy para o model User por meio de seu relacionamento com o model Article:

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
irb> user = User.first
=> #<User id: 1>
irb> user.articles.create!
=> #<Article id: 1, user_id: 1>
irb> user.destroy
Article destroyed
=> #<User id: 1>

8 Callbacks Condicionais

Tal como acontece com as validações, também podemos tornar a chamada de um método de callback condicional à satisfação de um determinado predicado. Podemos fazer isso utilizando as opções :if e :except, que podemo recever um símbolo, uma Proc ou um Array. Você pode utilizar a opção :if quando quiser especificar sob quais condições o callback deve ser chamado. Se você deseja especificar as condições sob as quais o callback não deve ser chamado, você pode utilizar a opção :unless.

8.1 Utilizando :if e :unless com um Symbol

Você pode associar as opções :if e :unless com um symbol correspondente ao nome de um método predicado que será chamado logo antes do callback. Ao utilizar a opção :if, o callback não será executado se o método predicado retornar false (falso); ao utilizar a opção :unless, o callback não será executado se o método predicado retornar true (verdadeiro). Esta é a opção mais comum. Utilizando esta forma de registro também é possível registrar vários predicados diferentes que devem ser chamado para verificar se o callback deve ser executado.

class Order < ApplicationRecord
  before_save :normalize_card_number, if: :paid_with_card?
end

8.2 Utilizando :if e :unless com uma Proc

É possível associar :if e :unless com um objeto Proc. Esta opção é mais adequada ao escrever métodos curtos de validação, geralmente de uma linha:

class Order < ApplicationRecord
  before_save :normalize_card_number,
    if: Proc.new { |order| order.paid_with_card? }
end

Como a proc é avaliada no contexto do objeto, também é possível escrever isso como:

class Order < ApplicationRecord
  before_save :normalize_card_number, if: Proc.new { paid_with_card? }
end

8.3 Usando :if e :unless juntos

callbacks condicionais, podem misturar :if e :unless na mesma declaração do callback:

class Comment < ApplicationRecord
  before_save :filter_content,
    if: Proc.new { forum.parental_control? },
    unless: Proc.new { author.trusted? }
end

8.4 Multiplas Condições de Callback

As opções :if e :unless também aceitam um array de procs ou nomes de métodos como symbols:

class Comment < ApplicationRecord
  before_save :filter_content,
    if: [:subject_to_parental_control?, :untrusted_author?]
end

O callback só é executado quando todas as condições :if e nenhuma das condições :unless retornarem true.

9 Classes Callback

Em algumas situações, os métodos de Callback que iremos escrever serão úteis para serem reutilizados por outros models. O Active Record possibilita a criação de classes que encapsulam os métodos callback , para que eles possam ser reutilizados.

Aqui está um exemplo onde criamos uma classe com um callback after_destroy para o model PictureFile:

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

Quando declaramos dentro da classe, como foi feito acima, os métodos callback irão receber o model como parâmetro. Agora poderemos usar a classe callback no model:

class PictureFile < ApplicationRecord
  after_destroy PictureFileCallbacks.new
end

Perceba que precisamos instanciar um novo objeto chamado PictureFileCallbacks, já que declaramos nosso callback como um método de instância. Particularmente, isso é útil se os callbacks fazem uso do estado do objeto instanciado. Porém fará mais sentido declarar os callbacks como métodos de classe com mais frequência:

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

Se o método callback é declarado dessa forma, não será necessário instanciar o objeto PictureFileCallbacks

class PictureFile < ApplicationRecord
  after_destroy PictureFileCallbacks
end

Você pode declarar dentro de suas classes callback quantos callback achar necessário.

10 Callbacks de Transação

Existem dois callbacks adicionais que são disparados quando se completa uma transação de banco de dados: after_commit e after_rollback. Estes callbacks são muito parecidos com o callback after_save, exceto que eles não são executados até que as mudanças no banco de dados sejam confirmadas ou desfeitas. Eles são mais úteis quando seus active record models precisam de interagir com sistemas externos que não fazem parte da transação do banco de dados.

Considere, por exemplo, o exemplo anterior onde o model PictureFile precisa de apagar um arquivo depois que um registro correspondente é destruído. Se algo lançar uma exceção depois que o callback after_destroy for chamado e a transação for desfeita, o arquivo terá sido deletado e o model será deixado em um estado inconsistente. Por exemplo, suponha que picture_file_2 no código abaixo não é valido e o método save! lança um erro.

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

Usando o callback after_commit nós podemos responder por esse caso.

class PictureFile < ApplicationRecord
  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

A opção :on especifica quando um callback vai ser disparado. Se você não fornecer a opção :on o callback será disparado para cada ação.

Já que usar o callback after_commit para criar, atualizar ou deletar é comum, existem aliases para as operações:

class PictureFile < ApplicationRecord
  after_destroy_commit :delete_picture_file_from_disk

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

Quando uma transação é completada, os callbacks after_commit ou after_rollback são chamados para todos os models criados, atualizados ou destruidos em uma transação. No entanto, se uma exceção é lançada em um desses callbacks, a exceção vai interromper a execução e quaisquer métodos restantes de after_commit ou after_rollback não serão executados. Assim sendo, se o código do seu callback pode lançar uma exceção, você precisará recuperá-la e tratá-la dentro do callback para que outros callbacks possam ser executados.

O código executado dentro dos callbacks de after_commit ou after_rollback não está incluido em uma transação.

Usando ambos after_create_commit e after_update_commit com o mesmo nome de método só permitirá que o último retorno de chamada definido tenha efeito, pois ambos são internamente alias para after_commit que substitui os retornos de chamada definidos anteriormente com o mesmo nome de método.

class User < ApplicationRecord
  after_create_commit :log_user_saved_to_db
  after_update_commit :log_user_saved_to_db

  private
  def log_user_saved_to_db
    puts 'User was saved to database'
  end
end
irb> @user = User.create # não imprime nada

irb> @user.save # atualizando @user
User was saved to database

Existe também um alias para o usar o callback after_commit, juntamente, tanto para criar quanto para atualizar:

class User < ApplicationRecord
  after_save_commit :log_user_saved_to_db

  private
  def log_user_saved_to_db
    puts 'User was saved to database'
  end
end
irb> @user = User.create # criando um Usuário
User was saved to database

irb> @user.save # atualizando o Usuário @user
User was saved to database

Feedback

Você é incentivado a ajudar a melhorar a qualidade deste guia.

Por favor, contribua caso veja quaisquer erros, inclusive erros de digitação. Para começar, você pode ler nossa sessão de contribuindo com a documentação.

Você também pode encontrar conteúdo incompleto ou coisas que não estão atualizadas. Por favor, adicione qualquer documentação em falta na main do Rails. Certifique-se de checar o Edge Guides (en-US) primeiro para verificar se o problema já foi resolvido ou não no branch main. Verifique as Diretrizes do Guia Ruby on Rails para estilo e convenções.

Se, por qualquer motivo, você encontrar algo para consertar, mas não conseguir consertá-lo, por favor abra uma issue no nosso Guia.

E por último, mas não menos importante, qualquer tipo de discussão sobre a documentação do Ruby on Rails é muito bem vinda na lista de discussão rubyonrails-docs e nas issues do Guia em português.


dark theme icon