Railsでトランザクションを使いたい方向けに実例を踏まえてわかりやすく解説をいたします。
- RailsにおけるTransaction
- 例外が発生すればロールバックされる
- transactionブロック内でrescueするとロールバックされないので注意!
- ロールバック後も処理を継続したい場合はどうする?
RailsにおけるTransaction
Railsでトランザクションを使用する場合は、トランザクションの範囲をActiveRecord::Base.transactionブロックで囲む必要があります。
ActiveRecord::Base.transaction do # ここに処理を書く end
transactionブロック内で例外が発生した場合、ブロック内で行われたデータベース更新処理が全てロールバックされます。
Railsでtransactionを機能されるためには例外を発生させる必要があるのです。
例外が発生すればロールバックされる
例外が発生すればロールバックされますので、例外を意識して実装する必要があります。
以下のサンプルコードをご覧ください。
def some_method # 省略... ActiveRecord::Base.transaction do user1.save # 実行される raise 'error' # 例外が発生してuser1.saveの処理がロールバックされる user2.save # 実行されない end foo # 実行されない rescue => e bar # ロールバック後に実行される end
raise 'error'
が実行されるとuser1.save
で更新された処理はロールバックされます。
user2.save
はそもそも実行されません。
例外が発生した時点でfoo
も実行されないことになります。
ただし、rescue句に記述されたbar
はロールバック後に実行されます。
transactionブロック内でrescueするとロールバックされないので注意!
よくある間違いに、transactionブロック内でrescue句を記述してしまうというのがあります。このように実装してしまうとロールバックされませんので注意が必要です。
例えば下記を実行した場合、ロールバックされません。
def some_method # 省略... ActiveRecord::Base.transaction do # BEGIN user1.save raise 'error' user2.save rescue => e bar # 実行されるがロールバックは発動しない end end
なぜこのようになるかというと、Rails本体に実装されているtransactionメソッドでは、与えられたtransactionブロックに対して例外が発生した場合に、rescueしてロールバックするという処理が実装されています。しかし、transactionブロック内で直接rescue句を記述してしまうとそれが実行されなくなってしまうのです。(詳細は下記コードを参照)
rails/transaction.rb at 291a3d2ef29a3842d1156ada7526f4ee60dd2b59 · rails/rails · GitHub
ロールバック後も処理を継続したい場合はどうする?
ときにはロールバック後にも処理を継続したいケースがあるかと思います。 例えば、ロールバック後に処理が失敗したことを特定のテーブルに書き出すといったケースです。
その場合は、ロールバックする処理自体をメソッド化して例外を握りつぶせばロールバック後も処理を継続することが出来ます。
def do_transaction ActiveRecord::Base.transaction do user1.save raise 'error' end rescue StandardError false end def fuga # 省略... if do_transaction # 成功時の処理 foo else # ロールバック後の処理 bar end end