Composite Primary Key の `find([])` が空配列を返すように修正
Composite Primary Key を持つモデルで Model.find([]) が例外を投げていた問題を解消し、シングルキーと同様に空配列を返すようになりました。この変更により、空結果がそのまま次の find に流れるケースでコードがシンプルになります。
背景
シングルプライマリキーのモデルでは Model.find([]) が空配列 [] を返すのが期待動作ですが、複合プライマリキーでは ActiveRecord::RecordNotFound が発生していました。これは find_with_ids が引数の形状から配列検索か単一検索かを判定するロジックが、空配列を単一 ID と誤認したためです。結果として、空配列が渡されたときに 1 件のレコード検索を試み、0 件で例外が上がっていました。
技術的な変更
activerecord/lib/active_record/relation/finder_methods.rb の find_with_ids 内で判定ロジックを修正し、空配列を配列検索として扱うようにしました。
def find_with_ids(*ids)
raise UnknownPrimaryKey.new(model) if primary_key.nil?
first_item = ids.first
return [] if first_item.is_a?(Array) && first_item.empty?
expects_array = if model.composite_primary_key?
first_item.first.is_a?(Array)
else
first_item.is_a?(Array)
end
ids = first_item if expects_array
ids = ids.compact.uniq
...
end
変更点は三つです。1) first_item を先頭要素として取得し、空配列であれば即座に [] を返す早期リターンを追加。2) expects_array の判定を first_item に基づき統一し、複合キーでも配列かどうかを正しく判定。3) ids = first_item if expects_array に変更し、従来の ids = ids.first を置き換えました。これにより、空配列は例外なく空結果として扱われます。
テストも activerecord/test/cases/finder_test.rb に新規ケースが追加され、複合キーで find([]) が空配列を返すことを検証しています。
test "find with an empty array on a composite primary key" do
empty_array = []
result = Cpk::Book.find(empty_array)
assert_equal [], result
assert_not_same empty_array, result
end
このテストは、戻り値が新しい配列であること、元の空配列オブジェクトと同一ではないことも確認し、実装が期待通りに動作することを保証します。
設計判断
空配列を特別扱いして即座に空結果を返す方針は、既存の API 挙動との一貫性 を保つことを最優先にした設計判断です。expects_array 判定を拡張しただけで、他の検索パターン(find([id])、find([[id1, id2], ...]))には影響を与えません。変更範囲を最小限に抑えることで、既存コードの後方互換性を維持しながらバグ修正を実現しています。
また、テスト追加により 回帰防止 が組み込まれ、将来的に同様のロジック変更があっても期待動作が破壊されないようにしています。設計上のトレードオフは、判定ロジックを若干複雑にした点ですが、可読性と安全性のバランスが取れた実装と評価できます。
まとめ
find([]) が複合プライマリキーでも空配列を返すようになり、シングルキーと同様の振る舞いが保証されました。判定ロジックの拡張と早期リターンによるシンプルな実装で、既存機能への影響はなく、テストで挙動を明示的にカバーしています。これにより、空結果をそのまま流用できるユースケースで余計な例外処理を書く必要がなくなります。