0
1

コメントに対する返信機能 モデルの親子関係を自己結合で実装

Posted at

この記事はプログラミング学習者がアプリ開発中に躓いた内容を備忘録として記事におこしたものです。内容に不備などあればご指摘頂けると助かります。

記事投稿の背景

ここでは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カラムを参照するようにします。
comment.rb
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というカラムが存在し、このカラムが親コメント(返信先)を指しています。:parentparent_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が入ります。

comments_controller.rb
  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`に値を入れるようにしています。

show.html.slim
.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メソッドで必要な値を準備できます。

最後まで読んで頂き有難うございます。
同じ様なところで躓いた方の一助になれば幸いです。

今回参考にしたサイト

Railsでコメントに対する返信機能を実装した!

【Ruby on Rails】双方向関連付けを宣言するinverse_of

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1