【Ruby on Rails】モデルの関連付け(アソシエーション)をざっと理解してみる 〈図解あり〉

## はじめに
初めまして！この記事では、RailsのActiveRecordの関連付け(アソシエーション)について解説します。
私自身もRailsの初学者で、関連付けを学ぶ際にモデル同士の関係が把握しにくかった経験があります。そこで、基本的な内容ではありますが、この記事では一対一、一対多、多対多の関連付けに焦点を当て、解説していきます。もし記事の中に不正確な情報や改善点があれば、ご指摘いただけると助かります🙇‍♀️
## アソシエーションとは
![3.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3556556/c8509ece-2211-b85c-d18a-54e20578f17e.jpeg)


データベースの異なるテーブルや**モデル同士を関連づけ、効果的にデータを結びつける仕組み**のことです。
これにより、アプリケーション内で違うデータ同士を組み合わせて<INS>**取り出したり、更新したりすることが簡単**</INS>にできるようになります！

## メジャーな関連付けのパターン3つ
まずはよく使う基本的な関連付けを紹介します。

**①一対一**
**②一対多**
**③多対多**

## 一対一
:::note info
**POINT**
* has_one
* belongs_to
:::

![7.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3556556/5d6d0c30-b3a8-78d0-196b-719b8e0e4b8d.jpeg)
一番わかりやすい例は、**「Userモデル」と「Profileモデル」の関係**です。
この場合、ユーザーとプロフィールの関係は
* 「ある1人のユーザーは、**1つのプロフィールを持っている**」
* 「プロフィールは、**ユーザーに所属**している」
といえます。

それぞれのテーブルには以下のようなカラムが設定されているとします。(一部省略)
![スクリーンショット 2023-11-28 14.16.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3556556/4cb7d58d-3f64-45d0-13dd-58d4ec5daa8a.png)
※user_idについては後ほど解説します。


id⇨1のユーザーが、id⇨1のプロフィールを持っている。
<font color="green">◼︎</font> ユーザーに対してプロフィールは一つなので、「has_**one** :profile」となります。
<font color="green">◼︎</font> プロフィールはユーザーに属する(belongs to)ことになるので、「**belongs_to** :user」となります。
モデルに記載する際は以下の通りになります。

```ruby:app/models/user.rb
class User < ApplicationRecord
  has_one :profile
end
```
```ruby:app/models/profile.rb
class Profile < ApplicationRecord
  belongs_to :user
end
```

## 一対多
:::note info
**POINT**
* has_many
* belongs_to
:::
![8.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3556556/dd8903f1-74c8-de30-90cc-bc4b6917812b.jpeg)

一対多の関係は、**「Userモデル」** と **「Post(投稿)モデル」** の関係で説明します。
この場合、ユーザーと投稿の関係は
* 「1人のユーザーは、**複数の投稿を持っている**」
* 「1つの投稿は、**1人のユーザーが作成している**」
といえます。

それぞれのテーブルには以下のようなカラムが設定されているとします。(一部省略)
![スクリーンショット 2023-11-28 15.10.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3556556/e623ab76-5b4b-d598-21e2-5e30eeb5a5e3.png)

(例)id⇨1のユーザーが、id⇨1、5、10、24の投稿を持っている。
<font color="green">◼︎ </font>ユーザーに対して**投稿は複数**なので、「has_**many** :post**s**」となります。(たくさん持つのでsがつく)
<font color="green">◼︎ </font>投稿はユーザーに属する(belongs to)ことになるので、「**belongs_to** :user」となります。
モデルに記載する際は以下の通りになります。

```ruby:app/models/user.rb
class User < ApplicationRecord
  has_many :posts
end
```

```ruby: app/models/post.rb
class Post < ApplicationRecord
  belongs_to :user
end
```

## 多対多
:::note info
**POINT**
* has_many
* belongs_to
* 中間テーブル
:::
![tataiat.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3556556/e7c85666-45ea-398c-cb27-3a304e97ddb3.jpeg)

多対多の関係はお気に入り機能が一般的です。
この場合、ユーザーと投稿の関係は
* 「1人のユーザーは、**お気に入りしている投稿を複数持っている**」
* 「1つの投稿は、**お気に入りされた複数のユーザーの情報を持っている**」

といえます。

お気に入りを持っているので、「ユーザーとお気に入り、投稿とお気に入りの関係が多対多なのでは？？」とうっかりわからなくなってしまいそうですが、
**お気に入り(Favoriteモデル)を通してユーザーと投稿の関係は多対多**であるということになります。

#### 中間テーブル
>Favoritesテーブルは、ユーザーがどの投稿をお気に入りにしたのか、投稿がどのユーザーにお気に入りされたのかを管理していることになります。(その機能を使ってお気に入り機能を実装できる。)
Favoriteモデルのように、多対多の関係を取り持つテーブルのことを「<font color="green">**中間テーブル**</font>」と呼びます。

それぞれのテーブルには以下のようなカラムが設定されているとします。(一部省略)
![スクリーンショット 2023-11-28 16.30.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3556556/3a111470-64ed-421f-b03e-0d0d33f384fc.png)

多対多の関係のテーブルは中間テーブルなしでは複雑になってしまいます。
中間テーブルを使用することで、クエリが簡素化されたり、将来的な変更や拡張が容易になり、新しい要件に対応しやすくなったりするというメリットがあります。

---
### こんな時はどう？？

#### ▶︎ 1つの投稿にカテゴリーが1つ付けられる
![10.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3556556/67bcfa20-418f-3834-92b9-d21ce95b5175.jpeg)
これはわかりやすいですね。
この場合、カテゴリーと投稿の関係は
* 「1つのカテゴリーは、**複数の投稿を持っている**」
* 「1つの投稿は、**1つのカテゴリーを持っている**」
ので、一対多の関係です。
---

#### ▶︎ 1つの投稿に複数のカテゴリーが付けられる場合
![11.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3556556/05114fb2-5b9f-5f5d-6d60-f92b427c1068.jpeg)
この場合、カテゴリーと投稿の関係は
* 「1つのカテゴリーは、**複数の投稿を持っている**」
* 「1つの投稿は、**複数のカテゴリーを持っている**」

ので、多対多の関係です。
**カテゴリーと投稿の組み合わせを管理するため**に、post_categoriesという中間テーブルを作成します。

この場合のコードは以下のようになります。
```ruby:models/post.rb
class Post < ApplicationRecord
  has_many :post_categories
  has_many :categories, through: :post_categories
end
```

```ruby:models/category.rb
class Category < ApplicationRecord
  has_many :post_categories
  has_many :posts, through: :post_categories
end
```

```ruby:models/post_category.rb
# 中間テーブル
class PostCategory < ApplicationRecord
  belongs_to :post
  belongs_to :category
end
```
---
### もう少し複雑になる
話は先ほどのお気に入り機能に戻ります。

この多対多の説明では、「お気に入りのテーブル(Favoriteモデル)を通して、**ユーザー(Userモデル)と投稿(Postモデル)は多対多の関係である**」としましたが、

一対多の説明では、**「ユーザー(Userモデル)と投稿(Postモデル)は一対多の関係である」** としました。

「ユーザーと投稿の関係は一対多、多対多のどっちなんだ？？」と疑問を持った方もいらっしゃるかもしれませんが(私がそうでした)、これはどっちも正解です。

![中間テーブル(お気に入り).jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3556556/6e0e7699-ae5c-784d-3820-7b503ac77e45.jpeg)

**一対多**
* 「1人のユーザーは、**複数の投稿を持っている**」
* 「1つの投稿は、**1人のユーザーが作成している**」

**多対多**
* 「1人のユーザーは、**お気に入りしている投稿を複数持っている**」
* 「1つの投稿は、**お気に入りされた複数のユーザーの情報を持っている**」

この2つは同時に成り立ちます。


この場合のコードは以下のようになります。
```Ruby:app/models/user.rb
class User < ApplicationRecord
  has_many :posts
  has_many :favorites
  has_many :favorite_posts, through: :favorites, source: :post
end
```

```Ruby:app/models/post.rb
class Post < ApplicationRecord
  belongs_to :user
  has_many :favorites
  has_many :favorited_users, through: :favorites, source: :user
end
```

```Ruby:app/models/favorite.rb
class Favorite < ApplicationRecord
  belongs_to :user
  belongs_to :post
end
```
#### sourceオプション
has_many :favorited_usersのように書いただけでは、Railsは自動的に:favorited_usersなどの名前を探しに行くため、sourceオプションを使って明示的に関連するモデルを指定することで、名前の重複を避けつつ正しい関連付けを行います。


## dependent: :destroy
dependent: :destroyを使用すると、「ユーザーを削除すると、作成した投稿も消える」　といったように、親モデルのレコードが削除されたときに、それに関連する子モデルのレコードも同時に削除されます。
逆に、dependent: :destroyを使用しない場合、親モデルが削除されても関連する子モデルのレコードは削除されません。

![12.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3556556/ce6c3e89-4c99-2b0b-3a3d-a0305ca2e4de.jpeg)
※親モデルに:dependentオプションを追加

```ruby:app/models/user.rb
class User < ApplicationRecord
  has_many :posts, dependent: :destroy #親モデルに追加
end
```

```ruby:app/models/post.rb
class Post < ApplicationRecord
  belongs_to :user
end
```



コントローラーでこのようにすると、ユーザーが削除された時にそのユーザーに関連する全ての投稿も削除されます。
```ruby:app/controllers/users_controller.rb
class UsersController < ApplicationController
  def destroy
    @user = User.find(params[:id])
    @user.destroy

    redirect_to users_path, notice: 'ユーザーが削除されました。'
  end
end
```

## おわりに
ここまでActive Record の関連付け機能(アソシエーション)について、簡単にですが解説させていただきました。他にも、単一テーブル継承　(STI)やポリモーフィック関連付けなどもあるので調べてみてください。最後までご覧いただきありがとうございます。

https://qiita.com/niwa1903/items/218713c076fb0075712f

https://qiita.com/itkrt2y/items/32ad1512fce1bf90c20b

https://railsguides.jp/association_basics.html#%E5%8D%98%E4%B8%80%E3%83%86%E3%83%BC%E3%83%96%E3%83%AB%E7%B6%99%E6%89%BF-%EF%BC%88sti%EF%BC%89