Railsの外部キー制約とreference型について

[株式会社TECH LUCK](https://techluck.co.jp/?utm_source=qiita&utm_medium=article&utm_campaign=myarticle)という会社で代表兼エンジニアをしている齊藤です。

DXプロジェクト、開発プロジェクト、Rails開発などでお困りごとがありましたら[弊社HP](https://techluck.co.jp/?utm_source=qiita&utm_medium=article&utm_campaign=myarticle)からご相談をいただけますと幸いです。
以下のような問題に対応することが可能です。
- プロジェクトでRailsエンジニアが足りなくて困っている
- Railsのバージョンアップをしたいがノウハウ・リソースが足りなくて困っている
- オフショア開発をしているが、要件の齟齬やコード品質が悪いので改善したい

また、Railsエンジニアも募集しておりますので、興味がありましたら[弊社HP](https://techluck.co.jp/?utm_source=qiita&utm_medium=article&utm_campaign=myarticle)からご連絡いただけますと幸いです。

# 前提

Railsで外部キーのカラムを追加する際に、reference型を使うことがあると思います。
reference型の使い方に関しては以下のように様々な記事があります。

[外部キーをreferences型カラムで保存する](https://qiita.com/sinagaki58/items/7edea51ef00e393834ca)
[Rails4 外部キーをテーブルに設定するための、3通りのマイグレーションの書き方。](
http://tkymtk.hatenablog.com/entry/rails-4-three-way-to-write-migration-2014-1)

しかし、reference型で作成する時は`add_index: true`必要なの？`foreign_key`必要なの？
と混乱してしまうので、備忘録のためにまとめておきます。

# 前提
アプリケーションとしてはTwitterの簡易版で、登録しているユーザーがツイートができるアプリケーションです。
テーブル構造としては
**user : tweet = 1 : N**
の関係になっています。
テーブル名はそれぞれ、`users`と`tweets`です。

# reference型を使わない場合
まずはreference型を使わないで、外部キー制約のついたカラムを作成します。

### 制約などなにもないカラムの作成
以下のように記述すると、インデックスが貼られない、外部キー制約もつかないカラムを作成することができます。

```2017_create_tweets.rb
class CreateTweets < ActiveRecord::Migration[5.0]
  def change
    create_table :tweets do |t|
      t.string   :text
      t.integer  :user_id
    end
  end
end
```

### インデックスを貼るカラムの作成
インデックスを貼る場合、2通りの書き方があるようです。
インデックスとはなんぞやという方は [こちら](https://qiita.com/kkyouhei/items/e3502ef632c48d94541d) をご覧ください。

```2017_create_tweets.rb
class CreateTweets < ActiveRecord::Migration[5.0]
  def change
    create_table :tweets do |t|
      t.string    :text
      t.integer   :user_id, index: true #indexオプション
    end
  end
end
```

```2017_create_tweets.rb
class CreateTweets < ActiveRecord::Migration[5.0]
  def change
    create_table :tweets do |t|
      t.string    :text
      t.integer   :user_id
    end
    add_index :tweets, :user_id
    # add_index :対象のテーブル名, インデックス対象のカラム名
  end
end
```

### 外部キー制約が付いているカラムの作成
**reference型を使わない場合、`foreign_key: true`では外部キー制約にならないので注意が必要です。**
また、外部キー制約をつける場合、インデックスは自動で付与されるので、先ほどの`index: true`は不要になります！（便利ですね）

```2017_create_tweets.rb
class CreateTweets < ActiveRecord::Migration[5.0]
  def change
    create_table :tweets do |t|
      t.string   :text
      t.integer  :user_id
    end
    add_foreign_key :tweets, :users
    # add_foreign_key :対象のテーブル名, :指定先のテーブル 
  end
end
```

# reference型を使う場合
次に、reference型を使う場合の外部キー制約のついたカラムを作成します。

### reference型を使うメリット
reference型を使い、`t.reference :user`と記述すると2つのメリットがあります。

- `user`ではなく`user_id`というカラム名を作成してくれる
- インデックスを自動で張ってくれる

```2017_create_tweets.rb
class CreateTweets < ActiveRecord::Migration[5.0]
  def change
    create_table :tweets do |t|
      t.string    :text
      t.references :user
    end
  end
end
```

**しかし、`t.reference :user`だけでは外部キー制約はつきません。**
ですので、reference型を使う場合は以下のように記述して、外部キー制約をつけることになります。
**reference型では外部キー制約をつけるときに、`foreign_key: true`が使えるようになります!**

```2017_create_tweets.rb
class CreateTweets < ActiveRecord::Migration[5.0]
  def change
    create_table :tweets do |t|
      t.string     :text
      t.references :user, foreign_key: true
    end
  end
end
```


ちなみに以下のように`add_foreign_key`でも大丈夫です。

```2017_create_tweets.rb
class CreateTweets < ActiveRecord::Migration[5.0]
  def change
    create_table :tweets do |t|
      t.string     :text
      t.references :user
    end
    add_foreign_key :tweets, :users
  end
end
```

# まとめ
外部キー制約のついたカラムを作る際は、以下のように記述するのが手っ取り早いです。

```2017_create_tweets.rb
class CreateTweets < ActiveRecord::Migration[5.0]
  def change
    create_table :tweets do |t|
      t.string     :text
      t.references :user, foreign_key: true
    end
  end
end
```

ちなみに`add_column`の場合は以下の形が手っ取り早いです。

```2017_add_column.rb
class AddColumn < ActiveRecord::Migration[5.0]
  def change
    add_reference :tweets, :user, foreign_key: true
  end
end
```

## 追記
紐付き先のテーブル名とカラム名に別名にしたい場合は以下のようになります。

```ruby
class CreateUsers < ActiveRecord::Migration[5.0]
  def change
    create_table :teams do |t|
      t.bigint :owner_id, index: true
    end

    add_foreign_key :tweets, :users, column: :owner_id
  end
end
```

```ruby
class AddColumnToUsers < ActiveRecord::Migration[5.0]
  def change
    add_reference :tweets, :owner, foreign_key: { to_table: :users }
  end
end
```


# 参考にしたサイト
[Railsマイグレーションのindex、foreign_keyの設定](https://qiita.com/shitake4/items/2f08c0d9b13d090c6071)
[Railsで外部キー制約のついたカラムを作る時のmigrationの書き方](https://qiita.com/publichtml/items/1fba15d8071fab66d043)
[Rails4 外部キーをテーブルに設定するための、3通りのマイグレーションの書き方。](http://tkymtk.hatenablog.com/entry/rails-4-three-way-to-write-migration-2014-1)
[Railsマイグレーションの外部キー制約を表現するreferencesについて](https://qiita.com/suin/items/ad8e6c844d68cedf6150)
[Railsで別名の外部キーを設定する方法](https://qiita.com/j-sunaga/items/ee1fb558807c04243f0f)