/ nested_commenting

Nested commenting system without using any Gem

We all know, Comments are every where Blogs, Social Networking sites, e-learning forums etc.

In this article, we've got an Article model and we want to create a nested commenting system onto that. Such that Article can have comments, comments can have replys, replys can have more replys and so on.

For example -

This is an article
     This is an comment
         this is a reply
             this is a reply's reply
                 .... (so on)
      This is another comment.

Now, we're going to talk about polymorphic associations and what they are and how they can be implemented for nested commenting system.

What is Polymorphic Association ?

Using polymorphic associations, a model can belong to more than one other model, on a single association.
For example, you might have a picture model that belongs to either an employee model or a product model.

Example for how it should be declared:

class Picture < ApplicationRecord
  belongs_to :imageable, polymorphic: true
class Employee < ApplicationRecord
  has_many :pictures, as: :imageable
class Product < ApplicationRecord
  has_many :pictures, as: :imageable

Now lets talk about nested commenting.


For this tutorial, we’ll be having a new Rails app. We’re only going to add what we need to show in this feature, so for starters, let’s create the article model, so it’s just a Title and the body.

rails new nested_comments_app
rails generate model Article title:string body:text
rake db:migrate

We decided to use “text” for the body, so we’re not limited by length. Now let’s add the model for comments.

rails generate model Comment body:text commentable:references{polymorphic}
rake db:migrate

Now that we have these two models created, we need to create the associations between them. Lets start with Articles. An article can have many comments, so we need to add that. However, Rails would normally assume that comments would have a column called “article_id” which it doesn’t so we need include the name we gave to the polymorphic association:

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

Since a comment can also have many comments, we’re going to include the same thing in the Comments model. But before that, we need to let Rails know that Comments can belong to more than one model (articles or comments), so we need to specify that it belongs to a polymorphic association.

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

Now let’s see how this works. Lets check in Rails console:

   rails c

Now lets create some entries in article model

  Article.create(title: 'This is the title for article', body: 'this is the body for article')

We then want to add a comment to that article, to make sure our associations work properly. Let’s enter it through the comments association with the article, to mimic if we were to leave a comment on a article on the article’s show page. We can just use Article.first since it’s the only entry in our database.

    Article.first.comments.create(body: "This is a comment on article!")

When we write Comment.new in rails console, we’ll see that Rails knew that the commentable type was Article, since it was a comment on a article, and that it used the article id (1, since this was our first entry) as the commentable_id.

Now let’s add a comment to that first comment, creating our first nested comment. We could do it the same way:

   Comment.first.comments.create(body: "This is a reply on comment!")

Now when we check this comment we'll see commentable type as Comment and commentable id will have comment's id

Now our models are setup correctly.


Now lets see how routes should be created

Rails.application.routes.draw do
root 'articles#index'

      resources :articles do
          resources :comments

      resources :comments do
          resources :comments

Let’s see if everything is routing correctly, this will help you see how it’s all nested. In the Terminal, check your routes with:

  rake routes

This is what you should see:
Prefix Verb URI Pattern Controller#Action
root GET / articles#index
article_comments GET /articles/:article_id/comments(.:format) comments#index
POST /articles/:article_id/comments(.:format) comments#create
new_article_comment GET /articles/:article_id/comments/new(.:format) comments#new
edit_article_comment GET /articles/:article_id/comments/:id/edit(.:format) comments#edit
article_comment GET /articles/:article_id/comments/:id(.:format) comments#show
PATCH /articles/:article_id/comments/:id(.:format) comments#update
PUT /articles/:article_id/comments/:id(.:format) comments#update
DELETE /articles/:article_id/comments/:id(.:format) comments#destroy
articles GET /articles(.:format) articles#index
POST /articles(.:format) articles#create
new_article GET /articles/new(.:format) articles#new
edit_article GET /articles/:id/edit(.:format) articles#edit
article GET /articles/:id(.:format) articles#show
PATCH /articles/:id(.:format) articles#update
PUT /articles/:id(.:format) articles#update
DELETE /articles/:id(.:format) articles#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

Now lets talk about the controllers

As we all know controllers are the main brain behind an application which allows the model to talk to the view. So in our case there are two different models. So we would require two controllers Articles and Comments Controllers.

** app/controllers/articles_controller.rb**

  class ArticlesController < ApplicationController

    before_action :find_article, only: [:show, :edit, :update, :destroy]

    def index

    def show

    def new

    def create
      @article= Article.new(article_params)
      redirect_to @article if @article.save

    def edit

    def update
      redirect_to @article if @article.save(article_params)

    def destroy
      redirect_to :back if @article.destroy


    def find_article
      @article = Article.find(params[:id])

    def article_params
      params.require(:article).permit(:title, :body)


Now we move onto the comments controller. Here we don’t need index and show, as comments always will be on article view pages, and don’t have their own pages. But we’ll need new and create, because we want to create new comments. We also need to create a method to let Rails know if we’re creating a comment for a Article or for a comment.

** app/controllers/comments_controller.rb**

  class CommentsController < ApplicationController

  before_action :find_commentable

    def new
      @comment = Comment.new

    def create
      @comment = @commentable.comments.new(comment_params)

      if @comment.save
        redirect_to :back, notice: 'Your comment was successfully posted!'
        redirect_to :back, notice: "Your comment wasn't posted!"


    def comment_params

    def find_commentable
      @commentable = Comment.find_by_id(params[:comment_id]) if params[:comment_id]
      @commentable = Article.find_by_id(params[:article_id]) if params[:article_id]

As our comments are nested within other comments or articles, we’re using the instance variable @commentable in the create action. We have a private method (find_commentable) that is telling Rails that if the params contains a comment_id, it’s a comment on a comment, and if it has article_id, it’s a comment on an article. We then added a filter at the top of the controller, telling Rails to run the private method before performing any other action.

Lastly, we need to create the views to see all the code we written works.


So there are four things we need for all of this to work. We need a show page for a article, and index page for articles, a way to see comments, and a way to post comments.

Let’s start with the index page and show page. The index page will display all of the articles in our database, along with a link to the show page for each article.


  <% @articles.each do |article| %>
      <%= link_to(article.title, article.body, target: "_blank") %> - <%= link_to("Show Page", article) %>
  <% end %>

Now we need the show page for each article, this page will have details about the article, a form for submitting a comment about the article, the display of each comment, and the ability to comment on a comment.


  <%= link_to(@article.title, @article.body, target: "_blank") %><br/>
  <small>Submitted <%= time_ago_in_words(@article.created_at) %> ago</small>


    <%= form_for [@article, Comment.new] do |f| %>
    <%= f.text_area :body, placeholder: "Add a comment" %><br/>
    <%= f.submit "Add Comment" %>
  <% end %>

    <%= render @article.comments %>

As we have broken the file into partials, we need to add that view file.


    <%= comment.body %> -
    <small>Submitted <%= time_ago_in_words(comment.created_at) %> ago</small>

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

        <%= render comment.comments %>


As we’re passing this partial the collection of comments, it displays each comment and the form for replying to that comment. But then it renders itself within itself, to show the replies each comment might have. So by using the ul/li structure, we’re making sure they all nest correctly when they display.

Run the server, and take a look at what we did.

rails server

Because we created a article, a comment, and a reply already, so you should see them all. Then we can see by adding a new comment on that article, and then a reply to that comment.

If you want to see the bitbucket repo for this project, it’s posted here.

Nested commenting system without using any Gem
Share this

Subscribe to Engineering At Kiprosh