Hisakeyのブログ

気になったことについて、調べながら投稿するブログです。

AASMから学ぶ、Railsのtransaction処理について

はじめに

トランザクション処理とは

トランザクションの基本について

トランザクションとは、一連のデータベース操作をまとめて実行し、すべてが成功した場合にのみ変更を確定させる(コミット)機能です。もし途中でエラーが発生した場合は、すべての変更をキャンセル(ロールバック)して、データベースを元の状態に戻します。

たとえば、次のようにトランザクションを使うと、エラーが発生した場合にデータベース操作が全て取り消されます。

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が発生し、トランザクション内のuseraccountの作成はどちらもロールバックされます。

AASMとは?

次に、AASM(acts_as_state_machine)の紹介です。AASMは、Railsアプリケーションにおける状態管理を簡単にするためのgemです。これを使えば、例えば注文の「受付中」から「発送済み」への状態遷移を定義することができます。

github.com

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の状態遷移は常に新しいトランザクションで実行され、外側のトランザクションロールバックされても、状態遷移自体はコミットされたままになります。

api.rubyonrails.org

このため、次のような現象が発生します:

  1. 外側のトランザクションでデータベース操作がロールバックされる。
  2. しかし、AASMによる状態遷移は別の新しいトランザクションで行われるため、ロールバックされない。

これが、AASMを使用している際に、ロールバックが発生しても状態が変更されたままになる理由です。

結論

AASMから、トランザクションについて詳しく学ぶことができました! AASMは、非常に便利なgemですが、デフォルト設定のままでは、ロールバックしても状態が変化する場合があるため、使い方をちゃんと勉強しないとだめだなーと思いました!

最後まで、読んでいただきありがとうございました。