`create_join_table`で主キーを指定できるように

rails/rails

create_join_table:primary_keyオプションが追加され、PostgreSQLの論理レプリケーションなど主キーを必要とするユースケースでも、結合テーブルをRailsのマイグレーションAPIで扱えるようになりました。

背景

create_join_tableはこれまで、内部でcreate_tableを呼び出す際にid: falseをハードコードしており、主キーの設定手段を持ちませんでした。:primary_keyオプションを渡してもid: falseが優先されるため、完全に無視されていました。

PostgreSQLの論理レプリケーションは、対象テーブルに主キーが存在することを要件とします。この制約のもとでcreate_join_tableを使おうとしても、主キーなしの結合テーブルしか作れないため、代替手段としてcreate_tableで結合テーブルを手書きするか、可逆性の管理が煩雑なカスタムマイグレーションメソッドを定義するしかありませんでした。

本PRはこの制約を解消し、create_join_tableのAPIの範囲内で主キー付き結合テーブルを作成できるようにしています。

技術的な変更

schema_statements.rbcreate_join_tableメソッドからid: falseのハードコードが取り除かれ、:primary_keyオプションの有無によってidの値を動的に決定するようになりました。

変更前:

def create_join_table(table_1, table_2, column_options: {}, **options)
  join_table_name = find_join_table_name(table_1, table_2, options)

  column_options.reverse_merge!(null: false, index: false)

  t1_ref, t2_ref = [table_1, table_2].map { |t| reference_name_for_table(t) }

  create_table(join_table_name, **options.merge!(id: false)) do |td|
    td.references t1_ref, **column_options
    td.references t2_ref, **column_options
    yield td if block_given?
  end
end

変更後:

def create_join_table(table_1, table_2, column_options: {}, **options)
  join_table_name = find_join_table_name(table_1, table_2, options)

  column_options.reverse_merge!(null: false, index: false)
  options.reverse_merge!(id: options[:primary_key] ? :primary_key : false)

  t1_ref, t2_ref = [table_1, table_2].map { |t| reference_name_for_table(t) }

  create_table(join_table_name, **options) do |td|
    td.references t1_ref, **column_options
    td.references t2_ref, **column_options
    yield td if block_given?
  end
end

追加されたのは1行のみです。options.reverse_merge!(id: options[:primary_key] ? :primary_key : false)により、:primary_keyが指定されている場合はid: :primary_key、指定されていない場合は従来通りid: falseがデフォルトとして設定されます。reverse_merge!を使うことで、呼び出し側が明示的にid:を指定した場合はそちらが優先される設計になっています。

複合主キーを持つ結合テーブルは以下のように作成できます。

create_join_table :assemblies, :parts, primary_key: [:assembly_id, :part_id]

これはPostgreSQLでは次のDDLを生成します。

CREATE TABLE assemblies_parts (
  assembly_id bigint NOT NULL,
  part_id bigint NOT NULL
);

ALTER TABLE ONLY "assemblies_parts"
    ADD CONSTRAINT assemblies_parts_pkey PRIMARY KEY (assembly_id, part_id);

テストではtest_create_join_table_can_set_primary_keytest_drop_join_table_with_primary_keyが追加されており、作成・削除の両方向での動作が確認されています。

設計判断

インターフェースの簡潔さを優先し、id:の明示を不要とする設計が採用されました。

PR内ではid: :primary_keyを明示する形も検討されています。

# 検討された代替案
create_join_table :assemblies, :parts, id: :primary_key, primary_key: [:assembly_id, :part_id]

# 採用された形
create_join_table :assemblies, :parts, primary_key: [:assembly_id, :part_id]

:primary_keyを渡した場合にid:を自動的に:primary_keyへ設定することで、結合テーブルに主キーを設定したいという意図を1つのオプションで表現できます。後方互換性については、reverse_merge!によりデフォルト値を注入する方式を採ることで、既存のcreate_join_table呼び出しへの影響を完全に排除しています。

なお、id: falseがハードコードから外れたことに伴い、create_table:idオプションのドキュメントも「Join tables should set it to false」から「Join tables set it to false by default」へ表現が修正されており、仕様の変化が文書レベルでも一貫して反映されています。

まとめ

変更の核心は1行のreverse_merge!であり、既存APIとの後方互換性を保ちながら論理レプリケーション要件への対応を実現しています。結合テーブルをcreate_tableで代替していたコードベースでは、create_join_tableの可逆性(drop_join_tableとの対称性)を活かしたマイグレーションへの移行が可能になります。

記事メタデータ

Generated by:
Claude Sonnet 4.6 for DiffDaily
LLM Trace:
596df65f

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

品質レビュー結果

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

Review Criteria:

記事構成 ✓ PASS

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

「リード文(総論)→背景・技術詳細・設計判断(各論)→まとめ(結論)」という理想的な3部構成が明確に実現されています。各セクションの役割が明確で、論理的な流れが非常に分かりやすいです。

カスタムMarkdown構文 ✓ PASS

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

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

対象読者への適合性 ✓ PASS

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

「論理レプリケーション」や「可逆性」といった用語を前提として使用しており、専門知識を持つエンジニアという対象読者に適切にフォーカスできています。

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

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

各セクションが総論→各論の構造を持ち、各段落がトピックセンテンスで始まるなど、パラグラフ・ライティングの原則が見事に実践されています。これにより、非常に高い可読性が確保されています。

Diff内容との照合 ✓ PASS

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

変更前後のコードブロック、ファイルパス、テストメソッド名など、記事内で引用されているコード関連情報が提供されたDiffと完全に一致しており、正確です。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

`reverse_merge!`, `DDL`, `id: false` など、PRの文脈で使われる技術用語が正確かつ適切に使用されています。

説明の技術的正確性 ✓ PASS

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

`reverse_merge!`の動作原理や、`:primary_key`オプションが`id`オプションに与える影響についての説明は、コードの変更内容と完全に整合しており、技術的に正確です。

事実の突合 ✓ PASS

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

記事内のすべての主張(PostgreSQLの要件、従来の代替策、設計判断の背景など)は、PRのDescriptionやDiffの内容によって裏付けられており、ハルシネーションは一切見られません。

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

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

PR番号(#56195)が正確に記載・リンクされています。

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

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

記事タイトル「`create_join_table`で主キーを指定できるように」は、PRのタイトル「Allow create_join_table to accept a primary key」の内容を的確に和訳し、記事全体の主題を正確に反映しています。

外部知識の正確性 ✓ PASS

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

記事に含まれる情報はすべてPRで提供された情報(Description、Diff)に基づいており、PRに記載のない外部知識の追加はありません。

時間表現の正確性 ✓ PASS

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

「これまで...できなかった」「本PRで...できるようになった」といった時間表現が、PRの文脈(問題の修正)と正しく対応しています。