Ruby on rails Rails 3:如何在观察者中识别提交后的操作?(创建/更新/销毁)

Ruby on rails Rails 3:如何在观察者中识别提交后的操作?(创建/更新/销毁),ruby-on-rails,ruby-on-rails-3,transactions,observer-pattern,Ruby On Rails,Ruby On Rails 3,Transactions,Observer Pattern,我有一个观察者,我在提交后注册一个回调。 如何判断它是在创建还是更新后触发的? 我可以通过询问item.destromed?来判断某个项目是否已被销毁,但\new\u record?自项目保存后不起作用 我打算在创建后添加,,在更新后添加,,然后在内部执行类似于@action=:create,并在提交后的处检查@action,但observer实例似乎是一个单例,我可能只是在它到达提交后的之前重写一个值。因此,我用一种更丑陋的方式解决了它,在创建/更新后基于item.id将动作存储在一个映射中,

我有一个观察者,我在提交后注册一个
回调。
如何判断它是在创建还是更新后触发的?
我可以通过询问
item.destromed?
来判断某个项目是否已被销毁,但
\new\u record?
自项目保存后不起作用

我打算在创建后添加
,在更新后添加
,然后在内部执行类似于
@action=:create
,并在提交后的
处检查
@action
,但observer实例似乎是一个单例,我可能只是在它到达提交后的
之前重写一个值。因此,我用一种更丑陋的方式解决了它,在创建/更新后基于item.id将动作存储在一个映射中,并在提交后检查其值。真难看

还有别的办法吗

更新 正如@tardate所说,
事务包含动作?
是一个很好的指示,尽管它是一个私有方法,在观测者中,应该使用
\send
访问它

class ProductScoreObserver < ActiveRecord::Observer
  observe :product

  def after_commit(product)
    if product.send(:transaction_include_action?, :destroy)
      ...
class ProductScoreObserver
不幸的是,
:on
选项在观察者中不起作用

只需确保您测试了大量的观察者(如果您使用事务性装置,请在提交后查找
test\u
gem),这样当您升级到新的Rails版本时,您就会知道它是否仍然有效

(根据第3.2.9节进行测试)

更新2
我现在使用ActiveSupport::Concern和
after_commit:blah,on::create
代替了Observer。您可以将事件挂钩从after_commit更改为after_save,以捕获所有创建和更新事件。然后,您可以使用:

id_changed?

…观察者中的助手。创建时为真,更新时为假。

查看测试代码:

你可以在那里找到:

after_commit(:on => :create)
after_commit(:on => :update)
after_commit(:on => :destroy)


这类似于您的第一种方法,但它只使用一种方法(在保存或验证真正安全之前),我不明白为什么这会覆盖任何值

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_记录可以在项目_2触发_提交后从项目_1获取其值。为了克服这个问题,应该有一个
item.id
检查/映射到“post synchornize”两个回调方法:hackish

我想这就是你想要的。它给出了正在进行的具体交易的可靠指示(在3.0.8中验证)

形式上,它确定事务是否包含以下操作:创建、:更新或:销毁。用于筛选回调

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

您可以使用两种方法来解决此问题

  • @nathanvda建议的方法,即检查在处创建的_和在处更新的_。如果它们相同,则记录是新创建的,否则为更新

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

    • 在模型中添加一个字段,其中包含新创建的代码
      attr\u访问器
    • 在创建之前的
      和更新回调之前的
      中更新相同内容

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

基于leenasn的想法,我创建了一些模块,可以在更新时提交后使用
,在创建时提交后使用

用法:

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
class用户
我今天了解到,您可以执行以下操作:

after_commit :do_something, :on => :create

after_commit :do_something, :on => :update
其中,do\u something是您希望在某些操作上调用的回调方法

如果要为更新创建调用相同的回调,而不是销毁,还可以使用:
提交后:做某事,:if=>:持久化?


它确实没有很好的文档记录,我很难用谷歌搜索它。幸运的是,我认识一些才华横溢的人。希望有帮助

我很想知道为什么您不能将提交后的
逻辑移到创建后的
和更新后的
。在后两次调用和提交后的
之间是否发生了一些重要的状态更改

如果您的创建和更新处理有一些重叠的逻辑,您可以让后两个方法调用第三个方法,传递操作:

# 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
#提示:在ruby 1.9上,您可以使用uu callee_uuu获取当前方法名,因此不必硬编码:创建和:更新。
类WidgetObserver
如果仍然使用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
类WidgetObserver# 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
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
previous_changes[:id] && previous_changes[:id][0].nil?