Build a server updated async progress bar with Rails in 5 steps
Tags: rails, ruby, javascript, tutorial
Published: 2020-01-07 15:18:03

---
title: Build a server updated async progress bar with Rails in 5 steps
published: true
description: How to build an asynchronously server updated progress bar with Rails & CableReady in 5 simple steps
tags: rails, ruby, javascript, tutorial
---

This tutorial demonstrates how simple it is to perform DOM updates from Rails background jobs with [CableReady](https://github.com/hopsoft/cable_ready).

![Progress Bar Demo](https://thepracticaldev.s3.amazonaws.com/i/7yczivl0n4hks0zn0smj.gif)

# Intro

Ruby on Rails supports websockets out of the box via a built in library known as [ActionCable](https://guides.rubyonrails.org/action_cable_overview.html). I created a library named [CableReady](https://github.com/hopsoft/cable_ready) 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

```sh
rails new progress_bar_demo
cd progress_bar_demo
```

## 2. Create the [restful](https://en.wikipedia.org/wiki/Representational_state_transfer) resource

First create the controller and HTML page.

```sh
bundle exec rails generate controller progress_bars
touch app/views/progress_bars/show.html.erb
```

```html
<!-- app/views/progress_bars/show.html.erb -->
<h1>Progress Bar Demo</h1>
<div id="progress-bar">
  <div></div>
</div>
```

Then update the routes file.

```ruby
# 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.

```sh
mkdir app/javascript/stylesheets
touch app/javascript/stylesheets/application.scss
```

```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.

```js
// 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.

```erb
<!-- 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
```

```js
// 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)
  }
})
```

```ruby
# app/channels/progress_bar_channel.rb
class ProgressBarChannel < ApplicationCable::Channel
  def subscribed
    stream_from "ProgressBarChannel"
  end
end
```

## 5. Setup the backend

```sh
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.


```ruby
# 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
```

```ruby
# 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

```sh
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.