Spec-Zone.ru › Ruby on Rails 4.2

модуль ActiveRecord::Callbacks

Включенные модули:
ActiveModel::Validations::Callbacks

Active Record Обработчики событий

Обработчики событий — это крючки в жизненном цикле объекта Active Record, которые позволяют вызывать логику до или после изменения состояния объекта. Это может быть использовано для того, чтобы убедиться, что связанные и зависимые объекты удаляются, когда destroy вызывается (переопределяя before_destroy) или для обработки атрибутов перед их валидацией (переопределяя before_validation). Пример обработки событий при создании новой записи:

  • (-) 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 и запустить унаследованные обработчики событий.

ВАЖНО: Чтобы наследование работало для очередей обработчиков событий, вы должны указать обработчики событий до указания ассоциаций. В противном случае вы можете вызвать загрузку потомка до того, как родитель зарегистрирует обработчики событий, и они не будут унаследованы.

Типы обработчиков событий

Существует четыре типа обработчиков событий, которые принимают макросы обработчиков событий: ссылки на методы (метод), объекты обработчиков событий, встроенные методы (используя proc) и встроенные методы eval (используя строку). Ссылки на методы (метод) и объекты обработчиков событий являются рекомендуемыми подходами, встроенные методы с использованием proc иногда подходят (например, для создания модулей), а встроенные методы 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, все последующие обработчики событий и связанное действие отменяются. Обработчики событий обычно выполняются в порядке их определения, за исключением обработчиков событий, определенных как методы в модели, которые вызываются последними.

Порядок обработчиков событий

Иногда код требует, чтобы обработчики событий выполнялись в определенном порядке. Например, обработчик событий 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 proc, определенный как rest_when_dead, используйте свойство filter объекта обработчика событий:

Topic._save_callbacks.select { |cb| cb.kind.eql?(:before) }.collect(&:filter).include?(:rest_when_dead)

Возвращает true или false в зависимости от того, содержится ли proc в цепочке обработчиков событий before_save модели Topic.

Константы

CALLBACKS

© 2004–2018 David Heinemeier Hansson
Licensed under the MIT License.

Spec-Zone.ru

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