class Export < ApplicationRecord
enum status: { queued: 0, processing: 1, completed: 2, failed: 3 }
after_update_commit -> {
broadcast_replace_later_to(
self,
target: dom_id(self, :status),
partial: 'exports/status',
locals: { export: self }
)
}, if: :saved_change_to_status?
end
<%= turbo_frame_tag dom_id(export, :status) do %>
<% klass = {
'queued' => 'bg-gray-100 text-gray-700',
'processing' => 'bg-blue-100 text-blue-800',
'completed' => 'bg-green-100 text-green-800',
'failed' => 'bg-red-100 text-red-800'
}.fetch(export.status) %>
<span class="inline-flex items-center rounded px-2 py-1 text-xs font-medium <%= klass %>">
<%= export.status %>
</span>
<% end %>
A lot of Rails apps have records that transition through states: queued, processing, done. With Hotwire, I render a status badge partial and broadcast replacements when the state changes. A background job updates the record, and the model broadcasts a replace to the stream. On the page, I wrap the badge in a turbo_frame_tag dom_id(record, :status) so it can be swapped cleanly. The benefit is instant feedback without polling. The key is broadcasting a partial that’s cheap to render and doesn’t accidentally load a bunch of associations. If the job can fail, I also broadcast an error badge with a retry link.