🕌

【Rails】コメント機能

2023/06/28に公開

本の投稿サイトにコメント機能を追加します。
いいね機能の実装も参考になったら嬉しいです。
https://zenn.dev/ganmo3/articles/c071ba9aecaa51

実装機能の要件

実装機能の要件は以下の通りです。

コントローラ アクション 用途
book_comments create コメントを作成する
book_comments destroy コメントを削除する

注意点:

  1. 自分のコメントしか削除できないこと
  2. コメント、コメント削除後は行う前の画面に遷移すること
モデル 用途 備考
book_comment 投稿に対するコメントを管理する 空のコメントは保存できないこと
ビュー 要素
投稿一覧画面 コメント数表示
投稿詳細画面 投稿に対するコメント一覧表示
コメント数表示
ユーザー詳細画面 コメント数表示

いいね機能のテーブル設計

以下のような、テーブル設計にします。

カラム名 データ型 カラムの説明
id integer コメントごとのID
comment text コメント本文
user_id integer コメントしたユーザーのID
book_id integer コメントされた投稿のID

Model作成

ターミナルでの入力方法は以下の通りです。

  1. BookCommentモデルの生成
ターミナル
rails g model BookComment comment:text user_id:integer book_id:integer

上記のコマンドを実行すると、db/migrateディレクトリにマイグレーションファイルが生成されます。

  1. データベースへのマイグレーション実行
ターミナル
rails db:migrate

上記のコマンドを実行することで、マイグレーションファイルに基づいてデータベースにテーブルが作成されます。

以上の手順により、BookCommentモデルが作成され、commentuser_idbook_idのカラムが含まれるテーブルがデータベースに作成されます。

関連付けの設定

BookCommentモデルと他のモデルの関連付けを設定します。

モデルの関係性

図表で表すと以下の通りです。

  • User(ユーザー)とBookComment(コメント)の関係は、1対多の関係です。1人のユーザーが複数のコメントを行えます。
  • Book(投稿)とBookComment(コメント)の関係も、1対多の関係です。1つの投稿が複数のコメントを持つことができます。

つまり、ユーザーとコメントの関係は「1:N」であり、投稿とコメントの関係も「1:N」です。

関連付けの実装

以下のようにそれぞれのモデルに関連付けを記述します。

  • BookCommentモデル
app/models/book_comment.rb
class BookComment < ApplicationRecord
    belongs_to :user
    belongs_to :book
    
    validates :comment, presence: true
end

上記のコードでは、BookCommentモデルがUserモデルとBookモデルに対してそれぞれ1対多の関連付けを持つことを表しています。
また、validates :comment, presence: trueを記載することでバリデーションの定義をします。ここでは、空のコメントが保存されないようにします。

  • Userモデル
app/models/user.rb
class User < ApplicationRecord
  has_many :book_comments, dependent: :destroy
end
  • Bookモデル
app/models/book.rb
class Book < ApplicationRecord
  has_many :book_comments, dependent: :destroy
end

上記のコードでは、Userモデル、Bookモデルにも同様にhas_many :BookCommentの関連付けを定義しています。これにより、Userモデル、Bookモデルのインスタンスは複数のBookCommentモデルのインスタンスと関連付けられます。また、dependent: :destroyオプションによって、UserBookが削除された場合に関連するBookCommentも同時に削除されます。

以上の設定により、UserモデルとBookモデルはそれぞれ複数のBookCommentモデルと関連付けられ、1対多の関係が成立します。

Routing

以下の通りルーティングの設定をします。

config/routes.rb
Rails.application.routes.draw do
 :
  resources :books, only: [:index, :show, :edit, :create, :destroy, :update] do
+   resources :book_comments, only: [:create, :destroy]
    resource :favorites, only: [:create, :destroy]
  end
  resources :users, only: [:index, :show, :edit, :update]
end

コメントは投稿に対して関連付けられるためbooksと`book_commentsの親子関係を設定します。コメントは特定の投稿に紐付いており、その投稿の下にコメントを追加する形で表示や操作を行いたいからです。

Controller作成

ターミナルで以下のコマンドを実行してコントローラを作成します。

ターミナル
rails g controller book_comments

book_comment_controllerへの記述

book_comments_controller.rbファイルに以下のコードを記述します。

app/controllers/book_comments_controller.rb
class BookCommentsController < ApplicationController
  def create
    book = Book.find(params[:book_id])
    comment = current_user.book_comments.new(book_comment_params)
    comment.book_id = book.id
    comment.save
    redirect_to request.referer
  end
  
  def destroy
    comment = BookComment.find(params[:id])
    comment.destroy
  end
  
  private
  
  def book_comment_params
    params.require(:book_comment).permit(:comment)
  end
end

以下の通りコードの解説をします。

  1. createメソッドは、新しいコメントを作成するためのアクションです。まず、URLのbook_idから関連する本を見つけます。次に、現在のユーザーによる新しいコメントを作成します。comment.book_id = book.idで作成したコメントに関連する本のIDを設定し、保存します。最後に、リクエスト元のページにリダイレクトします。

  2. destroyメソッドは、コメントを削除するためのアクションです。与えられたコメントのIDを使用して、対応するコメントを見つけて削除します。その後、リクエスト元のページにリダイレクトします。

  3. book_comment_paramsは、ストロングパラメータを使用して、安全なパラメータのみを受け入れるようにしています。コメント作成時に必要なcommentパラメータのみを許可しています。

books_controllerへの記述

コメントを投稿するためのインスタンス変数を定義します。

app/controllers/books_controller.rb
class BooksController < ApplicationController
 :
  def show
    @book = Book.find(params[:id])
    @books = Book.new
    @user = @book.user
+   @book_comment = BookComment.new
  end
 :
end

今回は本の詳細ページであるshowアクションで、コメント投稿フォームを表示します。
そのため、上述のコードを追加することで、新しいコメントの投稿フォームを表示できるようします。

Viewページ

コメント投稿ページと、コメント件数の表示を記述していきます。

コメント投稿ページ

今回は部分テンプレートファイルで作成します。

  • コメント投稿フォーム
app/views/book_comments/_form.html.erb
<%= form_with model: [book, book_comment] do |f| %>
  <%= f.text_area :comment, rows:'5', placeholder: "コメントをここに", class: "w-100" %>
  <%= f.submit "送信" %>
<% end %>

解説:

  1. <%= form_with model: [book, book_comment] do |f| %>
    form_withに対して、[book, book_comment]のように、配列で2つの変数を指定しています。
    特定の本に対してコメントを投稿する場合、そのコメントはその本と関連付けられます。この場合、コメント投稿フォームはコメントの内容だけでなく、どの本に対するコメントなのかもフォームで指定する必要があります。

  2. <%= f.text_area :comment, rows:'5', placeholder: "コメントをここに", class: "w-100" %>
    rows:'5':テキストエリアを5行に表示しています。
    placeholder: "コメントをここに":テキストエリア内に表示されるテキストを指定します。このテキストは、ユーザーに対して入力する内容のヒントを与える役割があります。
    class: "w-100":Bootstrapのクラスで、テキストエリアの幅を100%(width: 100%)に設定します。

  • コメント一覧部
app/views/book_comments/_index.html.erb
<table>
  <tbody>
    <% book.book_comments.each do |book_comment| %>
    <tr>
      <td>
        <%= link_to user_path(book_comment.user) do %>
          <%= image_tag book_comment.user.get_profile_image, size: "50x50" %><br>
          <%= book_comment.user.name %>
        <% end %>
      </td>
      <td><%= book_comment.comment %></td>
      <td>
        <% if book_comment.user == current_user %>
          <%= link_to "Destroy", book_book_comment_path(book, book_comment), method: :delete, class: "btn btn-sm btn-danger float-end" %>
        <% end %>
      </td>
    </tr>
    <% end %>
  </tbody>
</table>

解説:
コメント一覧を表示し、各コメントにはユーザーのプロフィール画像と名前を表示します。コメントの所有者のみが削除ボタンを表示し、ボタンを右側に寄せています。

コメント投稿ファイルの呼び出しとコメント件数表示

先ほどの部分テンプレートファイルを詳細画面から呼び出します。
また、詳細画面と一覧画面にそれぞれにコメント件数を表示する記述を追加します。

  • 詳細画面
app/views/book/show.html.erb
 <div class="container">
   <div class="row">
     <div class="col-md-3">
       <h2>User info</h2>
       <%= render "users/info", user: @user %>
       <h2 class="mt-3">New book</h2>
       <%= render "form", book: @books %>
     </div>
     <div class='col-md-8 offset-md-1'>
       <h2>Book detail</h2>
       <table class="table">
         <tr>
           <td><%= link_to @book.user do %>
               <%= image_tag @book.user.get_profile_image, size:"100x100" %><br>
               <%= @book.user.name %>
               <% end %>
           </td>
           <td><%= link_to @book.title, @book %></td>
+	   <td>コメント数: <%= @book.book_comments.count %></td>
           <td><%= @book.body %></td>
           <td><%= render "favorites/btn", book: @book %></td>
          
           <% if @book.user == current_user %>
           <td><%= link_to "Edit", edit_book_path(@book), class: "btn btn-sm btn-success" %></td>
           <td><%= link_to "Destroy", @book, method: :delete, data: { confirm: "本当に消しますか?" }, class: "btn btn-sm btn-danger" %></td>
           <% end %>
         </tr>
       </table>
      
+      <%= render "book_comments/index", book: @book %>
+      <%= render "book_comments/form", book: @book, book_comment: @book_comment %>
      
     </div>
   </div>
 </div>
  • 一覧画面
app/views/book/_index.html.erb
  <table class="table table-hover table-inverse">
   <thread>
     <tr>
       <th></th>
       <th>Title</th>
       <th>Opinion</th>
       <th colspan="3"></th>
     </tr>
   </thread>
   <tbody>
     <% books.each do |book| %>
     <tr>
       <td><%= link_to book.user do %>
           <%= image_tag book.user.get_profile_image, size:'50x50' %>
           <% end %>
       </td>
       <td><%= link_to book.title,book %></td>
       <td><%= book.body %></td>
       <td><%= render "favorites/btn", book: book %></td>
+      <td>コメント数: <%= book.book_comments.count %></td>
     </tr>
    <% end %>
    </tbody>
  </table>

コメント機能の実装でした。

Discussion