Chat

How To Build A Real-time Commenting System with Rails

3 min read Anmol Agrawal on Aug 23, 2017

Waving hand

Good News! We’ve launched an all new Chat Resource Center.

We recommend checking out our new Chat Resource Center, which includes overviews, tutorials, and design patterns for building and deploying mobile and web chat.

Take me to the Chat Resource Center →

This tutorial is a quick and easy example of Collaboration and Real-time Updates using PubNub to build an interactive real-time commenting system where a comment made in one user’s browser will appear in a second user’s the moment it’s submitted.

We’ll build a simple Rails application for a blog with multiple articles with individual comment streams. In a nutshell, we’ll be taking the basic comments system built in this tutorial, which only takes a couple minutes using Rails scaffolding. With that, I slightly adjusted the CSS to style it a bit as well.

Gemfile

Firstly, we add pubnub gem to Gemfile and then bundle install in terminal.

gem 'pubnub', '~> 4.0.21'

app/views/layouts/application.html.erb

We added PubNub’s CDN to base the template on which every other template is rendered.

We have also added Turbolinks. As per their GitHub page:

Turbolinks makes navigating your web application faster. Get the performance benefits of a single-page application without the added complexity of a client-side JavaScript framework. Use HTML to render your views on the server side and link to pages as usual. When you follow a link, Turbolinks automatically fetches the page, swaps in its, and merges its, all without incurring the cost of a full page load.

We need to use Turbolinks because we want to use the functionality of a client-side framework without actually bringing it in, otherwise, it’d become more complex.

Flash block is only to show the success/error messages happening inside the application.

<!DOCTYPE html>
<html>
<head>
 <title>Blog</title>
 <%= stylesheet_link_tag    'application', media: 'all', 'data-turbolinks-track' => true %>
 <%= javascript_include_tag 'application', 'data-turbolinks-track' => true %>
 <script src="https://cdn.pubnub.com/sdk/javascript/pubnub.4.8.0.min.js"></script>
 <%= csrf_meta_tags %>
</head>
<body>
  <div id="container">
     <%= link_to "Home", root_path %>
     <%= render 'layouts/user_widget' %>
     <% flash.each do |name, msg| %>
       <%= content_tag :div, msg, id: "flash_#{name}" %>
     <% end %>
     <%= yield %>
   </div>
</body>
</html>

config/initializers/pubnub.rb

Here, we are instantiating PubNub on the server side to be used for server side operations, which we will see next. The keys between the client side and server side should be the same, so ideally, something like environment variables should be used, but for now we are hard coding it.

require 'pubnub'
$pubnub = Pubnub.new(
   subscribe_key: :demo,
   publish_key: :demo
)

app/controllers/comments_controller.rb

This is where the magic is happening that when a new comment is created, whoever is watching that same article will get the new comment in real time. There are a couple of ways this could be done, but I am showing a simpler approach.

As soon as the comment is saved in a database, a message is published to the channel of that article. For this example purpose, I have created naming conventions for channel name as “comments-[article_id]”.

def create
  @commentable = Article.find(params[:article_id])
  @comment = @commentable.comments.new(comment_params)
  if @comment.save
    $pubnub.publish(
 	   channel: "comments-" + "#{@commentable.id}",
 	   message: { comment: comment_params["content"] }
    )
    respond_to do |format|
     format.js { render :nothing => true }
     format.html
   end
  else
    render :new
  end
end

app/views/articles/show.html.erb

The first few parts of this file is just rendering HTML. Code in the script handles how to receive the message and how to render it. Firstly, we instantiated PubNub on client side with same keys as on server side. Then we subscribed to the particular channel to receive comments as per the naming conventions earlier mentioned. Then it listens for new messages and appends it to HTML.

<h1><%= @article.name %></h1>
<%= simple_format @article.content %>
<p>
    <%= link_to "Edit", edit_article_path(@article) %>
</p>
<h2>Comments</h2>
<%= render 'comments/comments' %>
<p>
    <%= render 'comments/form' %>
</p>
<script>
$(document).ready(function(){
   var $pubnub = new PubNub({
       publishKey : 'demo',
       subscribeKey : 'demo'
   })
  
   var article_id = "comments-" + window.location.pathname.split("/")[2];
   console.log(article_id);
   $pubnub.subscribe({
       channels: [article_id]
   });
   $pubnub.addListener({
       message: function(message) {
           console.log(message.message["comment"]);
           $("#comments").append("<div class=\"comment\"><p>" + message.message["comment"] + "</p></div>")
       }
   })
   $("#new_comment").bind("ajax:complete", function(event,xhr,status){
    $('#comment_content').val('');
   });
    $('.actions input').keypress(function (e) {
        if (e.which == 13) {
            $('form#new_comment').submit();
            return false;
        }
    });
});
</script>

And that’s it! We now have a blog where comments are streamed in real time, no manual refreshing required.

0