CPKモデルでの `eager_load` + `select` 併用時に不正なSELECT句が生成されるバグを修正

rails/rails

Composite Primary Key(CPK)を持つモデルに対して eager_loadselect を同時に使用すると、SQLのSELECT句に配列を文字列化した不正なカラム名が混入するバグを修正しました。

背景

複合主キーを持つモデルで eager_load(:association).select("...") を呼び出すと、生成されるSQLのSELECT句に "posts"."[""id"", ""other_id""]" のような不正なフィールドが含まれていました。これは #57296 で報告されたバグで、単独の eager_loadselect では問題が発生せず、両者を組み合わせた場合にのみ顕在化していました。

問題の根本は、JoinDependency がSELECT句に追加すべき主キーカラム名を決定する処理にありました。複合主キーモデルでは primary_key が配列を返すため、その配列がそのまま文字列化されてカラム名として扱われてしまっていました。

技術的な変更

修正箇所は activerecord/lib/active_record/associations/join_dependency.rbaliases メソッド内、わずか2行です。

変更前:

column_names = if join_part == join_root && !join_root_alias
  primary_key = join_root.primary_key
  primary_key ? [primary_key] : []
else
  join_part.column_names
end

変更後:

column_names = if join_part == join_root && !join_root_alias
  Array(join_root.primary_key)
else
  join_part.column_names
end

変更前のコードでは、primary_keynil の場合に備えて primary_key ? [primary_key] : [] という三項演算で処理していました。単一主キーの場合は primary_key が文字列(例: "id")を返すため ["id"] となり正常に動作しますが、CPKモデルでは primary_key が配列(例: ["id", "other_id"])を返すため、[["id", "other_id"]] という配列のネストが生じ、後続の処理で不正なカラム名として展開されていました。

修正後の Array() は、Rubyの Kernel#Array を活用したシンプルな変換です。nil を渡せば []、文字列を渡せば ["id"]、配列を渡せばそのまま ["id", "other_id"] が返るため、単一主キー・複合主キー・主キーなしの3パターンを1行で正しく処理できます。

テストには activerecord/test/cases/associations/eager_test.rb に新規ケースが追加されました。Cpk::Order モデルを使って eager_load(:order_agreements).select("cpk_orders.status") を実行し、アソシエーションが正しくロードされることを確認しています。

設計判断

Array() による単一表現への統一が今回の核心的な設計判断です。

変更前のコードは「nil ガード」と「配列へのラップ」を手動で行っており、primary_key の返り値の型(nil / 文字列 / 配列)に対して明示的に分岐していませんでした。Array() への置き換えは、この暗黙的な型の前提を取り除き、CPKで導入された配列返却という新たな型バリアントを自然に扱える形に整理しています。変更行数が2→1行に削減される副次効果もありますが、主眼はロジックの正確性にあります。

まとめ

Kernel#Array の多態的な変換特性を活用することで、単一主キー・複合主キー・主キーなしの全ケースを1行で正しく処理できるようになりました。本修正により、CPKモデルに対して eager_loadselect を組み合わせたクエリが正常に動作するようになります。

記事メタデータ

Generated by:
Claude Sonnet 4.6 for DiffDaily
LLM Trace:
53bbd1d9

この記事は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リンク記法の正確性

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

対象読者への適合性 ✓ PASS

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

CPKやJoinDependencyといったRails内部の知識を前提としており、専門知識を持つエンジニアという対象読者に適合しています。

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

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

各セクション、各パラグラフが「総論→各論」の構成で書かれ、トピックセンテンスが明確です。非常に読みやすい構成です。

Diff内容との照合 ✓ PASS

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

Diff内のコード変更箇所、ファイル名、およびテストコードの追加に関する言及が、提供されたDiff情報と正確に一致しています。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

CPK, eager_load, JoinDependency, Kernel#Arrayなど、使用されている技術用語はすべて正確で、文脈に適しています。

説明の技術的正確性 ✓ PASS

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

CPKモデルで primary_key が配列を返し、それがネストした配列になる問題点や、Kernel#Array を使うことで解決するロジックの説明が技術的に正確かつ明快です。

事実の突合 ✓ PASS

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

記事内のすべての主張が、PRのタイトル、Diff、関連Issue番号から裏付け可能であり、ハルシネーション(創作)は見られません。

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

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

PR番号(#57298)、Issue番号(#57296)が正確に記載されています。

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

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

記事タイトルはPRのタイトル「Fix `eager_load` for CPK models with explicit `select`」の内容を、より具体的に分かりやすく要約しています。

外部知識の正確性 ✓ PASS

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

PR情報に基づかないバージョン情報やリリース予定などの外部知識の捏造はありません。Kernel#Arrayの挙動説明はコード解説に必要な妥当な情報です。

時間表現の正確性 ✓ PASS

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

「修正しました」「含まれていました」など、過去の事象を示す時間表現が正確に使用されています。