DEV Community

Lucy Suddenly
Lucy Suddenly

Posted on • Updated on

Nested Comments in Ruby on Rails

Nesting Dolls

Nested comments was a highly-desirable deliverable for my most recent project, so I learned how to make it happen! Here are the steps necessary so you can make it happen, too:

Step 1: Polymorphic Association

Set up your model to be polymorphic.

Polymorphic associations

from the Ruby on Rails guide

So in our case, we want to make comments polymorphic because they can be made on a post or another comment. We set up the migration this way:

rails generate model comment content:string commentable_id:integer commentable_type:string

When the comment is saved, either "post" or "comment" will be saved to the commentable_type, and the primary key of either the post or the comment will be saved to commentable_id. This acts like a switch for the belongs_to/has_many relationship.

The models for post and comments respectively should look like this:

class Post < ActiveRecord::Base
    has_many :comments, as: :commentable

class Comment < ActiveRecord::Base
    belongs_to :commentable, polymorphic: true
    has_many :comments, as: :commentable

Step 2: Routing

routing monorails

Your routes.rb should look like this, since we want to be able to pass in the primary key (params[:id]) for commentable_id and either "comment" or "post" for commentable_type -- we get these from the URI.

Rails.application.routes.draw do

  resources :posts do
    resources :comments

  resources :comments do
    resources :comments



rake routes

will look like this:

              Prefix Verb   URI Pattern                                       Controller#Action
       post_comments GET    /posts/:post_id/comments(.:format)                comments#index
                     POST   /posts/:post_id/comments(.:format)                comments#create
    new_post_comment GET    /posts/:post_id/comments/new(.:format)            comments#new
   edit_post_comment GET    /posts/:post_id/comments/:id/edit(.:format)       comments#edit
        post_comment GET    /posts/:post_id/comments/:id(.:format)            comments#show
                     PATCH  /posts/:post_id/comments/:id(.:format)            comments#update
                     PUT    /posts/:post_id/comments/:id(.:format)            comments#update
                     DELETE /posts/:post_id/comments/:id(.:format)            comments#destroy
               posts GET    /posts(.:format)                                  posts#index
                     POST   /posts(.:format)                                  posts#create
            new_post GET    /posts/new(.:format)                              posts#new
           edit_post GET    /posts/:id/edit(.:format)                         posts#edit
                post GET    /posts/:id(.:format)                              posts#show
                     PATCH  /posts/:id(.:format)                              posts#update
                     PUT    /posts/:id(.:format)                              posts#update
                     DELETE /posts/:id(.:format)                              posts#destroy
    comment_comments GET    /comments/:comment_id/comments(.:format)          comments#index
                     POST   /comments/:comment_id/comments(.:format)          comments#create
 new_comment_comment GET    /comments/:comment_id/comments/new(.:format)      comments#new
edit_comment_comment GET    /comments/:comment_id/comments/:id/edit(.:format) comments#edit
     comment_comment GET    /comments/:comment_id/comments/:id(.:format)      comments#show
                     PATCH  /comments/:comment_id/comments/:id(.:format)      comments#update
                     PUT    /comments/:comment_id/comments/:id(.:format)      comments#update
                     DELETE /comments/:comment_id/comments/:id(.:format)      comments#destroy
            comments GET    /comments(.:format)                               comments#index
                     POST   /comments(.:format)                               comments#create
         new_comment GET    /comments/new(.:format)                           comments#new
        edit_comment GET    /comments/:id/edit(.:format)                      comments#edit
             comment GET    /comments/:id(.:format)                           comments#show
                     PATCH  /comments/:id(.:format)                           comments#update
                     PUT    /comments/:id(.:format)                           comments#update
                     DELETE /comments/:id(.:format)                           comments#destroy

You'll notice we have three sets of comment CRUD actions: one for post comments, one for comment comments, and one for comments -- but they all share one set of controller actions, so all is as it should be. In the controller is where we will differentiate which commentable will be passed into the build method.

Step 3: Controllers

video game controller

Here's what our comment controller should look like:

class CommentsController < ApplicationController
before_action :find_commentable, only: :create

    def new
      @comment =

    def create


    def comment_params

    def find_commentable
      if params[:comment_id]
        @commentable = Comment.find_by_id(params[:comment_id]) 
      elsif params[:post_id]
        @commentable = Post.find_by_id(params[:post_id])


Did you catch it? This is where the magic happens. If the params, which come from the URI, contain a comment_id, the commentable we pass in will be a comment; if the params contain a post_id, the commentable we pass in will be a post!

Step 4: Views

great view

At the end of our post show view, we should include the following reference to a partial we are about to create:

<%= render partial: 'comments/comment', collection: @post.comments %>

Here's how collection works with the render method:


from the Ruby on Rails guide

Now we have access to each individual comment -- it's almost like being in an each block with |comment| delared. Here's what our partial view "_comment" might look like:

  <%= comment.content %> -

  <%= form_for [comment,] do |f| %>
      <%= f.text_area :content, placeholder: "Add a Reply" %><br/>
      <%= f.submit "Reply"  %>
      <% end %>

      <%= render partial: 'comments/comment', collection: comment.comments %>


So we display the comment's content, include a form_for for a new nested comment, and then render our partial again! Recursion can be very useful! But notice the differences between our post render method and the comment partial render method: the former's collection renders the post's comments, whereas each comment will render any of its comments. Pretty neat, right?

Here's a sneak peak of what your nested comments can look like:

nested comments

Go forth! Nest your comments!

Discussion (8)

ben profile image
Ben Halpern

Nice post.

DEV is a Rails app that has nested comments. Our code looks a lot like this. We use Ancestry to help manage this as well.

GitHub logo stefankroes / ancestry

Organise ActiveRecord model into a tree structure

Gitter Security


Ancestry is a gem that allows the records of a Ruby on Rails ActiveRecord model to be organised as a tree structure (or hierarchy). It employs the materialised path pattern and exposes all the standard tree structure relations (ancestors, parent, root, children, siblings, descendants), allowing all of them to be fetched in a single SQL query. Additional features include STI support, scopes, depth caching, depth constraints, easy migration from older gems, integrity checking, integrity restoration, arrangement of (sub)trees into hashes, and various strategies for dealing with orphaned records.


  • Ancestry 2.x supports Rails 4.1 and earlier
  • Ancestry 3.x supports Rails 5.0 and 4.2
  • Ancestry 4.0 only supports rails 5.0 and higher


Follow these simple steps to apply Ancestry to any ActiveRecord model:


  • Add to Gemfile:
# Gemfile
gem 'ancestry'
Enter fullscreen mode Exit fullscreen mode
  • Install required gems:
$ bundle install
Enter fullscreen mode Exit fullscreen mode

Add ancestry column to your table

  • Create migration:
$ rails g migration
Enter fullscreen mode Exit fullscreen mode
lucysuddenly profile image
Lucy Suddenly Author

Wow! If I had known about this shortcut I would have taken it. Thanks for sharing!

flip437 profile image
Philippe DIOLLOT

Hello Guyz!!
I'm trying to make it work but I have a issue in the partial.
When i'm trying to run this view i have this :
"undefined local variable or method `comment' "

comment is not defined.
Can someone tell me where "comment" is defined ?

Thanks for helping.

poafernandes profile image
Alexandre Porto Alegre

Thanks for the post, I'm learning rails and trying to implement nested comments on a project of mine, the problem is that I get an error that comments is an undefined method on my comment controller create. Is there any hidden steps that I should've done in between?

vhsoto profile image
Victor Soto

Hi, I get the same error. How you fixed it?

yarotheslav profile image
Yaroslav Shmarov

Good approach.
For example, unlike the example from chris-gorails you do not create a separate controller for each commentable model - by this your approach is better.

def find_commentable is good, althrough something tells me it can be further improved.

alexvirtualbr profile image
Alexandre Ferreira

I'm implement comments the same way to you but now I want use Action Cable and I started learn about this resource.

nicobobb profile image
Nicolás Bobb

Thanks for sharing!