みなさん、こんにちは!
筆者は大学生限定のプログラミングスクール「GeekSalon」でメンターをしています!
興味のある方や話だけでも聞いてみてい方はぜひのぞいてみてください👍
さっそく今回の本題に入っていきます!
今回は投稿の詳細を表示するように、投稿に対してなされたコメントの詳細を表示する機能を実装していきます。
##記事を書くに至った背景
筆者は、投稿機能を用いてお店の情報を、投稿へのコメント機能を通じてお店の商品を、コメントへのコメントを通じて商品への口コミを記録できるようなプロダクトを実装しようと思い立ちました!
このようなプロダクトを完成させるためには、①投稿に対するコメントの詳細を見ることができる機能、そして②投稿に対するコメントにさらにコメントできる機能が必要になります。
そのため、コメントの詳細ページを実装する記事、コメントにさらにコメントできる機能を実装する記事を探したのですがなかなかいいものがなく、自力で実装することにしました。
この記事は筆者の実装の振り返りのようなものです。
上記で述べたように、①投稿に対するコメントの詳細を表示し、②コメントに対しさらにコメントできる機能の実装の2つを行っていきますので、記事も2本立てで行きます。今回はその(後編)です。
前編の記事はこちらから
【Rails】(前編)投稿のコメントの詳細ページを表示する
##実装環境
Ruby 3.0.2-1
Rails 6.1.4.1
(※投稿機能、投稿に対するコメント機能は実装済みという前提で記事を書いていきます。)
##実装① ~モデルの作成~
今回は、replyモデルを作成していきます。
コマンドプロンプトに以下を入力し、モデルを作りましょう
$ rails g model Reply content:string user:references comment:references
$ rails db:migrate
今回は、コメントに対するコメントのため、アソシエーションはtweet
に対してではなく、comment
に対して行いましょう。
次に、モデルのファイルに記述を加えていきましょう。
has_many :replies, dependent: :destroy
has_many :replies, dependent: :destroy
##実装② ~コントローラーの作成~
次にreplies
コントローラーを作成します。
$ rails g controller replies
そして、作成したコントローラーに以下を記述していきます。
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
はこんな感じになっていると思います。
resources :tweets do
resources :comments, only: [:create, :destroy, :show]
end
今回はコメントに対してコメントしたいので、コメントのresources
に対してネスト構造をもう一つ設けてあげましょう。記述は以下の感じです。
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
ファイルに、以下を記述してください。
#省略
<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
を用いることができるように、コントローラーに以下を記述しましょう。
def show
@comment = Comment.find(params[:id])
#追記ここから
@replies = @comment.replies
@reply = Reply.new
#追記ここまで
end
これで完成です。
rails s
で確認してみてください!
・
・
・
・
・
??
おそらく、NoMethodError undefined method `~_path'
というエラーが生じたとおもいます。
このエラーに対処してから記事を終わらせましょう。
##実装⑤ ~routes.rb その2~
今replies#create
のパスは、tweet
とcomment
の両方のネスト構造の中にあります。しかしよく考えてください。コメントテーブルはコメントIDを持っているため、わざわざツイートテーブルと紐づけしなくてもコメントIDさえ取得すれば唯一無二のコメントを取得してくることができます。つまり、コメントを特定するにあたり、ツイートの情報はいらない(=ツイートと結びつけなくていい)のです。
そのため、今の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````に飛ぶためのリンク、つまりコメント詳細へ飛ぶためのリンクの記述です。現在は、
#省略
<%= link_to "コメントの詳細へ", tweet_comment_path(@tweet.id, c.id) %>
#省略
とツイートに紐づいたリンクが記述されているはずなので、これを
#省略
<%= link_to "コメントの詳細へ", comment_path(c.id) %>
#省略
のように書き直してあげましょう。これで完了です。
さて、改めてrails s
してたしかめてみてください。先ほどのエラーは解消されてコメントに対してコメントができるようになっているはずです。
ここまで実装お疲れ様でした。
もしよければ前編の記事もお読みください。
【Rails】(前編)投稿のコメントの詳細ページを表示する