DelegateClass が暗黙ブロックを正しく転送するよう修正

rails/rails

Rails 7.0 の ActiveSupport::Delegation.DelegateClass が、ブロックを暗黙的に受け取るメソッドへのブロック転送を行わず、結果として呼び出し側に空の結果を返す問題を解消しました。

背景

ActiveSupport::Delegation.DelegateClass はラップしたクラスの公開メソッドごとにデリゲータメソッドを生成し、各メソッドのシグネチャは Method#parameters から自動構築されます。しかし、yield のみでブロックを受け取る(暗黙ブロック)メソッドは parameters:block エントリが存在しないため、生成されたシグネチャにブロックが含まれず、実行時にブロックが 黙って落とされ ます。

この挙動は Ruby 標準ライブラリの DelegateClass と異なり、String#scanArray#map などブロックを必須とするメソッドが期待通りに動作しないという正確性の回帰を引き起こします。コードベース全体で DelegateClass を利用するデコレータ(例: ActiveRecord の型メタデータや楽観的ロック)にも影響が波及するため、見逃しやすいが重大なバグです。

技術的な変更

activesupport/lib/active_support/delegation.rbforwards_implicit_block フラグを導入し、シグネチャが "..." 以外かつ parameters:block が無い場合に匿名ブロック & を付加して転送します。このロジックは既存のシグネチャ生成フローに最小限の分岐を追加するだけで、...(可変長引数)シグネチャは既にブロックを転送するため変更しません。

変更前:

method_def <<
  "def #{method_name}(#{signature})" <<
  "  __getobj__.#{method_name}(#{signature})"

変更後:

# Methods that yield to an implicit block do not list a `:block`
# parameter, so forward an anonymous block to them too. The `...`
# signature already forwards the block.
forwards_implicit_block = signature != "..." && parameters.none? { |type, _name| type == :block }

if forwards_implicit_block
  signature = signature.empty? ? "&" : "#{signature}, &"
end

method_def <<
  "def #{method_name}(#{signature})" <<
  "  __getobj__.#{method_name}(#{signature})"

さらに、activesupport/test/delegation_test.rb が新規追加され、暗黙ブロックを持つ Target#each_doubled 等のメソッドが正しくブロックを受け取ることを検証します。テストは 6 件追加され、修正前は 5 件が失敗またはエラーになる点をカバーしています。

設計判断

ブロック転送の欠如は シグネチャ生成時の情報不足 が原因であるため、シグネチャに匿名ブロック & を明示的に付加 する方針が採られました。これにより、DelegateClass が Ruby 標準と同等の振る舞いを示すようになり、既存コードへの影響は最小限です。signature == "..." のケースは既にブロックを転送する実装があるため、条件分岐で除外しています。

この変更は 後方互換性を維持 しつつ、ブロックを受け取るメソッド全般で正しい動作を保証します。デリゲータ生成ロジックへの侵入は数行に留められ、他の機能やパフォーマンスへの副作用は報告されていません。

まとめ

DelegateClass が暗黙的にブロックを受け取るメソッドでもブロックを正しく転送できるようになり、Rails のデコレータ層全体で標準的な Ruby 挙動と一致しました。最小限のロジック追加で既存のインターフェースを保ちつつ、正確性の回帰を防止する重要な修正です。

記事メタデータ

Generated by:
gpt-oss-120b for DiffDaily
LLM Trace:
828651c8

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

品質レビュー結果

Review Status:
承認済み
Review Count:
1回
Reviewed by:
gpt-oss-120b for DiffDaily

Review Criteria:

記事構成 ✓ PASS

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

リード文・背景・技術的変更・設計判断・まとめの5セクションが揃い、総論→各論→結論の流れが明確です。

カスタムMarkdown構文 ⚠ WARNING

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

コードブロックは正しい ````ruby:ファイルパス` 形式です。しかし PR リンクは `[PR #57634](URL)` と記述されており、要求された `[#57634](URL)` 形式と完全に一致していません。

対象読者への適合性 ✓ PASS

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

Rails/ActiveSupport の内部実装に関する専門的な記述で、エンジニア向けとして適切です。

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

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

各段落はトピックセンテンスで始まり、1段落1トピック、文数も 6 以下に収まっています。空行で区切りも適切です。

Diff内容との照合 ✓ PASS

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

記事中のコードブロックは Diff に示された変更と完全に一致し、ファイル名も合致しています。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

用語は PR 内容と合致し、誤用は見られません。

説明の技術的正確性 ✓ PASS

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

技術的な説明は PR の要点と合致し、因果関係も正しく記述されています。

事実の突合 ✓ PASS

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

すべての主張は PR の説明・Diff・テスト追加情報に裏付けられ、外部知識の付加はありません。

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

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

テスト件数の増減(6 件追加、5 件が失敗)などの数値は PR に記載された通りです。

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

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

記事タイトルは PR タイトルの内容を正確に日本語化しています。

外部知識の正確性 ✓ PASS

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

バージョンサポートやリリース予定といった外部知識は含まれていません。

時間表現の正確性 ✓ PASS

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

時間表現のずれはなく、PR の記述と合致しています。