我有一个观察者,我注册了一个after_commit
回调。
如何判断它是在创建或更新后被触发的?
我可以通过询问item.destroyed?
告诉某个项目已被销毁,但#new_record?
因项目已保存而无效。
我打算通过添加after_create
/ after_update
并在@action = :create
内部执行此操作来解决问题,并在@action
检查after_commit
,但似乎观察者实例是一个单例,我可能只是在它到达after_commit
之前覆盖一个值。所以我以一种更丑陋的方式解决了这个问题,根据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
选项在观察者中不起作用。
请确保测试观察者的地狱(如果使用use_transactional_fixtures则查找test_after_commit
gem),因此当您升级到新的Rails版本时,您将知道它是否仍然有效。
(在3.2.9上测试)
而不是Observers我现在使用ActiveSupport :: Concern,after_commit :blah, on: :create
在那里工作。
答案 0 :(得分:55)
我今天已经知道你可以做这样的事情:
after_commit :do_something, :on => :create
after_commit :do_something, :on => :update
do_something 是您要对某些操作调用的回调方法。
如果您要为更新和创建而不是销毁调用相同的回调,您还可以使用:
after_commit :do_something, :if => :persisted?
它真的没有记录好,我很难用谷歌搜索它。幸运的是,我认识一些聪明的人。希望它有所帮助!
答案 1 :(得分:53)
我认为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或:destroyed。
Rails 4的更新
对于那些寻求在Rails 4中解决问题的人,现在不推荐使用此方法,您应该使用接受transaction_include_any_action?
操作的array
。
用法示例:
transaction_include_any_action?([:create])
答案 2 :(得分:7)
您可以使用两种技术解决。
@ 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
答案 3 :(得分:3)
基于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
答案 4 :(得分:2)
查看测试代码: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)
答案 5 :(得分:0)
我很想知道为什么你无法将after_commit
逻辑移到after_create
和after_update
。在后两次调用和after_commit
之间是否会发生一些重要的状态变化?
如果您的创建和更新处理有一些重叠的逻辑,您可以让后两种方法调用第三种方法,传递操作:
# 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
答案 6 :(得分:0)
我使用以下代码来确定它是否是新记录:
previous_changes[:id] && previous_changes[:id][0].nil?
它基于以下想法:新记录的默认ID等于nil,然后在保存时更改它。 当然,id更改并不常见,因此在大多数情况下可以省略第二个条件。
答案 7 :(得分:-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是Singleton,它只有一个实例。因此,如果同时保存了2个项目,@ new_record可以从item_1获取其值,而after_commit由item_2触发。为了解决这个问题,应该有item.id
检查/映射到“后同步”2个回调方法:hackish。
答案 8 :(得分:-4)
您可以将事件挂钩从after_commit更改为after_save,以捕获所有创建和更新事件。然后您可以使用:
id_changed?
...在观察者中帮助。这在创建时为true,在更新时为false。