insert_all!にunique_byオプションを追加

rails/rails

ActiveRecord の insert_all! メソッドに unique_by オプションが追加され、insert_all との機能的な一貫性が確保されました。これにより、重複時に例外を発生させる場合でも、特定のユニークインデックスを指定した競合検出が可能になります。

背景

insert_all メソッドは既に unique_by オプションをサポートしており、PostgreSQL と SQLite において特定のユニークインデックスを競合ターゲットとして指定できました。しかし、insert_all! では同じオプションが公開されていませんでした。#45317 で最初に指摘されたこのインターフェースの不整合が、#56454 で解消されています。

内部実装である ActiveRecord::InsertAll クラスは既に unique_by パラメータをサポートしていたため、公開インターフェースが実装の能力に追いついていない状態でした。この変更により、バング(!)の有無に関わらず同じオプションセットで呼び出せるようになります。

技術的な変更

activerecord/lib/active_record/relation.rbinsert_all! メソッドのシグネチャが拡張されました。

変更前:

def insert_all!(attributes, returning: nil, record_timestamps: nil)
  InsertAll.execute(self, attributes, on_duplicate: :raise, returning: returning, record_timestamps: record_timestamps)
end

変更後:

def insert_all!(attributes, returning: nil, unique_by: nil, record_timestamps: nil)
  InsertAll.execute(self, attributes, on_duplicate: :raise, returning: returning, unique_by: unique_by, record_timestamps: record_timestamps)
end

unique_by パラメータがメソッドシグネチャに追加され、InsertAll.execute への呼び出しに渡されるようになりました。これにより、以下のような使用が可能になります:

Book.insert_all!(
  [{ name: "UniqueBy", author_id: 1, isbn: "unique-isbn" }],
  unique_by: :isbn
)

テストケースでは、部分インデックスを持つ books.isbn に対する動作が検証されています。このインデックスは published_on IS NOT NULL の条件付きで有効になるため、unique_by: :isbn を指定した場合の競合検出が正しく機能することが確認されています。

設計判断

既存の insert_all と同じオプション体系 を採用する判断がなされました。

unique_by オプションは PostgreSQL と SQLite でのみ機能し、カラム名、カラム名の配列、またはインデックス名を指定できます。ドキュメントには以下の例が追加されています:

unique_by: :isbn
unique_by: %i[ author_id name ]
unique_by: :index_books_on_isbn

on_duplicate: :raise の動作はそのまま維持され、unique_by で指定されたインデックスに対する重複が検出された場合に ActiveRecord::RecordNotUnique 例外が発生します。insert_all では on_duplicate: :skipon_duplicate: :update との組み合わせで使用されていたオプションが、例外発生時にも同じインターフェースで利用できるようになりました。

まとめ

本PRは、insert_all! のインターフェースを insert_all と整合させる変更です。内部実装が既にサポートしていた機能を公開APIとして露出させることで、バングメソッドの有無に関わらず一貫したオプション指定が可能になりました。部分インデックスを持つテーブルにおいて、特定のユニークインデックスを明示的に指定した競合検出が insert_all! でも利用できるようになります。

記事メタデータ

Generated by:
Claude Sonnet 4.5 for DiffDaily

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

品質レビュー結果

Review Status:
承認済み
Review Count:
1回
Reviewed by:
Gemini 2.5 Pro for DiffDaily

Review Criteria:

記事構成 ✓ PASS

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

「総論→各論→結論」の構成が明確です。リード文で要旨を述べ、背景、技術的変更、設計判断の各セクションで詳細を解説し、最後にまとめで意義を述べるという、理想的な構成になっています。

カスタムMarkdown構文 ✓ PASS

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

ファイル名付きシンタックスハイライト(```ruby:path/to/file.rb)およびGitHubのPR番号リンク記法([#123](URL))が正しく使用されています。

対象読者への適合性 ✓ PASS

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

ActiveRecordに関する知識を持つエンジニアを対象としており、専門用語を適切に使用し、過度な説明を省いた簡潔な内容になっています。

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

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

各セクションが総論・各論で構成され、各段落がトピックセンテンスで始まるなど、パラグラフ・ライティングの原則が守られています。これにより、非常に読みやすい記事になっています。

Diff内容との照合 ✓ PASS

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

記事内で引用されているコードは、提供されたDiffの内容と正確に一致しています。変更前後のコードブロックや、テストコードを基にした使用例も適切です。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

`unique_by`、`バングメソッド`、`部分インデックス`、`ActiveRecord::RecordNotUnique`などの技術用語が、PRの文脈に沿って正確に使用されています。

説明の技術的正確性 ✓ PASS

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

`insert_all!`に`unique_by`が追加された背景、技術的な実装、そして重複時の挙動に関する説明は、すべて技術的に正確で論理的です。

事実の突合 ✓ PASS

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

記事内のすべての主張は、PRのDescriptionやDiffの内容に基づいており、根拠のない推測や創作(ハルシネーション)は見られませんでした。

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

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

PR番号(#56454)や関連PR番号(#45317)などの固有名詞が正確に記載されています。

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

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

記事のタイトルはPRのタイトル「Add `unique_by` option to `insert_all!`」を的確に反映しており、内容もPRの主旨と完全に一致しています。

外部知識の正確性 ✓ PASS

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

記事の内容はすべてPR情報(Description、Diff内のドキュメント)に基づいており、PRで言及されていない外部知識の持ち込みはありません。

時間表現の正確性 ✓ PASS

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

「`insert_all` メソッドは既に `unique_by` オプションをサポートしており」など、時間に関する表現がPR Descriptionの「already accepts」と正確に一致しています。