модуль ActiveRecord::Callbacks
Active Record Обработчики
Обработчики — это крючки в жизненном цикле объекта Active Record, которые позволяют вам запускать логику до или после изменения состояния объекта. Это можно использовать для обеспечения удаления связанных и зависимых объектов при вызове destroy (переопределяя before_destroy) или для изменения атрибутов перед валидацией (переопределяя before_validation). В качестве примера инициированных обработчиков рассмотрим вызов Base#save для нового записи:
-
(-)
save -
(-)
valid -
(1)
before_validation -
(-)
validate -
(2)
after_validation -
(3)
before_save -
(4)
before_create -
(-)
create -
(5)
after_create -
(6)
after_save -
(7)
after_commit
Также обработчик after_rollback может быть настроен на срабатывание всякий раз, когда происходит откат. Подробную информацию об обработчиках after_commit и after_rollback см. в ActiveRecord::Transactions.
Кроме того, обработчик after_touch срабатывает всякий раз, когда объект изменяется.
Наконец, обработчики after_find и after_initialize срабатывают для каждого объекта, который находится и инициализируется поисковиком, причём after_initialize срабатывает после инициализации новых объектов.
Всего существует девятнадцать обработчиков, которые предоставляют вам огромные возможности реагировать и готовиться к каждому состоянию в жизненном цикле Active Record. Последовательность вызова Base#save для существующей записи аналогична, за исключением того, что каждый обработчик _create заменяется соответствующим обработчиком _update.
Примеры:
class CreditCard < ActiveRecord::Base
# Strip everything but digits, so the user can specify "555 234 34" or
# "5552-3434" and both will mean "55523434"
before_validation(on: :create) do
self.number = number.gsub(/[^0-9]/, "") if attribute_present?("number")
end
end
class Subscription < ActiveRecord::Base
before_create :record_signup
private
def record_signup
self.signed_up_on = Date.today
end
end
class Firm < ActiveRecord::Base
# Destroys the associated clients and people when the firm is destroyed
before_destroy { |record| Person.destroy_all "firm_id = #{record.id}" }
before_destroy { |record| Client.destroy_all "client_of = #{record.id}" }
end
Наследуемые очереди обработчиков
Помимо перезаписываемых методов обратного вызова, также можно регистрировать обратные вызовы с помощью макросов обратного вызова. Их основное преимущество заключается в том, что макросы добавляют поведение в очередь обратного вызова, которая сохраняется в иерархии наследования.
class Topic < ActiveRecord::Base before_destroy :destroy_author end class Reply < Topic before_destroy :destroy_readers end
Теперь, когда выполняется Topic#destroy, вызывается только destroy_author. При выполнении Reply#destroy вызываются как destroy_author, так и destroy_readers. Сравните это с ситуацией, когда переопределяется метод before_destroy.
class Topic < ActiveRecord::Base def before_destroy() destroy_author end end class Reply < Topic def before_destroy() destroy_readers end end
В этом случае Reply#destroy будет вызывать только destroy_readers, а не destroy_author. Таким образом, используйте макросы обратного вызова, когда хотите убедиться, что определённый обратный вызов вызывается для всей иерархии, и используйте обычные перезаписываемые методы, когда хотите предоставить каждому потомку возможность решить, нужно ли вызывать super и вызывать унаследованные обратные вызовы.
ВАЖНО: Для того чтобы наследование работало для очередей обратных вызовов, вы должны указать обратные вызовы до указания ассоциаций. В противном случае вы можете инициировать загрузку потомка до того, как родитель зарегистрирует обратные вызовы, и они не будут унаследованы.
Типы обработчиков
Существует четыре типа обработчиков, принимаемых макросами обратного вызова: ссылки на методы (метод) (символ), объекты обратного вызова, встроенные методы (с использованием блока) и встроенные методы eval (с использованием строки). Ссылки на методы и объекты обратного вызова являются рекомендуемыми подходами; встроенные методы с использованием блока иногда уместны (например, при создании плагинов), а встроенные методы eval устарели.
Обработчики ссылок на методы работают путём указания защищённого или приватного метода, доступного в объекте, например так:
class Topic < ActiveRecord::Base
before_destroy :delete_parents
private
def delete_parents
self.class.delete_all "parent_id = #{id}"
end
end
Объекты обратного вызова имеют методы, названные в честь обратного вызова, вызываемого с записью в качестве единственного параметра, например:
class BankAccount < ActiveRecord::Base
before_save EncryptionWrapper.new
after_save EncryptionWrapper.new
after_initialize EncryptionWrapper.new
end
class EncryptionWrapper
def before_save(record)
record.credit_card_number = encrypt(record.credit_card_number)
end
def after_save(record)
record.credit_card_number = decrypt(record.credit_card_number)
end
alias_method :after_initialize, :after_save
private
def encrypt(value)
# Secrecy is committed
end
def decrypt(value)
# Secrecy is unveiled
end
end
Таким образом, вы указываете объект, которому вы хотите передать сообщение в определённом обратном вызове. Когда этот обратный вызов срабатывает, у объекта есть метод с именем сообщения обратного вызова. Вы можете сделать эти обратные вызовы более гибкими, передавая другие данные инициализации, такие как имя атрибута для работы с ним:
class BankAccount < ActiveRecord::Base
before_save EncryptionWrapper.new("credit_card_number")
after_save EncryptionWrapper.new("credit_card_number")
after_initialize EncryptionWrapper.new("credit_card_number")
end
class EncryptionWrapper
def initialize(attribute)
@attribute = attribute
end
def before_save(record)
record.send("#{@attribute}=", encrypt(record.send("#{@attribute}")))
end
def after_save(record)
record.send("#{@attribute}=", decrypt(record.send("#{@attribute}")))
end
alias_method :after_initialize, :after_save
private
def encrypt(value)
# Secrecy is committed
end
def decrypt(value)
# Secrecy is unveiled
end
end
Макросы обратных вызовов обычно принимают символ для метода, который они должны выполнить, но вы также можете передать «строку метода», которая будет затем вычислена в контексте обратного вызова. Пример:
class Topic < ActiveRecord::Base
before_destroy 'self.class.delete_all "parent_id = #{id}"'
end
Обратите внимание, что используются одинарные кавычки ('), поэтому часть #{id} не вычисляется до запуска обратного вызова. Также обратите внимание, что эти встроенные обратные вызовы могут быть сложены, как и обычные:
class Topic < ActiveRecord::Base
before_destroy 'self.class.delete_all "parent_id = #{id}"',
'puts "Evaluated after parents are destroyed"'
end
before_validation* возвращающие выражения
Если возвращаемое значение обработчика before_validation может быть вычислено в false, процесс будет прерван, и Base#save вернёт false. Если вызывается ActiveRecord::Validations#save!, будет возбуждено исключение ActiveRecord::RecordInvalid. Ничего не будет добавлено в объект ошибок.
Отмена обработчиков
Если обработчик before_* возвращает false, все последующие обратные вызовы и связанное действие отменяются. Если обработчик after_* возвращает false, отменяются все последующие обратные вызовы. Обработчики обычно выполняются в порядке их определения, за исключением обратных вызовов, определённых как методы в модели, которые вызываются последними.
Порядок обработчиков
Иногда код требует, чтобы обратные вызовы выполнялись в определённом порядке. Например, обработчик before_destroy (log_children в данном случае) должен выполняться до уничтожения потомков с помощью опции +dependent: destroy+.
Давайте рассмотрим следующий код:
class Topic < ActiveRecord::Base
has_many :children, dependent: destroy
before_destroy :log_children
private
def log_children
# Child processing
end
end
В данном случае проблема заключается в том, что при выполнении обработчика before_destroy потомки недоступны, поскольку обработчик destroy выполняется первым. Вы можете использовать опцию prepend в обработчике before_destroy для предотвращения этого.
class Topic < ActiveRecord::Base
has_many :children, dependent: destroy
before_destroy :log_children, prepend: true
private
def log_children
# Child processing
end
end
Таким образом, before_destroy выполняется до вызова dependent: destroy, и данные по-прежнему доступны.
Транзакции
Весь цепочку обратных вызовов вызова save, save! или destroy выполняется в рамках транзакции. Это включает в себя обработчики after_*. Если всё пройдёт хорошо, после завершения цепочки выполняется COMMIT.
Если обработчик before_* отменяет действие, выполняется ROLLBACK. Вы также можете инициировать ROLLBACK, возбудив исключение в любом обработчике, включая обработчики after_*. Однако в этом случае клиент должен быть об этом осведомлён, потому что обычный save возбудит такое исключение вместо того, чтобы тихо вернуть false.
Отладка обработчиков
Цепочка обратных вызовов доступна через метод _*_callbacks в объекте. ActiveModel Обработчики поддерживают :before, :after и :around в качестве значений свойства kind. Свойство kind определяет, в какой части цепочки выполняется обратный вызов.
Чтобы найти все обратные вызовы в цепочке обратного вызова before_save:
Topic._save_callbacks.select { |cb| cb.kind.eql?(:before) }
Возвращает массив объектов обратных вызовов, образующих цепочку before_save.
Чтобы проверить, содержит ли цепочка обратного вызова before_save блок, определённый как rest_when_dead, используйте свойство filter объекта обратного вызова:
Topic._save_callbacks.select { |cb| cb.kind.eql?(:before) }.collect(&:filter).include?(:rest_when_dead)
Возвращает true или false в зависимости от того, содержится ли блок в цепочке обратного вызова before_save модели Topic.
Постоянные
- CALLBACKS
© 2004–2016 David Heinemeier Hansson
Licensed under the MIT License.