# Terminal
rails g controller heavy_tasks
rails g job heavy_tasks
rails g job small_task
rails g stimulus progress-bar
resources :heavy_tasks, only: :create
class HeavyTasksController < ApplicationController
before_action :authenticate_user!
def create
HeavyTaskJob.perform_later(current_user.id)
end
end
class HeavyTaskJob < ApplicationJob
queue_as :default
before_perform :broadcast_initial_update
def perform(current_user_id)
total_count.times do |i|
SmallTaskJob.perform_later(current_user_id, i, total_count)
end
end
private
def broadcast_initial_update
Turbo::StreamsChannel.broadcast_replace_to ["heavy_task_channel", current_user.to_gid_param].join(":"),
target: "heavy_task",
partial: "heavy_tasks/progress",
locals: {
total_count: total_count
}
end
def total_count
@total_count ||= rand(10..100)
end
def current_user
@current_user ||= User.find(self.arguments.first)
end
end
class SmallTaskJob < ApplicationJob
queue_as :default
def perform(current_user_id, i, total_count)
current_user = User.find(current_user_id)
sleep rand
Turbo::StreamsChannel.broadcast_action_to ["heavy_task_channel", current_user.to_gid_param].join(":"),
action: "append",
target: "heavy_task",
content: "<div></div>"
end
end
<%= render partial: "heavy_tasks/button" %>
<%= turbo_stream_from ["heavy_task_channel", current_user] if user_signed_in? %>
<div id="heavy_task">
<%= button_to "Perform Heavy Task", heavy_tasks_path, class: "btn btn-primary" %>
</div>
<div id="heavy_task"
data-controller="progress-bar"
data-progress-bar-total-jobs-value="<%= total_count %>">
<div class="progress">
<div class="progress-bar progress-bar-striped active"
role="progressbar"
data-progress-bar-target="progress">
</div>
</div>
import { Controller } from "@hotwired/stimulus"
// Connects to data-controller="progress-bar"
export default class extends Controller {
static values = {
totalJobs: 0,
completedJobs: 0
}
static targets = ["progress"]
connect() {
this.observer = new MutationObserver((mutationsList, _observer) => {
for (let mutation of mutationsList) {
if (mutation.type === 'childList') {
this.increment()
}
}
})
this.observer.observe(this.element, { childList: true })
}
increment() {
this.completedJobsValue++
this.updateProgress()
}
updateProgress() {
let progress = (this.completedJobsValue / this.totalJobsValue) * 100
this.progressTarget.style.width = `${progress}%`
}
}