コメント機能
モデルの作成
userとboardに紐ずいたcommentモデルを作成します
bundle exec rails generate model comment body:text user:references board:references
db/migrate/YYYYMMDDhhmmss_create_comments.rb
class CreateComments < ActiveRecord::Migration[5.2] def change create_table :comments do |t| t.text :body, null: false t.references :user, foreign_key: true t.references :board, foreign_key: true t.timestamps end end end
モデルを作成する際にreferencesとすることでindexと外部キー制約(foregin_key: true)が自動で追加される。indexをuser_idとmicropost_idに追加することによってそれぞれに関連したコメントを探す際にデータを高速に調べられるようになり、外部キー制約がつくことによってuser_id(micropost_id)にはUserテーブルに存在するidのみデータベースレベルで保存するようになる。 また、コメントが空だとコメント機能の意味をなさないためnull:falseを追加する。 commentモデルに、bodyのバリデーションを追加
class Comment < ApplicationRecord belongs_to :user belongs_to :board validates :body, presence: true, length: { maximum: 65_535 } end
自動でbelongs_toで一対一の関連付けができている。User,Boardモデルにはそれぞれ手動で追加する必要がある。また、バリテーションも追加する。テーブル作成の時点でcontentにはnull:falseで空で保存させないようにしていますが空文字("")は保存できます。なのでpresence:trueを追加することによって空文字も拒否するようになる。また、バリデーションに基づいたエラーメッセージも保存されます。
Boardモデルにコメントとの関連を追加
掲示板とコメントが1対多の関係であることをモデルに追加する。
class Board < ApplicationRecord mount_uploader :board_image, BoardImageUploader belongs_to :user has_many :comments, dependent: :destroy validates :title, presence: true, length: { maximum: 255 } validates :body, presence: true, length: { maximum: 65_535 } end
ユーザーとコメントが1対多の関連であることをモデルに追加する。
class User < ApplicationRecord authenticates_with_sorcery! has_many :boards, dependent: :destroy has_many :comments, dependent: :destroy
Comenntsコントローラーの作成、ルーディングの設定
rails g controller comments
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
ルーティングの設定
resources :boards, only: %i[index new create show] do resources :comments, only: %i[create], shallow: true end end
commetsはboardsとネストして親子の関係を持たせる。こうすることによってコメントを作成する際に関連しているmicropostのidを取得することが容易になる。 shallowオプションとは何か? ルーティングの記述を複雑にせず、かつ深いネストを作らないという絶妙なバランスを保つことのできるオプション
対応するビューの作成
app/controllers/boards_controller.rb
def show @board = Board.find(params[:id]) @comment = Comment.new @comments = @board.comments.includes(:user).order(created_at: :desc) end
コメントのビューを実装する 掲示板の編集と削除のボタンは、掲示板の一覧と詳細ページで同じものを表示するので、部分テンプレートとして作成する。 app/views/boards/_crud_menus.html.erb
<ul class='crud-menu-btn list-inline float-right'> <li class="list-inline-item"> <%= link_to '#', id: "button-edit-#{board.id}" do %> <%= icon 'fa', 'pen' %> <% end %> </li> <li class="list-inline-item"> <%= link_to '#', id: "button-delete-#{board.id}" do %> <%= icon 'fas', 'trash' %> <% end %> </li> </ul>
app/views/boards/_board.html.erb
<%= image_tag board.board_image_url, class: 'card-img-top', size: '300x200' %> <div class="card-body"> <h4 class="card-title"> <%= link_to board.title, board_path(board) %> </h4> <%= render 'crud_menus', board: board %> <ul class="list-inline"> <li class="list-inline-item"> <%= icon 'far', 'user' %>
app/views/boards/show.html.erb
<% content_for(:title, @board.title) %> <div class="container pt-5"> <div class="row mb-3"> <div class="col-lg-8 offset-lg-2"> <h1><%= t('.title') %></h1> <!-- 掲示板内容 --> <article class="card"> <div class="card-body"> <div class='row'> <div class='col-md-3'> <%= image_tag @board.board_image.url, class: 'card-img-top img-fluid', size: '300x200' %> </div> <div class='col-md-9'> <h3 class="d-inline"><%= @board.title %></h3> <%= render 'crud_menus', board: @board %> <ul class="list-inline"> <li class="list-inline-item">by <%= @board.user.decorate.full_name %></li> <li class="list-inline-item"><%= l @board.created_at, format: :long %></li> </ul> </div> </div> <p><%= simple_format(@board.body) %></p> </div> </article> </div> </div> <!-- コメントフォーム --> <%= render 'comments/form', { board: @board, comment: @comment } %> <!-- コメントエリア --> <%= render 'comments/comments', { comments: @comments } %> </div>
app/models/user.rb
def own?(object) id == object.user_id end
ユーザーのコメントであるかを判定するメソッドをuserモデルに追加する。
コメントフォームのテンプレートを作成する。 app/views/comments/_form.html.erb
<div class="row mb-3"> <div class="col-lg-8 offset-lg-2"> <%= form_with model: comment, url: [board, comment], local: true do |f| %> <%= render 'shared/error_messages', object: f.object %> <%= f.label :body %> <%= f.text_area :body, class: 'form-control mb-3', id: 'js-new-comment-body', row: 4, placeholder: Comment.human_attribute_name(:body) %> <%= f.submit t('defaults.post'), class: 'btn btn-primary' %> <% end %> </div> </div>
コメント一覧のテンプレートを作成する。 app/views/comments/_comments.html.erb
<div class="row"> <div class="col-lg-8 offset-lg-2"> <table id="js-table-comment" class="table"> <%= render comments %> </table> </div> </div>
app/views/comments/_comment.html.erb
<tr id="comment-<%= comment.id %>"> <td style="width: 60px"> <%= image_tag 'sample.jpg', class: 'rounded-circle', size: '50x50' %> </td> <td> <h3 class="small"><%= comment.user.decorate.full_name %></h3> <div id="js-comment-<%= comment.id %>"> <%= simple_format(comment.body) %> </div> <div id="js-textarea-comment-box-<%= comment.id %>" style="display: none;"> <textarea id="js-textarea-comment-<%= comment.id %>" class="form-control mb-1"><%= comment.body %></textarea> <button class="btn btn-light js-button-edit-comment-cancel" data-comment-id="<%= comment.id %>">キャンセル</button> <button class="btn btn-success js-button-comment-update" data-comment-id="<%= comment.id %>">更新</button> </div> </td> <% if current_user.own?(comment) %> <td class="action"> <ul class="list-inline justify-content-center" style="float: right;"> <li class="list-inline-item"> <a href="#" class='js-edit-comment-button' data-comment-id="<%= comment.id %>"> <%= icon 'fa', 'pen' %> </a> </li> <li class="list-inline-item"> <a href="#" class='js-delete-comment-button' data-comment-id="<%= comment.id %>"> <%= icon 'fas', 'trash' %> </a> </li> </ul> </td> <% end %> </tr>
参考文献