はじめに
コメント機能を実装するための忘備録です。
Userモデル、Articleモデル(Postモデルと同じ)は作成済みで行っています。
実行環境
- Rails 7.0.4.1
- Ruby 3.0.4
- Devise 4.8.1
実装
1. アソシエーション
articlesテーブルとcommentsテーブルを1対多でつなぎ、comment_contentのレコードを追加していくだけでコメントを残すことは出来そうですが、
今回は、誰がコメントを追加したかまで実装したいため、articlesテーブルだけでなく、usersテーブルもcommentsテーブルにつなげます。
この場合、usersテーブルとariticlesテーブルは多対多の関係になっており、commentsテーブルはそれを実現させる為の関係テーブルです。
まとめると、
・usersとcommentsは1対多の関係
・articlesとcommentsは1対多の関係
・usersとariticlesは多対多の関係
になっています。
2.モデルの作成
$ rails g model Comment comment_content:text user:references post:references
データベースへ反映。
$ rails db:migrate
3.マイグレーション
Userモデル:
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
has_many :articles, dependent: :destroy
has_many :comments, dependent: :destroy #userの削除と同時に、commentも削除される
end
Articleモデル:
class Article < ApplicationRecord
belongs_to :user
has_many :comments, dependent: :destroy #articleの削除と同時に、commentも削除される
end
Commentモデル:
class Comment < ApplicationRecord
belongs_to :user
belongs_to :article
validates :user_id, presence: true
validates :article_id, presence: true
validates :comment_content, presence: true #空のコメントを送信できないようにする
end
4.コントローラの作成
コントローラなので複数系を使って作成します。頭文字は大文字でも小文字でも平気なようです。
$ rails g controller posts index show
$ rails g controller comments
articlesコントローラ:
class ArticlesController < ApplicationController
before_action :authenticate_user!, only: %i[show create destroy]
before_action :correct_user, only: :destroy
def index
@articles = current_user.articles.all
@article = current_user.articles.new
end
def show
@article = Article.find(params[:id])
@comment = Comments.new
@comments = @article.comments
end
.
.
end
今回は、コメント機能の作成のため記事の作成方法は省略します。
viewでarticleのデータとコメントのデータを使いたいので、インスタンス変数で宣言します。(@記号で始まる変数)
commentsコントローラ:
class CommentsController < ApplicationController
before_action :authenticate_user!
before_action :correct_user, only: :destroy
def create
@comment = current_user.comments.create(comment_params)
redirect_to request.referer || root_url
end
def destroy
@comment.destroy
redirect_to request.referer || root_url, status: :see_other
end
private
def comment_params
params.require(:comment).permit(:comment_content, :article_id)
end
def correct_user
@comment = current_user.comments.find_by(id: params[:id], article_id: params[:article_id])
redirect_to root_url, status: :see_other if @comment.nil?
end
end
Deviseのcurrent_userで、今ログインしているユーザーを使えます。
コメントの削除をする際には、viewでも設定するのですが、ログインしているユーザーがしたコメントと、削除するコメントのユーザーが同じか確認できるようにしています。
また、rails7 では、destroyアクションの後に、status: :see_other 付ける必要があります。
5.ルーティングの設定
resources :articles do
resources :comments, only:[:create, :destroy]
end
do..endで、ルーティングを行うと親子関係が作成できます。
$ rails routes を実行すると、
article_comments POST /articles/:article_id/comments(.:format) comments#create
article_comment DELETE /articles/:article_id/comments/:id(.:format) comments#destroy
/postsの中にcommetnsがあることが確認できます。この/postsの中にcommetnsがあることが確認できます。この関係をネストと言そうです。
DELETEのルーティングをみると、リクエストを送信するのに articleのid, commentのid それぞれが必要になることが分かります。
6.viewの作成
.
.
<div class="comments">
<h2>コメントをする</h2>
<%= form_with(model:[@article, @comment], local: true) do |f| %>
<%= f.text_area :comment_content %>
<%= f.hidden_field :article_id, value: @article.id %>
<%= f.submit 'コメントする' %>
<% end %>
<h2>コメント一覧</h2>
<span>
<% @comments.each do |comment| %>
<div class="comments-index">
<%= comment.user.name %>
<%= comment.comment_content %><br>
Commented <%= time_ago_in_words(comment.created_at) %>前
<% if current_user === comment.user %> # ログインしているユーザーと、コメントがそのユーザーのものかの確認
<div class="comment-delete">
<%= link_to "delete", article_comment_path(comment.article, comment), data: { "turbo-method": :delete,
turbo_confirm: "You sure?" } %>
</div>
</div>
<% end %>
<% end %>
</span>
</div>
.
.
articlesコントローラでshowの中に、@article
, @comments
, @comment
を宣言しているので、viewで使うことができます。
form_with(model:[@article, @comment], method: :post) となっていますが、
先程のネストされたルーティングへ、リクエストを送る際には、articleとcomment両方の情報が必要になるからです。
form_withについて:
So when passing such a model record, Rails infers the URL and method.
<%= form_with model: @post do |form| %> ... <% end %>
is then equivalent to something like:
<%= form_with scope: :post, url: post_path(@post), method: :patch do |form| %> ... <% end %>
参考にさせていただいた記事