Creating a nested comment system in Ruby on Rails

Posted by Jonathan Weyermann on January 16, 2018 at 12:00 AM

Comments

One of the most classic things to build in Ruby on Rails is a blog app. In fact, this site is built with Ruby on Rails. Each blog Post object instance can have multiple Comments attached to it. While this is very simple and functional, comments on blogs often have additional functionality: The ability to respond to a specific comment, and thus create a nested comment structure. Thus I've recently begun exploring how I could add this to my own blog. Here are my results:

class PostsController < ApplicationController
  expose :post, find_by: :slug
  expose(:posts) { Post.public }
  expose(:user) { post.user }
end

This is my PostsController. It uses Decent Exposure to simplify the controller actions. On this controller, there are show and index actions, but they're not explicitly written out because the expose applies to all controller actions. The post uses Friendly Id to show by slug not not by id (for prettier urls), and therefore I've set the post to find_by slug. posts is for the index view, and public is a method in the post model that determines which posts should be shown (in my case, based on a specific date, and whether I've set them to published).


class CommentsController < PostsController

  def create
    comment = post.comments.create(comment_params)

    if comment.valid? && comment.save
      flash[:notice] = 'Comment Added'
      session.delete(:comment)
      redirect_to post_path(post, anchor: 'comment')
    else
      session[:comment] = comment
      flash[:alert] = Array(comment.errors).to_sentence
      redirect_to new_post_comment_path(post, comment_id: params["comment"]["reply_comment"], anchor: 'comment')
    end
  end

  private

  def comment_params
    params.require(:comment).permit(:name, :email, :body, :post_id, :reply_comment)
  end
end

The comments controller inherits from the post controller (the comments are also route nested beneath the posts ). Depending on whether validations pass, I create the comment and display appropriate error messaging.


class Comment < ActiveRecord::Base
  belongs_to :post

  validates_presence_of :name, :body
  validates_email_format_of :email, :message => 'please enter a valid email'

  def sub_comments
    Comment.where(reply_comment: id)
  end
end

The important part of the comment model is sub_comments. We look for the active record attribute reply_comment to find the comments that reply to this comment. This helps us know where to place the comment in the view.

we set the the reply_comment attribute as a hidden attribute inside a _comments.html.erb partial

<div class="comments">
  <h2 class="page-header"><%= post.comments.length %> Comments</h2>
  <%= render partial: 'partials/comment', collection: post.root_comments %>

  <div class="well add-comment">
    <h2 class="page-header"><%= comment_descriptor %></h2>
    <a name="comment"></a>
    <% if flash[:notice] %>
      <div class="alert alert-success"><%= flash[:notice] %></div>
    <% end %>
    <% if flash[:alert] %>
      <div class="alert alert-danger"><%= flash[:alert] %></div>
    <% end %>
    <%= simple_form_for [post, post.comments.build(session[:comment]) ], html: { class: 'form-horizontal', data: { toggle: 'validator' }} do |f| %>
      <%= f.error :base, error_method: :to_sentence %>
      <%= f.hidden_field :reply_comment, value: params[:comment_id] %>
      <%= f.input :name, required: true, input_html: {maxlength: 60 } %>
      <%= f.input :email, error: 'Please specify a valid email', required: true, type: 'email', input_html: { maxlength: 100 } %>
      <%= f.input :body, required: true, label: "Comment", type: 'text', input_html: { rows: '8', maxlength: 3600 } %>
      <%= f.button :submit, "Submit Comment", class: 'btn btn-primary' %>
    <% end %>
  </div>
</div>

the partial is called from a  <%= render 'partials/comments' %> inside the post/show. This partial represents all comments and the reply form. It renders a collection comment partials which represent the individual comments.

here is the comment partial. the comment_id is passed from the current comment when 'reply' is clicked

<div class="comment">
  <div class="row">
    <div class="col-sm-2 col-xs-3">
      <%= image_tag("user.jpg") %>
    </div>
  <div class="col-sm-8 col-xs-7">
    <div class="well">
      <h4><%= comment.name %><span>/<%= comment.created_at.to_time.strftime('%B %e at %l:%M %p') %></span></h4>
        <%= comment.body %>
      </div>
    </div>
    <div class="col-xs-2">
      <%= link_to "Reply", "#{post_path(post.slug,comment_id: comment.id)}#comment" %>
    </div>
  </div>
  <div class="row">
    <div class="col-xs-1">
    </div>
    <div class="col-xs-11">
      <%= render partial: 'partials/comment', collection: comment.sub_comments %>
    </div>
  </div>
</div>

this partial is rendered once for every comment that is added to a post. You'll notice that sub_comments are rendered as a sub-collection of the same comment partial - It can recursively render itself as long as there are comments replying to comments.


Root comments are all comments that don't have don't have a reply comment attached - They are not replying to anyone and are at highest level of the comment structure

class Post < ActiveRecord::Base

  ...

  def root_comments
    comments.where(reply_comment: nil)
  end
end




The result is what's visible in the opening screenshot. 

User

EyeDig/July 22 at 7:27 AM

<a href="https://kamagra50.com/">kamagra</a>
User

DenDig/July 23 at 7:48 AM

<a href="https://kamagra50.com/">buy kamagra</a>
User

KiaDig/July 23 at 11:22 AM

<a href="http://kamagra50.com/">kamagra effervescent</a>
User

JasonDig/July 26 at 12:41 PM

<a href="https://kamagra50.com/">kamagra 50mg</a>
User

EyeDig/July 26 at 4:32 PM

<a href="https://kamagra50.com/">kamagra 100mg</a>
User

KiaDig/July 27 at 4:23 PM

<a href="http://kamagra50.com/">kamagra 100mg</a>
User

DenDig/July 27 at 5:49 PM

<a href="https://kamagra50.com/">buy kamagra</a>
User

how is maryland live casino/July 29 at 8:44 AM

Think about what do makes you uncomfortable? These pads be gifted to their loved on special occasions such as birthdays and anniversaries. Offer my dream; that everyone know that ways within the Lord. http://weizhengmao688.com/comment/html/?28996.html
User

Annie Byerly/July 31 at 4:54 PM

https://www.track-r.net is the future of Google rank trackers, we wanted to see if you'd like to try it for free for two weeks 14 days to see the data it brings you. You can use that and our free advice on how to increase your Google rankings so you bring in more customers. Sounds good? Course it does, it's free. No credit card details, just register and start straight away. If you like it, keep it, if it's not for you that's cool. Hope to see you join the 3,000 others since the start of April 2019!
User

aixedorewede/August 8 at 5:04 PM

http://mewkid.net/order-amoxicillin/ - Amoxicillin No Prescription <a href="http://mewkid.net/order-amoxicillin/">Buy Amoxicillin</a> nbc.fcml.jonathanweyermann.com.rwf.tb http://mewkid.net/order-amoxicillin/
User

ehikerix/August 8 at 5:21 PM

http://mewkid.net/order-amoxicillin/ - Amoxil Children <a href="http://mewkid.net/order-amoxicillin/">18</a> two.auau.jonathanweyermann.com.mgq.wi http://mewkid.net/order-amoxicillin/
User

Ellbees/August 15 at 2:15 AM

Levitra Bayer 5mg <a href=http://curerxshop.com>cialis online</a> Propecia Blutspende
User

Ellbees/August 17 at 11:32 AM

Www Ezonlinepharmacy Progesterone Online Best Website Truro <a href=http://66pills.com>buy viagra online</a> Ed Drugs
User

Thoully/August 17 at 12:35 PM

Wiki citalopram cialis generic pills <a href="http://cialisndbrx.com/#">buy cialis online</a> safely buying cialis overseas <a href="http://cialischmrx.com/#">buy cheap cialis online</a> buy generic cialis <a href="http://cialischbrx.com/#">buy generic cialis online</a> citrate dosage buy tadalafil <a href="http://cialisdbrx.com/#">buy cheap cialis online</a> viagra alternative <a href="http://chviagranrxusa.com/#">buy generic viagra online</a>
User

Ellbees/August 22 at 2:38 AM

Order Metronidazole 200mg Online Canada Cialis Viagra Precios <a href=http://sildenaf100.com>viagra online</a> Sore Throat Antibiotic Amoxicillin Protocol
User

Kathy/August 22 at 6:18 AM

Hello I wanted to share a quick ranking case study with you (I'm a copywriter and onpage SEO expert). Google Loves the RIGHT WORDS in the RIGHT PLACE. I Got It Wrong. I changed 8 words on a page...and you won't believe what happened. So I wrote a case study about it. Read it here: https://kathreadwrites.net/onpage-us/ 👈 Put the WRONG WORDS in the WRONG PLACE and you will not rank. PERIOD. I hope this case study inspires you to check out your own on-page optimization. Stay Amazing Kathy P.S. To get in touch, please use the contact form on my website instead of emailing me. <a style="font-size: 8px;" href="https://kathreadwrites.net/unsub/?ca=KRW&dom=jonathanweyermann.com">Unsubscribe</a>