Turbo is slow on JavaScript side that I am forced to delay Ruby side. If I don't delay the server response, there are no turbo stream updates on the page. My logs show every turbo stream is rendering and all the jobs get executed and transmitted.
Controller:
# app/controllers/grids_controller.rb
class GridsController < ApplicationController
def play
@job_id = PlayJob.perform_later(**@grid_data.attributes).job_id # Turbo broadcasts
render :play, status: :created
end
end
Turbo stream view:
<!-- app/views/grids/play.html.erb -->
<%= turbo_stream_from :play, @job_id %>
<div id="grid">
<% unless Rails.env.test? %>
<p><strong>Phase 0</strong></p>
<div class="cells">
<%= image_tag asset_url('loading.gif'), alt: 'loading', class: 'loading' %>
</div>
<% end %>
</div>
Turbo broadcasts:
# lib/grid.rb
class Grid
def play(job_id)
sleep 1 # I am forced to add delay of one second to get turbo to respond!
loop do
broadcast_to :play, job_id
break if phase >= @phases
sleep @phase_duration
next_phase
end
end
def broadcast_to(*streamable)
Turbo::StreamsChannel.broadcast_update_to(
streamable,
target: 'grid',
partial: 'grids/grid',
locals: { grid: self }
)
end
end
Here is the code of all my app: https://github.com/chiaraani/life-game-web
In your play
action you're broadcasting to a channel that you haven't connected yet, then you're rendering play.html.erb
where turbo_stream_from
connects. By that time all the jobs have finished, which is why when you add delay there is time to connect and see updates.
You can also see it in the logs, all the jobs get done and then you see:
# jobs
# jobs
Turbo::StreamsChannel is streaming from ...
but there is nothing left to stream.
You have to have turbo_stream_from
already connected before you submit the form and have #grid
target already on the page ready to catch:
# app/views/new.html.erb
# NOTE: you can create Play model in `new` action and just do update.
# this way you have an `id` to broadcast to. I just made a random one
# in place.
<%= turbo_stream_from :play, (play_id = SecureRandom.uuid) %>
<%= form_with model: @grid_data, url: play_path, builder: GridFormBuilder do |f| %>
# send `id` back to controller
<%= hidden_field_tag :id, play_id %>
<% GridData.attribute_names.each do |attribute| %>
# just use `scope`
<%= f.label attribute, t(attribute, scope: :questions) %>
<%= f.number_field attribute %>
<% end %>
<%= f.submit %>
<% end %>
<%= tag.div id: "grid" %>
# app/controllers/grids_controller.rb
def create
@grid_data = GridData.new(**grid_params)
respond_to do |format|
if @grid_data.valid?
# pass `id` so we can broadcast to it
@grid_data.to_grid.play(params[:id])
# it just needs to be an empty response, but it has to be `turbo_stream`
format.turbo_stream { render inline: "" }
else
format.html { render :new, status: :unprocessable_entity }
end
end
end
You can remove all sleep
delays and see how fast it is.