作成したアプリのコードレビューで外部キー制約を追加するように指摘されたので調べて見ました。
結論
外部キーを保存するカラムに対して関連付けしているテーブルの主キーのみを保存するように制限し、データ不整合を防止すること。 具体的には次の制約が追加される。
- 主キーの値のみを保存できる
- 存在しない値を外部キーとして保存させない
- 子テーブルの外部キーに値が保存されている親テーブルのレコードは削除できない
外部キーの概要と制約を使うことのメリット・デメリット - Qiita
外部キー制約とreferences型
Railsは外部キーを保存するカラムにはreferences型を使います。 references型のカラムには次のような特徴があります。
- インデックスは自動で付与され
index: true
が不要になる - カラム名が自動で
xxx_id
の形になる foreign_key: true
が使えるようになる(references型以外でforeign_key: true
を追加するだけでは外部キー制約にはならない)foreign_key:
はadd_foreign_key
を使って書き換え可能
Railsの外部キー制約とreference型について - Qiita
外部キー制約の追加方法
外部キー制約を追加方法は2つあります。
- テーブル作成時にカラムに外部キー制約を追加する
- カラム追加時に外部キー制約を追加する
Railsで外部キー制約のついたカラムを作る時のmigrationの書き方 - Qiita
1. テーブル作成時に外部キー制約を追加する
テーブル作成のマイグレーションファイルで作成するカラムに外部キー制約を追加できます。
具体的にはreferences型のカラムを作成し、foreign_key
を指定することで外部キー制約をカラムに追加することができます。
作成するカラム名によってforeign_key:
の指定が2パターンあります。
- 外部キー制約を追加するカラム名が参照テーブル名と同じ:
foreign_key: true
- 外部キー制約を追加するカラム名が参照テーブル名と異なる:
foreign_key: { to_table: }
で参照するテーブル名を指定が必要
また、references型のカラム名は自動でxxx_id
の形になるので_id
の部分は記述しない点に注意してください。
# マイグレーションファイルの基本構文 def change create_table :テーブル名 do |t| # 外部キー制約を追加するカラム名が参照テーブル名と同じ t.referencess :外部キー制約を追加するカラム名, foreign_key: true # 外部キー制約を追加するカラム名が参照テーブル名と異なる t.referencess :外部キー制約を追加するカラム名, foreignj_key: { to_table: :参照先のテーブル名 } end end
実装例
例) Booksテーブルに作成時にUsersテーブルを参照する外部キー制約付きのカラムを追加する。
下記コマンドでマイグレーションファイルを作成します。
rails g model
でマイグレーションファイルを生成する場合は次のことに注意してください。
null: false
は自動で付与されるindex: true
は記述されませんがインデックスは付与されているto_table:
の記述は自動生成されないので手動で修正が必要
# 外部キー制約付きのuser_idカラムを追加するマイグレーションファイルの作成 $ rails g model Book user:references # 外部キー制約付きのauthor_idカラムを追加するマイグレーションファイルの作成 $ rails g model Book author:references
下記マイグレーションファイルが作成されます。必要に応じて修正を追加します。
# 外部キー制約付きのuser_idカラムを追加するマイグレーションファイル class CreateBooks< ActiveRecord::Migration[6.1] def change create_table :books do |t| t.references :user, null: false, foreign_key: true end end end
# 外部キー制約付きのauthor_idカラムを追加するマイグレーションファイル class CreateBooks< ActiveRecord::Migration[6.1] def change create_table :books do |t| # to_tableは手動で追記 t.references :author, null: false, foreign_key: { to_table: :users } end end end
修正後、rails db:migrate
します。
2. カラム追加時に外部キー制約を追加する
既存のテーブルへのカラム追加時のマイグレーションファイルで外部キー制約付きのカラムを追加できます。
add_references
でカラム追加と外部キー制約の追加を行います。
この際もforeign_key
の追加が必要になります。
テーブル作成時に外部キー制約を追加するのと同様にカラム名によってforeign_key
の書き方は2通りあります。
- 外部キー制約を追加するカラム名が参照テーブル名と同じ:
foreign_key: true
- 外部キー制約を追加するカラム名が参照テーブル名と異なる:
foreign_key: { to_table: }
で参照するテーブル名を指定が必要
# マイグレーションファイルの基本構文 def change # 外部キー制約を追加するカラム名が参照テーブル名と同じ add_references :外部キー制約を追加するテーブル名, :外部キー制約付きのカラム名, foreign_key: true # 外部キー制約を追加するカラム名が参照テーブル名と異なる add_references :外部キー制約を追加するテーブル名, :外部キー制約付きのカラム名, foreign_key: { to_table: :参照先のテーブル名 } end
実装例
例) Usersテーブルを参照する外部キー制約付きのカラムを既存のBooksテーブルに追加する。
下記コマンドでマイグレーションファイルを作成します。
rails g migration
でマイグレーションファイルを生成する場合はrails g model
と同様に次のことに注意してください。
null: false
は自動で付与されるindex: true
は記述されませんがインデックスは付与されているto_table:
の記述は自動生成されないので手動で修正が必要
# 外部キー制約付きのuser_idカラムを追加するマイグレーションファイルの作成 $ rails g migration AddUserRefToBooks user:references # 外部キー制約付きのauthor_idカラムを追加するマイグレーションファイルの作成 $ rails g migration AddAuthorRefToBooks user:references
下記マイグレーションファイルが作成されます。必要に応じて修正を追加します。
# 外部キー制約付きのuser_idカラムを追加するマイグレーションファイル class AddUserRefToBooks < ActiveRecord::Migration[6.1] def change add_reference :books, :user, null: false, foreign_key: true end end
# 外部キー制約付きのauthor_idカラムを追加するマイグレーションファイル class AddAuthorRefToBooks < ActiveRecord::Migration[6.1] def change # カラム名の修正、to_tableを手動で追記 add_references :books, :author, foreign_key: { to_table: :users } end end
修正後、rails db:migrate
します。
親テーブルに紐づく子テーブルのレコードを削除するには?
外部キー制約を追加する子テーブルの外部キーに値が保存されている親テーブルのレコードは削除できないようになっています(参照整合性)。 これは保存されている子テーブルのレコードが親テーブルのレコードを削除することで外部キーで参照できなくなるのを防止するためです。
この制限を無効化し、レコードを削除するにはdependent: destroy
を使います。
dependent: destroy
を指定することで関連付けられたレコードも全て削除されます。
Active Record マイグレーション - Railsガイド
Active Record の関連付け - Railsガイド
例) 親テーブルUserに関連付いた子テーブルBookのレコードも削除する
# 親テーブル class User < ApplicationRecord # 関連付けたレコードも削除する has_many :books, dependent: destroy end # 子テーブル class Book < ApplicationRecord belongs_to :user end
感想
外部キー制約についての曖昧さが少し解消したので良かった。 マイグレーション周りは苦手なのでガンガン触って理解していこう。
参照
外部キーの概要と制約を使うことのメリット・デメリット - Qiita
Railsの外部キー制約とreference型について - Qiita
Railsで外部キー制約のついたカラムを作る時のmigrationの書き方 - Qiita