目標物の確認
今回の目標物は以下のようです。

ユーザーAのポストに対し、ユーザーBがコメントを投稿します。
コメントの投稿フォームはposts/show内にあります。
コメントはposts/show内で表示され、自分のコメントに対しては削除することが可能です。
分からなかったこと
ポスト表示ページのパスがposts/:idであり、ページが持つ固有のparams要素はidのみであるならば、、、
- コメントを作成する際はCommentモデルの
post_idカラムにparams[:id]を代入すれば良いのは分かる。 - コメントを削除する時、該当するコメントの
idをfind_byを使って探すことができない。
(paramsが持つidはPostモデルのidであり、Commentモデルのidではないから。)
解決策
- モデル同士を紐づけて
- ルーティングをネストして
- コントローラを設計し
- ビューファイルの
form_withとlink_toにひと手間加える
モデルの紐づけ
PostモデルとCommentモデルに主従関係を持たせます。
Post: 大元の投稿
has_many :comments
Comment: postへのコメント
belongs_to :post
ルーティングのネスト
今回のケースのように、あるコントローラ内で別のコントローラを動かすときは、ルーティングをネストさせます。
resources :posts do
# 今回使用するコメント機能は投稿と削除だけ
resources :comments, only: [:create, :destroy]
end
これにより、どのようなパスが構成されるのか確認しましょう。
$ rails routes
の結果を見ると
posts GET /posts(.:format) posts#index
post_comments POST /posts/:post_id/comments(.:format) comments#create
post_comment DELETE /posts/:post_id/comments/:id(.:format) comments#destroy
ここでPOSTとDELETEのHTTPリクエストに対して:post_idと:idがあることに注目します。
post_idはCommentモデルがもつカラム
idはPostモデルがもつカラム
です。
コントローラを設計する際、paramsに何を入れて情報を送るのかを考える際に必要になりますので意識しましょう。
コントローラの設計
コメントの投稿
コメントを作成するアクションはこちらです。
def create
# 紐づけ先の@postを定義
@post = Post.find(params[:post_id])
# @commentは@postに紐づけるのでnewではなくbuild
@comment = @post.comments.build(comment_params)
@comment.save
# リダイレクト先は@commentに紐づいたpost/:id
redirect_to post_path(@comment.post_id)
end
private
# comment_paramsはストロングパラメータ
def comment_params
params.requier(:comment).permit(:content, :post_id)
# 必要であればmerge内にuser_id: @current_user.idも追加
end
コメントの削除
コメントを削除するアクションは以下です
def destroy
# @commentは:idパラメータから探す
@comment = Comment.find(params[:id])
@comment.destroy
# リダイレクト先は、ひとつ前のページ又はposts/index
redirect_to request.referrer || posts_path
end
ビューファイル
コメントの投稿
<%= form_with model: [@post, @comment], local: true do |f| %>0
<%= f.text_area :content, required: true %>
<%= f.submit "送信" %>
<% end %>
コメントを投稿する際は、form_withのmodel要素にコントローラのアクションで定義したインスタンス変数を配列として与えます。
これによってRailsはpost_comments_pathというcreateメソッドのパスにパラメータを送信します。
コメントの削除
# コメントしたユーザーが自分かどうかを確認
<% if @comment.user.id == @current_user.id %>
# post_comment_pathには2つのidがある
<%= link_to("削除", post_comment_path(@comment.post_id, @comment.id),
{method: "delete", data: {confirm: "削除しますか?"}})
%>
<% end %>
コメントを削除するパスは、rails routesで確認したpost_comment_pathを使用します。
このパスは上で確認した通り/posts/:post_id/comments/:idであり、2つのidをもっています。
:post_idには@comment.post_id
:idには@comment.id
を与えます。
また、削除パスのメソッドは"delete"なので、これも明記する必要があります。
ついでにリンクをクリックするといきなり削除されてしまうのを防ぐため、JavaScriptで削除の確認コメントが出るようにdata要素も追加しました。
以上で、コントローラの異なるページ上で投稿/削除するしくみをつくることができました。
参考
【Rails基礎】プログラミング初学者がつまずきやすい「ルーティングのネスト」について簡単に解説