行動すれば次の現実

ほどよくモダンなシステム開発を目指しています。メインテーマは生産性、Ruby、Javascriptです。

最低限知っておきたい!Railsのトランザクション実装例

Railsでトランザクションを使いたい方向けに実例を踏まえてわかりやすく解説をいたします。

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