модуль ActiveRecord::Callbacks
Обработчики событий Active Record
Обработчики событий — это крючки в жизненном цикле объекта Active Record, которые позволяют вам вызывать логику до или после изменения состояния объекта. Это можно использовать для обеспечения удаления связанных и зависимых объектов при вызове ActiveRecord::Base#destroy (переопределяя before_destroy) или для обработки атрибутов перед их валидацией (переопределяя before_validation). В качестве примера обработчиков, инициируемых при вызове, рассмотрим вызов ActiveRecord::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. Последовательность вызова ActiveRecord::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
# Disables access to the system, for associated clients and people when the firm is destroyed
before_destroy { |record| Person.where(firm_id: record.id).update_all(access: 'disabled') }
before_destroy { |record| Client.where(client_of: record.id).update_all(access: 'disabled') }
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.
ВАЖНО: Для работы наследования с очередями обработчиков событий необходимо указать обработчики событий до указания ассоциаций. В противном случае вы можете вызвать загрузку дочернего элемента до того, как родительский элемент зарегистрирует обработчики событий, и они не будут унаследованы.
Типы обработчиков событий
Существует три типа обработчиков событий, принимаемых макросами обработчиков событий: ссылки на методы (символы), объекты обработчиков событий, встроенные методы (с использованием блока). Ссылки на методы и объекты обработчиков событий являются рекомендуемыми подходами, встроенные методы с использованием блока иногда подходят (например, для создания плагинов).
Обработчики событий со ссылками на методы работают путем указания защищенного или частного метода, доступного в объекте, например:
class Topic < ActiveRecord::Base
before_destroy :delete_parents
private
def delete_parents
self.class.delete_by(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
before_validation* возвращающие операторы
Если обработчик событий before_validation вызывает исключение :abort, процесс будет прерван, и ActiveRecord::Base#save вернет false. Если вызовется ActiveRecord::Base#save!, будет возбуждено исключение ActiveRecord::RecordInvalid. Ничего не будет добавлено к объекту ошибок.
Отмена обработчиков событий
Если обработчик событий before_* вызывает исключение :abort, все последующие обработчики событий и связанное действие отменяются. Обработчики событий, как правило, выполняются в порядке их определения, за исключением обработчиков событий, определенных как методы в модели, которые вызываются последними.
Порядок выполнения обработчиков событий
Иногда в коде приложения требуется, чтобы обработчики событий выполнялись в определенном порядке. Например, обработчик событий before_destroy (log_children в данном случае) должен выполняться до уничтожения записей в ассоциации 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, записи в ассоциации children больше не существуют, потому что обработчик событий ActiveRecord::Base#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, и данные все еще доступны.
Также есть случаи, когда вы хотите, чтобы несколько обработчиков событий одного типа выполнялись в порядке.
Например:
class Topic < ActiveRecord::Base
has_many :children
after_save :log_children
after_save :do_something_else
private
def log_children
# Child processing
end
def do_something_else
# Something else
end
end
В этом случае обработчик событий log_children выполняется до do_something_else. Это относится ко всем нетранзакционным обработчикам событий и к before_commit.
Для транзакционных обработчиков событий after_ (after_commit, after_rollback и т. д.) порядок может быть задан через настройки.
config.active_record.run_after_transaction_callbacks_in_order_defined = false
При установке в true (по умолчанию с Rails 7.1) обработчики событий выполняются в порядке их определения, как и в примере выше. При установке в false порядок инвертируется, поэтому do_something_else выполняется до log_children.
Транзакции
Вся цепочка обработчиков событий вызова #save, #save! или #destroy выполняется в рамках транзакции. Это включает в себя after_*. Если все прошло успешно, COMMIT выполняется после завершения цепочки.
Если обработчик событий before_* отменяет действие, выполняется ROLLBACK. Вы также можете инициировать ROLLBACK, вызвав исключение в любом из обработчиков событий, включая after_*. Однако в этом случае клиент должен об этом знать, потому что обычный вызов #save вызовет такое исключение вместо тихого возвращения false.
Отладка обработчиков событий
Цепочка обработчиков событий доступна через метод _*_callbacks объекта. Обработчики событий Active Model поддерживают :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–2021 David Heinemeier Hansson
Licensed under the MIT License.