この記事はプログラミング学習者がアプリ開発中に躓いた内容を備忘録として記事におこしたものです。内容に不備などあればご指摘頂けると助かります。
記事投稿の背景
ここではXのクローンサイトを作成中に実装した機能についてご紹介します。投稿に対してコメントをする機能までは実装していたのですが、コメントに対する返信機能を実装する時に実装方法が分からずに調べた内容となります。
実装したコードの紹介と解説
class AddParentIdToComments < ActiveRecord::Migration[7.0]
def change
add_reference :comments, :parent, foreign_key: { to_table: :comments }
end
end
-
parent_id
というカラムをcommentsテーブルに追加します。 -
foreign_key: {to_table: :comments}
の部分でこのカラムにforeign_keyとしての制約を与え、そのキーがcommentsテーブルのidカラムを参照するようにします。
class Comment < ApplicationRecord
belongs_to :user
belongs_to :tweet
belongs_to :parent, class_name: 'Comment', optional: true # 親コメント(返信先) Nillも許容
has_many :replies, class_name: 'Comment', foreign_key: 'parent_id', dependent: :destroy # 返信
validates :sentence, presence: true, length: { in: 1..140 }
has_many_attached :images
end
-
belongs_to :parent, class: 'Comment', optional: true
では
Commentモデルが他のComment(親コメント)に属することを示しています。
belongs_to :parent
の部分でCommentモデルにはparent_id
というカラムが存在し、このカラムが親コメント(返信先)を指しています。:parent
はparent_id
カラムを使用して関連付けを行うためのエイリアスです。実際にbelongs_toで関連付けされるのはparent_id
というカラムでこのカラムに親コメントのid
が入ります。
class_name: 'Comment'
を入れることにより、:parent
がCommentモデルを指していることを表しています。
optional: true
ではparent
(親コメント)が存在しなくても(parent_id
がnilでも)保存できることを意味しています。トップレベルのコメントは親コメントを持たないため、parent_id
がnilでも保存できるようにしています。
-
has_many :replies, class_name: 'Comment', foreign_key: 'parent_id', dependent: :destroy
ではCommentモデルが複数の返信(コメント)を持つことを示しています。
has_many :replies
の部分ではCommentモデルが複数のreplies
(返信)を持つという関連付けをしています。ここでのreplies
はCommentモデルの別名(エイリアス)として使われています。但し、実際にはreplies
というカラムはcommentsテーブルには存在しません。
replies
はあくまで「Commentの集合」であり、データベースの集合ではありません。実際の関連付けは、parent_id
を通じて行われます。replies
は親コメントにparent_id
を通して関連づけられたコメント(返信)の集合を意味しています。
class_name: 'Comment'
ではreplies
がCommentモデルのインスタンスであることを意味しています。
foreign_key: 'parent_id
ではparent_id
という外部キーを使って関連付けを行います。あるコメントが他のコメントに対する返信の場合、そのコメントのparent_id
には親コメントのid
が入ります。
def new
@comment = Comment.new
end
def create
@comment = Comment.new(comment_params)
@comment.user_id = current_user.id
@comment.tweet_id = params[:tweet_id]
@comment.parent_id = params[:id] if params[:id].present? && Comment.exists?(params[:id]) # 親コメントに対して返信する場合
if @comment.save
redirect_to home_index_path, notice: 'コメントを投稿しました。'
else
flash.now[:alert] = 'コンテンツの投稿に失敗しました。'
@tweets = Tweet.all.includes(:user,
{ images_attachments: :blob }).order(created_at: 'DESC').page(params[:recommend])
@followers_tweets = Tweet.includes(:user,
{ images_attachments: :blob }).where(user_id: current_user.followers.pluck(:user_id)).page(params[:follow])
render template: 'home/index', status: :unprocessable_entity
end
end
private
def comment_params
params.require(:comment).permit(:sentence, images: [])
end
@comment.parent_id = params[:id] if params[:id].present? && Comment.exists?(params[:id])
の所でparams[:id]が取得できる場合は親コメントが存在するという認識で
parent_id`に値を入れるようにしています。
.comment
= form_with model: @comment, scope: :comment, url: tweet_comments_path(@tweet, id: @comment.id), local: true, data: { turbo: false }, method: 'post' do |f|
ul
li
- if user_signed_in?
- if current_user.icon.attached?
= link_to image_tag(current_user.icon, alt: "Icon image", class: "icon_image", size: '50x50'), user_path(current_user.id)
li
.form-group
= f.text_field :sentence, placeholder: '返信をポスト', class: "form-group form-control", style: "border: none", value: ''
= f.label :images do
= image_tag('photo.png', size: '35x35', class: 'image')
= f.file_field :images, style: 'display:none', multiple: true
end
= f.submit '返信', class: 'reply btn btn-primary'
hr
form-with
ヘルパーの所でurlをtweet_comments_path(@tweet, id: @comment.id)としています。 これにより
form_with`ヘルパーより値を受け取る時、paramsにtweet(投稿)のidとcommentのidを取得して、controllerのcreateメソッドで必要な値を準備できます。
最後まで読んで頂き有難うございます。
同じ様なところで躓いた方の一助になれば幸いです。