Rails 3:如何在观察者中识别after_commit动作? (创build/更新/销毁)
我有一个观察员,我注册了一个after_commit
callback。 如何判断在创build或更新后是否被解雇? 我可以告诉一个项目被要求item.destroyed?
销毁item.destroyed?
但#new_record?
自从该项目被保存以来不起作用。
我打算通过添加after_create
/ after_update
来解决它,并执行像@action = :create
内部@action = :create
和检查@action
在after_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_create
和before_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_update
和after_commit_on_create
callbackafter_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_create
和after_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和错误将是真实的。