#なぜTransactionが必要なのか
Transationの目的は、あるいコードブロックにあるSQL文の変更を、全部成功することを守るための存在である。Transactionにより、データの統一性を保ことができる。銀行などの受け入れと引き出しの処理には必要でしょう。二つの処理の中一つが失敗すると、コードブロークにあるSQL処理を全部ロールバックされるのが、Transactionの特徴である。
ActiveRecord::Base.transaction do
david.withdrawal(100)
mary.deposit(100)
end
#Transationのロールバックが発火条件
Railsでは、ロールバックが発火するには、「例外」が必要である。これがTransactionを使うときのもっとも重要なことである。
例えば、Railsでは #update_attribute
は例外を発火せずに、false
に返すとなる。そのため、#update_attribute
を使うには、結果を見て、例外をスローする必要がある。Railsではびっくりマーク!
がついているメソッドは、失敗したら例外をスローすると意図するので、transactionを使うときは、save
ではなくsave!
、destroy
ではなくdestroy!
を使うべきでしょう。
例外が発生するときは、transactionがまず受け取って、ロールバックを行う。ロールバックが終わった後は、例外がそのまま外にスローされるため、アプリケーション側もその例外を対応する必要がある。
例外を使わずに、Transactionをロールバックさせたい場合は、ActiveRecord::Rollback
を使えばよい。ActiveRecord::Rollback
は外側にスローされないため、アプリケーション側は対応しなくてもいいという利点がある。
#いつNested Transactionを使うべき
Nested Transactionの例ですが:
User.transaction do
User.create(username: 'Kotori')
User.transaction do
User.create(username: 'Nemu')
raise ActiveRecord::Rollback
end
end
Nested Transactionを使うときの最も重要な注意点は、一番中の処理がActiveRecord::Rollback
が投げても、子transactionで受け取った後広められず、親Transactionは受け取れないことである。広めたいときは、子transactionに :require_new => true
を設定する必要がある。
じゃいつNested Transactionを使うべきかの話ですが、マルチデータベースをマルチモデルで使うときは、メソッドをNested Transctionの中で囲まれる必要がある:
Client.transaction do
Product.transaction do
product.buy(@quantity)
client.update_attributes!(:sales_count => @sales_count + 1)
end
end
#Transactionsでのコールバック
もし#save
とdestroy
がTransactionで使う場合、#after_save
もTransactionの中で実行されてしまう。そのため
Transactionの外で実行したい場合は、#after_commit
と#after_rollback
で実装してください。
#Transactionで細かいの注意点
Transactionの中で ActiveRecord::Invalid
をしない方がよい。なぜなら、Postgresではその例外をキャッチしたら、囲まれるTransactionもロールバックされてしまう振る舞いがある。
#結論
Transactionを使うときのアンチパータン
- シングルレコードを更新するときにTransaction使う
- Nested Transactionを使いすぎる
- Transactionにある処理がどうしてもロールバックを発火できない
- ControllerでTransactionを使う
#参考資料
http://markdaggett.com/blog/2011/12/01/transactions-in-rails/