掲示板にコメント機能を実装する③
前回からの続きです。
モデル・ビュー側の設定は終わったので、コントローラーを設定していきます。
(どういう順番でやるのが正しいんでしょうか…)
boards_controllerへの追記
先にViewを作成していきましたが、掲示板詳細画面(show)でコメントの新規作成フォーム(@comment使う)とコメント一覧(@comments使う)を表示していました。
そのため、boards_controllerでもそれぞれインスタンス変数を定義していきます。
def show @board = Board.find(params[:id]) @comment = Comment.new @comments = @board.comments.includes(:user).order(created_at: :desc) end
N + 1問題
コメントを全件取ってくるのに、以下の取得方法はNGです。(私は最初こうしてしまった。)
SQLが無駄に発行されてしまいます。
①コメントを全件取り出し
②各コメントの一覧画面で、コメントしたユーザーを呼び出す(コメントの数だけ呼び出してしまう!)
@comments = @board.comments.order(created_at: :desc) #N + 1問題が起こる書き方
ここでは、呼び出しでinclude
を指定することで、読み込まれる全ての関連付けを事前に指定し、最小限のクエリ回数で読み込むことができます。
:user
をinclude
で含めて呼び出している。
@comments = @board.comments.includes(:user).order(created_at: :desc)
これで、またコメント一覧画面で再度ユーザー名を呼び出すことがなくなる
Active Record クエリインターフェイス - Railsガイド
comments_controllerの生成
$ rails g controller comments
今回は、登録が失敗したときもredirect_to
にしています。(次回以降に実装する内容の都合上)
そのため、view側で参照しないので、インスタンス変数にしていません。
class CommentsController < ApplicationController def create comment = current_user.comments.build(comment_params) if comment.save redirect_to board_path(comment.board), success: t('defaults.message.created', item: Comment.model_name.human) else redirect_to board_path(comment.board), danger: t('defaults.message.not_created', item: Comment.model_name.human) end end private def comment_params params.require(:comment).permit(:body).merge(board_id: params[:board_id]) #ストロングパラメーターで、 end end
難しいところをみていきます。
comment = current_user.comments.build(comment_params)
「ログインしているユーザー」に紐付いた「コメントデータ」を登録しています。
つまり、ログインしているユーザーのuser_id
もこれで代入しています。
comment = Comment.new(comment_params.merge(user_id: current_user.id))
この書き方でも内容は同じですが、前者の書き方のほうが一般的のようです。
def comment_params params.require(:comment).permit(:body).merge(board_id: params[:board_id]) end
コメントを保存するときには、関連付けしているuser_idとboard_idも一緒に保存する必要があります。
ここの部分は、ストロングパラメーターで生成される際に、入力した値(body)と一緒に、board_idも保存するといった内容です。
board_id: params[:board_id]
は、urlから取ってきています。
最初のルーティングの設定を思い出すと、boards/:board_id/comments
こうなっていたはず。
ここから、board_id
をパラメーターで引っ張ってきているんですね。
Action Controller の概要 - Railsガイド
merge
メソッドは、複数のハッシュを結合させるメソッド。
Hash#merge (Ruby 2.7.0 リファレンスマニュアル)
ここでは、{ body => "入力されたもの", board_id => "urlから取得したboard_id"}
こんな風に結合されます。
残すもの恥ずかしいけど、後学のために…。
私が初めて実装しようとして、間違えた書き方。
なぜcreateアクションで@commentsを出そうとしてるのかも謎だし、各idも全部代入しようとしている。
これがあんなにスマートに書けるんですね
class CommentsController < ApplicationController def create @board = Board.find_by(params[:board_id]) @comment = @board.comments.new(comment_params) @comment.user_id = current_user.id @comment.board_id = params[:board_id] @comments = @board.comments.order(created_at: :desc) if @comment.save redirect_to board_path(params[:board_id]), success: t('defaults.message.created', item: Comment.model_name.human) else flash.now[:danger] = t('defaults.message.not_created', item: Comment.model_name.human) render '/boards/show' end end private def comment_params params.require(:comment).permit(:body) end end