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
before_validation
after_validation
before_save
around_save
before_create
around_create
after_create
after_save
-
after_commit
/after_rollback
3.2 Atualizando um Objeto
before_validation
after_validation
before_save
around_save
before_update
around_update
after_update
after_save
-
after_commit
/after_rollback
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 forum oficial do Ruby on Rails e nas issues do Guia em português.