Rails コメント機能の作り方

## はじめに
この記事はプログラミング初学者が他の記事を参考にしたり、実際に実装してみたりして、アウトプットの一環としてまとめたものです。内容に不備などあればご指摘いただますと幸いです。<br>
今回Xクローン作成中にコメント機能を実装しました。
そのコメント機能について備忘録も兼ねて記事を作成していきます。

## 実現したいこと
投稿されたツイートに対して、コメントをつけることができる機能を実装すること。

## コメント機能について
#### コメントする時のイメージ
<img width="500" alt="comment_1.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3628757/7f387fd3-de18-4742-bad8-102d271c6c16.png">

* ①のユーザーに注目すると、１人のユーザーから複数の線が伸びています。
→ つまりユーザーは「たくさんのツイートにコメント」することができます。
* ②のツイートに注目すると、１つのツイートから複数の線が伸びています。
→つまりツイートは「たくさんのユーザーにコメント」されます。

このように 「ユーザーもツイートもたくさん持っている」関係を多対多(M:N)の関係 といいます。
そして、多対多(M:N)の関係には中間テーブルが必要！

#### 中間テーブルを用いてER図を表すと
<img width="700" alt="comment_erd.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3628757/d42b751a-0166-4ce4-b673-29091222ebc0.png">

このようになります。

## Commentモデルとテーブルの作成
それでは、実装していきましょう！
以下のコマンドでモデルを作成します。
```
$ rails g model comment user_id:integer tweet_id:integer content:text
```
マイグレーションファイルは以下のように生成されます。
```ruby:app/model/migrate/○○○_create_comments.rb
class CreateComments < ActiveRecord::Migration[7.0]
  def change
    create_table :comments do |t|
      t.integer :user_id
      t.integer :tweet_id
      t.text :content

      t.timestamps
    end
  end
end
```
以下のコマンドでマイグレーションを実行しましょう。
```
$ rails db:migrate
```

## アソシエーションの設定
それぞれのファイルに以下を追加します。
```ruby:app/models/comment.rb
  belongs_to :user
  belongs_to :tweet
```
```ruby:app/models/user.rb
has_many :comments, dependent: :destroy
```
```ruby:app/models/tweet.rb
has_many :comments, dependent: :destroy
```
`dependent: :destroy`は、`has_many`で使えるオプションです。
1:Nの関係において、「1」のデータが削除された場合、関連する「N」のデータも削除される設定。
ここでは、ツイートが削除された場合に関連するコメントも削除されるように設定してます。

## ルーティングの設定
コメントは、ツイートに対して、コメントするのでネストしたルーティングを作成します。
```ruby:config/routes.rb
resources :tweets do
  resources :comments, only: [:create]
end
```
ルーティングを見てみると
![comment_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3628757/907533eb-273b-4e87-83f6-5fb1f8f65a1d.png)

こんな感じで作成されます。
また、ネストしたルーティングを作成したことで`tweet_id`は`params[:tweet_id]`で取得できます。

## Controllerの作成
以下のコマンドでコントローラを作成します。
```
$ rails g controller comments
```
生成されたコントローラのファイルを以下のように編集します。
```ruby:app/controllers/comments_controller.rb
class CommentsController < ApplicationController
  def create
    @tweet = Tweet.find(params[:tweet_id])
    # @tweetに関連したCommentクラスの新しいインスタンスを作成。
    # つまり、comment.tweet_id = @tweet.idが済んだ状態で生成されている。
    # buildはnewと同じ意味で、アソシエーションしながらインスタンスをnewする時に形式的に使われる。
    @comment = @tweet.comments.build(comment_params)
    # コメントの所有者を現在ログインしているユーザーに設定。
    @comment.user_id = current_user.id
    @comment.save
    # 直前のページにリダイレクトする。
    redirect_to request.referer, notice: 'コメントをしました。'
  end

  private

  def comment_params
    params.require(:comment).permit(:content, images: [])
  end
end
```

## コメントを投稿するためのインスタンス変数を定義
```ruby:app/controllers/tweets_controller.rb
def show
  @tweet = Tweet.find(params[:id])
  @comments = @tweet.comments.order(created_at: 'DESC')
  @comment = current_user.comments.new # 追加
end
```

## Viewの作成
ツイートの詳細画面でコメントを投稿できるようにします。
```ruby:app/views/tweets/show.html.slim
= form_with model: [@tweet, @comment], url: tweet_comments_path(@tweet) do |f|
  = f.text_area :content, class: "post-content", placeholder: "返信をポスト" # テキスト投稿フォームを作成。
  div.d-flex.justify-content-between.pt-2.pb-2
    ul.post-icon-list.d-flex
      li.ms-2
        div
          = f.label :images
            i.bi.bi-card-image style="cursor: pointer"
            = f.file_field :images, multiple: true, style: "display: none" #投稿画像のファイル選択ボックスを作成。Bootstrapを用いてfile_fieldの見た目をアイコンにしてます。
      li
        div
        i.bi.bi-filetype-gif
      li
        div
        i.bi.bi-emoji-smile
      li
        div
        i.bi.bi-geo-alt
    div
      = f.submit "返信", class: "post-btn" # 送信ボタンを作成。
```
form_withを記述する際、ルーティングでネストを定義している時は`[@post, @comment]`のように、**配列で二つ ([関連元のインスタンス, 関連先のインスタンス])** 渡す必要があります。

## おわりに
最後まで読んでいただきありがとうございました。
少しでも皆さんの参考になれば幸いです。

## 参考にしたサイト

https://qiita.com/kazukimatsumoto/items/14bdff681ec5ddac26d1

https://qiita.com/kuuuuumiiiii/items/6db65c77d2d2cb051c5e

https://zenn.dev/goldsaya/articles/cfe875600d825a