Build a server updated async progress bar with Rails in 5 steps
Hopsoft
Posted on January 7, 2020
This tutorial demonstrates how simple it is to perform DOM updates from Rails background jobs with CableReady.
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
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
<!-- app/views/progress_bars/show.html.erb -->
<h1>Progress Bar Demo</h1>
<div id="progress-bar">
<div></div>
</div>
Then update the routes file.
# config/routes.rb
Rails.application.routes.draw do
resource :progress_bar, only: [:show]
root "progress_bars#show"
end
3. Setup the styling
First create the stylesheet.
mkdir app/javascript/stylesheets
touch app/javascript/stylesheets/application.scss
// 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;
}
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
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>
4. Setup the ActionCable channel
yarn add cable_ready
bundle exec rails generate channel progress_bar
// 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)
}
})
# app/channels/progress_bar_channel.rb
class ProgressBarChannel < ApplicationCable::Channel
def subscribed
stream_from "ProgressBarChannel"
end
end
5. Setup the backend
bundle add cable_ready
bundle exec rails generate job progress_bar
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
# app/controllers/progress_bars_controller.rb
class ProgressBarsController < ApplicationController
def show
ProgressBarJob.set(wait: 1.second).perform_later
end
end
6. Run and watch the magic
bundle exec rails s
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.
Posted on January 7, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.