Rails 3:如何识别观察者中的 after_commit 动作? (创建/更新/销毁)
Posted
技术标签:
【中文标题】Rails 3:如何识别观察者中的 after_commit 动作? (创建/更新/销毁)【英文标题】:Rails 3: How to identify after_commit action in observers? (create/update/destroy) 【发布时间】:2011-11-06 12:16:13 【问题描述】:我有一个观察者,我注册了一个after_commit
回调。
我如何判断它是否在创建或更新后被触发?
我可以通过询问 item.destroyed?
来判断某个项目已被销毁,但 #new_record?
无法正常工作,因为该项目已保存。
我打算通过添加after_create
/after_update
并在里面执行类似@action = :create
并检查after_commit
处的@action
来解决它,但似乎观察者实例是一个单例,我可能只需在它到达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 上测试)
更新 2
我现在使用 ActiveSupport::Concern 而不是观察者,after_commit :blah, on: :create
在那里工作。
【问题讨论】:
当 after_commit 被触发时,您是否想知道您的记录是否是新的?重新阅读您的问题和答案,我觉得很困惑。你能改写一下或者给我们一个明确的例子吗? 如果您使用单线程服务器,您的初始解决方案确实有效。如果您不使用一个,则切换到一个,例如独角兽,这将以干净的方式解决此问题。它使编程模型变得如此简单,您将不再那么头疼(就像这个一样),并且最终速度更快。使用 +transaction_include_action?+ 并不干净,因为它是不受支持的受保护的 rails 方法,不受 rails 测试套件中的任何测试的支持。下一个版本可能没有那个方法。 @elado 我很困惑。接受的答案(迟到的)不适用于观察者(如 ches 的评论所述)。你改用回调了吗?请对您的问题进行解释。 @Kelvin,请参阅我关于如何使其与 Observers 一起工作的问题更新。 【参考方案1】:我认为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])
【讨论】:
太棒了。这是我见过的最干净的方法。在其他情况下,我使用了@charlysisto 提出的解决方案(确实有效),但这感觉更好。我会试试这个。 不要在新版本的rails上使用这个,使用以下方法:你可以这样做after_commit :method_name, on: :create
@chris-finne ,@preston-marshall 是的,你是对的。如果您在 3.1/3.2 上,那么 after_commit :do_something, :on => :create
是要走的路。但在 3.0 中,情况有所不同。 “面向未来的铁轨”有点矛盾!
最初的问题是指观察者。你仍然不能在观察者中使用:on => :create
,否则这是一种非常干净的方式来处理模型类不负责的事情,比如发送通知。必须使用回调会破坏将此类逻辑排除在模型之外的目的。
+1。很高兴知道,但我同意它不适用于观察者。【参考方案2】:
我今天了解到你可以这样做:
after_commit :do_something, :on => :create
after_commit :do_something, :on => :update
其中 do_something 是您要在某些操作上调用的回调方法。
如果您想为 update 和 create 调用相同的回调,而不是 destroy,您还可以使用:
after_commit :do_something, :if => :persisted?
确实没有很好的记录,我很难用谷歌搜索它。幸运的是,我认识一些才华横溢的人。希望对您有所帮助!
【讨论】:
这不适用于观察者,如问题描述中所述 @SujoyGupta - 不应该,因为问题是关于观察者的。【参考方案3】:您可以使用两种技术来解决。
@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
【讨论】:
【参考方案4】:基于 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
【讨论】:
为什么要对此投反对票?这段代码在我的应用程序中运行没有问题,我发现它非常有用......【参考方案5】:看一下测试代码: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)
【讨论】:
谢谢,但它不起作用。#<NoMethodError: undefined method 'after_commit' for ...>
当我在观察者中执行 after_commit(:on => :create)|record| puts "XXX"
时(它可能正在处理活动记录,但我在这里使用观察者)。
它确实适用于更高版本的 rails。你可以做after_commit :method_name, on: :create
【参考方案6】:
我用下面的代码判断是否是新记录:
previous_changes[:id] && previous_changes[:id][0].nil?
它基于新记录的默认 id 等于 nil 的想法,然后在保存时更改它。 当然 id 变化不是常见的情况,所以大多数情况下第二个条件可以省略。
【讨论】:
【参考方案7】:我很想知道为什么你不能将你的 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
【讨论】:
性能和数据库锁定。如果我使用 after_create/destroy 它会使包装事务更长,而我不需要事务。【参考方案8】:这类似于您的第一种方法,但它只使用一种方法(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
检查/映射到“同步后”两个回调方法:hackish。
【讨论】:
这行不通,因为观察者的实例是单例。意思是,@ 变量为所有记录共享。如果多个记录由同一个观察者处理,则值将不正确。这就是我创建 ID 和操作地图的原因。 是的,我明白你在这个问题中的意思,因为我可以看到它。相应地更新了答案。你从错误中学习......【参考方案9】:您可以将事件挂钩从 after_commit 更改为 after_save,以捕获所有创建和更新事件。然后你可以使用:
id_changed?
...观察者的助手。这将在创建时为真,在更新时为假。
【讨论】:
after_create
中为真,after_commit
中为假(用于创建和更新)。
已更新。使用 after_save 事件来捕获创建/更新并能够区分两者。
但是after_save
在事务内部。我需要在它之外执行代码,因此使用after_commit
。以上是关于Rails 3:如何识别观察者中的 after_commit 动作? (创建/更新/销毁)的主要内容,如果未能解决你的问题,请参考以下文章
如何在 Rails 5 上的 ruby 中添加人脸识别?
Rails 3 和 Paperclip:“'identify' 命令无法识别”