【Rails】マイグレーションファイルで外部キー、インデックスを設定する方法

Ruby on Rails

はじめに

この記事の概要

こんにちは、Rubyをメインで書いている株式会社TOKOSのツキヤです!
今回は、マイグレーションファイル内で外部キー制約やインデックスの設定をする方法を説明します!

ツキヤ

$ rails g model User$ rails g migrationで作成される20220711134216_create_users.rbのようなファイルのことだね!

外部キー制約とは?

外部キー制約の具体例

外部キー制約とは、外部キーに対して親テーブルのIDとして存在するものしか指定できないようにする制約のことです。
userテーブルとtweetテーブルが1対多の関係だとすると、下記画像のような関係になります!

外部キー制約を指定することで、データの整合性をより正確にすることができます😎

インデックスとは?

かなり簡単に説明すると、カラムに指定すると、データを取得する時に早くなるものです!
また、ユニーク(一意)制約をかけることも可能です!
ここで詳しく説明するとかなり長くなってしまうので、詳しく調べたい方はググって見てください🙇‍♂️

対象読者

  • マイグレーションファイルに外部キー制約を指定したい方
  • マイグレーションファイルにインデックス・ユニーク制約を指定したい方

外部キー系

references

よく使用されるreferencesには、下記のようにforeign_key: trueを入れるだけで完了です!

class CreateTweets < ActiveRecord::Migration[7.0]
  def change
    create_table :tweets do |t|
      t.references :user, null: false, foreign_key: true
      t.string :body, null: false

      t.timestamps
    end
  end
end

今まで入れてなかった人は、とりあえず書いておいた方が無難です!

ツキヤ

foreign_key: trueが無くても、モデルファイル(user.rb)でhas many tweetsとすればリレーション自体は可能だけど、意図しない値の混入を防ぐためにも設定しておこう!

また、外部キー制約を詳細にカスタマイズすることもできます!

class CreateTweets < ActiveRecord::Migration[7.0]
  def change
    create_table :tweets do |t|
      t.references :user, null: false, foreign_key: { on_update: :cascade, on_delete: :cascade }
      t.string :body, null: false

      t.timestamps
    end
  end
end

on_updateは、親のID(この場合はuser.id)が変更された場合にどうするかの挙動を決めています。
:cascadeは、親の変更に合わせるという意味です!
on_deleteも同様に、親のIDが削除された際の挙動の設定です!
ここまで設定をしておくと、更にデータの整合性がとれそうですね😎

後から設定する場合

テーブル作成の際に忘れてしまった場合や、後で制約を変更したい場合は下記のように記述すればOKです!

class AddForeignKeyTweets < ActiveRecord::Migration[7.0]
  def change
    add_foreign_key :tweets, :users, column: :user_id, on_update: :cascade, on_delete: :cascade
  end
end

 
逆に削除したい場合は下記のような記述です!

class RemoveIndexTweets < ActiveRecord::Migration[7.0]
  def change
    remove_foreign_key :tweets, :users, column: :user_id
  end
end

ツキヤ

add_foreign_keyremove_foreign_keyの違いだね!

インデックス系

基本形

インデックスに関しては、つけたいカラムに対してindex: trueと記述するだけです!

class CreateTweets < ActiveRecord::Migration[7.0]
  def change
    create_table :tweets do |t|
      t.references :user, null: false, foreign_key: true
      t.string :body, null: false, index: true

      t.timestamps
    end
  end
end

referencesの場合は、自動でインデックスが付いているので特になにもしなくてOKです!

ユニーク制約

ここからはユニーク制約です!
テーブル設計の際に、has_one(1対1)の実装をする時があると思います。
この際に同じ親IDをもつレコードは要らないのでユニーク制約を下記のようにかけます!

class CreateUserInformations < ActiveRecord::Migration[7.0]
  def change
    create_table :user_informations do |t|
      t.references :user, null: false, foreign_key: true, index: { unique: true }
      t.string :body, null: false

      t.timestamps
    end
  end
end

index: { unique: true }の部分ですね!
こうするだけでユニーク制約がかかります!

複合ユニーク制約

お次は複数のカラムに対するユニーク制約です!
例えば、お気に入り機能を実装する際に「同じユーザーが1つの投稿に2回以上お気に入りをさせない」場合に必要かと思います!

class CreateFavorites < ActiveRecord::Migration[7.0]
  def change
    create_table :favoritess do |t|
      t.references :user, null: false, foreign_key: true
      t.references :tweet, null: false, foreign_key: true

      t.timestamps
    end
    add_index :favorites, [:user_id, :tweet_id], unique: true
  end
end

9行目のような記述をすることで、複合ユニーク制約の実装ができます!
2つのカラムにまたがる設定のため、別途設定のための文書を記述してあげる必要があります!

後から設定する場合

こちらも外部キー制約と同じく下記の様な形になります!
削除もまとめて書いちゃいます!

class AddIndexTweets < ActiveRecord::Migration[7.0]
  def up
    add_index :favorites, [:user_id, :tweet_id], unique: true
  end
	
  def down
    remove_index :favorites, [:user_id, :tweet_id], unique: true
  end
end

終わりに

今回はモデルファイルでは無く、DB側での制約を紹介しました!
ユニーク制約の場合、アプリケーション側(モデルファイル)で同様のバリデーションをかけないとエラーになってしまいますので注意してください(ex: validates: column_name, uniquness: true
アプリケーション側でちゃんとvalidationをかけることも大事ですが、DB側でも今回のような制約を加えることでより堅牢なサービスを目指しましょう😆
Railsの勉強をしたい方はこの本が参考になるかと思いますので興味がある方は是非読んで見て下さい🙇‍♂️