テーブル同士を関連付ける設定

[テーブル同士を関連付けさせる方法]

既存のそれぞれのテーブルに以下のようにマイグレーションファイルを記述し、データベース上で紐付けをする。 (1つのUserに対して複数のBoard[掲示板]を持つように[1:多]の関係になるように、BoardにUserを示す外部キー[user_id]を与える。)

[db/migrateファイル]

def up
    execute 'DELETE FROM boards;'                
#executeでクエリが実行される。(既存のboardレコードが存在すると紐付くuserが決められず、[user_id]にNOT NULL制約をしていた場合、設定していたNOT NULL制約に引っかかってしまう為、既存のBoardsテーブルを全て削除)
    add_reference :boards, :user, null: false,  foreign_key: true        
#既存のテーブルに外部キーを追加する。(foreign_keyは、外部キー制約)
end

def down
    remove_reference :boards, :user, foreign_key: true 
end

別な書き方で以下のように記述することもできる。(テーブル作成時に外部キーと外部キー制約を付与)

[db/migrate]

def change
    create_table :boards do |t|
      t.string :title,     null:false
      t.text :body,        null:false
      t.references :user, :null: false, :foreign_key: true
      t.timestamps
    end
end

※注意
・外部キー制約を付ける場合は、自動でインデックスが付与される。
・以下のようにreference型を使わない場合は、[foreign_key: true]では外部キー制約にならない。

[db/migrateファイル]

def change
    create_table :boards do |t|
      t.string :title,     null:false
      t.text :body,        null:false
      t.integer :user_id
      t.timestamps
    end
     add_foreign_key :boards, :users    # add_foreign_key :対象のテーブル名, :指定先のテーブル
end

[外部キー制約とは?]

1.存在しない値を外部キーとして登録できない。
2.子テーブルの外部キーに値が登録されている場合、親テーブルのレコードは削除できない。(親テーブルだけを削除して子テーブルの外部キーの親が存在しないことを防ぐため)
上記の2の条件を破る方法は、下記のようにモデルに[dependent: :destroy]オプションを指定する。

[app/models]

has_many :boards, dependent: :destroy       #親モデルが削除された際に、子モデルも一緒に削除する

[関連したモデルのインスタンスを取得する設定方法] 以下のように[1:多]の関係にあるモデルに以下のように記述する。

[app/models]

has_many :boards, dependent: :destroy     #関連付けさせる先のモデル名を複数形にする。
[app/models]

belongs_to :user    #関連付けさせる先のモデル名を単数形にする。

※注意
[has_many]や[belongs_to]は、[validates]より上に記載した方が良い。(rubocop規約のため。)

※ポイント

Railsガイドなどには外部キーを付与する際に以下のように[t.belongs_to]と記載があるが、[t.references]と何が違うのか気になったのでこちらにまとめます。
結論、[t.belongs_to]と[t.references]は同じです。

    create_table :appointments do |t|
      t.belongs_to :physician  ①
      t.belongs_to :patient
      t.datetime :appointment_date
      t.timestamps
    end
    create_table :appointments do |t|
      t.references:physician           ②
      t.references:patient
      t.datetime :appointment_date
      t.timestamps
    end

上記のように[t.belongs_to]や[t.references]を使用すると以下のようになる
・[モデル名_id]というカラムを自動生成してくれる
・インデックスを自動で付与してくれる
・外部キー制約は付与してくれないので、付与する場合は[foreign_key:true]を指定する必要がある

また、テーブル作成時に以下のようにターミナルで実行すれば

$ rails g model Appointment physician:references patient:references appointment_date:datetime

以下のようなマイグレーションを1発で生成してくれる。

    create_table :appointments do |t|
      t.belongs_to :physician
      t.belongs_to :patient
      t.datetime :appointment_date
      t.timestamps
    end

既に存在するテーブルにカラムを追加する際は、以下のようにターミナルで実行することで外部キーを追加できる。

rails g migration AddPhysicianRefToAppointments physician:references
class AddPhysicianRefToAppointments < ActiveRecord::Migration
  def change
    add_reference :appointments, :physician, index: true
  end
end

[dependent: destroy]を付与すると、関連づけられたレコードも削除してくれる
ex)

classUser < ApplicationRecord
  has_many :posts, dependent: :destroy
end
classPost < ApplicationRecord
  belongs_to :user
end

上記の場合は、ユーザーを削除すると削除したユーザーに関連づいている投稿も削除される

ex)

class Cooking < ApplicationRecord
  has_many :cooking_informations
  has_many :fish, through: :cooking_informations, dependent: :destroy
end
class Fish < ApplicationRecord
  has_many :cooking_informations
  has_many :cookings, through: :cooking_informations, dependent: :destroy
end
class CookingInformation < ApplicationRecord
  belongs_to :cooking
  belongs_to :fish
end

上記の場合は、魚を削除すると削除した魚に関連づいている中間テーブルのレコードも削除される
上記の場合は、料理を削除すると削除した料理に関連づいている中間テーブルも削除される
上記の書き方だと魚を削除すると削除した魚に関連づいている料理も削除されるような書き方だが、throughを使っている場合は、中間テーブルのレコードのみ削除される
また、上記は以下のように記述することもできる

class Cooking < ApplicationRecord
  has_many :cooking_informations, dependent: :destroy
  has_many :fish, through: :cooking_informations
end
class Fish < ApplicationRecord
  has_many :cooking_informations, dependent: :destroy
  has_many :cookings, through: :cooking_informations
end
class CookingInformation < ApplicationRecord
  belongs_to :cooking
  belongs_to :fish
end

参考記事

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

外部キーをreferences型カラムで保存する - Qiita

【Ruby on Rails】「外部キー」の設定のされ方 | プログラミングマガジン

【rails】多対多モデルのデータdestroyで出たエラー - Qiita

dependent: :destroyとdependent: :delete_allの違い【Rails】 - 箱のプログラミング日記。

has_many :through時のdependent: :destroyの挙動 - 35歳からの中二病エンジニア

Cannot delete or update a parent row: a foreign key constraint fails【MySQLエラー】 - 箱のプログラミング日記。