Рельсы 3:Как определить после совершения действия в наблюдателях? (создать / обновить / уничтожить)
у меня есть наблюдатель, и я регистрирую after_commit
обратный.
Как я могу сказать, был ли он уволен после создания или обновления?
Я могу сказать, что предмет был уничтожен, спросив item.destroyed?
но #new_record?
не работает с момента сохранения элемента.
я собирался решить ее, добавив after_create
/after_update
и сделать что-то вроде @action = :create
внутри и проверьте @action
at after_commit
, но кажется, что экземпляр observer является одноэлементным, и я мог бы просто переопределить значение, прежде чем он доберется до after_commit
. Поэтому я решил его более уродливым способом, сохранив действие на карте на основе item.id на after_create / update и проверка его значения на after_commit. Очень некрасиво.
есть ли другой способ?
обновление
как сказал @tardate,transaction_include_action?
это хороший признак, хотя это частный метод, и в наблюдателе он должен быть доступен с #send
.
class ProductScoreObserver < ActiveRecord::Observer
observe :product
def after_commit(product)
if product.send(:transaction_include_action?, :destroy)
...
к сожалению,:on
опция не работает наблюдатели.
просто убедитесь, что вы проверяете ад своих наблюдателей (ищите test_after_commit
gem если вы используете use_transactional_fixtures) так что при обновлении до новой версии Rails вы будете знать, если он все еще работает.
(проверено на 3.2.9)
обновление 2
вместо наблюдателей я теперь использую ActiveSupport:: Concern и after_commit :blah, on: :create
там работает.
9 ответов:
Я думаю transaction_include_action? это то, что вам нужно. Это дает надежную индикацию конкретной транзакции в процессе (проверено в 3.0.8).
формально он определяет, включала ли транзакция действие для: create,: update или: destroy. Используется при фильтрации обратных вызовов.
class Item < ActiveRecord::Base after_commit lambda { Rails.logger.info "transaction_include_action?(:create): #{transaction_include_action?(:create)}" Rails.logger.info "transaction_include_action?(:destroy): #{transaction_include_action?(:destroy)}" Rails.logger.info "transaction_include_action?(:update): #{transaction_include_action?(:update)}" } end
также интерес может быть transaction_record_state который можно использовать для определения, была ли запись создана или уничтожена в транзакции. Государство должно быть одним из следующих :new_record или уничтожены.
обновление для Rails 4
для тех, кто стремится решить проблему в Rails 4, Этот метод является теперь устаревшим, вы должны использовать
transaction_include_any_action?
, которая принимаетarray
действий.Пример Использования:
transaction_include_any_action?([:create])
Я сегодня узнал, что вы можете сделать что-то вроде этого:
after_commit :do_something, :on => :create after_commit :do_something, :on => :update
здесь выполнить_действие - это метод обратного вызова, который вы хотите звонить на определенные действия.
Если вы хотите вызвать тот же обратный вызов для обновление и создать, но не уничтожить, вы также можете использовать:
after_commit :do_something, :if => :persisted?
это действительно не задокументировано хорошо, и мне было трудно гуглить его. К счастью, я знаю несколько прекрасных людей. Надеюсь на это помогает!
вы можете решить с помощью двух методов.
подход, предложенный @nathanvda, т. е. проверка created_at и updated_at. Если они одинаковы, запись создается заново, иначе ее обновление.
С помощью виртуальных атрибутов в модели. Шаги:
- добавить поле в модель с кодом
attr_accessor newly_created
обновить то же самое в
before_create
иbefore_update callbacks
какdef before_create (record) record.newly_created = true end def before_update (record) record.newly_created = false end
основываясь на идее leenasn, я создал несколько модулей, которые позволяют использовать
after_commit_on_update
иafter_commit_on_create
обратные вызовы:https://gist.github.com/2392664использование:
class User < ActiveRecord::Base include AfterCommitCallbacks after_commit_on_create :foo def foo puts "foo" end end class UserObserver < ActiveRecord::Observer def after_commit_on_create(user) puts "foo" end end
взгляните на тестовый код: https://github.com/rails/rails/blob/master/activerecord/test/cases/transaction_callbacks_test.rb
здесь вы можете найти:
after_commit(:on => :create) after_commit(:on => :update) after_commit(:on => :destroy)
и
after_rollback(:on => :create) after_rollback(:on => :update) after_rollback(:on => :destroy)
мне любопытно знать, почему вы не могли переместить свой
after_commit
логика вafter_create
иafter_update
. Есть ли какое-то важное изменение состояния, которое происходит между последними 2 вызовами иafter_commit
?если ваша обработка создания и обновления имеет некоторую перекрывающуюся логику, вы можете просто вызвать последние 2 метода третьим методом, передав действие:
# Tip: on ruby 1.9 you can use __callee__ to get the current method name, so you don't have to hardcode :create and :update. class WidgetObserver < ActiveRecord::Observer def after_create(rec) # create-specific logic here... handler(rec, :create) # create-specific logic here... end def after_update(rec) # update-specific logic here... handler(rec, :update) # update-specific logic here... end private def handler(rec, action) # overlapping logic end end
если вы все еще предпочитаете использовать after_commit, вы можете использовать переменные потока. Это не будет утечка памяти, пока мертвые потоки разрешается собирать мусор.
class WidgetObserver < ActiveRecord::Observer def after_create(rec) warn "observer: after_create" Thread.current[:widget_observer_action] = :create end def after_update(rec) warn "observer: after_update" Thread.current[:widget_observer_action] = :update end # this is needed because after_commit also runs for destroy's. def after_destroy(rec) warn "observer: after_destroy" Thread.current[:widget_observer_action] = :destroy end def after_commit(rec) action = Thread.current[:widget_observer_action] warn "observer: after_commit: #{action}" ensure Thread.current[:widget_observer_action] = nil end # isn't strictly necessary, but it's good practice to keep the variable in a proper state. def after_rollback(rec) Thread.current[:widget_observer_action] = nil end end
Это похоже на ваш 1-й подход, но он использует только один метод (before_save или before_validate, чтобы действительно быть безопасным), и я не понимаю, почему это переопределит любое значение
class ItemObserver def before_validation(item) # or before_save @new_record = item.new_record? end def after_commit(item) @new_record ? do_this : do_that end end
обновление
это решение не работает, потому что, как указано @eleano, ItemObserver является Одноэлементным, он имеет только один экземпляр. Так что если 2 элемента сохраняются в то же время @new_record может принять его значение из item_1 в то время как after_commit запускается item_2. Чтобы преодолеть эту проблему есть должно быть
item.id
проверка / сопоставление с "постсинхронизацией" 2 метода обратного вызова : hackish.
Я использую следующий код, чтобы определить, является ли это новый рекорд или нет:
previous_changes[:id] && previous_changes[:id][0].nil?
Он основан на идее, что новая запись имеет идентификатор по умолчанию, равный нулю, а затем изменяет его при сохранении. Конечно, изменение идентификатора не является распространенным случаем, поэтому в большинстве случаев второе условие может быть опущено.