I'm about to give up on Turbo

…at least streaming. Stimulus took a little while to get the hang of, but you had a handbook the explained just about everything - you just had to absorb it. If you look at the few Rails demos using Turbo there’s not much in the handbook that looks the same (turbo handbook is generic html - not Rails). There is also not much documentation for the hotwire-rails gem unless you can read/understand the documented code. I guess I’m not absorbing Turbo yet and I probably don’t need it - but I keep trying. I dusted off an old rails demo app I toyed with that was jQuey/coffeescript based and tried to replicate it using Turbo and a little stimulus.

  • I have a simple model Bingo that only has a few attributes.
  • The controller only has 3 actions and associated views :show, :edit, :update
  • The show view is more or less a status view of the current game.
    • A board displaying what numbers have been called
    • Some stats on last and recent calls
  • The edit view is almost the same as the show view but responds to clicks that trigger an update action
    • Clicking on one of the 75 numbers will add or remove that number from the calls (attribute - serialized array)
    • Clicking on a button in the button bar will trigger an update (patch form) that will change game state

My goal was to stream any changes made to the current game to anyone viewing the show view. I actually got it to semi-work trying various schemes I’ve seen in the chat demos.

class Bingo < Game
  broadcasts
  #   resource :bingo, only: [:show, :edit, :update]
end
// _bingo.html.slim rendered from show.html.slim
#header.w3-white MY PROBLEM
= turbo_stream_from bingo
= turbo_frame_tag dom_id(bingo) do
  #display.w3-row
    #game-status = [nil,"Resume","New","Current","current"].include?(bingo.state) ? nil : bingo.state
    - [['B',1],['I',16],['N',31],['G',46],['O',61]].each do |r|
      - c = r[0].downcase
      .square class="#{c}ltr npoint" = r[0]
      - r[1].upto(r[1] + 14) do |i|
        - id = "#{r[0]}#{i}"
        - if bingo.calls.include?(id)
          .square.num-on[id="#{id}" class="num-on#{c}"] = i
        - else
          .square.num-off[id="#{id}" class="num-off#{c}"] = i
  = render partial: 'bingos/status', locals: {bingo: bingo}

// _edit.html.slim rendered from edit.html.slim
= turbo_frame_tag dom_id(@bingo,:caller) do
  #display.w3-row[data-controller="callerBoard"]
    #game-status = [nil,"Resume","New","Current","current"].include?(@bingo.state) ? nil : @bingo.state
    - [['B',1],['I',16],['N',31],['G',46],['O',61]].each do |r|
      - c = r[0].downcase
      .square class="#{c}ltr npoint" = r[0]
      - r[1].upto(r[1] + 14) do |i|
        - id = "#{r[0]}#{i}"
        - if @bingo.calls.include?(id)
          .square.num-on(id="#{id}" data-callerBoard-target="toggle" data-action="click->callerBoard#clear" class="num-on#{c}") = i
        - else
          .square.num-off(id="#{id}" data-callerBoard-target="toggle" data-action="click->callerBoard#call" class="num-off#{c}") = i
    = render partial: 'status', locals: {bingo: @bingo}
    = render partial:"button_bar", locals:{bingo: @bingo}

The semi-works comment is that it appears to append the turbo-frame tag. The #header.w3-white MY PROBLEM outside the frame (added to debug) will double on every change and the html will scroll down. I’ve tried replacing the broadcasts in the model wih:

broadcast_replace_to target:"bingo_#{self.id}", partial: 'bingos/bingo', locals:{bingo:self}

on a after commit action and nothing happens. I can seen the html render in console, but the show view does not change. I also tried a couple respond_to.turbo_stream approaches and they also did not work.

I posted something similar a few weeks ago but was probably too vague and received zero response. Being able to stream from the model, or the controller and maybe from the turbo_stream_tag probably got me mixing apples and oranges. I have tried adding numerous stuff to the tags, tried what little I could find on broadcast_tos but the only thing that seems to semi-work is the broadcasts.

Any ideal what I’m not absorbing?

1 Like

I’m also struggling to wrap my head around Hotwire and agree that the handbook seems to be lacking. I haven’t personally had much time to dig in yet, but I hope that when I do there will be more resources available.

As for your specific issue, I don’t believe that steams and frames work together. Again, I don’t understand the framework either. But since no one else has chimed in yet, I’ll offer what I can…

I believe you need to swap the turbo_frame_tag within the turbo_stream for a template

e.g.

<turbo-stream ...>
  <template>
  ...

the steam is supposed to perform the chosen action (append, replace, etc) using the template like a partial.

Can anyone chime in to confirm or help lead the blind?

I’ll be interested to see if you come around on this. Good luck!

Update

You made me curious, so I just went and re-read the Turbo Streams section of the handbook and I think we’re coming at it from the wrong end…

You do need to use a respond_to block in your controller action.

I’m guessing here, but try this…

Put the game board that you want replaced in its own partial, render that partial on the main page as you normally would. Just a regular partial, no turbo-stream or turbo-frame. Just wrap the part you want replaced in a div with id of dom_id(bingo) or whatever your resource is. Then use something equivalent to the pseudo code below in your controller action…

basically straight out of the handbook…

respond_to do |format|
  format.turbo_stream { render turbo_stream: turbo_stream.replace(:bingo, partial: "your/bingo_partial", locals: {bingo: @bingo}) }
  format.html         { redirect_to bingo_url(@bingo) }
end

I realize this doesn’t utilize the broadcast in your model. I have no knowledge of how that works, sorry.

Thanks for the suggestions, I’m blending them with some things I’ve discovered.

The = turbo_stream_from bingo is a helper method that wrap that partial in a <template> tag but it does not appear to have optional arguments like the tags in the handbook.

I think I’m having mixing apples with oranges problems. You can do things in the controller, model or view but with slightly different ways/syntax. I can get it broadcast to the viewer with just the stream and frame tags. It seems to default to a replace option, even if one is not called. Problem is that every update seems call a broadcast replace multiple times.

From web inspector after initial load of page

 <div class="w3-text-white">hi viewer</div>
  <turbo-cable-stream-source channel="Turbo::StreamsChannel" signed-stream-name="IloybGtPaTh2WW1sdVoyOHRaR1Z0Ynk5Q2FXNW5ieTh5Ig==--daff349a1b6f4e2a94a7cbb9323971d72e6070fa335d4e22845974887a3713ea"></turbo-cable-stream-source>
  <turbo-frame id="bingo_2"> ...

After two updates the will be 9 of the <turbo-cable-stream-source channel=... tags before the frame tag. Another update will have 17 tags, etc, etc. Things will slow down after a few hundred!

Don’t think it should work that way so I have something wrong.

I did find a site that has some hotwire stuff on it Noel Rappin Writes Here he seems to take a slightly different approach/style.

Too many options, But I keep trying.

I figured it out and of course it was my stupid mistake.

The hotwire-rails gem is not documented very well and the turbo_stream_from @bingo helper got me. I was curious in that it looked like a tag helper, but was not a block. I guess it wraps everything below it in a <template> tags.

I was getting multiple turbo-cable-stream-source tags:

<div id="container">
  <div class="w3-text-white" id="header">Hi Viewer</div>
  <turbo-cable-stream-source channel="Turbo::StreamsChannel" signed-stream-name="IloybGtPaTh2WW1sdVoyOHRaR1Z0Ynk5Q2FXNW5ieTh5Ig==--daff349a1b6f4e2a94a7cbb9323971d72e6070fa335d4e22845974887a3713ea"></turbo-cable-stream-source>
  <turbo-cable-stream-source channel="Turbo::StreamsChannel" signed-stream-name="IloybGtPaTh2WW1sdVoyOHRaR1Z0Ynk5Q2FXNW5ieTh5Ig==--daff349a1b6f4e2a94a7cbb9323971d72e6070fa335d4e22845974887a3713ea"></turbo-cable-stream-source>
  <turbo-cable-stream-source channel="Turbo::StreamsChannel" signed-stream-name="IloybGtPaTh2WW1sdVoyOHRaR1Z0Ynk5Q2FXNW5ieTh5Ig==--daff349a1b6f4e2a94a7cbb9323971d72e6070fa335d4e22845974887a3713ea"></turbo-cable-stream-source>
  <turbo-cable-stream-source channel="Turbo::StreamsChannel" signed-stream-name="IloybGtPaTh2WW1sdVoyOHRaR1Z0Ynk5Q2FXNW5ieTh5Ig==--daff349a1b6f4e2a94a7cbb9323971d72e6070fa335d4e22845974887a3713ea"></turbo-cable-stream-source>
  <turbo-frame id="bingo_2">
    payload html
  </turbo-frame>
</div>

The problem was that the turbo_stream_from @bingo helper was in my class partial that was getting replaced. That then created multiple/duplicate streams! I simply moved it to the show page that renders the partial.

/ bingos/show.html.slim
#container
  #header.w3-text-white Hi Viewer
  = turbo_stream_from @bingo
  = render @bingo, bingo: @bingo

I seem to remember having these 'I really don’t understand how this works??` phase with Stimulus. Just be careful where you put that tag.

While everything works, I’m still curious on why with only broadcasts in the model it knew I wanted a replace without me having any code to tell it to replace?

1 Like

I did put up a bare-bones example on GitHub

turbo-bingo-demo

1 Like