Rails ActiveRecord before_create フィルター内のデータベース変更が false を返したときにロールバックされないようにするにはどうすればよいですか?

StackOverflow https://stackoverflow.com/questions/1597434

質問

Rails ActiveRecord モデルの 1 つに before_create フィルターを追加し、そのフィルター内でデータベースの更新を行っています。

ターゲットモデルの作成を防ぐためにフィルターから false を返す場合がありますが、 しかし それがすべての原因となっている 他の ロールバックするために (フィルター内で) データベースに加えた変更。

どうすればそれを防ぐことができますか?

アップデート #1:私の問題を説明する疑似コードは次のとおりです。

class Widget < ActiveRecord::Base
  before_create :update_instead

  def update_instead
    if some_condition?
      update_some_record_in_same_model # this is getting rolled back
      return false # don't create a new record
    else
      return true # let it continue
    end
  end
end

アップデート #2:以下にいくつかの良い回答がありますが、それぞれに欠点があります。最終的に次のように create メソッドをオーバーライドしました。

def create
  super unless update_instead? # yes I reversed the return values from above
end
役に立ちましたか?

解決

私は最近、これをしなければなりませんでした。あなたは、特にARから別の接続を要求する必要があります。そして、その接続上で、変更を実行します。作成が失敗し、ロールがトランザクションをバック場合は、この方法では、あなたのコールバックの変更は、すでに別のトランザクションでコミットされました。

上記の私の答えを無視します。あなただけ与えた例のコードは、実際に物事を明らかにします。

class Foo < ActiveRecord::Base
  before_create :update_instead

  def update_instead
    dbconn = self.class.connection_pool.checkout
    dbconn.transaction do
      dbconn.execute("update foos set name = 'updated'")
    end
    self.class.connection_pool.checkin(dbconn)
    false
  end
end


>> Foo.create(:name => 'sam')
=> #<Foo id: nil, name: "sam", created_at: nil, updated_at: nil>
>> Foo.all
=> [#<Foo id: 2, name: "updated", created_at: "2009-10-21 15:12:55", updated_at: "2009-10-21 15:12:55">]

他のヒント

を使用するフィルタでトランザクションます。

作成/保存とその破壊版を上書きしてみましたか?ActiveRecord::Base.create、ActiveRecord::Base.save、およびそれらの破壊的なバージョンはトランザクションにラップされており、コールバックと検証をトリガーするものでもあります。これをオーバーライドする場合、スーパーによって実行されたものだけがトランザクションの一部になります。事前に検証を実行する必要がある場合は、明示的に valid を呼び出してすべてを実行できます。

例:

before_create :before_create_actions_that_can_be_rolled_back

def create
  if valid? && before_create_actions_that_wont_be_rolled_back
    super
  end
end

def before_create_actions_that_wont_be_rolled_back
 # exactly what it sounds like
end

def before_create_actions_that_can_be_rolled_back
 # exactly what it sounds like
end

警告:これらの変更により、メソッドは次の順序で呼び出されます。

  1. 検証前 (on_create)
  2. 検証
  3. 検証後 (on_create)
  4. before_create_actions_that_wont_be_rolled_back
  5. 検証前 (on_create)
  6. 検証
  7. 検証後 (on_create)
  8. コールバックを保存する前に
  9. コールバックを作成する前に
  10. 記録が作成される
  11. コールバック作成後
  12. コールバックを保存した後

いずれかの検証が失敗した場合、または手順 5 ~ 12 でコールバックが false を返した場合、データベースは手順 5 より前の状態にロールバックされます。

有効なら?失敗するか、before_create_actions_that_wont_be_rolled_back が失敗すると、チェーン全体が停止します。

これらの変更は?代わりにafter_createに行うことができる。

これは、Rich Cavanaugh の回答と同じ概念を使用しています。フィルターの動作が明確になるように親モデルを追加しました。重要なのは、スレッドと別の接続の自動チェックアウト/チェックインを使用することです。注記:実行する同時スレッドの数に応じて、接続仕様で :pool 値が少なくとも 2 に設定されていることを確認する必要があります。デフォルトは5になっていると思います。

class Gadget < ActiveRecord::Base
  has_many :widgets
end

class Widget < ActiveRecord::Base
  belongs_to :gadget
  before_create :update_instead

  def update_some_record_in_same_model
    # the thread forces a new connection to be checked out
    Thread.new do
      ActiveRecord::Base.connection_pool.with_connection do |conn|
        # try this part without the 2 surrounding blocks and it will be rolled back
        gadget.touch_count += 1
        gadget.save!
      end
    end.join
  end
  def some_condition?
    true
  end

  def update_instead
    if some_condition?
      update_some_record_in_same_model # this is getting rolled back
      p [:touch_count_in_filter, gadget.reload.touch_count]
      return false # don't create a new record
    else
      return true # let it continue
    end
  end
end

テスト:

  g = Gadget.create(:name => 'g1')
  puts "before:"
  p [:touch_count, g.reload.touch_count]
  p [:widget_count, Widget.count]

  g.widgets.create(:name => 'w1')
  puts "after:"
  # Success means the count stays incremented
  p [:touch_count, g.reload.touch_count]
  p [:widget_count, Widget.count]

参考文献: http://bibwild.wordpress.com/2011/11/14/multi-threading-in-rails-activerecord-3-0-3-1/

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top