简体   繁体   中英

Nested comments and replies in ruby on rails

I am working on my first project in ruby on rails and need to implement comments and replies functionality on it. I am facing of displaying replies under each comment as well as if any reply has child replies they need to display under it. structure will be some how as follow.

first comment
       Reply to first comment
            reply to first comment first reply
       reply to first  comment
Second comment
      Reply to second comment

and this nested structure continues. I have only one table for these all comments with parent key to treat as reply. table structure is as follow

Id | Comment_body | parent_id | user_id | project_id
1      comment                        2          2
2      comment/reply   1              2          2
3      comment/reply   2              2          2

this second comment is treated as reply for first comment and id 3 comment is treated as reply on first reply of first comment. kindly help regarding to this nested structure that how i can manage it in a best way. Comment table also has association with project table and user table. suggest the best way without gem as i already tried many of them but they are limited in depth level.

We've done this before . There's also a RailsCast about it..

在此输入图像描述

The term you're looking for is recursion - self replicating.


Use acts_as_tree : You can do this with has_many / belongs_to

#app/models/comment.rb
class Comment < ActiveRecord::Base
   belongs_to :project
   belongs_to :parent,  class_name: "Comment" #-> requires "parent_id" column
   has_many   :replies, class_name: "Comment", foreign_key: :parent_id, dependent: :destroy
end

This will allow the following:

#app/views/projects/show.html.erb
<%= render @project.comments %>

#app/views/comments/_comment.html.erb
<%= comment.body %>
<%= render comment.replies if comment.replies.any? %>

The recursion occurs with render comment.replies -- it will continue to loop through the replies until there are no more. Although this will take some DB processing to do, it will display the comments with nesting.

--

If you wanted to add a reply etc, you just have to populate the "parent" ID:

#config/routes.rb
resources :projects do 
   resources :comments #-> url.com/projects/:project_id/comments/:id
   end
end

#app/views/comments/_comment.html.erb
<%= form_for [comment.project, comment.new] do |f| %>
   <%= f.hidden_field :parent_id, comment.parent.id %>
   <%= f.text_field :body %>
   <%= f.submit %>
<% end %>

The above will submit to the comments#create action:

#app/controllers/comments_controller.rb
class CommentsController < ApplicationController
   def create
      @project = Project.find params[:project_id]
      @comment = @project.comments.new comment_params
   end

   private

   def comment_params
      params.require(:comment).permit(:parent_id, :body)
   end
end

This is a rough outline of one approach to this (some elements may be missing, there may well be better ways and there may be glaring errors but hopefully this could be useful):

=> Comment has_many :replies, dependent: :destroy

=> A comment will also need to accepts_nested_attributes_for :replies

=> Reply belongs_to :comment

=> Both will obviously need to belongs_to :user

=> A Comment will need to belong_to a post/article etc.


In Routes.rb you may want to nest replies within comments

resources :comments do
  resources :replies
end

How you integrate these with your post/article model is another question.


We'll need a CommentsController

class CommentsController < ApplicationController
  def index
   @users = User.all
   @inquiries = Inquiry.all
  end

  def new
    @user = User.find_by(id: params[:user])
    @post = Post.find_by(id: params[:post])

    @comment = @post.inquiries.new
    @message = @comment.replies.build
  end

  def create
    @user = User.find_by(id: params[:user_id])
    @post = Post.find_by(id: params[:post_id])

    @comment = Comment.create!(comment_params) #define these below
    @comment.user << @user
    redirect_to #somewhere
 end

And a replies controller:

class RepliesController < ApplicationController
  before_action do
    @comment = Comment.find(params[:comment_id])
  end

  def index
    @replies = @comment.replies
    @reply = @comment.replies.new
  end

  def new
    @reply = @comment.replies.new
  end

  def create
    @reply = @comment.replies.new(reply_params)
    #redirect somewhere
  end

You can then build some views based on the above. I should add that the closure_tree gem does seem like a useful one to look at for this. Having used the Mailboxer gem previously I would not recommend that though - as customising it is not always straightforwards.

I don't necessarily want to only suggest a gem, since you've said that you've already done your homework there, but I've used the mailboxer gem, ( https://github.com/mailboxer/mailboxer ), before for the same sort of use case to good effect. Admittedly, I had to hack it a little bit to handle some edge cases, but I think in your particular scenario, it would handle things just fine. Perhaps more to the point, even if you have to make some changes, that's probably better than rolling your own from scratch.

With that said, the data structure you've described is enough, in it's essentials to do what you're asking for. The final step would just be to set up the association on your Rails model:

class Comment < ActiveRecord::Base
  has_one :parent, class: 'Comment'
end

With that set up, you just need to make sure your views display your threading properly.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM