57

私はオブザーバーを持っており、after_commitコールバックを登録しています。 作成または更新後にそのファイルが解凍されたかどうかを確認するにはどうすればよいですか? アイテムが破棄されたとは、item.destroyed?と尋ねると表示されますが、アイテムが保存されてから#new_record?は機能しません。Rails 3:オブザーバーでafter_commitアクションを特定する方法は? (作成/更新/破棄)

私はafter_create/after_updateを追加することによってそれを解決し、内部@action = :createような何かをしてafter_commit@actionをチェックするつもりだったが、オブザーバーインスタンスがシングルトンであり、それはに到達する前に、私はちょうど値を上書きする可能性があると思われますafter_commit。だから私は醜いやり方でそれを解決し、after_create/updateのitem.idに基づいてマップにアクションを格納し、after_commitの値をチェックします。本当に醜い。

他の方法はありますか?それはプライベートな方法だし、観測者にそれが#sendでアクセスする必要があるものの

更新

@tardateが言ったように、transaction_include_action?は、良い兆候です。

class ProductScoreObserver < ActiveRecord::Observer 
    observe :product 

    def after_commit(product) 
    if product.send(:transaction_include_action?, :destroy) 
     ... 

残念ながら、:onオプションはオブザーバーでは機能しません。

observerの地獄をテストしてください(use_transactional_fixturesを使用している場合はtest_after_commit gemを探してください)ので、新しいRailsバージョンにアップグレードしても動作するかどうかを知ることができます。 (3.2.9でテスト)

Updateは、代わりにオブザーバーの2

は、私が今activesupportの::懸念して、そこafter_commit :blah, on: :create作品を使用しています。

+0

after_commitが実行されたときにレコードが新しいかどうかを確認しようとしていますか?あなたの質問と答えを再度読んで、私はそれが混乱していると思う。言い換えるか、わかりやすい例を教えてください。 – charlysisto

+0

シングルスレッドサーバーを使用している場合、初期のソリューションは機能します。あなたが使用していない場合は、この問題をきれいに解決するユニコーンなどのものに切り替えます。これはプログラミングモデルをはるかに容易にし、頭痛(これはこのようなもの)が少なくなり、最終的には速くなります。 + transaction_include_action?+を使用すると、レールテストスイートのテストでサポートされていない、サポートされていない保護されたrailsメソッドなので、クリーンではありません。次のバージョンにはその方法がないかもしれません。 –

+0

@elado私は混乱しています。受け入れられた答え(tardate's)は、オブザーバー(チェスのコメントに記されているように)ではうまくいきません。代わりにコールバックを使用するように切り替えましたか?あなたの質問に説明を追加してください。 – Kelvin

答えて

50

私は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で問題を解決しようとしている人のために4

ため

更新は、このメソッドは廃止され、あなたは、アクションのarrayを受け入れtransaction_include_any_action?を使用する必要があります。

使用例:

transaction_include_any_action?([:create]) 
+2

素晴らしい。これは私が見たこれを行う最もクリーンな方法です。他のケースでは、私は@charlysisto(これはうまくいく)によって提出された解決策を使用しましたが、これはより良い感じです。私はこれを試しています。 –

+0

これらのメソッドはどちらも保護されているので注意してください。オブザーバで呼び出す場合は、問題のメソッドでモデルオブジェクトに対して ':send'を呼び出さなければなりません。 – nirvdrum

+3

新しいバージョンのレールでこれを使用しないでください。以下のメソッドを使用してください: 'after_commit:method_name、on::create' –

-4

イベントフックをafter_commitからafter_saveに変更して、すべての作成イベントと更新イベントを取得できます。次に、使用することができます:

id_changed? 

...オブザーバーのヘルパー。これは、作成時にはtrueになり、更新時にはfalseになります。

+2

'after_create'ではtrueですが、' after_commit'ではfalseです(作成と更新の両方)。 – elado

+0

更新されました。 after_saveイベントを使用して、作成/更新の両方を捕捉し、2つを区別できるようにします。 – Winfield

+1

しかし、 'after_save'はトランザクション内にあります。私はそれの外側でコードを実行する必要があるので、 'after_commit'を使用します。 – elado

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) 
+0

ありがとうございますが、動作しません。 '#' after_commit(:on =>:create){| record |オブザーバーに "XXX"}を入れます(これはアクティブレコードで動作しているかもしれませんが、私はここでオブザーバーを使用します)。 – elado

+2

これは、それ以降のバージョンのレールでは機能します。 'after_commit:method_name、on::create'を実行することができます –

-1

をこれがあなたの第一のアプローチに似ていますが、それは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がシングルトンであるので、それはインスタンスを1つだけ持って動作しません。したがって、2つのItemが同時に保存された場合、@new_recordはitem_1から値を取り、after_commitはitem_2によってトリガーされます。この問題を克服するには、item.idチェック/マッピングが2つのコールバックメソッドを「ポスト同期化」する必要があります:hackish。

+0

これは、オブザーバのインスタンスがシングルトンなので動作しません。意味、@変数はすべてのレコードで共有されます。複数のレコードが同じオブザーバによって処理される場合、値は正しくありません。だから私はIDと行動のマップを作成しました。 – elado

+0

ええ、私はあなたが今それを見ることができるということを意味していることを理解しています。それに応じて答えを更新しました。あなたの間違いで学ぶ... – charlysisto

7

次の2つの手法を用いて解決することができます。

  • @nathanvdaが提案するアプローチは、created_atとupdated_atをチェックします。それらが同じであれば、そのレコードは新しく作成され、そうでなければ更新されます。

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

    • は、leenasn考えに基づきbefore_createbefore_update callbacks
3

def before_create (record) 
    record.newly_created = true 
end 

def before_update (record) 
    record.newly_created = false 
end 

ようにコード attr_accessor newly_created
  • アップデート同じでモデルにフィールドを追加します。 after_commit_on_updateafter_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 
    
  • +0

    これはなぜdownvoteですか?このコードは、私のアプリで問題なく動作していますが、私はそれが非常に便利だと思っています... – lacco

    53

    私はあなたがこのような何かを行うことができますことを、今日学んだ:do_somethingは、あなたが特定のアクションに呼び出したいコールバックメソッドである

    after_commit :do_something, :on => :create 
    
    after_commit :do_something, :on => :update 
    

    あなたがを破壊更新に同じコールバックを呼び出し、を作成しますが、しないようにしたい場合は、あなたも使用することができます。 after_commit :do_something, :if => :persisted?

    それは本当によく文書化されていないと私は苦労グーグルがありましたそれ。幸いにも、私はいくつかの素晴らしい人を知っています。それが役に立てば幸い!

    +0

    レールでの作業を確認してください。3.1.0 – sai

    +2

    これは受け入れられる回答である必要があります。 –

    +6

    質問の説明に記載されているように、これはオブザーバーでは機能しません。 –

    0

    after_commitロジックをafter_createafter_updateに移動できなかった理由がわかりました。後者の2つの通話とafter_commitの間で起こる重要な状態変化がありますか?

    あなたの作成および更新処理は、いくつかの重複ロジックを持っている場合は、アクションに渡し、後者の2つの方法は、第三の方法を呼び出すだけかもしれない:

    # 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 
    
    +2

    パフォーマンスとDBロック。 after_create/destroyを使用すると、トランザクションを必要としないもので、ラッピングトランザクションを長くすることができます。 – elado

    -1

    は、私は新しいレコードであるかどうかを判断するために、次のコードを使用します。

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

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

    関連する問題