`enqueue_after_transaction_commit` のバージョン変遷に対応したテスト修正
Rails 7.1から8.2にかけて enqueue_after_transaction_commit 設定が複数回変更されたことを受け、Solid Queueのテストコードをバージョン横断で正しく動作するよう修正しました。
背景
enqueue_after_transaction_commit は、トランザクション内でエンキューされたジョブをトランザクションのコミット後に遅延実行するかどうかを制御する設定です。この設定はRailsのバージョンごとに異なる挙動を示してきました(rails/rails #55788 参照)。
各Railsバージョンにおける挙動は次の通りです:
- Rails 7.1: メソッド自体が存在せず、遅延エンキューは行われない
-
Rails 7.2:
:default(アダプターの実装に委譲。Solid Queueではtrueを返す) -
Rails 8.0〜8.1:
false(遅延エンキューなし、設定は非推奨化) -
Rails 8.2:
true(遅延エンキューがデフォルトで有効化)
このような変遷により、従来のテストコードは「遅延エンキューが問題となるのはRails 7.2のみ」という前提で書かれていたため、Rails 8.2で再び true になったことで動作が崩れていました。
技術的な変更
スキップ条件のハードコードを廃止し、実行時の設定値を検査するよう変更されました。これにより、どのRailsバージョンでも正しくスキップ判定が行われます。
test/integration/concurrency_controls_test.rb および test/models/solid_queue/job_test.rb の2箇所で、バージョン番号による判定から設定値による判定へ変更されています。
変更前:
# Doesn't work with enqueue_after_transaction_commit? true on SolidQueueAdapter, but only Rails 7.2 uses this
skip if Rails::VERSION::MAJOR == 7 && Rails::VERSION::MINOR == 2
変更後:
# Doesn't work when enqueue_after_transaction_commit is enabled
skip if ActiveJob::Base.respond_to?(:enqueue_after_transaction_commit) &&
[ true, :default ].include?(ActiveJob::Base.enqueue_after_transaction_commit)
respond_to? でメソッドの存在確認(Rails 7.1対応)を行いつつ、値が true または :default の場合にスキップする構造になっています。:default を含めているのは、Solid QueueのアダプターがデフォルトでJOBの遅延エンキューを有効にするRails 7.2の挙動に対応するためです。
加えて、test/dummy/config/initializers/sqlite3.rb ではSQLiteのimmediate transactionに関するバージョン分岐も整理されています。
変更前:
def begin_db_transaction
if Rails.gem_version < Gem::Version.new("8.2")
log("begin immediate transaction", "TRANSACTION") do
with_raw_connection(allow_retry: true, materialize_transactions: false) do |conn|
conn.transaction(:immediate)
end
end
end
end
変更後:
def begin_db_transaction
log("begin immediate transaction", "TRANSACTION") do
with_raw_connection(allow_retry: true, materialize_transactions: false) do |conn|
conn.transaction(:immediate)
end
end
end
モジュールのロード時の条件分岐も変更されています。
変更前:
ActiveRecord::ConnectionAdapters::SQLite3Adapter.prepend SqliteImmediateTransactions
変更後:
# Rails 8.0+ has immediate transactions built-in
if Rails::VERSION::MAJOR < 8
ActiveRecord::ConnectionAdapters::SQLite3Adapter.prepend SqliteImmediateTransactions
end
Rails 8.0以降はimmediate transactionが組み込まれているため、SqliteImmediateTransactions モジュールのprepend自体をRails 7系のみに限定しています。これによりメソッド内のバージョン分岐が不要になり、コードがシンプルになっています。
また、.github/workflows/main.yml にRuby 3.2 + rails_main の組み合わせがCIマトリクスに追加されており、Rails 8.2系での継続的な検証が行われます。
タイミング依存のテストに対しては、sleep(0.1) が2箇所追加され、並行処理における競合状態を緩和しています。
設計判断
バージョン番号ではなく設定値を直接検査する方式 が採用されました。
バージョン番号による条件分岐(Rails::VERSION::MAJOR == 7 && Rails::VERSION::MINOR == 2)は、将来のバージョンで同じ挙動が再導入された際に漏れが生じるという脆弱性を持ちます。ActiveJob::Base.enqueue_after_transaction_commit の値を直接参照することで、実際の動作に基づいた判定が可能になり、Rails 8.2での true 再導入のような変化にも自動的に追従できます。
SQLiteのimmediate transaction設定においては、モジュールのprepend自体を条件付きにすることで、メソッドの実装をシンプルに保つ方針が選ばれています。
まとめ
バージョン番号のハードコードから設定値の動的検査への移行により、Solid Queueのテストスイートは将来のRailsバージョンでの挙動変化に対してより堅牢になりました。enqueue_after_transaction_commit のような設定が今後も変更される可能性がある中で、「バージョンではなく振る舞いで判定する」アプローチはメンテナンスコストを下げる実践的な設計といえます。