railsガイドを参考に、関連付けについて記載する。
自分のための振り返りメモなので、随時変更する可能性あり。
参考:Active Record の関連付け - Railsガイド
belongs_to
1対1の繋がりが設定される。
この宣言を行ったモデルのインスタンスは他方のモデルのインスタンスに従属(=belongs to)する。
例えば掲示板の投稿(board)とユーザー(user)で、一つの投稿に対して一人のユーザーが存在するので1対1の関係となる。
class board < ApplicationRecord
belongs_to :user
end

belongs_toで指定するモデルは必ず単数形。
関連付けの名前から自動的にモデルのクラス名を推測するので、複数形にしてしまうと推測されるクラス名も誤ったものになり、uninitialized constant(名前が間違っている)エラーがでる。
また、belongs_toを記載したモデルは、従属するモデルに対応する外部キーを作成する必要がある(今回の場合はuser_idカラムををboardモデルに追加)。
rails g migrationを実行する時、'従属される側のモデル:references'を追記すると外部キーのforeign_key:trueが従属モデルのmigrationファイルに追加され、modelにはbelongs_toが追加される。(公式では:belongs_toでも可とある)
例:userモデルとboardモデルの関連付け
$ rails g model user
$ rails g model board user:references
$ rails db:migrate
作成されるモデルクラス
class User < ApplicationRecord
end
class Board < ApplicationRecord
belongs_to :user
end
作成されるmigrationファイル
class CreateUsers < ActiveRecord::Migration[6.0]
def change
create_table :users do |t|
t.timestamps
end
end
end
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の関係になる。

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)していると言える。

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を記載。

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が記載される。

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視点

Appointment視点

Patient視点

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

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