【Rails】コメント機能の実装

テックキャンプ74日目、やっと実装課題終わりました。
でもそこからまた数日経ち、いつの間にか残り100日です。
焦る。

今日は実装課題のコメント機能で躓いていたことを思い出しながら記事を書いてみようと思います。

今回の実装課題

ある投稿に対してコメント機能があるサイトの作成。
コメントは投稿の詳細ページから閲覧、投稿が可能。

コメントが成功した場合は、投稿の詳細ページにリダイレクトする。
コメントが失敗した場合は、投稿の詳細ページに戻る(viewファイルにそのまま戻る)。

実装手順

投稿(prototype)の詳細ページを見るために、showアクションで定義。 prototypes_controller.rb

def show

    @prototype = Prototype.find(params[:id])
    @comment = Comment.new
    @comments = @prototype.comments.includes(:prototype)

end
  1. 詳細を表示したい投稿内容をビューに渡すために@prototype に代入。
  2. @comment = Comment.newという空のインスタンスを生成。そうすると、form_withを使用してcommentsコントローラーのcreateアクションにパラメーターとして送られる。
  3. @commentsに@prototypeに結びついた全てのコメント情報とそれに結びつくユーザー情報を代入。

prototypes/show.html.erb

<div class="prototype__comments">
        <% if user_signed_in? %>
          <%= form_with model: [@prototype, @comment],local: true do |f|%>
            <div class="field">
              <%= f.label :content, "コメント" %>
              <%= f.text_field :content, id:"comment_content" %>
            </div>
            <div class="actions">
              <%= f.submit "送信する", class: :form__btn  %>
            </div>
          <% end %>
        <% end %>
        <ul class="comments_lists">
          <% @comments.each do |comment| %>
          <li class="comments_list">
            <%= link_to comment.user.name, user_path(comment.user_id), class: :comment_user %>
            <%= comment.content %>
          </li>
          <% end %>
        </ul>
      </div>
ここでなんでform_withでモデルを2つ渡すの?という疑問。

commentsコントローラーのルーティングを確認すると、

routes.rb

resources :prototypes do
    resources :comments, only: :create

rails routes (ターミナル)

prototype_comments POST   /prototypes/:prototype_id/comments(.:format)                                                      comments#create

上記のように、プロトタイプのidがないとcommentsコントローラーのcreateアクションにパラーメーターが送られないよ〜とわかる。
これは、プロトタイプとコメントのルーティングをネストしているためであり、 コメントを保存したい場合、form_withに2つの引数(親クラスと子クラスの順)を指定する必要があります。

子クラスが空かご健在かでcreateかupdateに飛ぶという感じでしょうか。(今回はcreateのみ実装)

@commentsの表示方法

@commentsをそのまま

<%= @comments %>

と記載すると、@commentsの中身が複数あるからダメだよ。ってなりました。
複数あるものはeachでひとつずつ取り出そうと思い、コメント一覧にeachを使用しました。

コメントが成功した場合/失敗した場合

comments_controller.rb

def create
    @comment = Comment.new(comment_params)

    if @comment.save
      redirect_to prototype_path(@comment.prototype)
    else
      @prototype = @comment.prototype
      @comments = @prototype.comments
      render "prototypes/show"
    end

private

def comment_params
    params.require(:comment).permit(:content).merge(user_id: current_user.id, prototype_id: params[:prototype_id])
end
  1. @commentにストロングパラメーターを用いて、送られてきたパラメーターを代入。
  2. もし保存できた場合は、プロトタイプの詳細ページへいく。(リダイレクト)
  3. もし保存できなかった場合は、詳細ページのviewに戻る。 (render)

renderメソッド

あるビューをユーザーに提供するために使うメソッド。
renderメソッドは特定のアクションに対応したビューをユーザーに提供するだけであり、指定したビューに対応するアクションは実行しない。
元のインスタンス変数の値が上書きされず、何か入力していた場合は、それらを保持したままビューに戻る。

redirect_toメソッド

ブラウザに別のURLでHTTPリクエストを送って欲しいと指示するメソッド。
新たなリクエストを送信されたときと同じ動きになるので、再度コントローラーを経由してビューが表示される。
元のインスタンス変数の値が上書きされる。

使い分け

ビューを返すだけならrenderメソッドでよい。
アクション終了後にあるアクションを実行したい場合や、他のサイトへ接続したい場合にはredirect_toメソッドを使用。

なぜインスタンス変数@prototypeと@commentsを定義するのかという疑問

renderだとviewファイルにそのまま戻るので、戻るときにインスタンス変数を使わないと情報を渡すことができない。
そのため、ここでも行き先のviewファイルに渡すインスタンス変数@を定義する必要がある。
@prototype →@commentが存在するprototype
@comments→@prototypeにされたcommentsを全て取得
⚠️アソシエーションの定義によって単数形、複数形に注意。

ストロングパラメーターのmergeのところがよくわからんから深掘りしてみた

mergeメソッド
フォーム外のデータを安全に追加するために使用。
ちなみに、permitはフォームからのデータを許可するために使用。

今回のcurrent_user.idはdeviseのGemを導入しているため使用できます。
prototype_id: params[:prototype_id]で値を入れることができているのは、ルーティングでcommentsをネストしているためです。
コメントを入力するときに自分のidやそのコメント先のプロトタイプ名を直接入力することはないけど、
パラメーターに含めてDBに保存したいな。というときに、
mergeメソッドを使って、パラメーターに含めたいキーと値を記述できます。

まとめ

まとまりのない記事になってしまいましたが。。。

ネストしているときの注意点や、renderを使用した場合に再度変数を渡してあげる必要があることなどなど。
コメント機能1つで改めて学習できたなと感じました。
間違ったことも書いてあるかもしれませんので、何かあれば教えてください!