【Rails】掲示板アプリでTopicとPostを関連づける【アソシエーション】

## はじめに
現在、ポートフォリオとして掲示板アプリを作成しており、題目にある「TopicとPostを紐づける」部分で時間かかったので備忘録として。

早速ですが、紐づけるために必要な要素として以下の2点を中心に記載します。

1. model側の修正
2. controller側の修正

## やりたいこと
複数あるPostは必ず一つのTopicに紐づく
PostやTopicは必ず一つのUserに紐づく
各modelを関連付けて、「@post.topic.title」のような形で値を取得したい

## 環境
macOS:10.14.6
Ruby:2.5.7
Rails:5.2.4.1

## model側の修正（アソシエーション）

今回登場するmodelは、以下の3つ。
・Topic
・User（deviseを使ってます）
・Post

Topicがスレッド、Postがスレッドに対するレスで、
Topic(1)---(多)Post
User(1)---(多)Post/Topic
というような位置付けになります。

今回の場合は既に各modelを作成(generate)済みだったので、アソシエーション用のマイグレーションファイルを作って、マイグレートしていく形で進めます。

#### マイグレーションファイルを作成する

```
$ rails g migration AddUserToTopic
$ rails g migration AddTopicToPost
```

#### マイグレーションファイルを編集する
文法は、「add_reference table名、reference名」が基本形になります。
indexはオプションなのでお好みで。
ちなみに超ざっくりですが、indexは読み込み/取得速度を上げてくれるものです
（但し、書き込み速度は遅くなるので注意が必要）

```20200222110925_add_user_to_topic.rb
class AddUserToTopic < ActiveRecord::Migration[5.2]
  def change
    add_reference :topics, :user, index: true #追記箇所
  end
end
```

```20200223032448_add_topic_to_post.rb
class AddTopicToPost < ActiveRecord::Migration[5.2]
  def change
    add_reference :posts,:topic #追記箇所
  end
end
```

#### マイグレートする

```
$ rails db:migrate
```

上記コマンド実行後、以下のようなmigratedが出力されればOK

```
== 20200223032448 AddTopicToPost: migrating ===================================
-- add_reference(:posts, :topic)
   -> 0.0796s
== 20200223032448 AddTopicToPost: migrated (0.0797s) ==========================
```



#### モデルを編集する

```topic.rb
class Topic < ApplicationRecord
  validates :title, presence: true
  has_many :posts #追記
  belongs_to :user #追記
end
```

```post.rb
class Post < ApplicationRecord
  belongs_to :topic #追記
  belongs_to :user #追記
end
```

```user.rb
class User < ApplicationRecord
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable
  validates :name,:email,:encrypted_password, presence: true
  has_many :topics, through: :posts #追記
end
```

これでmodel側はOKです！


## controller側の修正

各modelの関連付けできたので以下のような形で、関連先の値を拾ってこれるようになりました。細かく言うとパラメーターの中から明示的に値を取得するイメージです。
params[:topic_id]という風な感じで。

```posts_controller.rb
class PostsController < ApplicationController

  def new
    @topic_id = params[:topic_id]
    @post = Post.new
  end

  def create
    @post = Post.new
    @topic_id = params[:topic_id] #paramsからtopic_idを取得し、インスタンス変数@topic_idに代入
    @post.topic_id = @topic_id #上記を@post.topic_idに代入
    @post.body = params[:post][:body]
    @topic = Topic.find(@topic_id)
    @post.user_id = current_user.id
    if @post.save
      redirect_to topic_path(@topic_id), notice: '投稿しました'
    else
      render 'posts/new', alert: '投稿できませんでした'
    end
  end
end
```

ちょっと汚いですね、、、汗
取得したい値は「paramsから取得してインスタンスに代入」と書く形です。
なので、取得したい値に応じて編集してください。上記はあくまで参考として。

#### viewの修正（おまけ）

以下はトピック一覧画面(topic#index）ですが、postの数やpostの投稿時間を取得して表示することができます。

```index.html.erb
<div class="container mt-5 ml-5">
  <div class="row">
      <% @topics.each do |topic| %>
        <div class="table tabel-hover">
          <%= link_to topic do %>
            <div class="list-group" style="max-width: 500px;">
              <div class="list-group-item list-group-item-action">
                <%= topic.title %> (<%= topic.posts.count %>)  #post数を計算
                <br>
                <small class="text-muted text-right">
                最終投稿日時：<%= topic.posts.last.created_at %> #最新のpost投稿時間を取得する
                </small>
              </div>
          <% end %>
          </div>
      <% end %>
      </div>
  </div>
</div>
```
こんな表示になります↓

<img width="741" alt="スクリーンショット 2020-02-25 1.42.56.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/526830/e475fc90-91d3-b943-47d5-29848ac146ca.png">

非常に便利ですね。

## 参考文献
[Active Record の関連付け](https://railsguides.jp/association_basics.html)