#はじめに
Railsの理解を深めるために、Twitterの機能を自装していく中で、あるTweetに対するコメントの削除機能をつけたところエラーが出続けるという沼にハマってしまいました。
エラー内容はActiveRecord::RecordNotFound (Couldn't find Comment without an ID)
です。
ParamsでID取得できてませんよーということでした。
一応解決はしたのでメモ程度に書いておきます。
※ 結論としては、ルート設定ミスでした。
#リレーション
CommentはUserとTweetの中間テーブルとして作成していきます。
今回のコメントの削除は、どのTweetのCommentか?というところが必要になります。
#エラー時実装内容
まずはエラーが起きてしまった時点での実装を書きます。
##ルート設定
# ---中略--- #
resources :tweets do
resources :comments, only: [:create, :destroy]
end
ネストにしているのはどのツイートに対するコメントかを簡単に設定するためです。
/tweets/:tweet_id/comments/:id(.:format)
結論としてはこれが引っかかってたみたいです。。
##コントローラー設定
削除機能に関するコントローラー設定は以下のように実装しました。
Commentテーブルから受け取ったCommentのidに該当するコメントを削除するというものです。
class CommentsController < ApplicationController
# ---中略--- #
def destroy
Comment.find(params[:id]).destroy
redirect_to tweets_path
end
end
今回は、削除後の先をTweets_pathとしTweet一覧画面に飛ばしてます。
##ビュー設定
前提として、@commentsに投稿されたコメントを引き渡した状態であるとして、、
# ---中略--- #
<% @comments.each do |comment| %>
<p><span>コメント内容: </span><%= comment.body %></p>
<p><%=link_to "削除", tweet_comments_path(comment.id), method: :delete %></p>
<% end %>
ルーティングを確認すると、Tweet_comments DELEATは
/tweets/:tweet_id/comments/:id(.:format)
であるので
パスの書き方はtweet_comments_path
。
()の中身は取得したいcomment.id
を入れました。
##エラー結果
これらの実装のもと、「削除」をポチッと押したところ先程のActiveRecord::RecordNotFound (Couldn't find Comment without an ID)
というエラーになってしまうというものでした。
#実験
エラー文からきっとtweet_comments_path(comment.id)
この書き方が正しくないのだろうと思ったので、試しにtweet_comments_path(comment.tweet.id)
としてみることに。
# ---中略--- #
<% @comments.each do |comment| %>
<p><span>コメント内容: </span><%= comment.body %></p>
<p><%=link_to "削除", tweet_comments_path(comment.tweet.id), method: :delete %></p>
<% end %>
コントローラーではTweet.idを引き受けます。
class CommentsController < ApplicationController
# ---中略--- #
def destroy
comment = Comment.find_by(tweet_id: params[:tweet_id])
comment.destroy
redirect_to tweets_path
end
end
Comment.idの引き渡しはしないので、ネストしている部分はresources
ではなくresource
にします。
※resources
のままだとルーティングエラーになります。
# ---中略--- #
resources :tweets do
resource :comments, only: [:create, :destroy]
end
実際の動かしてみると次のようになりました。
こちらの下の「コメント2つめ」の方を削除します。
すると結果は、
上の「コメントします」の古い方のコメントが削除されてしまいました。
実験的にTweet.idしか引き渡していないのでそうなりますよね。
どのコメントを削除するか指定していないので。。
けど、ParamでのTweet.idの引き渡しはできたようです。
ルーティングをネストした場合はcomment.tweet.id
は引き渡せるけど、comment.id
は引き渡せないのか、、よくわからん
※そもそも引き渡し部分をcomment.tweet.id
にしなくてはいけなかったのでは?
と思い、ルーティングとコントローラーの設定を戻してみましたが、その場合はcomment.id
を渡したいのに、Tweet.id
引き渡してしまっているので当然エラーとなりました。
#解決策
ルーティング設定でcommentのdestroy部分をネストさせるのを諦めました。
# ---中略--- #
resources :tweets do
resource :comments, only: [:create]
end
resources :comments, only: [:destroy]
comments#destroyのルーティングを確認すると以下のようになっています。
コントローラーとビューの設定も。
class CommentsController < ApplicationController
# ---中略--- #
def destroy
Comment.find(params[:id]).destroy
redirect_to tweets_path
end
end
# ---中略--- #
<% @comments.each do |comment| %>
<p><span>コメント内容: </span><%= comment.body %></p>
<p><%=link_to "削除", comment_path(comment.id), method: :delete %></p>
<% end %>
このようにしたら無事、コメントの削除をすることができました。
#まとめ
ルーティングをネストしてしまったら取れなくなっていしまう値があるとは考えにくいが、上手くいかなかった。
何かアドバイスがあれば教えていただきたいです。
#変更点
9/2
こちらの問題にアドバイスを頂きまして、解決いたしましたので記述します。
問題点は次の2つでした。
-
tweet_comments_path
ではなくtweet_comment_path
- ルーティングをネストしている場合は引数2つ必要
DELETEならs不要みたいですね。
# ---中略--- #
<% @comments.each do |comment| %>
<p><span>コメント内容: </span><%= comment.body %></p>
<p><%=link_to "削除", tweet_comment_path(comment.tweet.id, comment.id), method: :delete %></p>
<% end %>>
class CommentsController < ApplicationController
# ---中略--- #
def destroy
Comment.find_by(id: params[:id],tweet_id: params[:tweet_id]).destroy
redirect_to tweets_path
end
end
# ---中略--- #
resources :tweets do
resources :comments, only: [:create, :destroy]
end
これでうまくいきました。