# Terminal rails g controller heavy_tasks rails g job heavy_tasks rails g job small_task rails g stimulus progress-bar
# config/routes.rb sources :heavy_tasks, solely: :create
# app/controllers/heavy_tasks_controller.rb class HeavyTasksController < ApplicationController before_action :authenticate_user! def create HeavyTaskJob.perform_later(current_user.id) finish finish
# app/jobs/heavy_task_job.rb class HeavyTaskJob < ApplicationJob queue_as :default before_perform :broadcast_initial_update def carry out(current_user_id) total_count.instances do |i| SmallTaskJob.perform_later(current_user_id, i, total_count) finish finish non-public def broadcast_initial_update Turbo::StreamsChannel.broadcast_replace_to ["heavy_task_channel", current_user.to_gid_param].be part of(":"), goal: "heavy_task", partial: "heavy_tasks/progress", locals: { total_count: total_count } finish def total_count @total_count ||= rand(10..100) finish def current_user @current_user ||= Person.discover(self.arguments.first) finish finish
# app/jobs/small_task_job.rb class SmallTaskJob < ApplicationJob queue_as :default def carry out(current_user_id, i, total_count) current_user = Person.discover(current_user_id) sleep rand # Turbo::StreamsChannel.broadcast_replace_to ["heavy_task_channel", current_user.to_gid_param].be part of(":"), # goal: "heavy_task", # partial: "heavy_tasks/progress", # locals: { # progress: (i + 1) * 100 / total_count # } Turbo::StreamsChannel.broadcast_action_to ["heavy_task_channel", current_user.to_gid_param].be part of(":"), motion: "append", goal: "heavy_task", content material: "<div></div>" finish finish
# app/views/welcome/index.html.erb <%= render partial: "heavy_tasks/button" %>
# app/views/heavy_tasks/_button.html.erb <%= turbo_stream_from ["heavy_task_channel", current_user] if user_signed_in? %> <div id="heavy_task"> <%= button_to "Carry out Heavy Job", heavy_tasks_path, class: "btn btn-primary" %> </div>
# app/views/heavy_tasks/_progress.html.erb <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 energetic" position="progressbar" data-progress-bar-target="progress"> </div> </div>
# app/javascript/controllers/progress_bar_controller.js 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"] join() { this.observer = new MutationObserver((mutationsList, _observer) => { for (let mutation of mutationsList) { if (mutation.sort === 'childList') { this.increment() } } }) this.observer.observe(this.ingredient, { childList: true }) } increment() { this.completedJobsValue++ this.updateProgress() } updateProgress() { let progress = (this.completedJobsValue / this.totalJobsValue) * 100 this.progressTarget.fashion.width = `${progress}%` } }