web-dev-qa-db-ja.com

Rails 3:オブザーバーでafter_commitアクションを識別する方法(作成/更新/破棄)

オブザーバーがあり、after_commitコールバックを登録しています。作成または更新後に起動されたかどうかを確認するにはどうすればよいですか? item.destroyed?を要求することでアイテムが破壊されたとわかりますが、#new_record?はアイテムが保存されてから機能しません。

私はafter_create/after_updateを追加して解決し、内部で@action = :createのようなことをして@actionafter_commitを確認しましたが、オブザーバーはインスタンスはシングルトンであり、after_commitに到達する前に値をオーバーライドする場合があります。そこで、Iい方法で解決し、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が動作するようになりました。

60
elado

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])
55
tardate

今日、次のようなことができることを学びました。

after_commit :do_something, :on => :create

after_commit :do_something, :on => :update

do_somethingは、特定のアクションで呼び出すコールバックメソッドです。

pdatecreateに対して同じコールバックを呼び出したいが、destroyではない場合は、after_commit :do_something, :if => :persisted?

それは本当にうまく文書化されておらず、グーグルで苦労しました。幸いなことに、私は少数の素晴らしい人々を知っています。それが役に立てば幸い!

56
Jure Triglav

2つの手法を使用して解決できます。

  • @nathanvdaによって提案されたアプローチ、つまりcreated_atとupdated_atをチェックします。それらが同じ場合、レコードは新しく作成され、そうでない場合は更新されます。

  • モデルで仮想属性を使用する。手順は次のとおりです。

    • コード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
      
7
leenasn

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
3
lacco

テストコードを見てください: 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)
2
kenn

after_commitロジックをafter_createafter_updateに移動できなかった理由を知りたいです。後半の2つの呼び出しとafter_commitの間に発生する重要な状態の変化はありますか?

作成と更新の処理に重複するロジックがある場合、後者の2つのメソッドで3番目のメソッドを呼び出し、アクションを渡すだけで済みます。

# 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
0
Kelvin

次のコードを使用して、新しいレコードかどうかを判断します。

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

これは、新しいレコードのデフォルトIDがnilであり、保存時に変更されるという考えに基づいています。もちろん、IDの変更は一般的なケースではないため、ほとんどの場合、2番目の条件は省略できます。

0
hwo411