【Rails】transactionのrequires_newの挙動について整理する

先日、Railsのtransactionのrequires_newオプションを知りました。

techracho.bpsinc.jp

tech.smarthr.jp

記事などを見れば理解できるのですが、(非推奨の):joinableと混同して「どっちだっけ?」となるので自分なりに表にまとめて整理しました。

まとめ

No. requires_new 例外の種類 ロールバックする処理
1 false false AR:Transaction 子の処理もロールバックしない
2 true false AR:Transaction 子の処理もロールバックしない
3 false true AR:Transaction 子の処理のみロールバックする
4 true true AR:Transaction 子の処理のみロールバックする
5 false false AR:Transaction以外 親の処理までロールバックする
requires_newは無関係

<補足>

  • 検証バージョン: Rails.8.0
  • requires_newfalseがデフォルトです
  • AR::RollbackActiveRecord::Rollbackの略です
  • 「親の処理」「子の処理」は以下のイメージです
ActiveRecord::Base.transaction do
  Car.create! # ←「親の処理」と呼んでいます
  ActiveRecord::Base.transaction do
    Bike.create! # ←「子の処理」と呼んでいます
    # ここで例外が起きるイメージ
  end
end

検証用コード

上記の表No.1~No.5について、検証用のRSpecの結果を書いておきます。

No.1

it do
  expect do
    ActiveRecord::Base.transaction do
      Car.create!
      ActiveRecord::Base.transaction do
        Bike.create!
        raise ActiveRecord::Rollback
      end
    end
  end.to change(Car, :count).by(1)
    .and change(Bike, :count).by(1)
end

# ↓結果
# 1 example, 0 failures

No.2

it do
  expect do
    ActiveRecord::Base.transaction(requires_new: true) do
      Car.create!
      ActiveRecord::Base.transaction do
        Bike.create!
        raise ActiveRecord::Rollback
      end
    end
  end.to change(Car, :count).by(1)
    .and change(Bike, :count).by(1)
end

# ↓結果
# 1 example, 0 failures

No.3

it do
  expect do
    ActiveRecord::Base.transaction do
      Car.create!
      ActiveRecord::Base.transaction(requires_new: true) do
        Bike.create!
        raise ActiveRecord::Rollback
      end
    end
  end.to change(Car, :count).by(1)
    .and change(Bike, :count).by(0)
end

# ↓結果
# 1 example, 0 failures

No.4

it do
  expect do
    ActiveRecord::Base.transaction(requires_new: true) do
      Car.create!
      ActiveRecord::Base.transaction(requires_new: true) do
        Bike.create!
        raise ActiveRecord::Rollback
      end
    end
  end.to change(Car, :count).by(1)
    .and change(Bike, :count).by(0)
end

# ↓結果
# 1 example, 0 failures

No.5

it do
  expect do
    ActiveRecord::Base.transaction do
      Car.create!
      ActiveRecord::Base.transaction do
        Bike.create!
        nil.foo # NoMethodError
      end
    end
  end.to raise_error(NoMethodError)
    .and change(Car, :count).by(0)
    .and change(Bike, :count).by(0)
end

# ↓結果
# 1 example, 0 failures