Threaded Comments in Ruby on Rails

This is the basic structure of a threaded commenting system in Ruby on Rails (3.2.9). Comment model uses polymorphic associations to differentiate between which model it belongs to.

threaded_comments

config/routes.rb

1
2
3
4
5
6
resources :comments do
  resources :comments
end
resources :posts do                                                      
  resources :comments
end

app/models/comments.rb

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# == Schema Information
#
# Table name: comments
#
#  id               :integer          not null, primary key
#  user_id          :integer
#  commentable_id   :integer
#  content          :text
#  enabled          :boolean
#  created_at       :datetime         not null
#  updated_at       :datetime         not null
#  commentable_type :string(255)
#  title            :string(255)
#

class Comment < ActiveRecord::Base
  attr_accessible :comment_id, :content, :enabled, :user_id, :title, :parent_id
  belongs_to :user
  belongs_to :post
  belongs_to :comment

  belongs_to :commentable, :polymorphic => true

  has_many :comments, :as => :commentable
  has_many :votes, :as => :voteable

  validates :user_id, presence: true
  validates :content, presence: true, length: { maximum: 25000 }

app/controllers/comments_controller.rb

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
class CommentsController < ApplicationController
  before_filter :load_commentable
  def index
    @comments = @commentable.comments

    respond_to do |format|
      format.html # index.html.erb
    end
  end

  def show
    @comment = @commentable.comments.find(params[:id])

    respond_to do |format|
      format.html # show.html.erb
    end
  end

  def new
    @comment = @commentable.comments.new
    @comment.parent_id = params[:parent_id]

    respond_to do |format|
      format.html # new.html.erb
      format.js
    end
  end

  def edit
    @comment = @commentable.comments.find(params[:id])
  end
  def create
    @comment = @commentable.comments.new(params[:comment])
    @comment.user = current_user
    @comment.content = ActionView::Base.full_sanitizer.sanitize(@comment.content)
    @post = Post.find(@comment.parent_id)

   respond_to do |format|
      if @comment.save
        format.html { redirect_to @post} #only for replies
        format.js #ajax post, remove for testing
      else
        format.html { render :action => "new" }
      end
    end
  end

  def update
    @comment = @commentable.comments.find(params[:id])

    respond_to do |format|
      if @comment.update_attributes(params[:comment])
        format.html { redirect_to @comment, :notice => 'Comment was successfully updated.' }
      else
        format.html { render :action => "edit" }
      end
    end
  end

  def destroy
    @comment = @commentable.comments.find(params[:id])
    @comment.destroy

    respond_to do |format|
      format.html { redirect_to comments_url }
    end
  end

  private

  def load_commentable
    resource, id = request.path.split('/')[1, 2]
    @commentable = resource.singularize.classify.constantize.find(id)
  end
end

app/views/comments/_comment.html.erb

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<li class="comment" id="comment-<%= comment.id %>">
  <h5><%= comment.title %> (Score:0)</h5>
  <span class="detail">Posted <%= time_ago_in_words(comment.created_at) %> ago by <%= link_to comment.user.name, comment.user %></span>
  <div class="body">
    <%= comment.content %>
  </div>
  <% if @post %>
    <p><%= link_to 'Add a Reply', new_comment_comment_path(comment, :parent_id => @post.id) %></p>
    <% unless comment.comments.empty? %>
      <ul class="nested_comment">
        <%= render comment.comments %>
      </ul>
    <% end %>
  <% end %>
</li>

app/views/posts/_post.html.erb

1
2
3
4
5
6
<li>
  <span class="title"><%= link_to post.title, post %></span>
  <span class="timestamp">Posted <%= time_ago_in_words(post.created_at) %> ago</span>&nbsp;<span class="postedby">by <%= link_to post.user.name, post.user %></span>
  <span class="content"><%= raw post.content %></span>
  <span class="comment_count"><%= link_to post.comments.count > 0 ? "Read " + pluralize(count_threaded(post.comments), "comment"):"No comments yet", post %></span>
</li>
This entry was posted in Uncategorized and tagged . Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *