Composite Primary Key がデフォルト属性セットから除外されないように修正

rails/rails

Rails の ActiveRecord で、複合主キーを持つモデルが部分的な select 後に主キー列へアクセスすると ActiveModel::MissingAttributeError が発生していた問題を、デフォルト属性セット生成ロジックを修正して解消した。

背景

単一主キーのモデルでは、部分的な select 後に主キー列を参照すると nil が返るが、複合主キーのモデルでは同様の操作で例外が発生するという不整合があった。

# Single primary key — returns nil
Topic.select(:title).first.id        # => nil

# Composite primary key — raises
book = Cpk::Book.select(:title).first
book.author_id
# => ActiveModel::MissingAttributeError

この違いは開発者にとって予期せぬエラーとなっていた。

原因は attributes_builder がデフォルト属性セットを作成する際に、主キー列を除外しないように実装されていたが、複合主キーは配列として扱われていたため除外ロジックが失敗していた。

defaults = _default_attributes.except(*(column_names - [primary_key]))

primary_key が配列である場合、column_names - [primary_key] は配列要素と一致せず、結果として全列が除外され、主キー列が未初期化となっていた。

技術的な変更

修正では、attributes_builder のデフォルト属性生成ロジックを Array(primary_key) を用いる形に変更した。

@@ -449,7 +449,7 @@ def table_exists?

       def attributes_builder # :nodoc:
         @attributes_builder ||= begin
-          defaults = _default_attributes.except(*(column_names - [primary_key]))
+          defaults = _default_attributes.except(*(column_names - Array(primary_key)))
           ActiveModel::AttributeSet::Builder.new(attribute_types, defaults)
         end
       end

これにより、主キーが単一でも配列でも正しく除外対象から外れるようになり、複合主キー列がデフォルト属性セットに保持される。

テストケースが追加され、部分的な select 後に複合主キー列が nil を返すことが検証されている。

def test_reading_composite_primary_key_after_partial_select_returns_nil
  book = Cpk::Book.select(:title).first

  assert_nil book.author_id
  assert_equal [nil, nil], book.id
end

テストにより回帰が防止され、変更の正当性が自動的に担保される。

設計判断

今回の変更は、既存の API を変更せず、Array() ヘルパーで型統一を図る最小限の修正という設計判断が取られた。

Array("id")["id"] になるため、単一主キーの振る舞いは全く変わらず、既存コードへの影響を最小化できた。

また、_returning_columns_for_insert でも同様に Array(primary_key) が利用されており、今回の修正でコードベース全体の一貫性が保たれた。

同一ロジックの統一は保守性を向上させ、将来的な主キー周りの拡張でも同様のパターンを適用しやすくなる。

まとめ

デフォルト属性セット生成時に複合主キー列を保持するよう修正したことで、部分的な select 後の主キー取得が例外から nil へと統一された。これにより ActiveRecord の挙動がシンプルになり、既存の単一主キーと同様の扱いが保証された。

  • Primary Source: PR #57653
  • Generated by: gpt-oss-120b for DiffDaily

記事メタデータ

Generated by:
gpt-oss-120b for DiffDaily
LLM Trace:
35d4b25e

この記事は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 #57653" となっており、仕様の `[ #123 ](URL)` 形式に完全には一致していません。

対象読者への適合性 ✓ PASS

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

Rails の内部実装に関する専門的な記述が中心で、エンジニア向けに適切です。

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

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

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

Diff内容との照合 ✓ PASS

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

記事中の diff ブロックとテスト追加コードは PR の diff と完全に一致しています。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

`attributes_builder`、`Composite Primary Key`、`ActiveModel::MissingAttributeError` などの用語は PR と一致しており、誤用はありません。

説明の技術的正確性 ✓ PASS

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

修正内容とその効果(主キーが nil を返すようになること)が PR の説明と合致しており、技術的に正確です。

事実の突合 ✓ PASS

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

すべての主張は PR のタイトル、説明、diff で裏付けられており、ハルシネーションは見られません。

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

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

PR 番号 #57653 の記載は正確です。他の数値・固有名詞の誤りはありません。

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

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

記事タイトルは PR の内容「Composite Primary Key をデフォルト属性セットに保持する」ことを適切に表現しています。

外部知識の正確性 ✓ PASS

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

記事内に LTS やリリース日程等の外部知識は含まれていません。

時間表現の正確性 ✓ PASS

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

時間表現の記述はなく、PR と食い違いもありません。