Rails ActiveRecord before_create フィルター内のデータベース変更が false を返したときにロールバックされないようにするにはどうすればよいですか?
-
22-09-2019 - |
質問
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
警告:これらの変更により、メソッドは次の順序で呼び出されます。
- 検証前 (on_create)
- 検証
- 検証後 (on_create)
- before_create_actions_that_wont_be_rolled_back
- 検証前 (on_create)
- 検証
- 検証後 (on_create)
- コールバックを保存する前に
- コールバックを作成する前に
- 記録が作成される
- コールバック作成後
- コールバックを保存した後
いずれかの検証が失敗した場合、または手順 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/