【Ruby on Rails】Active Recordのモデル同士のアソシエーション（関連付け）

# はじめに

Railsなどを中心に勉強中のエンジニア初心者が他の記事を参考にしたり、実際に実装してみたりして、アウトプットの一環としてまとめたものです。
本投稿は、文献を参考にしてまとめてみたものですが、テーブル設計（ER図）については筆者が独自に行なったため、間違っていることもあると思われるので、その際は指摘いただけると幸いです。

# アソシエーション（関連付け）とは

アソシエーション（関連付け）とは、テーブル間のリレーションシップをモデル上の関係として操作できるようにする仕組みのこと。アソシエーションを利用することで、複数のテーブルにまたがるデータ操作が、より直感的に利用できるようになる。

# ER図

下記のER図を前提として、各アソシエーションについてまとめる。

![スクリーンショット 2023-02-26 19.48.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2658523/0089113a-9a41-8d40-0b8b-fa4d5df7a956.png)

# belongs_to（参照元テーブルから被参照テーブルにアクセスする）

`belongs_to`メソッドは、`現在のモデルから相手先のモデルを参照する`というアソシエーションを宣言する。

例えば、`reservations` → `rooms`テーブルのような関係性。

`reservations`テーブルが`room_id`を外部キーにして、`rooms`テーブルを参照している。

### 基本形

参照先のモデル名を単数形で指定する。

> 複数形で指定すると、エラーになるため注意。
Railsはアソシエーション宣言時の名前から自動的にモデルのクラス名を推測すため、宣言名が複数形になってしまっていると、そこから推測されるクラス名も複数形になってしまう。
> 

```ruby
# モデルの定義
belongs_to :単数形のモデル名

# 参照先テーブルへのアクセス
モデルオブジェクト.関連名
```

### 実装例

```ruby
# モデルの定義
class Reservation < ApplicationRecord
  belongs_to :room
end
```

```ruby
# 参照先テーブルへのアクセス
@reservation = Reservation.find(1)
@reservation.room
```

### 備考

`belongs_to`のみでは、一方向のアソシエーションのみである。

そのためアソシエーションを完成させるためには、双方向にアソシエーションを行う必要がある。

相手側のモデルに対して、`has_one`または`has_many`を指定する。

# has_one（1 : 1の関連付けを宣言する）

`has_one`メソッドは、`1:1（被参照テーブル → 参照先テーブル）`のアソシエーションを宣言する。

> 相手のモデルがこのモデルへの参照を持っていることを表す。
> 

例えば、1件のユーザ情報（`users`）が0、または1件のプロフィール（`profile`）をもつような関係性。

### 基本形

```ruby
# モデルの定義
has_one :単数形のモデル名

# 参照元テーブルへのアクセス
モデルオブジェクト.関連名
```

### 実装例

```ruby
# Userモデルの定義
class User < ApplicationRecord
  has_one :profile
end

# 参照元テーブルへのアクセス
@user = User.find(1)
@user.profile
```

### 注意

`belongs_to`メソッドとセットで利用することで、参照元／参照先テーブル双方向の関連を表現する。

`1:1`のアソシエーションにおいて、主となるモデルに`has_one`を宣言し、従となるモデルに`belongs_to`を宣言するのが基本。

# has_many（1 : nの関連を宣言する）

`has_many`メソッドは、`1:n（被参照テーブル → 参照元テーブル）`のアソシエーションを宣言する。

> 相手のモデルとの1:多のつながりを表している
> 

例えば、1件の`room`が複数の`reservations`を持つような関係を表している。

### 基本形

```ruby
# モデルの定義
has_many :複数形のモデル名

# 参照元テーブルへのアクセス
モデルオブジェクト.関連名
```

### 実装例

```ruby
# Postモデルの定義
class Room < ApplicationRecord
  has_many :reservations
end

# 参照元テーブルへのアクセス
@room = Room.find(1)
@room.reservations
```

# has_and_belongs_to_many（m : nの関連を宣言する、中間テーブル）

`has_and_belongs_to_many`メソッドは、`m:n`のアソシエーションを宣言する。

> 相手モデルのと多対多のつながりを表している
> 

リレーショナルデータベースでは、このような関係は直接表現できないため、中間テーブルを使って表すのが一般的。

例えば、`rooms`テーブルと`categories`テーブルとを中間テーブル`rooms_categories`が仲介するような関係性。

> この時、中間テーブルは主キーもタイムスタンプ（`created_at`や`updated_at`）などは持ってはいけない。
また、アプリケーション側で意識する必要はなく、モデルとして作成する必要もない。
> 

### 基本形

```ruby
# モデルの定義
has_and_belongs_to_many :複数形のモデル名

# 参照先テーブルへのアクセス
モデルオブジェクト.関連名
```

### 実装例

```ruby
# Room、Categoryモデルの定義
class Room < ApplicationRecord
  has_and_belongs_to_many :catogires
end

class Category < ApplicationRecord
  has_and_belongs_to_many :rooms
end

# 参照先テーブルへのアクセス
@room = Room.find(1)
@room.categories
```

### 備考

`hashas_and_belongs_to_many`メソッドは利用にあたって制限が多いため、できるだけ`has_many :through`を優先して利用するのが望ましいらしい。

# has_many :through（より複雑なm : nの関連を宣言する）

`has_many`メソッドの`through`オプションを使用することで、より複雑な`m:n`のアソシエーションが宣言できる。

このアソシエーションでは、2つのモデルの間に「第3のモデル」が介在し、それを経由（`through`）して相手のモデルの「0個以上」のインスタンスとマッチする。

> 中間テーブルが外部キー以外の情報を持たない場合は`has_and_belongs_to_many`メソッドが利用できる。
> 

例えば、`users`テーブルと`rooms`テーブルを、中間テーブル`reservations`が仲介するような関係性。

### 基本形

```ruby
# 経由元のモデルの定義
has_many :複数形のモデル名
has_many :経由先のモデル名（複数形）, throguh: :経由するモデル名（複数形）

# 経由するモデルの定義
belongs_to :経由元のモデル名（単数形）
belongs_to :経由先のモデル名（単数形）

# 経由先テーブルへのアクセス
モデルオブジェクト.関連名
```

### 実装例

```ruby
# User、Reservation、Roomモデルの定義
class User < ApplicationRecord
  has_many :reservations
	has_many :rooms, through: :reservations
end

class Reservation < ApplicationRecord
  belongs_to :user
	belongs_to :room
end

class Room < ApplicationRecord
  has_many :reservations
	has_many :users, through: :reservations
end

# 経由先テーブルへのアクセス
@user = User.find(1)
@user.rooms                  # Reservationモデルを介さずにRoomの情報を取得可能
```

### 備考

`has_many :through`によるアソシエーションは、ネストした`has_many`のアソシエーションを介して「ショートカット」を設定することもできる。

例えば、1つの組織（`organizaitons`）に複数の提供者（`suppliers`）が所属しており、複数の提供者が複数の部屋（`rooms`）を提供している状況において、組織に紐づく部屋の情報を提供者をスキップして取得したい場合など。

その場合は、以下のように宣言することができる。

```ruby
# Organization、Supplier、Roomモデルの定義
class Organization < ApplicationRecord
  has_many :suppliers
	has_many :rooms, through: :suppliers
end

class Supplier < ApplicationRecord
  belongs_to :organization
	has_many :rooms
end

class Room < ApplicationRecord
  belongs_to :supplier
end

# 経由先テーブルへのアクセス
@organization = Organization.find(1)
@organization.rooms                  # Supplierモデルを介さずにRoomの情報を取得可能
```

# has_one :through

省略。以下参照。

https://railsguides.jp/association_basics.html#has-one-through関連付け

# 参考

https://railsguides.jp/association_basics.html

https://amzn.asia/d/er9QdcH

https://qiita.com/ramuneru/items/32fbf3032b625f71b69d

# 最後に

いかがでしたでしょうか。
今回はRailsのモデル同士のアソシエーションについてまとめてみました。
ここ違うよ！でしたり、こうした方がいいよ！などがあればコメントいただけると幸いです。

他にも下記のような記事を投稿しております。
興味がありましたら、ぜひご覧ください。

https://qiita.com/sekkey_777/items/8a8b3ee2af3c46df50f0

https://qiita.com/sekkey_777/items/b40135995de01ae95dc2

https://qiita.com/sekkey_777/items/729dada2998d3914e138