Rails で外部キーのカラム(user_idなど)を作成する際、reference型を使うと関連モデルの外部キーを自動生成してくれて便利です。
僕もよくreference型を使うのですが、「外部キー制約」というワードについてはよく理解していないまま今まで何となく使ってきました。(お恥ずかしい限りw)
ということで、今回は「外部キー制約とはどういうもので、何のメリットがあるのか?」についての理解と外部キー制約が付いているカラムを作成する手順を、忘備録も兼ねてまとめてみました。
外部キー制約とは?
外部キーとはテーブル同士の紐付けに用いるカラム(user_id、post_idなどのカラム)のことを言いますが、外部キー制約とは外部キーを登録する際の制限みたいなものです。
外部キー制約を設けた場合、以下のような制限が入ります。
- 存在しない値を外部キーとして登録できない
- 子テーブルの外部キーに値が登録されている状態で親テーブルのレコードを削除できない
それぞれ詳しくみていきます。
存在しない値を外部キーとして登録できない
例えば、とある投稿サービスでユーザー登録者が10名いたとします。
10名のユーザーID(外部キーはuser_id)はそれぞれ1〜10とします。
この時、user_idカラムに存在しない値(例えば11)を登録しようとするとエラーになります。
子テーブルの外部キーに値が登録されている状態で親テーブルのレコードを削除できない
先ほどの例の続きて、今度は10名のユーザーのうちユーザーID=1のユーザーのみ、何かしらの投稿(post)をしたとします。
ここで、ユーザー10名分のアカウントを削除しようとすると、user_id=1の外部キーを含むレコード(ユーザーアカウント)だけ削除できずにエラーとなります。
つまり、user=1のアカウントを削除するためには、user_id=1を含む子テーブルのレコードを全て削除する必要があります。
ただし、この問題は以下のようにdependent: :destroy
のオプションを指定することで解決できます。
class User < ApplicationRecord
has_many :articles, dependent: :destroy
end
(特定のユーザーアカウントを削除するとuser_idに紐づいた子テーブルのレコードを全て削除してくれる)
reference型を使って外部キーを生成、および外部キー制約を設ける
今回はreference型を使ってarticlesテーブルに外部キーとしてuser_idを作成し、さらに外部キー制約を設けていきます。
reference型を使うメリットは、
- user_idというカラムを自動生成してくれる
- user_idのインデックスを自動で貼ってくれる
の2つあります。
ただし、reference型には外部キー制約を自動で設ける機能はありません。
そこで、以下のようにforeign_key: true
オプションを記述することで外部キー制約を設けることができます。
class CreateArticles < ActiveRecord::Migration[7.0]
def change
create_table :articles do |t|
t.string :title
t.text :content
t.references :user, foreign_key: true
t.timestamps
end
end
end
外部キー制約を設けない場合はforeign_key: true
オプションは必要ありません。
rails db:migrate
でマイグレーションを実行すると、以下のようにテーブルが作成されます。
create_table "articles", force: :cascade do |t|
t.string "title"
t.text "content"
t.integer "user_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["user_id"], name: "index_articles_on_admin_id"
end
user_idが生成されており、user_idのインデックスも振られていることがわかります。
reference型を使わずに外部キー制約を設ける場合
reference型を使わない場合、foreign_key: true
オプションでは外部キー制約を設けることができないので注意が必要です。
この場合、以下のように記述します。
class CreateArticles < ActiveRecord::Migration[7.0]
def change
create_table :articles do |t|
t.string :title
t.text :content
t.timestamps
end
add_foreign_key :articles, :users
# add_foreign_key :対象のテーブル名, :紐付けしたいテーブル名
end
end
reference型を使わない場合、indexは自動で貼られないので注意が必要です。
(外部キー制約を設けたいけどindexは貼りたくない場合に有効です)
コメント