`run_after_transaction_callbacks_in_order_defined` 有効時に `prepend: true` が無視されるバグを修正
config.active_record.run_after_transaction_callbacks_in_order_defined = true が設定された環境で、after_commit / after_rollback コールバックに渡した prepend: true オプションが無視される問題を修正しました。これにより、定義順実行モードを使いつつも、特定のコールバックだけを先頭に割り込ませる制御が可能になります。
背景
#46992 で導入された run_after_transaction_callbacks_in_order_defined 設定は、after_commit コールバックの実行順序をモデルへの定義順に統一するものでした。しかしこの実装では、after_commit 等を登録する際に prepend_option を問答無用で上書きしていたため、ユーザーが prepend: true を明示しても効果がなくなるというリグレッションが生じていました。
#50118 はこの問題を報告したIssueです。Rails 7.1 以前は prepend: true でコールバックの先頭挿入が可能でしたが、run_after_transaction_callbacks_in_order_defined を有効にした途端にそのオプションが静かに無視されるようになっていました。ドキュメントにもこの制限は記載されておらず、利用者が気づきにくい状態でした。
技術的な変更
問題の根本は、set_options_for_callbacks! の呼び出し側が prepend_option を強制マージしていた点にあります。今回の修正では、その強制マージを呼び出し側から取り除き、set_options_for_callbacks! の内部ロジックへ責務を移しました。
変更前:
def after_commit(*args, &block)
set_options_for_callbacks!(args, prepend_option)
set_callback(:commit, :after, *args, &block)
end
def after_save_commit(*args, &block)
set_options_for_callbacks!(args, on: [ :create, :update ], **prepend_option)
set_callback(:commit, :after, *args, &block)
end
変更後:
def after_commit(*args, &block)
set_options_for_callbacks!(:after, args)
set_callback(:commit, :after, *args, &block)
end
def after_save_commit(*args, &block)
set_options_for_callbacks!(:after, args, on: [ :create, :update ])
set_callback(:commit, :after, *args, &block)
end
after_create_commit・after_update_commit・after_destroy_commit・after_rollback も同様に変更されています。また before_commit の呼び出しも :before を第1引数として渡す形式に統一されました。
テストでは prepend: true を指定した2つのコールバックを追加し、期待される実行順序を検証しています。run_after_transaction_callbacks_in_order_defined = true の場合、定義順に従いつつ prepend: true なコールバックが適切に先頭へ割り込むことを確認しています。
# 期待される実行順序(setting = true の場合)
assert_equal [1, 2, "prepend 2", "prepend 1", 3, 4, "save", "create"], topic.history
prepend: true で登録された2つのコールバックは、通常の定義順コールバック(1, 2)の後ろに来るのではなく、その直後に後入れ先出しで挿入される("prepend 2" → "prepend 1" の順)ことがわかります。
設計判断
コールバック種別(:before / :after)を set_options_for_callbacks! の第1引数で受け取る形式 に変更することで、メソッドが種別に応じた prepend 処理を自律的に判断できるようになりました。
変更前の設計では、呼び出し側が prepend_option を解決してから渡していたため、ユーザーの意図した prepend: 指定が上書きされる構造的な問題がありました。種別を渡す形式にしたことで、after 系コールバックにおける prepend の扱い(定義順モード時は内部でのみ制御する)を一か所に集約できます。
この変更は set_options_for_callbacks! の内部実装への責務移転であり、各コールバック定義メソッドのシグネチャや外部API(after_commit 等)は変わっていません。
まとめ
本PRは、run_after_transaction_callbacks_in_order_defined 導入時に意図せず失われた prepend: true の機能を回復する修正です。呼び出し側で prepend_option を強制上書きしていた設計を改め、種別情報を set_options_for_callbacks! に渡す形式へ整理することで、ユーザーのオプション指定が正しく尊重されるようになりました。