Composite Primary Key がデフォルト属性セットから除外されないように修正
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