Programming Journal

学習したことの整理用です。

掲示板にコメント機能を実装する③

前回からの続きです。

モデル・ビュー側の設定は終わったので、コントローラーを設定していきます。

(どういう順番でやるのが正しいんでしょうか…)

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を指定することで、読み込まれる全ての関連付けを事前に指定し、最小限のクエリ回数で読み込むことができます。

:userincludeで含めて呼び出している。

    @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