※Rails公式ドキュメントから、平易な日本語でポイントとプラスαまとめています。(公式ドキュメント: http://api.rubyonrails.org/classes/ActiveRecord/Transactions/ClassMethods.html)
Transaction - 失敗→Save前
Transactionは、複数のSQL文を囲んで、そのSQLそれぞれが成功した場合のみに、すべてのSQLの変更を反映するよというもの。典型的な例は銀行口座の例で、片方の口座からの引き落としに成功した時のみ、もう片方の口座にお金がプラスされるよ、みたいなね。
こんな感じで、Transactionはデータベースの統一性をプログラムやdbのエラーから守るもの。一緒に処理されなきゃいけない処理の塊を持ってる時は絶対に使わなきゃいけない。
例えば、以下の例ではkevinの口座からの引き落としに成功した時のみ、kenに100depositeが行くようになってる。例外(error)が時発生した場合にはROLLBACKが発生し、今までの処理をTransaction前の状態に戻してくれる。
ActiveRecord::Base.transaction do
kevin.withdrawal(100)
ken.deposit(100)
end
1クラスのTransactionで複数まとめる
TransactionはActiveRecordのクラスメソッドなので、どのActiveRecordのクラスからも同じものがTransactionが呼び出される。だからTransactionのブロック内のrecordのインスタンスが必ずしもそのtransactionのクラスに属す必要はない。
以下の例では、BalanceクラスとAccountクラスがブロック内にあるが、AccountのTransActionのみで囲われてる。もちろんこれでおk。
Account.transaction do
balance.save!
account.save!
end
ちなみに、クラスメソッドで使うことが多い気はするが、インスタンスメソッドとしてインタンスからアクセスすることもできる。
balance.transaction do
balance.save!
account.save!
end
複数databaseにまたがってtransaction監視はできない
これまでの例で、複数レコードに対して一つのtransactionでカバーできることを述べてきたが、それはdatabaseを同一のものを使っている条件下での話である。
もし、レコードごとに別のdatabaseを使っている場合、そのdatabaseをまたがる処理に関して一つのclassのtransactionではカバーしきれない。でもそれじゃ困るので、そういう時は以下のように書きましょう。
StudentクラスとCourseクラスで異なるdbを使用してる時。
両方をtransactionで囲ってあげましょう。
Student.transaction do
Course.transaction do
course.enroll(student)
student.units += course.units
end
end
両方のクラスのtransactionで囲まなきゃいけないのはかっこわるいけど、これで両方のdbに対してtransaction保証される。公式ドキュメントにこうしろと書いてあるので、おそらくこれ以上の策は現状のところないのでしょう。
ありがちなミスに注意
transactionでこのrecordの処理ミスったらROLLBACK発生させたい!ってやつにはsave!
、destroy!
みたいにビックリマーク!
をつけることをお忘れなく!!transactionはerrorに反応してROLLBACK起こすので!
some_record.save # true/falseを返す
some_record.save! # 失敗時にerrorを返す
transactionで囲んだぜって一安心した時にこちらきちんと見直しておきましょう!
以上、railsを書く上で是っていに抑えなきゃいけないtransactionのポイント3つでした。callbackなど、もっと深堀したい方は以下など参照。
参考