Rails コメント機能
今回はコメント機能を作っていきます。
コメント機能とは何らかのデータに対してコメントができるものになります。
本記事は以下のマガジンで実装した内容を引き継いでいますが、以下のマガジンの内容を実装していなくても問題ありません。
コメントテーブルを追加
まずはコメントテーブルを追加します。
本記事では、コメントはuserとbookに紐づくようにしますが、お好みのデータに紐づくように実装していただいて問題ありません。
まずは以下のコマンドを実行してモデルを追加します。
bundle e rails g model comment body:text user:reference book:reference
コメントの内容を保存するカラムは bodyとしました。たまに comments テーブルに comment カラムを追加する記事を見かけますがカラム名とテーブル名が被っていてわかりづらいので避けた方が良いでしょう。
以下のコマンドを実行し、テーブルの作成を行います。
bundle e rails db:migrate
モデルを作成するだけではテーブルは作成されません。必ず migrate を実行するようにしましょう。
以下のようなテーブルが作成されていれば成功です。
テーブルが作成済みかどうかは schema.rb を見て確認します。
create_table "comments", force: :cascade do |t|
t.text "body"
t.integer "user_id", null: false
t.integer "book_id", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["book_id"], name: "index_comments_on_book_id"
t.index ["user_id"], name: "index_comments_on_user_id"
end
これでテーブルの作成は完了です。
コメントを登録するためのURLをつくる
次に、コメントを登録する際のURLを作成します。
config/routes.rb を修正します。本記事の場合は以下のように修正します。
resources :books do
...
resources :comments, only: [:create, :destroy]
end
作成されたURLを確認してみます。以下のコマンドを実行します。
bundle e rails routes -g comment
以下のように出力されていれば成功です。
Prefix Verb URI Pattern Controller#Action
book_comments POST /books/:book_id/comments(.:format) comments#create
book_comment DELETE /books/:book_id/comments/:id(.:format) comments#destroy
今回はコメントを作成、削除する機能しか追加する予定がないため、create と destroy のURLのみを追加しています。
has_many を追加する
紐付けたいデータに対して has_many を追加します。
class Book < ApplicationRecord
...
has_many :comments, dependent: :destroy
...
end
class User < ApplicationRecord
...
has_many :comments, dependent: :destroy
end
コメントを登録するフォームをつくる
次に、コメントを登録するためのフォームを作成します。
お好みのページに作成してください。今回は book の詳細ページにフォームを追加します。
<%= form_with local: true do |form| %>
<%= form.text_area :body %>
<%= form.submit '作成' %>
<% end %>
現在の状態では入力欄にテキストを入れて作成ボタンを押しても何も起こりません。コメントの作成を可能な状態にするため、徐々に修正していきます。まずはformに model の指定を追加します。
<%= form_with model: @comment, local: true do |form| %>
<%= form.text_area :body %>
<%= form.submit '作成' %>
<% end %>
@comment という変数を追加しました。この変数はどこにも定義していないため、コントローラ側に変数の定義を追加します。
def show
@book = Book.find(params[:id])
@comment = current_user.comments.new(book: @book)
end
次に、コメントを登録するためのURLのオプションを追加します。URLは以下のコマンドを実行して出力された結果から探します。
bundle e rails routes -g comment
create のURLを指定すればOKです。
<%= form_with model: @comment, url: book_comments_path(@book), local: true do |form| %>
<%= form.text_area :body %>
<%= form.submit '作成' %>
<% end %>
次にコントローラを作成します。
bundle e rails g controller comments create destroy --no-helper --skip-routes
次にコントローラの処理を修正してコメントを作成する処理を追加します。
comments_controller.rb を修正していきます。
ストロングパラメータを追加します。
private
def comment_params
params.require(:comment).permit(:body)
end
comment を保存する処理を追加します。
def create
@book = Book.find(params[:book_id])
@comment = @book.comments.new(comment_params.merge(user: current_user))
if @comment.save
redirect_to book_path(@book)
else
render 'books/show'
end
end
book と紐づける必要があるため、まずは book のデータを取得する必要があります。ルーティングを見てみると、URLから book の id が取得できることがわかります。ただし、id ではなく book_id となっている点に注意です。
Prefix Verb URI Pattern Controller#Action
book_comments POST /books/:book_id/comments(.:format) comments#create
これでコメントを作成することができるようになったと思います。
空のままでは保存できないよう、バリデーションを追加しましょう。
models/comment.rb に以下のコードを追加します。
validates :body, presence: true
これでコメントが空のまま登録されることはなくなりました。
コメントを表示する
次に、登録したコメントを表示します。
今回は本の詳細ページに表示してみます。
コメント
<br />
<% @book.comments.each do |comment| %>
<%= comment.body %><br />
<% end %>
簡単ですね。
コメントを削除する
次に、コメントを削除する処理を追加します。
まずはコメントを削除するリンクを追加しましょう。
<% @book.comments.each do |comment| %>
<%= comment.body %>
<%= link_to '削除', book_comment_path(@book, comment), method: :delete %><br />
<% end %>
URLヘルパーを使用する際は引数に注意しましょう。rails routes の出力結果を確認し、URLに : から始まる値が含まれている数の分だけ引数の数が必要となります。以下のコメントを削除するURLの場合は :book_id と :id がURLに含まれているため、2つの引数が必要となります。そのため、book_comment_path には引数を2つ(@book と comment)与えています。
book_comment DELETE /books/:book_id/comments/:id(.:format) comments#destroy
コントローラにコメントを削除する処理を追加します。
def destroy
book = Book.find(params[:book_id])
book.comments.find(params[:id]).destroy
redirect_to book_path(book)
end
これで削除が完成しました。
ただし、今のままではコメントの削除が誰でもできてしまうため、コメントを行なった本人しか削除できないようにしたいと思います。
まずは削除のリンクを表示させないようにしてみます。
<% if comment.user == current_user %>
<%= link_to '削除', book_comment_path(@book, comment), method: :delete %><br />
<% end %>
このように実装するとコメントを行なったユーザーとログインユーザーが同一の場合のみ削除することができます。ただし、実装の方法がイケてないという問題があります。そのため、以下のようなメソッドを使用するようにします。
<% if comment.user.me?(current_user.id) %>
<%= link_to '削除', book_comment_path(@book, comment), method: :delete %><br />
<% end %>
me? というメソッドを使うようにしました。このメソッドは以下のように models/user.rb に作成することで使えるようになります。
def me?(user_id)
id == user_id
end
テストは以下のようになります。
describe '#me?' do
subject { user.me?(user_id) }
let(:user) { create(:user) }
context 'レシーバと引数のID が同一の場合' do
let(:user_id) { user.id }
it { is_expected.to be true }
end
context 'レシーバと引数のID が同一ではない場合' do
let(:other_user) { create(:user) }
let(:user_id) { other_user.id }
it { is_expected.to be false }
end
end
もちろんテストを実行するとグリーンになります。
これでコメント機能が完成しました。
この記事が気に入ったらサポートをしてみませんか?