0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Railsで作ったSNSアプリでユーザの投稿へのコメント作成機能を実装する

Last updated at Posted at 2020-01-15

・参考URL
https://sadah.github.io/rails-training/ja/004_comments.html

今回紹介するコードは、プログラミングスクールで学んだTwitterクローンを基にしたRailsのアプリ(Micropost)への追加機能であることを前提にするとコードが読みやすくなると思います。

#モデルの作成

rails g model Comment content:string user:references micropost:references

コメントはUserとMicropostの多:多の関係性を表すため、中間テーブルが必要になります。
Userがコメントする投稿が複数あって、MicropostにはコメントしたUserが複数いるためです。
###マイグレーションファイル

年月日時_create_relationships.rb
class CreateComments < ActiveRecord::Migration[5.2]
  def change
    create_table :comments do |t|
      t.string :content
      t.references :user, foreign_key: true
      t.references :micropost, foreign_key: true

      t.timestamps
    end
  end
end

マイグレーションを忘れずに。

rails db:migrate

自動作成されたコメントモデルにバリデーションを付けておきましょう。

###コメントモデル

comment.rb
class Comment < ApplicationRecord
  belongs_to :user
  belongs_to :micropost

  validates :content, presence: true, length: { maximum: 255 }
end

コメントを文字なしで投稿できないように設定します。

#関連モデル

###Userモデル

user.rb
class User < ApplicationRecord
  has_many :microposts
  has_many :comments
end

一つのUserに対して多数のコメントがあるためこの関係になります。
###Micropostモデル

micropost.rb
class Micropost < ApplicationRecord
  belongs_to :user
  has_many :comments, dependent: :destroy
end

一つのMicropostに対して多数のコメントがあるためこの関係になります。

Micropostモデルにdependent: :destroyを追記したことで、投稿が削除されるとそれに紐づいたコメントが削除されるようになります。
dependent: :destroyがないと、コメントの付いた投稿を削除するときにエラーが起きます。

micropost.rb
has_many :commenters, through: :comments, source: :user

Micropost側から見ると、コメントしてくるユーザが多数いるので、正確にモデル作成するなら以下のコードの追記が必要かもしれません。(今回使うことはありませんでしたが、間違っていたらごめんなさい)
#ルーティング

routes.rb
Rails.application.routes.draw do
  resources :microposts, only: [:create, :destroy, :show] do
    resources :comments, only: [:create, :destroy]
  end
end

どのMicropostに対してコメントなのかを明らかにするためにルーティングのネスト(入れ子構造)をします。
#コントローラー作成

$ rails g controller comments create destroy

コメントで必要な機能は作成機能(のちに削除機能)の2つだけなのでターミナルへの入力はこのようになります。

comments_controller.rb
class CommentsController < ApplicationController
  before_action :require_user_logged_in
  
  def create
    @micropost = Micropost.find(params[:micropost_id]) 
    @comment = @micropost.comments.build(comment_params)
    @comment.user_id = current_user.id
    if @comment.save
      flash[:success] = '投稿にコメントしました。'
      redirect_back(fallback_location: root_path)
    else
      @micropost = Micropost.find(params[:micropost_id]) 
    @comments = @micropost.comments.includes(:user)
      flash.now[:danger] = '投稿へのコメントに失敗しました。'
      render 'microposts/show'
    end
  end

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

コメントはmicropostと紐づいているため@micropostを頭につけます。
コメントとuserを紐づけるために@comment.user_id = current_user.idが必要です。
これがないとコメントからuserデータを得られません。

else文が実行されるときrenderするファイルに@micropost@comments
2つのインスタンス変数を渡すのは、micropostのshowファイルを表示するために必要だからです。
片方でも欠けるとエラーになります。

###関連コントローラー
コメント作成を投稿の詳細ページ(show)で実行できるようにします。

microposts_controller.rb
class MicropostsController < ApplicationController
  before_action :require_user_logged_in

  def show
    @micropost = Micropost.find(params[:id])
    @comments = @micropost.comments.includes(:user)
    @comment = @micropost.comments.build(user_id: current_user.id) if current_user # form_with 用
  end
end

showページの@micropost = Micropost.find(params[:id])で特定の一つのmicropostを表示できるように設定します。

@comments = @micropost.comments.includes(:user)により、対応する投稿へのコメントを一覧表示することができます。
includes(:user)はコメントしたuserを表示するためにuserカラムを取得するメソッドです。これがないとgravatar_urlなどを表示する時にuserがnilになりエラーが起こります。

@commentには、コメント入力フォームの表示エラーを避けるために空のコメントを入れておきます。

comments_controller.rb
class CommentsController < ApplicationController
  before_action :require_user_logged_in
  
  def create
    @micropost = Micropost.find(params[:micropost_id]) 
    @comment = @micropost.comments.build(comment_params)
    @comment.user_id = current_user.id
    if @comment.save
      flash[:success] = '投稿にコメントしました。'
      redirect_back(fallback_location: root_path)
    else
      @micropost = Micropost.find(params[:micropost_id]) 
      @comments = @micropost.comments.includes(:user)
      flash.now[:danger] = '投稿へのコメントに失敗しました。'
      render 'microposts/show'
    end
  end
  
  private
  
  def comment_params
    params.require(:comment).permit(:content)
  end
end

基本的にはmicroposts_controllerのcreateと同じようにします。(microposts_controllerに関してはRails Tutorialなどを参考にしてください)
コメントはmicropostに紐づくように作成したいので、対応するmicropost
インスタンスを記入しておきます。
@comment.user_id = current_user.idがないとコメントにuser情報が入らないため必須です。
@micropost@comennts両方のインスタンスを代入しないとrender先のshowファイルで
コメント失敗メッセージを表示できずにエラーが起こります。

#投稿詳細ページのルーティングの確認
投稿詳細ページに飛ぶためのルーティングをターミナルを使って確認します。

$ rails routes

microposts#showページに飛ぶために必要なPrefixを確認します

micropost GET    /microposts/:id(.:format)      microposts#show

投稿一覧のパーシャルに以下の1行を追加して、投稿詳細ページに移動できるようにしましょう。

microposts一覧のパーシャルファイルの一部
<% microposts.each do |micropost| %><%= link_to 'Comments', micropost_path(micropost), class: 'btn btn-link btn-sm' %><% end %>

microposts#showのPrefixがmicropostなのでmicropost_pathと記入します。
micropost一覧から特定の|micropost|のページに飛びたいので、
micropost_pathの()内には||内のmicropostを代入します。

#Viewファイル

###投稿詳細ページ
コメントしたい特定のmicropostを表示するファイルです。
micropostを表示させるためには、controllerのdef showで定義した
@micropostのインスタンスをshowファイルに渡せるように注意しましょう。

microposts/show.html.erb
<ul class="list-unstyled">
  <li class="media mb-3">
    <img class="mr-2 rounded" src="<%= gravatar_url(@micropost.user, { size: 50 }) %>" alt="">
    <div class="media-body">
      <div>
        <%= link_to @micropost.user.name, user_path(@micropost.user) %> <span class="text-muted">posted at <%= @micropost.created_at %></span>
      </div>
      <div>
        <p><%= @micropost.content %></p>
      </div>
      <div class="btn-group">
        <% if current_user == @micropost.user %>
          <%#=詳細ページで削除するとid見つからないエラー発生 link_to "Delete", @micropost, method: :delete, data: { confirm: "You sure?" }, class: 'btn btn-danger btn-sm' %>
        <% end %>
      <%= render 'favorites/favorite_button', micropost: @micropost %>
      </div>
    </div>
  </li>
</ul>

<%# コメント入力フォームのパーシャル %>
<%= render 'comments/form', micropost: @micropost %>

<%# コメント一覧のパーシャル %>
<%= render 'comments/comments', micropost: @micropost %>

投稿詳細ページでmicropostを削除すると、micropostの削除自体はできても
ルーティングエラーが起きてしまうため、現在は削除ボタンを使えないようにしています。

###コメント入力フォーム
form_withのmodelに@micropost@commentが入るようにしないとエラーが起こります。

comments/_form.html.erb
<div class="col-sm-8">
<%= form_with(model: [@micropost, @comment], local: true) do |f| %>
  <div class="form-group">
    <%= f.text_area :content, class: 'form-control', rows: 3 %>
  </div>
  <%= f.submit 'Comment', class: 'btn btn-primary btn-block' %>
<% end %>
</div>

###コメント一覧

comments/_comments.html.erb
<ul class="list-unstyled">
  <% @comments.each do |comment| %>
    <li class="media mb-3">
      <img class="mr-2 rounded" src="<%= gravatar_url(comment.user, { size: 50 }) %>" alt="">
      <div class="media-body">
        <div>
          <%= link_to comment.user.name, user_path(comment.user) %> <span class="text-muted">posted at <%= comment.created_at %></span>
        </div>
        <div>
          <p><%= comment.content %></p>
        </div>
        <div class="btn-group">
          <% if current_user == comment.user %>
            <%#= link_to "Delete", micropost_comment_path(comment), method: :delete, data: { confirm: "You sure?" }, class: 'btn btn-danger btn-sm' %>
          <% end %>
        </div>
      </div>
    </li>
  <% end %>
  <%#= paginate comments %>
</ul>

コメント削除機能は正しいルーティングができていないので、まだ未実装です。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?