Spec-Zone.ru › Ruby on Rails 7.2

модуль ActiveRecord::Callbacks

Включенные модули:
ActiveModel::Validations::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 Callbacks поддерживают :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.

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API