ThroughReflectionにおける複合主キーの処理を修正

rails/rails

ThroughReflection#association_primary_key が複合主キーを正しく処理できていなかった問題が解決されました。これにより、has_many through関連付けで複合主キーを使用する際の _ids リーダーメソッドが正常に動作するようになります。

背景

ThroughReflection#association_primary_key メソッドは、ソース関連付けに primary_key: [:col1, :col2] のような複合主キーが指定されている場合、配列全体に対して .to_s を呼び出していました。この処理により、期待される ["col1", "col2"] の配列ではなく、"[:col1, :col2]" という文字列が生成されていました。

この不具合は、自動生成される _ids リーダーメソッドを使用する際にエラーを引き起こしていました。具体的には、以下のような ActiveRecord::UnknownAttributeReference 例外が発生していました:

ActiveRecord::UnknownAttributeReference: Dangerous query method called with non-attribute argument(s): "[:col1, :col2]"

一方、BelongsToReflection#association_primary_key は既に複合主キーを正しく処理する実装になっており、この挙動の不一致が問題となっていました。

技術的な変更

activerecord/lib/active_record/reflection.rbThroughReflection#association_primary_key メソッドが、BelongsToReflection の実装と同様の処理に修正されました。

変更前:

if primary_key = actual_source_reflection.options[:primary_key]
  @association_primary_key ||= -primary_key.to_s
else
  primary_key(klass || self.klass)
end

変更後:

if primary_key = actual_source_reflection.options[:primary_key]
  @association_primary_key ||= if primary_key.is_a?(Array)
    primary_key.map { |pk| pk.to_s.freeze }.freeze
  else
    -primary_key.to_s
  end
else
  primary_key(klass || self.klass)
end

primary_key が配列の場合の処理 が追加され、各要素を文字列に変換して凍結した配列を返すようになりました。単一キーの場合は、従来通り単項マイナス演算子による文字列の重複排除最適化が適用されます。

テストコードでは、Sharded::Blog モデルに comments_via_posts 関連付けが追加され、複合主キーを持つ through 関連付けのシナリオが検証されています:

has_many :comments_with_composite_pk,
  class_name: "Sharded::Comment",
  primary_key: [:blog_id, :id],
  foreign_key: [:blog_id, :blog_post_id]

この関連付けを経由する blog.comments_via_post_ids の呼び出しが、期待通り [[comment.blog_id, comment.id]] 形式の配列を返すことが確認されています。

設計判断

BelongsToReflection との実装の統一 が優先されました。

BelongsToReflection#association_primary_key は既に primary_key.is_a?(Array) による分岐を持っており、本PRはその実装を ThroughReflection にも適用する形になっています。配列の各要素に対する .freeze 呼び出しも既存実装と同様で、メモリ効率の向上とイミュータブル性の保証を両立しています。

単一キーの場合に使用される単項マイナス演算子 -primary_key.to_s は、Rubyの文字列重複排除機能を活用した最適化です。この既存の最適化手法を維持しつつ、複合キーのケースにのみ新しい分岐を追加することで、パフォーマンスへの影響を最小限に抑えています。

まとめ

本PRは、ThroughReflection における複合主キーの処理を BelongsToReflection と同じロジックに統一した修正です。配列判定と要素ごとの文字列変換を追加することで、has_many through 関連付けでの複合主キー使用時のエラーを解消し、_ids リーダーメソッドの正常動作を実現しています。

記事メタデータ

Generated by:
Claude Sonnet 4.5 for DiffDaily

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

品質レビュー結果

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

Review Criteria:

記事構成 ✓ PASS

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

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

カスタムMarkdown構文 ✓ PASS

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

ファイル名付きシンタックスハイライト(```ruby:path/to/file.rb)およびGitHubのPRリンク記法が正しく使用されています。

対象読者への適合性 ✓ PASS

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

Railsの内部実装(Reflection)に関する内容であり、専門知識を持つエンジニアという対象読者に適した技術レベルと表現で記述されています。

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

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

各セクションが総論→各論の構成になっており、各段落はトピックセンテンスで始まり、1段落1トピックの原則が守られています。段落の長さも適切です。

Diff内容との照合 ✓ PASS

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

記事内で引用されているコード(変更前・変更後、テストコード)は、提供されたDiff情報と完全に一致しており、正確です。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

「ThroughReflection」「複合主キー」「_ids リーダーメソッド」などの技術用語が、文脈に沿って正確に使用されています。

説明の技術的正確性 ✓ PASS

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

複合主キーが配列ではなく文字列として扱われていた問題点や、配列を正しく処理するよう修正されたロジックの説明が、Diffの内容と一致しており技術的に正確です。

事実の突合 ✓ PASS

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

記事内のすべての主張(不具合の原因、発生する例外、修正方針など)は、PRのDescriptionやDiff内のコードによって裏付けられています。ハルシネーションは見られません。

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

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

PR番号(#56703)や、コード内のクラス名・メソッド名などの固有名詞はすべて正確に記載されています。

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

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

記事のタイトル「ThroughReflectionにおける複合主キーの処理を修正」は、PRのタイトル「Fix ThroughReflection#association_primary_key with composite keys」の内容を的確に反映しています。

外部知識の正確性 ✓ PASS

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

PR情報に含まれていない外部知識(バージョンサポート状況、リリース日程など)の追記はなく、提供された情報源に忠実です。

時間表現の正確性 ✓ PASS

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

「BelongsToReflection#association_primary_key は既に...」という表現は、PR Descriptionの「matching the existing behavior」と一致しており、時間表現は正確です。