kouの技術的メモ

学習した内容の定着やアウトプット用に開設しました

成果物の作成10 投稿記事に対するコメント機能の追加

今回はコメント機能を追加していこうと思います。 独自にComentモデルを作り、PostデータとコメントするUserモデルとの関係性も記述しなければならないので、少し難易度が高いです。

どんなモデルを作るか

コメントはさほど長い文章で無くても構わないと思うのでstring型で行こうと思います。

Commentモデルはcontentをstring型で作成し、外部主キーをPost_idとし、コメントする自分自身Userモデルとの関連付けにUser_idのカラムも作ります。

こんな感じでしょうか

Commentモデル

外部主キー コメント文 投稿者のid
post_id content user_id
モデルの作成
rails g model comment content:string user:references post:references
  1. post_idはコメントをたくさん持てる= has_many

  2. コメントはpost_idを一つだけ持てる=belong_to

  3. コメントは投稿者(つまりcurrent_user)のuser_id一つだけ持っている

  4. 投稿者(current_user)はコメントををたくさん持てる=has_many

Railsチュートリアルの復習ですが、関係性としてはこんな感じになると思います。

そして、先程のCommentモデルの作成でcomment.rbに

class Comment < ApplicationRecord
  belongs_to :user
  belongs_to :post
end

が含まれているので2. 3はもう大丈夫です。

残りの1. 4をuser.rbとpost.rbに追加します。

 has_many :comments

そしてrails db:migrate.

リソースの追加

以下の入れ子構造の書き方でpost_idと絡めたresouceとします。

  resources :posts,    only: [:show,:create, :destroy]  do    
    resources :comments, only: [:create]                                        #/posts/posts_id/comentにpostアクセスでcreateアクションを実行
  end
コメントの表示とフォーム画面の追加

以下で記事詳細表示ページにコメントの表示部を追加します

<% @comments.each do |c| %>
  <div>
    <a href="/users/<%= @post.user.id %>"><%= c.user.name %></a>
    <%= c.content %>
    <hr>
  </div>
<% end %>

form_forで、ネストされたURLにどうやってpostすればいいのか悩んだんですが、 インスタンス変数を配列として渡せば、よしなにしてくれるようです。

例えば/posts/posts_id/commentリソースにpostしたければ

<%= form_for [@post, @comment] do |f| %>

<% end %>

等と書くといいようです。

よって、以下を記事詳細画面に追加します

<%= form_for [@post, @comment] do |f| %>
  <%= f.text_field :content %>
  <br>
  <%= f.submit 'コメントする' %>
<% end %>

後はコントローラを下のように書けば動くはず…

class CommentsController < ApplicationController
  
  def create                                                                    #/posts/posts_id/comentにpostアクセスでcreateアクションを実行。コメントを投稿する
    @comment = Comment.new(comment_params)                                      #paramsから@commentを作成
    @comment.user_id = current_user.id                                          #ログインしている自分自身のidを代入
    if @comment.save                                                            #もし無事に保存できたら
      flash[:success] = "comment created!" 
      redirect_back(fallback_location: root_path)
    else
      flash[:danger] = "comment abort"
      redirect_back(fallback_location: root_path)
    end

  end

  private
  def comment_params
    params.require(:comment).permit(:content,:post)
  end
end

ここで問題発生。DBにcommentが保存できない。ここでまたも大ハマリしてしまいました。。(泣)

色々試してみるけど、一向に保存してくれない

原因を探っていくために、どもうsaveのままだとただfalseを返すだけなので、save!ににして例外を返してもらうようにする(エラー内容を吐いてくれる)

Validation failed: Post must exist, Post can’t be blank

とのこと。Postがない?@commentにはpost_idはあってもPostデータそのものは含まれていないはずだが…と考えながら調べてみると

以下の記事を発見

qiita.com

bryankawa.hatenablog.com

どうも紐付いているデータを検査しているので、紐付いているPostが空だとエラーが出るらしい。

つまり、紐付いているPostが空ということになります

Postデータと紐付いた形で@comment を作りたいが。。。小一時間悩んでいろんな資料を見たり、ググったり考えました。

ふと、外部主キーpost_id - content - user_id という形で紐付いているはずなので、

post_idの部分にUrlのパラメーターからPostデータを引っ張ってきて、Postを作り.commentsでつなげればPostモデルと紐付いたcommentデータが作れるのではないかと気づき、実際やってみました

  def create                                                                    #/posts/posts_id/comentにpostアクセスでcreateアクションを実行。コメントを投稿する
    @comment = Post.find(params[:post_id]).comments.new(comment_params)         #paramsから@commentを作成。その際Postも紐付ける
    @comment.user_id = current_user.id                                          #ログインしている自分自身のidを代入
    if @comment.save!                                                            #もし無事に保存できたら
      flash[:success] = "comment created!" 
      redirect_back(fallback_location: root_path)
    else
      flash[:danger] = "comment Error"
      redirect_back(fallback_location: root_path)
    end

  end

結果無事成功!commentと紐付いているPostデータが取得できていなかったんですね!

後はテストを書いて成功したらgit push してデプロイして完成です。