Composite Primary Key の単一レコード更新をサポート
ActiveRecord のクラスメソッド update と update! が、単一レコードの複合主キー更新で誤って RecordNotFound を発生させる問題を修正し、シングルキーと同様に扱えるようになりました。
背景
これまで update / update! は引数 id が配列かどうかで「単一」か「複数」かを判定していました。
複合主キー を持つモデルでは、単一レコードの id 自体が配列(例: ["author_id","id"])になるため、配列判定に引っ掛かり複数 ID と誤認識され、内部で find が個々の要素に対して実行され ActiveRecord::RecordNotFound が送出されました。
この挙動はシングルプライマリキーと非対称であり、Model.update(record.id, attrs) が期待通りに動作しないという実装上の不整合を生んでいました。
技術的な変更
activerecord/lib/active_record/persistence.rb に update_multiple_ids? ヘルパーメソッドが新設され、複合主キーかどうかを踏まえて ID の形状を正しく判定します。
def update_multiple_ids?(id)
if composite_primary_key?
id.is_a?(Array) && id.first.is_a?(Array)
else
id.is_a?(Array)
end
end
update と update! の先頭条件が if id.is_a?(Array) から if update_multiple_ids?(id) に置き換えられ、単一レコードの配列 ID が複数判定に混入しなくなります。
また、:all(デフォルト)パスのチェックが最初に行われるよう順序が調整され、既存の引数省略動作は維持されています。
テスト activerecord/test/cases/persistence_test.rb では、単一および複数の複合キー対象レコードに対する update / update! の正しい動作を検証するケースが追加され、リグレッション防止が確保されています。
設計判断
判定ロジックの共通化 と Relation#destroy の判定方式の踏襲 が採用されました。
Relation#destroy では「配列の配列」かどうかで複数削除を判定しており、同様の判断基準を update 系に持ち込むことでコードの一貫性と保守性が向上します。
新たなメソッドはプライベートとし、外部 API には影響を与えないため、既存利用者への破壊的変更は発生しません。
まとめ
この PR は、複合主キーを持つモデルでも Model.update(id, attrs) が単一レコード更新を正しく処理できるようにし、シングルキーと同等の使い勝手を提供します。判定ロジックの共通化により内部実装がシンプルになり、将来的な類似機能拡張にも対応しやすくなります。