【Rails】異なるモデル同士の情報を紐づける必要性とその方法

# どうした？
ProgateのRailsレッスンにてSNSサイトを作成していくなかで、投稿に関するモデルとユーザーに関するモデルを紐づける方法を学びました。

本記事では
+ 異なるコントローラを紐づける必要性
+ コントローラ同士を紐づける方法

を初学者なりに整理したものです。

# 目標物の確認
今回作成したいページはこのような見た目です。
![スライド1.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2688241/264a898d-79b3-4b54-a4dc-5e4f361525e2.jpeg)


# 準備したもの
今回の目標物を実現するために最低限必要なファイルは以下です。
## モデル
**Postモデル** 投稿に関するモデル
`id`カラム: 投稿idとして使用。モデルに情報が入力されると自動生成される。
`content`カラム: 投稿内容
`user_id`カラム: 投稿者のユーザーid。誰が投稿したか？を示す。

**Userモデル**  ユーザーに関するモデル
`id`カラム: ユーザーidとして使用。モデルに情報が入力されると自動生成される。
`name`カラム: ユーザー名
`image_name`カラム: ユーザーが登録したユーザーに紐づく画像ファイル


## コントローラ
**postsコントローラ**
`show`アクション: 投稿内容を表示する
showアクション内では、どのidの投稿かを`params[:id]`で受け取り、そのidをもつ投稿情報(インスタンス形式)を`@post`に代入しています。
```ruby:posts_controller.rb
# showアクション内で
@post = Post.find_by(id: params[:id])
```
`params[:id]`はurlから得られる値です。


**（usersコントローラ）**
もちろん存在はしますが、今回の機能実装に必要なアクションはありません。

## ビューファイル
**posts/show**
目標物を表示するためのビューファイルです。

## ルーティング
```ruby:route.rb
get "posts/:id" => "posts/show"
```
url内の`:id`によってどの投稿を表示しているか？を示します。

# 異なるコントローラを紐づける必要性

今、投稿内容を表示するページを作るとします。

投稿内容の詳細を表示するshow.html.erb 内で、
+ 投稿内容
+ ユーザー名
+ ユーザー画像

を表示したいとき、
**投稿内容** を表示するにはビューファイルで
```ruby:show.html.erb
# 投稿内容
@post.content
```
で表します。
`@post`は`params[:id]`によって定義されているので、投稿内容は**ページがもつ情報だけで表示する**ことができます。

では、ユーザー名やユーザー画像はどのように表示させれば良いでしょうか？
paramsハッシュにはユーザーに関する情報はありませんので、ページが持つ情報だけでユーザーを定義することができません。
ページがもつ情報からユーザーを割り出すとき、**投稿とユーザーを紐づける必要性**が発生します。
![スライド2.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2688241/13158149-ff3c-fa63-9cd0-3056963d65a0.jpeg)


# コントローラ同士を紐づける方法
異なるコントローラを紐づける方法として、下記の3種類を紹介します。
+ アクション内で2重構造の定義をする
+ モデル内で定義したメソッドを変数に対して行う
+ has_one、has_manyとbelongs_to

これらについてまとめます。

## アクション内で2重構造の定義をする
2重構造の定義(私が勝手に使っている言葉です)とは、**params情報だけで完結する変数を定義し、その変数を用いて別の変数を定義する方法**です。
showアクション内でユーザー情報を定義するには
```ruby:posts_controller.rb
# showアクション内で
@post = Post.find_by(id: params[:id])
@user = User.find_by(id: @post.user_id)
```
と、`@post`の`user_id`カラム情報をユーザーidに代入することで`@user`を定義します。

これを用いると投稿詳細ページで
```ruby:show.html.erb
# ユーザー名
@user.name

# ユーザー画像
@user.image_name
```
で表示することができます。
`.name`と`.image_name`はUserポストが持つカラムに対応する値を呼び出すメソッドです。
![スライド3.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2688241/8f77327a-c26b-4656-a7d3-24f6d8883435.jpeg)


## モデル内で定義したメソッドを変数に対して行う
先ほど紹介した方法だと、すべてのアクションで`@user`とPostモデルを紐づける定義をする必要があります。
また、投稿一覧を表示するために`@posts = Post.all`に対してeach文を使うときに`@user`の定義が複雑になります。
（`@posts.each`で生成される変数`post`は、Postモデルに紐づいているため）

そこで、Postモデル内でユーザー情報と紐づけるメソッドを定義します。
```ruby:post.rb
#クラス定義式内で
def user
  return User.find_by(id: self.user_id)
end
```
ここで、`self.user_id`はPostモデル自身が持つインスタンス変数(カラム)です。

これにより投稿詳細ページで
```ruby:show.html.erb
# ユーザー名
@post.user.name

# ユーザー画像
@post.user.image_name
```
により、変数`@post`を使用してユーザー情報を出力できます。

**解説**
モデルファイル内で定義したuserメソッドを`@post`に対して行うと、その`@post`インスタンスが持つ`user_id`の値と一致するidを持つuserに関するインスタンスを検索することができます。
文章だとなんのこっちゃという感じなので、図解します。
![スライド4.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2688241/bdbfd00d-9cc8-a4bb-e1c4-3ee9ba699b6f.jpeg)



## has_one、has_many、belongs_to
userとpostには主従関係があります。
userは複数のpostを持つことができるので、userが「主」、postが「従」の関係です。
「主」のモデルには`has_one`もしくは`has_many`
「従」のモデルには`belongs_to`
を設定することで、上で紹介したモデル内で定義したメソッドを変数に対して行うのと同じことができます。

```ruby:user.rb
#userモデルファイル内で
has_many :post
```
```ruby:post.rb
#postモデルファイル内で
belongs_to :user
```
を設定すると

```ruby
# @postからユーザー名を出力
@post.user.name

# @userから投稿内容を全て出力
@user.post.each do |post|
  post.content
end
# ただし、@user = User.find(params[:id]) とする
```
が可能です。
これにより投稿詳細ページで
```ruby:show.html.erb
# ユーザー名
@post.user.name

# ユーザー画像
@post.user.image_name
```
により、変数`@post`を使用してユーザー情報を出力できます。
ビューページのコード自体はモデル内で定義したメソッドを変数に対して行う方法と同じです。
![スライド5.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2688241/7b37b404-1d5a-b668-0cef-2c7953dc3685.jpeg)


# まとめ
本記事では
+ 異なるコントローラを紐づける必要性
+ コントローラ同士を紐づける方法

について記載しました。
コントローラ同士を紐づける方法として
+ アクション内で2重構造の定義をする
+ モデル内で定義したメソッドを変数に対して行う
+ has_one、has_manyとbelongs_to

を紹介しました。

Progateや書籍で勉強していると、「そもそもこの処理はなんで必要なんだっけ？」と思うことが多いです。
処理の必要性を意識して勉強していくとより身につくと感じました。