disable_joins:true の through アソシエーションで複合キーが空結果になるバグを修正

rails/rails

has_many / has_one :throughdisable_joins: true を指定した際、ソースモデルが複合主キーを持つと結果が空配列になる不具合を解消しました。これにより、disable_joins を利用したパフォーマンス最適化が安全に適用可能になります。

背景

disable_joins: true が付与されたアソシエーションは、結合クエリを分割して個別に取得し、最終的に in‑memory でのグルーピング にフォールバックします。ここで順序付け (order) が存在し、最終スコープに order が無い場合、ActiveRecord::DisableJoinsAssociationRelation#loadrecords.group_by を実行します。その際に使用されるキーは reflection.join_primary_key で、複合キーの場合は 配列(例: ["shop_id", "id"])になります。従来は record[key] がそのまま呼び出され、配列全体を属性名として検索するため常に nil が返り、全レコードが同一の nil グループに収束し最終的に [] が返されました。この挙動は例外を投げずに結果が消失するため、バグの発見が困難でした。

技術的な変更

activerecord/lib/active_record/disable_joins_association_relation.rbload メソッド内で キーが配列かどうかを判定し、配列の場合は各カラムを個別に取得して配列に変換するロジックを追加しました。変更前は record[key] だけでしたが、変更後は次のようになります。

records_by_id = records.group_by do |record|
  if key.is_a?(Array)
    key.map { |column| record[column] }
  else
    record[key]
  end
end

この修正により、複合キーでも正しく [shop_id, id] のタプルが取得され、期待通りのグルーピングが行われます。加えて、activerecord/test/cases/associations/has_many_through_disable_joins_associations_test.rbtest_ordered_disable_joins_through_with_composite_primary_key_source を追加し、Cpk::Author, Cpk::Book, Cpk::Order を用いた実際のシナリオで no_joins_orders が正しい結果を返すことを検証しています。テストは SQLite3 と PostgreSQL の両方で緑化され、既存の 29 + 5 + 173 + 320 のテストもすべて合格しています。

設計判断

複合キーの処理は 既存の load ロジック内に最小限の分岐として実装され、他のコードパスやインターフェースは変更されていません。新たにキー型を判定するだけなので、後方互換性が保たれdisable_joins: true を従来通り true のみで使用しているアプリケーションに影響はありません。また、別途 disable_joins 用のヘルパーメソッドやオプションを増やすことなく、一箇所でのロジック追加で全体の挙動を統一できる設計となっています。

まとめ

この PR は disable_joins:true で複合主キーを持つモデルを通過する has_many :throughhas_one :through の結果が空になるバグを解消し、既存の設定やコードに影響を与えずに正しいレコード取得を保証します。これにより、disable_joins を安全に活用できる範囲が広がり、パフォーマンス最適化の選択肢が増えました。

記事メタデータ

Generated by:
gpt-oss-120b for DiffDaily
LLM Trace:
03c2496d

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

品質レビュー結果

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

Review Criteria:

記事構成 ✓ PASS

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

リード文、背景、技術的変更、設計判断(任意)、まとめが明確に配置されており、総論→各論→結論の流れが保たれています。

カスタムMarkdown構文 ⚠ WARNING

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

コードブロックは正しい `言語:ファイルパス` 形式ですが、PRリンクが `[PR #57534](URL)` となっており、要求される `[#57534](URL)` 形式になっていません。

対象読者への適合性 ✓ PASS

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

対象はRailsエンジニアであり、専門用語中心の記述で初心者向けの過剰な説明はありません。

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

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

各セクションは総論・各論・結論の段落構成で、段落冒頭がトピックセンテンスになっており、1段落に1トピック、長さも適切です。

Diff内容との照合 ✓ PASS

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

記事内のコードブロックは提供されたDiffの変更内容と一致しており、ファイル名・コード内容ともに正確です。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

使用している技術用語はPRで用いられているものと一致し、誤用はありません。

説明の技術的正確性 ✓ PASS

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

バグの原因と修正内容の説明がPRの記述と合致しており、技術的に正確です。

事実の突合 ✓ PASS

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

記事の全ての主張はPRの説明・Diff・テストコードで裏付けられており、ハルシネーションは見られません。

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

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

PR番号やテストケース数(29, 5, 173, 320)などの数値は正確です。

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

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

記事タイトルはPRタイトルの内容を日本語で要約しており、一致しています。

外部知識の正確性 ✓ PASS

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

記事内にPRに無い外部情報(LTS、リリース日程等)は含まれていません。

時間表現の正確性 ✓ PASS

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

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