はじめに
トランザクション処理とは
トランザクションの基本について
トランザクションとは、一連のデータベース操作をまとめて実行し、すべてが成功した場合にのみ変更を確定させる(コミット)機能です。もし途中でエラーが発生した場合は、すべての変更をキャンセル(ロールバック)して、データベースを元の状態に戻します。
たとえば、次のようにトランザクションを使うと、エラーが発生した場合にデータベース操作が全て取り消されます。
ActiveRecord::Base.transaction do user = User.create(name: "John") account = Account.create(user: user, balance: 100) raise ActiveRecord::Rollback if account.balance < 0 end
このコードでは、account.balance
が負の値であればActiveRecord::Rollback
が発生し、トランザクション内のuser
とaccount
の作成はどちらもロールバックされます。
AASMとは?
次に、AASM(acts_as_state_machine)の紹介です。AASMは、Railsアプリケーションにおける状態管理を簡単にするためのgemです。これを使えば、例えば注文の「受付中」から「発送済み」への状態遷移を定義することができます。
class Order < ApplicationRecord include AASM aasm do state :pending, initial: true state :processed state :shipped event :process do transitions from: :pending, to: :processed end event :ship do transitions from: :processed, to: :shipped end end end
下記のように使えます
order = Order.create order.process! # 状態を "pending" から "processed" に変更 order.ship! # 状態を "processed" から "shipped" に変更
このように、AASMを使うことで、状態ごとに特定の処理や条件を指定できるので便利です。
AASMをトランザクション内で使うとどうなるか
トランザクション内でAASMの状態を変更すると?
トランザクション内でAASMを使用して以下のように状態を変更すると、
ActiveRecord::Base.transaction do order = Order.create order.ship! # AASMによる状態遷移 raise ActiveRecord::Rollback end
このコードは、Order
の状態をpending
からshipped
に変更し、その後でActiveRecord::Rollback
を発生させています。一般的には、ロールバックが発生すると、データベース内の変更は元に戻るはずです。しかし、ここで問題になるのが、AASMによる状態遷移はロールバックされないという点です。
orderの状態は、shipの状態になっています。
なぜロールバックしてもAASMの状態は変化しているのか?
この挙動は、AASMがデフォルトでネストされたトランザクション(nested transactions)を使用していることに起因します。具体的には、AASMは状態遷移を行う際に、transaction(requires_new: true)
を使用しています。これにより、AASMの状態遷移は常に新しいトランザクションで実行され、外側のトランザクションがロールバックされても、状態遷移自体はコミットされたままになります。
このため、次のような現象が発生します:
これが、AASMを使用している際に、ロールバックが発生しても状態が変更されたままになる理由です。
結論
AASMから、トランザクションについて詳しく学ぶことができました! AASMは、非常に便利なgemですが、デフォルト設定のままでは、ロールバックしても状態が変化する場合があるため、使い方をちゃんと勉強しないとだめだなーと思いました!
最後まで、読んでいただきありがとうございました。