# アソシエーション補足資料 下書き
## はじめに
当資料は、カリキュラム【アプリケーションを完成させよう2】以降で登場する「アソシエーション」について、補足解説を記載したものになります。
## 目次
- [アソシエーションとは](#what-is-assosiation)
- [アソシエーションの種類(リレーション)](#relation)
- [アソシエーションの基本的な考え方](#assosiation-basic)
- [Meshiterroで考えてみよう](#examples-in-meshiterro)
<span id="what-is-assosiation"></span>
## アソシエーションとは
Railsにおけるアソシエーション(関連付け、assosiation)とは、2つのモデル同士のつながりを指します。
【アプリケーションを完成させよう2】以前で作成したアプリケーションでは、モデルは1種類しかなかったため、アソシエーションについて考える必要はありませんでした。
しかし、【アプリケーションを完成させよう2】で作成するアプリケーション`Meshiterro`
(※以下、当資料内での`Meshiterro`はこのアプリケーションを指します)
では、より実際のSNSの機能に近づけ、以下の4つのモデルが登場します。
- User: サイトを利用するユーザ
- PostImage: 投稿画像。これまでに登場したList, Blog, Bookと同じ立ち位置
- PostComment: 投稿画像に対してユーザがつけるコメント
- Favorite: 投稿画像に対してユーザがつける"いいね"
これらのモデルは、それぞれ無関係ではなく、
例えばFavoriteは「投稿画像(PostImage)に対してユーザ(User)がつける」という形でPostImage, Userと関係があります。
2つのモデル間で、具体的にどのような関係があるのかを定義するのがアソシエーションになります。
<span id="relation"></span>
## アソシエーションの種類(リレーション)
フェーズⅠで学習するアソシエーションは、以下の2種類です。
| 種類 | 意味 | 例 |
| --- | --- | --- |
| `has_many :models` | `model`を複数持つ。<br>has_manyの後ろは複数形。 | `has_many :post_images` |
| `belongs_to :model` | 1つの`model`に紐づく。belongs_toの後ろは単数形。 |`belongs_to :user` |
なお、このアソシエーションの種類1つ1つのことを「リレーション」(relation)または「リレーションシップ」(ralationship)と呼び、[Railsガイド](https://railsguides.jp/association_basics.html)では「has_manyリレーション」「belongs_toリレーション」のように使われていますが、アソシエーションもリレーションも、どちらもほぼ同じ意味合いに捉えられることが多いです。
また、上の2種類以外にも`has_one`, `has_many :through`などがありますが、ここでは割愛します。
<span id="assosiation-basic"></span>
## アソシエーションの基本的な考え方
アソシエーションの大半は、上で挙がった`has_many`, `belongs_to`です。
これらは親子関係に例えるとより分かりやすくなります。
`has_many :models`は`model`をたくさん持つ(has many)、つまり子がたくさんいるイメージです。
対して、`belongs_to :model`は1つの`model`に紐づく、つまり`model`という1人の親がいるイメージです。
これらを図にすると以下のようになります。
(図つくる)
このように、`has_many`と`belongs_to`はセットであり、どちらが親か分かれば簡単に定義することができます。
<span id="examples-in-meshiterro"></span>
## Meshiterroで考えてみよう
ではここからは、Meshiterroにおけるアソシエーションを考えてみましょう。
今回は、基本の形であるPostImageとUserを例にとります。
### アソシエーションをイメージする
まずは、ユーザが投稿していく流れをイメージしてみましょう。
post_imagesテーブルのカラムは以下の通りです(データ型は省略します)。
| カラム名 | 説明 |
| -------- | -------- |
| id | 投稿画像を識別するID。作成した順に1,2,3,...と振られていく |
| shop_name | お店の名前 |
| image_id | 画像を示すID。refileで用いる |
| caption | 画像(お店)の説明 |
| user_id | 投稿したユーザを識別するID(Usersテーブルのidを保存する) |
カリキュラムでは上記のように`user_id`カラムが既に提示されていますが、
一旦こちらは無いものとして考えてみましょう。
まず、ユーザAが投稿していくと以下のようになります。
(以下、`image_id`カラムを省略します)
<span style="background-color: #ff0;">**※図作った方がいいかなぁ...**</span>
| id | shop_name | caption | 備考 |
| --- | --- | --- | --- |
| 1 | パンケーキランド | 渋谷のおしゃれなカフェです | ユーザAが投稿 |
| 2 | 鉄板焼き 雅 | 最高に美味しかったです | ユーザAが投稿 |
| 3 | タピタピ | バリエーションが豊富で、また来たい | ユーザAが投稿 |
次に、ここに新しいユーザBが投稿しました。
| id | shop_name | caption | 備考 |
| --- | --- | --- | --- |
| 1 | パンケーキランド | 渋谷のおしゃれなカフェです | ユーザAが投稿 |
| 2 | 鉄板焼き 雅 | 最高に美味しかったです | ユーザAが投稿 |
| 3 | タピタピ | バリエーションが豊富で、また来たい | ユーザAが投稿 |
| 4 | 金だこ | 大阪も負けてへんで! | ユーザBが投稿 |
しかし、今のままではどのユーザが投稿したかわかりません。
ですので、何かユーザを識別できるカラムが欲しいですね。
例えばユーザ名`user_name`を追加してみましょう。
ユーザAがtaro、ユーザBがtakeshiだったとします。
| id | shop_name | caption | user_name | 備考 |
| --- | --- | --- | --- | --- |
| 1 | パンケーキランド | 渋谷のおしゃれなカフェです | taro | ユーザAが投稿 |
| 2 | 鉄板焼き 雅 | 最高に美味しかったです | taro | ユーザAが投稿 |
| 3 | タピタピ | バリエーションが豊富で、また来たい | taro | ユーザAが投稿 |
| 4 | 金だこ | 大阪も負けてへんで! | takeshi | ユーザBが投稿 |
一見大丈夫そうですが、例えば更に新しいユーザCが増え、名前がtaroだった場合、
以下のようになってしまいます。
| id | shop_name | caption | user_name | 備考 |
| --- | --- | --- | --- | --- |
| 1 | パンケーキランド | 渋谷のおしゃれなカフェです | taro | ユーザAが投稿 |
| 2 | 鉄板焼き 雅 | 最高に美味しかったです | taro | ユーザAが投稿 |
| 3 | タピタピ | バリエーションが豊富で、また来たい | taro | ユーザAが投稿 |
| 4 | 金だこ | 大阪も負けてへんで! | takeshi | ユーザBが投稿 |
| 5 | Burger Jack | 本場の味です | taro | ユーザCが投稿 |
`taro`がユーザAか、ユーザCか分からなくなってしまいました。
ユーザ名に一意性を持たせる(同じ名前を許さない)という解決法もありますが、
もっと単純に解決する方法があります。
それが、ユーザのidを保存する=`user_id`カラムを持たせる方法です。
idは作成された順に1,2,3,...と振られ、重複することはありません。
`user_name`の代わりに`user_id`カラムを使用すると、以下のようになります。
| id | shop_name | caption | user_id | 備考 |
| --- | --- | --- | --- | --- |
| 1 | パンケーキランド | 渋谷のおしゃれなカフェです | 1 | ユーザAが投稿 |
| 2 | 鉄板焼き 雅 | 最高に美味しかったです | 1 | ユーザAが投稿 |
| 3 | タピタピ | バリエーションが豊富で、また来たい | 1 | ユーザAが投稿 |
| 4 | 金だこ | 大阪も負けてへんで! | 2 | ユーザBが投稿 |
| 5 | Burger Jack | 本場の味です | 3 | ユーザCが投稿 |
これにより、「`user_id = 1`→ id=1のユーザが投稿 → ユーザAが投稿」と特定することができるようになります。
ここまで行ってきた流れを「正規化」といい、【アプリケーションを完成させよう2】1章でも同じような説明をしています。
### アソシエーションを定義する
それでは実際にアソシエーションを定義するコードを記述します。
上で考えた通り、ユーザがたくさん投稿する、なので、「User has many post_images.」となります。
`user.rb`
```ruby=
class User < ApplicationRecord
# 他のコードは割愛します
has_many :post_images
end
```
逆に、投稿は1ユーザに紐づく(属する)なので、「Post_image is belongs to a user」となります。
`post_image.rb`
```ruby=
class PostImage < ApplicationRecord
belongs_to :user
end
```
### アソシエーションによって使用可能になるメソッド
実は上記の定義づけにより、使用可能になるメソッドがあります。
それは、Userクラスに対する`post_images`と、PostImageクラスに対する`user`になります。
先ほどと同じデータで考えてみます。
| id | shop_name | caption | user_id | 備考 |
| --- | --- | --- | --- | --- |
| 1 | パンケーキランド | 渋谷のおしゃれなカフェです | 1 | ユーザAが投稿 |
| 2 | 鉄板焼き 雅 | 最高に美味しかったです | 1 | ユーザAが投稿 |
| 3 | タピタピ | バリエーションが豊富で、また来たい | 1 | ユーザAが投稿 |
| 4 | 金だこ | 大阪も負けてへんで! | 2 | ユーザBが投稿 |
| 5 | Burger Jack | 本場の味です | 3 | ユーザCが投稿 |
#### post_imagesメソッド
`post_images`は、紐づくpost_imagesテーブルのレコード全てを返します。
具体的には`@user.post_images`のように使用します。
例えば`@user = User.find(1)`(=ユーザA)の場合、
`@user.post_images`によって、post_imagesテーブルのレコードのうち「user_id=1」のもの全てを返します。
上記の例では、id=1,2,3のレコードが返ることになります。
`@user.post_images`の時点では配列なので、`PostImage.all`の時と同様、
1つ1つを表示する際には`each`を用いましょう。
#### userメソッド
`user`は、対象の親であるusersテーブルのレコードを返します。
親は唯一つなので、返ってくるレコードは1件だけに定まります。
具体的には`@post_image.user`のように使用します。
例えば`@post_image = PostImage.find(4)`(=「金だこ」のレコード)の場合、
`@post_image.user`によって、usersテーブルのレコードのうち「id=2」のものを返します。
つまり、この場合`@post_image.user`と`User.find(2)`は同じ意味になります。
`@post_image.user`の時点でusersテーブルのレコード1件が返りますので、後ろにusersテーブルのカラムを続けることができ、
`@post_image.user.name`として、そのユーザの名前を出したり、
`@post_image.user.profile_image`を用いて、そのユーザのプロフィール画像を表示する、
といった風に使えます。
### dependent: :destroyについて
さて、実際のカリキュラムでは以下のようなコードを記載しました。
`user.rb`
```ruby=
class User < ApplicationRecord
has_many :post_images, dependent: :destroy
end
```
has_manyの後に`dependent: :destroy`がついています。
これは、「親レコードを削除した際、関連する子レコードも削除します」というオプションになります。
ユーザを削除することはあまりありませんが、
例えば上記の例でユーザAを削除した際、
post_imagesテーブルのid=1,2,3のレコードは「user_id=1だが、usersテーブルにid=1のユーザがいない」状態になります。
つまり、親がいない状態になり、データとしてあまりよろしくありません。
そこで`dependent: :destroy`を付けておくことで、
ユーザAの削除時に、ユーザAが投稿したpost_imagesも全て削除されます。
親レコードを消しても、子レコードを残しておきたい特別な理由がない限り、
`dependent: :destroy`は付けておくとよいでしょう。