# アソシエーション補足資料 下書き ## はじめに 当資料は、カリキュラム【アプリケーションを完成させよう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`は付けておくとよいでしょう。