Rails 3:如何在观察者中识别after_commit动作? (创build/更新/销毁)

我有一个观察员,我注册了一个after_commitcallback。 如何判断在创build或更新后是否被解雇? 我可以告诉一个项目被要求item.destroyed?销毁item.destroyed?#new_record? 自从该项目被保存以来不起作用。

我打算通过添加after_create / after_update来解决它,并执行像@action = :create内部@action = :create和检查@actionafter_commit ,但似乎观察者实例是一个单身人士,我可能只是覆盖一个值, after_commit 。 于是我以一种更丑陋的方式解决了这个问题,把action放在一个基于after_create / update的item.id的地图上,并在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选项在观察者中不起作用。

只要确保你testing了你的观察者的地狱(如果你使用use_transactional_fixtures查找test_after_commit gem),所以当你升级到新的Rails版本时,你会知道它是否仍然有效。

(testing3.2.9)

更新2

现在我使用ActiveSupport :: Concern和after_commit :blah, on: :create而不是观察者:: after_commit :blah, on: :create在那里工作。

我认为transaction_include_action? 就是你所追求的。 它给出了具体交易的可靠指示(在3.0.8中validation)。

forms上,它决定一个事务是否包含一个动作:create,:update或者:destroy。 用于过滤callback。

 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 ,它可以用来确定logging是否在事务中被创build或销毁。 国家应该是:new_record或者:destroy。

更新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 

do_something是您想调用某些操作的callback方法。

如果你想调用相同的callback进行更新创build ,但不能销毁 ,你也可以使用: after_commit :do_something, :if => :persisted?

这真的没有很好的logging,我很难用谷歌search。 幸运的是,我认识一些有才华的人。 希望能帮助到你!

你可以通过使用两种技术来解决。

  • @nathanvdabuild议的方法即检查created_at和updated_at。 如果它们是相同的,logging是新创build的,否则它是一个更新。

  • 通过在模型中使用虚拟属性。 步骤是:

    • 在模型中添加一个代码为attr_accessor newly_created
    • before_createbefore_update callbacks

       def before_create (record) record.newly_created = true end def before_update (record) record.newly_created = false end 

基于leenasn的想法,我创build了一些模块,可以使用after_commit_on_updateafter_commit_on_createcallbackafter_commit_on_create : https : after_commit_on_create

用法:

 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 

看看testing代码: https : //github.com/rails/rails/blob/master/activerecord/test/cases/transaction_callbacks_test.rb

在那里你可以find:

 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_createafter_update 。 在后两个调用和after_commit之间是否会发生一些重要的状态变化?

如果你的创build和更新处理有一些重叠的逻辑,你可以让后面的两个方法调用第三个方法,传入这个动作:

 # 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,你可以使用线程variables。 只要死线被允许垃圾收集,这将不会泄漏内存。

 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 

这与你的第一种方法类似,但它只使用一个方法(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 

更新

这个解决scheme不起作用,因为如@eleano所述,ItemObserver是一个单例,它只有一个实例。 因此,如果同时保存2个项目,@ new_record可以从item_1取值,而item_2触发after_commit。 为了克服这个问题,应该有一个item.id检查/映射到“后同步”2callback方法:hackish。

我使用下面的代码来确定它是否是新logging:

 previous_changes[:id] && previous_changes[:id][0].nil? 

它基于这样的想法,即新logging具有等于零的默认ID,然后在保存时更改它。 当然id改变不是一个常见的情况,所以在大多数情况下,第二个条件可以省略。

您可以将事件挂接从after_commit更改为after_save,以捕获所有创build和更新事件。 然后你可以使用:

 id_changed? 

观察员的助手 在更新上创build和错误将是真实的。