<Ruby on Rails> Active Record の関連付け

railsガイドを参考に、関連付けについて記載する。
自分のための振り返りメモなので、随時変更する可能性あり。
参考:Active Record の関連付け - Railsガイド

belongs_to

1対1の繋がりが設定される。
この宣言を行ったモデルのインスタンスは他方のモデルのインスタンスに従属(=belongs to)する。
例えば掲示板の投稿(board)とユーザー(user)で、一つの投稿に対して一人のユーザーが存在するので1対1の関係となる。

class board < ApplicationRecord
  belongs_to :user
end

Image from Gyazo

belongs_toで指定するモデルは必ず単数形。
関連付けの名前から自動的にモデルのクラス名を推測するので、複数形にしてしまうと推測されるクラス名も誤ったものになり、uninitialized constant(名前が間違っている)エラーがでる。
また、belongs_toを記載したモデルは、従属するモデルに対応する外部キーを作成する必要がある(今回の場合はuser_idカラムををboardモデルに追加)。
rails g migrationを実行する時、'従属される側のモデル:references'を追記すると外部キーのforeign_key:trueが従属モデルのmigrationファイルに追加され、modelにはbelongs_toが追加される。(公式では:belongs_toでも可とある)
例:userモデルとboardモデルの関連付け

# userモデル作成
$ rails g model user

# boardモデル作成
$ rails g model board user:references

$ rails db:migrate

作成されるモデルクラス

# app/models/user.rb
class User < ApplicationRecord
end

# app/models/board.rb
class Board < ApplicationRecord
  belongs_to :user
end

作成されるmigrationファイル

# db/migrate/...create_users.rb
class CreateUsers < ActiveRecord::Migration[6.0]
  def change
    create_table :users do |t|

      t.timestamps
    end
  end
end

# db/migrate/...create_boards.rb
class CreateBoards < ActiveRecord::Migration[6.0]
  def change
    create_table :boards do |t|
      t.references :user, null: false, foreign_key: true

      t.timestamps
    end
  end
end

作成されるschemaファイル

ActiveRecord::Schema.define(version: 2020_11_22_120323) do

  create_table "boards", force: :cascade do |t|
    t.integer "user_id", null: false
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
    t.index ["user_id"], name: "index_boards_on_user_id"
  end

  create_table "users", force: :cascade do |t|
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
  end

  add_foreign_key "boards", "users"
end

参考ページ: Active Record マイグレーション - Railsガイド

has_one

1対1の繋がりが設定される。
belongs_toとの違いは、宣言が行われているモデルのインスタンスが、他方のインスタンスを丸ごと含んでいる、または所有していること。
所有する側に従属するモデルのidがモデル名_idという形で関連付けられる。 例えば、一人の供給者(supplier)が一つのアカウント(account)を持つため、1対1の関係になる。

Image from Gyazo

class Supplier < ApplicationRecord
  has_one :account
end

宣言が行われているsupplierが一人につき、一つのaccountを所有しているため、has_oneを使用する。
この場合、accountモデルにはbelongs_toが設定される。

has_one :through

他方のモデルに対して「1対1」のつながりを設定する。
2つのモデルの間に「第3のモデル」(joinモデル)が介在する点が特徴。 相手モデルの1つのインスタンスとマッチする。 例えば1人の提供者(supplier)が1つのアカウントを持ち、アカウントが1つのアカウント履歴を持つ場合、
supplierはaccountを一つ所有(has_one)しており、accountを通して一つのaccount履歴を所有(has_one)していると言える。

Image from Gyazo

class Supplier < ApplicationRecord
  has_one :account
  has_one :account_history, through: :account
end

class Account < ApplicationRecord
  belongs_to :supplier
  has_one :account_history
end

class AccountHistory < ApplicationRecord
  belongs_to :account
end

has_many

他モデルとの間が1対多の繋がりがある。
belongs_toの例で使用したユーザー(user)と掲示板(board)の場合、ユーザー一人は複数の投稿ができるため、1対多の関係と言える。
Userモデルにはhas_manyを記載。

Image from Gyazo

class User < ApplicationRecord
  has_many :boards
end

has_manyで関連付けするモデルは必ず複数形。

has_many :through

他方のモデルと「多対多」のつながりを設定する。
2つのモデルの間に「第3のモデル」(joinモデル)が介在する点が特徴。
それによって、相手モデルの「0個以上」のインスタンスとマッチする。
例えば患者(patient)が医師(physician)との診察予約(appointment)を取る医療業務で考えると、
一人の患者が診察予約を一人の医師に対して行うことから、診察予約の視点だとpatientとphysicianにappointmentは従属している(belongs_to)、と言える。
一方、患者や医師の視点で見ると、複数の診察予約を持つことができるので、1対多となるhas_manyが設定される。 医師と患者を結びつけるために:throughオプションにappointmentsが記載される。

Image from Gyazo

class Physician < ApplicationRecord
  has_many :appointments
  has_many :patients, through: :appointments
end

class Appointment < ApplicationRecord
  belongs_to :physician
  belongs_to :patient
end

class Patient < ApplicationRecord
  has_many :appointments
  has_many :physicians, through: :appointments
end

絵にすると下図の通り。

Physician視点

Image from Gyazo

Appointment視点

Image from Gyazo

Patient視点

Image from Gyazo

has_and_belongs_to_many

他方のモデルと「多対多」のつながりを作成する。
through:オプションを使用する場合と異なり、第3のモデル(joinモデル)が介在しないが、結合テーブルは必須。
例えば、アプリケーションに完成品(assembly)と部品(part)があり、1つの完成品に多数の部品が対応し、逆に1つの部品にも多くの完成品が対応する場合は以下のようになる。

Image from Gyazo

class Assembly < ApplicationRecord
  has_and_belongs_to_many :parts
end

class Part < ApplicationRecord
  has_and_belongs_to_many :assemblies
end

has_and_belongs_to_manyとhas_many :throughの使い分けについて

Active Record の関連付け - Railsガイド

双方の関連付け

関連付けは基本的には双方向で定義される。

class User < ApplicationRecord
  has_many :boards
end

class Board < ApplicationRecord
  belongs_to :user
end

Userモデル(ユーザー)とBoardモデル(掲示板)があり、上記のように関連付けされている場合、以下のようにデータの関連を共有していることがわかる。

a = User.first
b = a.Boards.first
a.first_name == b.user.first_name # => true
a.first_name = 'David'
a.first_name == b.user.first_name # => true

ただし、Active Recordでスコープやthroughオプションやforeign_keyを使った場合、双方向の関連付けは自動的に認識されないため、その時は:inverse_ofオプションを使用する。
詳しくはRailsガイドの'ActiveRecordの関連付け:双方関連付け'を参照。