Katie H
Katie H

Reputation: 2293

Threading on polymorphic comments

I've setup two models that are commentable through same comments table:

My comments schema:

  create_table "comments", force: true do |t|
    t.text     "body"
    t.integer  "commentable_id"
    t.string   "commentable_type"
    t.integer  "user_id"
    t.datetime "created_at"
    t.datetime "updated_at"
  end

My comment model:

class Comment < ActiveRecord::Base
  belongs_to :commentable, polymorphic: true
  belongs_to :user
  acts_as_votable
end

My movie model

class Movie < ActiveRecord::Base
  belongs_to :user
  has_many :comments, as: :commentable
end

My book model:

class Book < ActiveRecord::Base
  belongs_to :user
  has_many :comments, as: :commentable
end

My comments controller:

def index
  @commentable = find_commentable
  @comments = @commentable.comments
end

def create
  @commentable = find_commentable
  @comment = @commentable.comments.build(params[:comment])
  @comment.user = current_user
  if @comment.save
    flash[:notice] = "Successfully created comment."
    redirect_to @commentable
  else
    render :action => 'new'
  end
end

  def upvote_movie
  @movie = Movie.find(params[:movie_id])
  @comment = @movie.comments.find(params[:id])
  @comment.liked_by current_user

  respond_to do |format|
    format.html {redirect_to :back}
  end
end


  def upvote_book
  @book = Book.find(params[:book_id])
  @comment = @book.comments.find(params[:id])
  @comment.liked_by current_user

  respond_to do |format|
    format.html {redirect_to :back}
  end
end


private

def find_commentable
  params[:commentable_type].constantize.find(params[:commentable_id])
end
end

How can I add threading(reply to comments) to what I already have?

Here is a blog that talks about threading:http://www.davychiu.com/blog/threaded-comments-in-ruby-on-rails.html

I'm just not sure how to put the two together.

Here is what I have in my movie show view:

<%= render partial: "comments/form", locals: { commentable: @movie } %>


<% @comments.each do |comment| %>

<hr>
  <p>
   <strong><%= link_to comment.user.username, user_path(comment.user), :class => "user" %>
</strong> <a><%= "(#{time_ago_in_words(comment.created_at)} ago)" %></a>
  </p>

  <p>

    <%= simple_format(auto_link(comment.body, :html => { :target => '_blank' } )) %>
<% end %>

Here is what my comment form looks like:

<%= form_for [commentable, Comment.new] do |f| %>
  <%= hidden_field_tag :commentable_type, commentable.class.to_s %>
  <%= hidden_field_tag :commentable_id, commentable.id %>
  <p>
    <%= f.text_area :body %>
  </p>
  <p><%= f.submit "Submit" %></p>
<% end %>

Upvotes: 1

Views: 391

Answers (2)

Billy Chan
Billy Chan

Reputation: 24815

I scanned the article you mentioned and found the solution there is quite limited.

The basic idea in the article is to set a comment itself as commentable. So a nested comment is actually NOT a comment of the post, but of the parent comment.

The drawbacks are apparent and unacceptable:

  1. It's hard to get other things right. For example, posts.comments.size is no long correct.

  2. You'll have hard dependency on this structure. If in one day you don't want to display comments in thread but plainly, you...will kick a stone.

  3. If you want to do it on current comment system, it's hard.

Actually a simple solution could solve the problem:

  1. Add an extra field reply_to to comment model, referring to other comment's id.

  2. When adding comment, add a reply_to id if it replied to one.

  3. When showing, show a list of all comments with reply_to null.

  4. Then for each comment, show nested comments has its id. And do it recursively.

  5. If you want to limit the nested level, you can add an extra nested_level field, getting in from the front-end. If nest limit is 3, no comments is allowed to reply a comment with nest level of 3.

add: demo helper to render recursively

def render_replied_comments(comment)
  if comment.has_reply
    comments.replies.each do |reply|
      render partial: 'comment', locals: {comment: reply}
      render_replied_comment(reply)
    end
  end
end

# View
@post.top_level_comments.each do |comment|
  render partial: 'comment', locals: {comment: comment}
end

Upvotes: 1

TheIrishGuy
TheIrishGuy

Reputation: 2583

You would add a parent_id to the comments model that is a self-referencing relationship. So parent comments would have a parent_id of nil and all child comments would have a parent_id of that parent comment. You are essentially constructing a tree.

The Ancestory Gem is ideal for this or roll your own, good learning experience.

Upvotes: 0

Related Questions