【Rails】（後編）投稿のコメントにさらにコメントしよう（備忘録）

みなさん、こんにちは！
筆者は大学生限定のプログラミングスクール「GeekSalon」でメンターをしています！
興味のある方や話だけでも聞いてみてい方はぜひのぞいてみてください👍

https://geek-salon.com/?utm_source=Direct&utm_medium=attract&utm_campaign=3Q&utm_content=Fukuoka_B1_3Q

さっそく今回の本題に入っていきます！
今回は投稿の詳細を表示するように、投稿に対してなされたコメントの詳細を表示する機能を実装していきます。

##記事を書くに至った背景
筆者は、投稿機能を用いてお店の情報を、投稿へのコメント機能を通じてお店の商品を、コメントへのコメントを通じて商品への口コミを記録できるようなプロダクトを実装しようと思い立ちました！
このようなプロダクトを完成させるためには、①投稿に対するコメントの詳細を見ることができる機能、そして②投稿に対するコメントにさらにコメントできる機能が必要になります。
そのため、コメントの詳細ページを実装する記事、コメントにさらにコメントできる機能を実装する記事を探したのですがなかなかいいものがなく、自力で実装することにしました。

この記事は筆者の実装の振り返りのようなものです。

上記で述べたように、①投稿に対するコメントの詳細を表示し、②コメントに対しさらにコメントできる機能の実装の２つを行っていきますので、記事も２本立てで行きます。今回はその（後編）です。

前編の記事はこちらから
[【Rails】（前編）投稿のコメントの詳細ページを表示する](https://qiita.com/Fu990628/private/ac017d8301d221f342fa)

##実装環境
Ruby 3.0.2-1
Rails 6.1.4.1
（※投稿機能、投稿に対するコメント機能は実装済みという前提で記事を書いていきます。）

##実装①　～モデルの作成～

今回は、replyモデルを作成していきます。
コマンドプロンプトに以下を入力し、モデルを作りましょう

```Ruby:コマンドプロンプト
$ rails g model Reply content:string user:references comment:references
$ rails db:migrate
```

今回は、コメントに対するコメントのため、アソシエーションは```tweet```に対してではなく、```comment```に対して行いましょう。

次に、モデルのファイルに記述を加えていきましょう。

```Ruby:app/models/user.rb
has_many :replies, dependent: :destroy
```

```Ruby:app/models/comment.rb
has_many :replies, dependent: :destroy
```

##実装②　～コントローラーの作成～

次に```replies```コントローラーを作成します。

```Ruby:コマンドプロンプト
$ rails g controller replies
```

そして、作成したコントローラーに以下を記述していきます。

```Ruby:controller/replies_controller.rb
class RepliesController < ApplicationController

    before_action :authenticate_user!

    def create
        comment = Comment.find(params[:comment_id])
        reply = comment.replies.build(reply_params)
        reply.user_id = current_user.id
        if reply.save
            flash[:success] = "コメントしました"
            redirect_back(fallback_location: root_path)
        else
            flash[:success] = "コメントできませんでした"
            redirect_back(fallback_location: root_path)
        end
    end

    private
    def reply_params
        params.require(:reply).permit(:content)
    end

end
```

これでコントローラーが完成です。
次にルーティングも行っていきましょう。

##実装③　～routes.rb～

今``routes.rb``はこんな感じになっていると思います。

```Ruby:routes.rb
resources :tweets do
  resources :comments, only: [:create, :destroy, :show]
end
```

今回はコメントに対してコメントしたいので、コメントの```resources```に対してネスト構造をもう一つ設けてあげましょう。記述は以下の感じです。

```Ruby:routes.rb
resources :tweets do
  resources :comments, only: [:create, :destroy, :show] do
    resources :replies, only: [:create]
  end
end
```

これで```rails routes```をしてパスを確認すると、```reolies#create```のパスは、```tweet_comment_replies```となっていることがわかります。
これで、リプライをコメントと紐づけたパスを取得することができました。

##実装④　～comments/show.html.erb～

ではViewページに記述を加えていきます。前編の記事で作成した```comments/show.html.erb```ファイルに、以下を記述してください。

```Ruby:comments/show.html.erb
＃省略
<div class="reply-wrapper">
    <p>コメントのコメント一覧</p>
    <% @replies.each do |r| %>
        <div>
        <%= r.user.email unless r.user.blank? %>
        <br>
        <%= r.content %>
        </div>
        <br>
    <% end %>

    <% if user_signed_in? %>
        <%= form_with(model: [@tweet, @comment, @reply], local: true) do |f| %>
            <%= f.text_area :content %>
            <%= button_tag type: "submit" do %>
                <i class="far fa-comments"></i> コメントする
            <% end %>
        <% end %>
    <% end %>
</div>
＃追記ここまで

<%= link_to "戻る", tweet_path(@comment.tweet_id) %>
```

これでViweページの完成です。
また、Viweページで```@reply```や```replies```を用いることができるように、コントローラーに以下を記述しましょう。

```Ruby:comments_controller.rb
def show
    @comment = Comment.find(params[:id])
　＃追記ここから
    @replies = @comment.replies
    @reply = Reply.new
　＃追記ここまで
end
```

これで完成です。
```rails s```で確認してみてください！

・
・
・
・
・
？？

おそらく、```NoMethodError undefined method `～_path'```というエラーが生じたとおもいます。
このエラーに対処してから記事を終わらせましょう。


##実装⑤　～routes.rb　その２～

今```replies#create```のパスは、```tweet```と```comment```の両方のネスト構造の中にあります。しかしよく考えてください。コメントテーブルはコメントIDを持っているため、わざわざツイートテーブルと紐づけしなくてもコメントIDさえ取得すれば唯一無二のコメントを取得してくることができます。つまり、コメントを特定するにあたり、ツイートの情報はいらない（＝ツイートと結びつけなくていい）のです。

そのため、今の```routes.rb```の記述を以下のように変更しましょう。

```Ruby:routes.rb
＃省略
resources :tweets do
  resources :comments, only: [:create, :destroy]
end

resources :comments, only: [:show] do
  resources :replies, only: [:create]
end
＃省略
```

つまり、```comments#show```のパスだけ```tweet```から独立させ、そして新たに定義した```comment#show```に紐づける形で```replies```のネスト構造を作ってあげるのです。

```comments#show```はコメントのIDさえあれば親となるツイートの情報がなくてもコメントの詳細を呼び出せますし、また```replies#create```もどのコメントに対するリプライかさえ特定できれば親であるツイートの情報を必要としません。そのため、このように独立させて記述させて問題がないのです。むしろ、独立させて記述したほうが余計なパス（ツイートのパス）と紐づくことがないため、より簡単にパスを記述できます。

またこのように記述を変えると、もう一つ変えないといけないものがあります。それは、```tweets/show.html.erb```から```comments/show.html.erb````に飛ぶためのリンク、つまりコメント詳細へ飛ぶためのリンクの記述です。現在は、

```Ruby:tweets/show.html.erb
＃省略
<%= link_to "コメントの詳細へ", tweet_comment_path(@tweet.id, c.id) %>
＃省略
```

とツイートに紐づいたリンクが記述されているはずなので、これを

```Ruby:tweets/show.html.erb
＃省略
<%= link_to "コメントの詳細へ", comment_path(c.id) %>
＃省略
```

のように書き直してあげましょう。これで完了です。

さて、改めて```rails s```してたしかめてみてください。先ほどのエラーは解消されてコメントに対してコメントができるようになっているはずです。

ここまで実装お疲れ様でした。
もしよければ前編の記事もお読みください。
[【Rails】（前編）投稿のコメントの詳細ページを表示する](https://qiita.com/Fu990628/private/ac017d8301d221f342fa)