DEV Community

Cover image for Ruby on Rails - Comment realtime loading
John Kevin Baluyot
John Kevin Baluyot

Posted on

Ruby on Rails - Comment realtime loading

The goal of this post was to create a real-time loading of comments on ruby on rails. Like in this demonstration:

screen-demo

Take note:

This is a continuation of my previous blog post here: Create Post and Comment in Ruby on Rails. I highly suggest that you look at it since it will be used as a base for this one. If you wanna get the base first, you could check this.

Let's start, shall we?

  1. Install react-rails gem by adding this to the Gemfile:

    gem 'react-rails'
    

    then run bundle install.

    After installing, run these commands to your console:

    $ bundle install
    $ rails webpacker:install         # OR (on rails version < 5.0) rake webpacker:install
    $ rails webpacker:install:react   # OR (on rails version < 5.0) rake webpacker:install:react
    $ rails generate react:install
    
  2. We would be using jquery for our API. Install jquery using:

     $ yarn add jquery
    

    Add this code to your environment.js.

     const webpack = require('webpack')
     environment.plugins.prepend('Provide',
       new webpack.ProvidePlugin({
         $: 'jquery/src/jquery',
         jQuery: 'jquery/src/jquery'
       })
    )
    
    module.exports = environment
    

    Edit the newly generated application.js under 'app/javascript/packs/'.

    // This file is automatically compiled by Webpack, along with any other files
    // present in this directory. You're encouraged to place your actual application logic in
    // a relevant structure within app/javascript and only use these pack files to reference
    // that code so it'll be compiled.
    
    // Support component names relative to this directory:
    var componentRequireContext = require.context("components", true);
    var ReactRailsUJS = require("react_ujs");
    ReactRailsUJS.useContext(componentRequireContext);
    
    require("@rails/ujs").start()
    require("jquery")
    

    Add the application.js to the head layout at the 'app/views/layouts/'.

    <%= javascript_pack_tag 'application' %>
    
  3. Create the React Component.

    $ rails g react:component CommentSection commentsPath:string
    

    This would generate the react component we will be using for the real-time loading of comments. The 'commentsPath:string' is the props that will pass the API URL to the component.

  4. Install the active model serializer gem after adding this to your Gemfile.

    gem 'active_model_serializers'
    

    Create the comment serializer by typing this to your console.

    $ rails g serializer Comment
    

    Then add the text field to the comment serializer.

    class CommentSerializer < ActiveModel::Serializer
        attributes :id, :text
    end
    
  5. Now we will create the controller we would be using for the API.

    Create the API folder first. Go to the controller folder in the rails app, then do this:

    $  mkdir api
    

    Then go to the newly created folder and make the controller we would be using.

    $ touch posts_controller.rb
    

    Edit posts_controller.rb with this code.

    class Api::PostsController < ApplicationController
        before_action :set_post, only: [:comments]
    
        def comments
            render json: @post.comments, each_serializer: CommentSerializer
        end
    
        private
    
        def set_post
           @post = Post.find(params[:id])
        end
     end
    

    The posts#show should return an array of comments.

  6. Add the API path to config/routes.rb.

    Rails.application.routes.draw do
     # other routes
    
     namespace :api do
        resource :posts, only: [], defaults: {format: "json"} do
            member do
                get "/:id/comments" => "posts#comments", as: "comments"
            end
        end
     end
    end
    

    Get the pathname of the newly added route by checking 'rails routes' to your console terminal. In my case, its 'comments_api_posts_path'.

  7. Add react component to post#show view. Pass the new pathname we just created in the react component.

    <!--app/views/posts/show.html.erb-->
    <p id="notice"><%= notice %></p>
    
    <%= @post.title %>
    <br>
    <%= @post.text %>
    <br>
    
    <b>Comments</b>
    <br>
    
    <%= react_component("CommentSection", { commentsPath: comments_api_posts_path(id: @post.id)}) %>
    
    <%= render "comments/form", comment: @comment, post_id: @post.id%>
    
    <%= link_to 'Edit', edit_post_path(@post) %> |
    <%= link_to 'Back', posts_path %>
    

    The commentsPath will be passed down the path as props in the react component.

  8. Update the React component CommentSection.js.

    import React from "react"
    import PropTypes from "prop-types"
    
    class CommentSection extends React.Component {
       constructor(props){
          super(props);
    
          this.state = {
              comments: []
          }
       }
    
       componentDidMount(){
           //Run fetchComments() for the first time
           this.fetchComments();
    
           //Set Interval for running fetchComments()
           this.interval = setInterval(() =>{
               this.fetchComments();
           }, 1000);
       }
    
       componentWillUnmount(){
           // Clear the interval right before component unmount
           clearInterval(this.interval);
       }
    
       // Fetches Comments
       fetchComments(){
    
           $.ajax({
               url: this.props.commentsPath,
               dataType: 'json',
               success: function (result){
                   //Set state based on result
                   this.setState({comments: result})
              }.bind(this)
           });
       }
    
       render () {
           return (
               <React.Fragment>
                   <ul>
                       {
                         this.state.comments.map(function(comment, index){
    
                         return <li key={index}>{comment.text}</li>
                       })
                       }
                  </ul>
               </React.Fragment>
           );
       }
     }
    
     export default CommentSection
    

    A little bit of explanation. The fetchComments() function, fetches the comments of the post based on the value of commentsPath props(with the value of the API path of the current post). The result of the fetch will return an array of comments and that will be set as a state, which will be rendered by the component.

  9. Change the form at the 'app/views/comments/_form.html.erb' and comments_controller.rb.

     <!-- app/views/comments/_form.html.erb -->
     <!--  add 'local:false' in the form_with-->
    
     <%= form_with(model: comment, local: false) do |form| %>
    

     # app/controllers/comments_controller.rb
     # edit create action
    
     def create
         @comment = Comment.new(comment_params)
    
         if @comment.save
             respond_to do |format|
                 format.js{ render :js => "document.getElementById('comment_text').value = '';" }
             end
         end
     end
    

    The javascript would remove the text after you submit the comment form.

And that's the last step! Try restarting your server, and check your localhost.

If you want to check the code, go to the Github Repository.

Latest comments (0)