# Terminal
rails g controller welcome index
rails g controller process index
rails g job progress_increment
rails g channel progress
yarn add stimulus
# view/welcome/index.html.erb
<%= link_to 'Start Large Process', process_index_path, id: 'large-process', class: 'btn btn-primary', remote: true %>
# controllers/process_controller.rb
class ProcessController < ApplicationController
def index
@id = (Time.now.to_f * 1000).to_i
ProgressIncrementJob.perform_later(@id)
end
end
# views/process/index.js.erb
document.getElementById('large-process').insertAdjacentHTML('afterend', "<%= j render 'progress_bar', id: @id %>")
document.getElementById('large-process').remove()
# config/application.rb
config.active_job.queue_adapter = :async
# in production, you may have something like = Rails.env.production? ? :sidekiq : :async
# jobs/progress_increment_job.rb
class ProgressIncrementJob < ApplicationJob
queue_as :default
def perform(id)
10.times do |i|
sleep rand() # Some heavy task
ActionCable.server.broadcast("progress_#{id}", "#{(i + 1) * 10}%")
end
end
end
# channels/progress_channel.rb
class ProgressChannel < ApplicationCable::Channel
def subscribed
stream_from "progress_#{params[:id]}" if params[:id]
end
def unsubscribed
# Any cleanup needed when channel is unsubscribed
end
end
# views/process/_progress_bar.html.erb
<%= content_tag :div, class: 'progress', data: { controller: 'progress-bar' } do %>
<%= tag :div, class: 'progress-bar', id: id, data: { target: 'progress-bar.progress_bar' } %>
<% end %>
# javascript/packs/application.js
import { Application } from "stimulus"
import { definitionsFromContext } from "stimulus/webpack-helpers"
const application = Application.start()
const context = require.context("../controllers", true, /\.js$/)
application.load(definitionsFromContext(context))
# javascript/controllers/progress_bar_controller.js
import { Controller } from "stimulus"
import consumer from '../channels/consumer'
export default class extends Controller {
static targets = ['progress_bar']
initialize() {
console.log('hello from progress bar controller')
var progressBar = this.progress_barTarget
consumer.subscriptions.create({ channel: 'ProgressChannel', id: this.progress_bar_id() }, {
received(data) {
progressBar.style.width = data
}
})
}
progress_bar_id() {
return this.progress_barTarget.getAttribute('id')
}
}
# assets/stylesheets/application.css
.progress {
background-color: black;
border-radius: 13px;
padding: 3px;
}
.progress > .progress-bar {
background-color: orange;
width: 0%;
height: 20px;
border-radius: 10px;
}