Build a server updated async progress bar with Rails in 5 steps

hopsoft

Hopsoft

Posted on January 7, 2020

Build a server updated async progress bar with Rails in 5 steps

This tutorial demonstrates how simple it is to perform DOM updates from Rails background jobs with CableReady.

Progress Bar Demo

Intro

Ruby on Rails supports websockets out of the box via a built in library known as ActionCable. I created a library named CableReady that works with ActionCable to perform common DOM operations from background jobs without requiring you to write any custom JavaScript. And, it's very performant.

1. Create the Rails project

rails new progress_bar_demo
cd progress_bar_demo
Enter fullscreen mode Exit fullscreen mode

2. Create the restful resource

First create the controller and HTML page.

bundle exec rails generate controller progress_bars
touch app/views/progress_bars/show.html.erb
Enter fullscreen mode Exit fullscreen mode
<!-- app/views/progress_bars/show.html.erb -->
<h1>Progress Bar Demo</h1>
<div id="progress-bar">
  <div></div>
</div>
Enter fullscreen mode Exit fullscreen mode

Then update the routes file.

# config/routes.rb
Rails.application.routes.draw do
  resource :progress_bar, only: [:show]
  root "progress_bars#show"
end
Enter fullscreen mode Exit fullscreen mode

3. Setup the styling

First create the stylesheet.

mkdir app/javascript/stylesheets
touch app/javascript/stylesheets/application.scss
Enter fullscreen mode Exit fullscreen mode
// app/javascript/stylesheets/application.scss
#progress-bar {
  background-color: #ccc;
  border-radius: 13px;
  padding: 3px;
}

#progress-bar>div {
  background-color: green;
  width: 0;
  height: 20px;
  border-radius: 10px;
}
Enter fullscreen mode Exit fullscreen mode

Then update the JavaScript pack to include the stylesheet.

// app/javascript/packs/application.js
require("@rails/ujs").start()
require("turbolinks").start()
require("@rails/activestorage").start()
require("channels")

import "../stylesheets/application.scss" // <-- add this line
Enter fullscreen mode Exit fullscreen mode

Finally, update the application layout to use the stylesheet pack.

<!-- app/views/layouts/application.html.erb -->
<!DOCTYPE html>
<html>
  <head>
    <title>ProgressBarDemo</title>
    <%= csrf_meta_tags %>
    <%= csp_meta_tag %>

    <!-- line below was updated to use stylesheet_pack_tag -->
    <%= stylesheet_pack_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
    <%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>
  </head>

  <body>
    <%= yield %>
  </body>
</html>
Enter fullscreen mode Exit fullscreen mode

4. Setup the ActionCable channel

yarn add cable_ready
bundle exec rails generate channel progress_bar
Enter fullscreen mode Exit fullscreen mode
// app/javascript/channels/progress_bar_channel.js
import consumer from "./consumer"
import CableReady from 'cable_ready'

consumer.subscriptions.create("ProgressBarChannel", {
  received: data => {
    if (data.cableReady) CableReady.perform(data.operations)
  }
})
Enter fullscreen mode Exit fullscreen mode
# app/channels/progress_bar_channel.rb
class ProgressBarChannel < ApplicationCable::Channel
  def subscribed
    stream_from "ProgressBarChannel"
  end
end
Enter fullscreen mode Exit fullscreen mode

5. Setup the backend

bundle add cable_ready
bundle exec rails generate job progress_bar
Enter fullscreen mode Exit fullscreen mode

When this job fires, it runs a loop that fills in the progress bar a little bit on each iteration. This is possible because CableReady allows us to send commands to the browser that update the DOM without the need to write custom Javascript.

# app/jobs/progress_bar_job.rb
class ProgressBarJob < ApplicationJob
  include CableReady::Broadcaster
  queue_as :default

  def perform
    status = 0
    while status < 100
      status += 10
      cable_ready["ProgressBarChannel"].set_attribute(
        selector: "#progress-bar>div",
        name: "style",
        value: "width:#{status}%"
      )
      cable_ready.broadcast
      sleep 1 # fake some latency
    end
  end
end
Enter fullscreen mode Exit fullscreen mode
# app/controllers/progress_bars_controller.rb
class ProgressBarsController < ApplicationController
  def show
    ProgressBarJob.set(wait: 1.second).perform_later
  end
end
Enter fullscreen mode Exit fullscreen mode

6. Run and watch the magic

bundle exec rails s
Enter fullscreen mode Exit fullscreen mode

Then visit http://localhost:3000 in a browser.

Disclaimer

⚠️ This demo is tailored for the development environment. In a production setup you'd need to configure both ActionCable and ActiveJob to use Redis. You'd also want to secure the ActionCable channel.

💖 💪 🙅 🚩
hopsoft
Hopsoft

Posted on January 7, 2020

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related