Composite Primary Key の `find([])` が空配列を返すように修正

rails/rails

Composite Primary Key を持つモデルで Model.find([]) が例外を投げていた問題を解消し、シングルキーと同様に空配列を返すようになりました。この変更により、空結果がそのまま次の find に流れるケースでコードがシンプルになります。

背景

シングルプライマリキーのモデルでは Model.find([]) が空配列 [] を返すのが期待動作ですが、複合プライマリキーでは ActiveRecord::RecordNotFound が発生していました。これは find_with_ids が引数の形状から配列検索か単一検索かを判定するロジックが、空配列を単一 ID と誤認したためです。結果として、空配列が渡されたときに 1 件のレコード検索を試み、0 件で例外が上がっていました。

技術的な変更

activerecord/lib/active_record/relation/finder_methods.rbfind_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([]) が複合プライマリキーでも空配列を返すようになり、シングルキーと同様の振る舞いが保証されました。判定ロジックの拡張と早期リターンによるシンプルな実装で、既存機能への影響はなく、テストで挙動を明示的にカバーしています。これにより、空結果をそのまま流用できるユースケースで余計な例外処理を書く必要がなくなります。

記事メタデータ

Generated by:
gpt-oss-120b for DiffDaily
LLM Trace:
554e9096

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

品質レビュー結果

Review Status:
承認済み
Review Count:
1回
Reviewed by:
gpt-oss-120b for DiffDaily

Review Criteria:

記事構成 ✓ PASS

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

リード文・背景・技術的な変更・設計判断・まとめがすべて揃っており、総論→各論→結論の構成が明確です。

カスタムMarkdown構文 ⚠ WARNING

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

コードブロックは `ruby:ファイルパス` 形式で正しく記載されていますが、GitHubリンクは `[PR #57649](URL)` の形で記載されており、仕様の `[ #123 ](URL)` 形式と完全に一致していません。

対象読者への適合性 ✓ PASS

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

技術者向けの具体的な実装解説のみで、初心者向けの冗長な説明はありません。

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

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

各セクションは総論・各論・結論の段落構成で、トピックセンテンスが冒頭に配置され、1段落1トピック・適切な長さが保たれています。

Diff内容との照合 ✓ PASS

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

記事中のコードスニペットは提供されたDiffと一致しており、変更点(first_item, 早期リターン, expects_array のロジック修正)が正確に反映されています。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

用語(Composite Primary Key、RecordNotFound、expects_array など)はPRで使用されているものと一致し、誤用はありません。

説明の技術的正確性 ✓ PASS

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

空配列が例外になる原因と修正内容の因果関係が正しく説明されており、PRの記述と整合しています。

事実の突合 ⚠ WARNING

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

「空結果がそのまま次の find に流れるケースでコードがシンプルになる」等の利点はPR本文に明示されておらず、推測的記述と判断されます。技術的に誤りではありませんが、ハルシネーションとして警告対象です。

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

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

PR番号 #57649 などの数値は正確です。

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

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

記事タイトルはPRタイトルの内容を日本語で正確に要約しています。

外部知識の正確性 ✓ PASS

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

記事はPR情報以外のバージョンサポートやリリース日程などの外部知識を含んでいません。

時間表現の正確性 ✓ PASS

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

時間表現は使用されておらず、PR情報と矛盾はありません。