`run_after_transaction_callbacks_in_order_defined` 有効時に `prepend: true` が無視されるバグを修正

rails/rails

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_commitafter_update_commitafter_destroy_commitafter_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! に渡す形式へ整理することで、ユーザーのオプション指定が正しく尊重されるようになりました。

記事メタデータ

Generated by:
Claude Sonnet 4.6 for DiffDaily
LLM Trace:
f1691420

この記事はAIによって自動生成されています。内容の正確性については、必ずソースコードやPRを確認してください。

品質レビュー結果

Review Status:
承認済み
Review Count:
1回
Reviewed by:
Gemini 2.5 Pro for DiffDaily

Review Criteria:

記事構成 ✓ PASS

Title, Context, Technical Detailの存在と明確さ

リード文(総論)→背景・技術詳細・設計判断(各論)→まとめ(結論)という「総論→各論→結論」の3部構成が明確で、非常に分かりやすいです。

カスタムMarkdown構文 ✓ PASS

シンタックスハイライト・GitHubリンク記法の正確性

ファイル名付きシンタックスハイライト、PR番号・Issue番号のリンク記法がすべて正しく使用されています。

対象読者への適合性 ✓ PASS

エンジニア向けの適切な技術レベルと表現

専門用語が適切に使用されており、過度な説明がなく、専門知識を持つエンジニアという対象読者に適合しています。

パラグラフ・ライティング ✓ PASS

トピックセンテンス・1段落1トピック・段落長

各セクションが「総論→各論」で構成され、各段落はトピックセンテンスで始まり、1段落1トピックの原則が守られており、可読性が非常に高いです。

Diff内容との照合 ✓ PASS

コードブロックとDiff内容の一致

記事内で引用されているコードブロックは、提供されたDiff情報と正確に一致しています。ファイルパスの指定も正確です。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

「リグレッション」「コールバック」「prepend」などの技術用語が文脈に応じて正確に使用されています。

説明の技術的正確性 ✓ PASS

技術的主張の正確性と論理性

変更の理由や影響についての技術的な説明は、Diff内容とPRの背景から論理的に導かれており、正確です。

事実の突合 ⚠ WARNING

PR情報による主張の裏付け(ハルシネーション検出)

「Rails 7.1 以前は...」「ドキュメントにもこの制限は記載されておらず...」という記述は、PR自体ではなく関連Issueから得られる情報であり、厳密にはPR情報に基づいた事実の範囲を超えています。

数値・固有名詞の確認 ✓ PASS

PR番号・コミットID・バージョン等の正確性

PR番号(#51244)、関連Issue番号(#50118, #46992)などの数値や固有名詞はすべて正確です。

タイトル・説明との一致 ✓ PASS

記事タイトル・説明とPR内容の一致

記事のタイトルはPRの主題を正確に要約しており、内容との一貫性が保たれています。

外部知識の正確性 ⚠ WARNING

PRに記載のない外部知識(LTS、サポート状況など)の不使用

「Rails 7.1 以前」というバージョンに関する言及は、PRに明記されていない外部知識に該当します。ただし、これは関連Issueを追えばわかる情報であり、捏造ではありません。

時間表現の正確性 ✓ PASS

時間表現がPR情報と一致しているか

「導入された」「生じていました」などの時間表現は、PRの文脈と一致しており正確です。