目標物の確認
今回の目標物は以下のようです。
ユーザー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基礎】プログラミング初学者がつまずきやすい「ルーティングのネスト」について簡単に解説