Can't get broadcast to work! What am I doing wrong

Just a short intro. Back in Rail 5.x error I threw together a Bingo caller app. I was a volunteer at a VFW Post(Quartermaster) and we were exploring of having a Bingo game at the Post. After looking at the cost of some stuff (the big Bingo call board), I wrote the app.

It was pretty simple. Just a game model that serialized the calls. This was all in coffeescript, jquery etc. It had a caller view that would add the calls to the game by clicking on a button for each of the 60 numbers.

I had a viewer view the just displayed the board. I ran the viewer on a RasberryPI hooked to a large screen TV. It would refersh about every 5 seconds. Fortunatly we decided not to add the Bingo night.

I didn’t have anything to do and threw togother a new app to recreate Bingo in Rails 7. The code was much smaller using TW, Stimulus etc. Problem was I was still using JS to refresh the page, why not used Turbo frames and broadcast!

The frames where not hard but I’m stuck on getting broadcast to work. The Game model has three methods (beside CRUD methods)

def play
  @game = Game.current
end

def call
  @game.set_call(params[:numb])
  if @game.changed?
    @game.save
    @game.broadcast_replace_later_to "watcher", partial: "games/watcher", locals: { game: @game }
  end
  render turbo_stream: turbo_stream.replace(
    'call',
    partial: '/games/call')
end

def watcher
  @game = Game.current
end

The views are also simple, I use slim. I also use a helper to add classes to the buttons depending on what column the number is in.

# games/play The callers control panel
div.bg-black.flex.gap-8
  div.text-white.m-2 Btn1
  div.text-white.m-2 Btn2
  div.text-white.m-2 Btnx
== render partial:"games/call"

# games/_call
=turbo_frame_tag "call"
  div.bg-black
    - colors = ["B","I","N","G","O"]
    - 0.upto(4) do |row|
      div.flex
        - box = "btnOff" + colors[row]
        div[class=send(box)] = colors[row]

        - 1.upto(15) do |col|
          - numb = row * 15 + col
          - if @game.calls.include?(numb)
            - klass = "btnOn"+ colors[row]
            = button_to(numb,call_game_path(@game),method:"patch",form_class:send(klass),params:{numb:numb})
          - else
            - klass = "btnOff"+ colors[row]
            = button_to(numb,call_game_path(@game),method:"patch",form_class:send(klass),params:{numb:numb})
    div.text-white.m-2 Call Stack First -> Last (left to right)

    div.flex.flex-wrap
     - @game.calls.each do |c|
       - btn = "btnOn" + call_color(c)
       div[class=send(btn)] = c
    div.text-white.p-8 more stuff

# games/watcher
div
  == render partial:'games/watcher', locals:{game:@game}

# games/_watcher
= turbo_frame_tag "watcher"
  div.bg-black.flex.gap-8
     div.text-white.m-2 = link_to("Exit",home_path,data:{action:"refresh#stopRefresh"})
     div[data-refresh-target="updated" class="text-white m-2"] = game.updated_at.to_s
# the rest is same a call, except the buttons are just div's not actions.

So all is working except for

@game.broadcast_replace_later_to "watcher", partial: "games/watcher", locals: { game: @game }

I’ve tried both broadcast_replace_to and broadcast_replace_later_to. neither change the watcher view and produce the following log after the call is saved:

#with later broadcast_replace_later_to
14:35:10 web.1  | [ActiveJob] [Turbo::Streams::ActionBroadcastJob] [e73ed3ee-a387-4a57-9f7d-f93c28d6c946]   Game Load (0.1ms)  SELECT "games".* FROM "games" WHERE "games"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
14:35:10 web.1  | [ActiveJob] [Turbo::Streams::ActionBroadcastJob] [e73ed3ee-a387-4a57-9f7d-f93c28d6c946]   CACHE Game Load (0.0ms)  SELECT "games".* FROM "games" WHERE "games"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
14:35:10 web.1  | [ActiveJob] [Turbo::Streams::ActionBroadcastJob] [e73ed3ee-a387-4a57-9f7d-f93c28d6c946] Performing Turbo::Streams::ActionBroadcastJob (Job ID: e73ed3ee-a387-4a57-9f7d-f93c28d6c946) from Async(default) enqueued at 2022-10-26T19:35:10Z with arguments: "watcher", {:action=>:replace, :target=>#<GlobalID:0x000000011225c840 @uri=#<URI::GID gid://bingo/Game/1>>, :targets=>nil, :partial=>"games/watcher", :locals=>{:game=>#<GlobalID:0x000000011225c278 @uri=#<URI::GID gid://bingo/Game/1>>}}
14:35:10 web.1  | [ActiveJob] [Turbo::Streams::ActionBroadcastJob] [e73ed3ee-a387-4a57-9f7d-f93c28d6c946]   Rendered games/_watcher.html.slim (Duration: 4.9ms | Allocations: 10099)
14:35:10 web.1  | [ActiveJob] [Turbo::Streams::ActionBroadcastJob] [e73ed3ee-a387-4a57-9f7d-f93c28d6c946] [ActionCable] Broadcasting to watcher: "<turbo-stream action=\"replace\" target=\"game_1\"><template><turbo-frame id=\"watcher\"><div class=\"bg-black flex gap-8\"><div class=\"text-white m-2\"><a data-action=\"refresh#stopRefresh\" href=\"/home\">Exit</a></div><div class=\"text-white m-2\" data-refresh-target=\"updated\">2022-10-26 1...
14:35:11 web.1  | [ActiveJob] [Turbo::Streams::ActionBroadcastJob] [e73ed3ee-a387-4a57-9f7d-f93c28d6c946] Performed Turbo::Streams::ActionBroadcastJob (Job ID: e73ed3ee-a387-4a57-9f7d-f93c28d6c946) from Async(default) in 8.89ms

# ascync broadcast_replace_to
14:41:03 web.1  |   Rendered games/_watcher.html.slim (Duration: 1.3ms | Allocations: 1468)
14:41:03 web.1  | [ActionCable] Broadcasting to watcher: "<turbo-stream action=\"replace\" target=\"game_1\"><template><turbo-frame id=\"watcher\"><div class=\"bg-black flex gap-8\"><div class=\"text-white m-2\"><a data-action=\"refresh#stopRefresh\" href=\"/home\">Exit</a></div><div class=\"text-white m-2\" data-refresh-target=\"updated\">2022-10-26 1...
14:41:03 web.1  |   Rendered games/_call.html.slim (Duration: 13.5ms | Allocations: 21910)
14:41:03 web.1  | Completed 200 OK in 30ms (Views: 0.1ms | ActiveRecord: 1.5ms | Allocations: 30007)
14:41:03 web.1  | 

So it’s beyond my knowledge. I was wondering broadcast_to was not documented in Rails - Because it’s in Turbo with little expaination on how it work,

I’m just stumped. I am on OSX if that makes a difference.

Rather than editing my jumbled post, I’ll just answer my own question.

Again, there is not very much written on Turbo Stream/Frames. I did find a post on David Colby’s site turbo-streams-on-rails. I tried to follow that post and mixed it with trying to understand dhh’s blog demo/video. My major problems was in not understanding the names/targets/ids on turbo-frame and Streams.

My solution was to add a few missing lines that did the broadcast and correct the target to what log was show as the id of the stream.

While I was populating the turbo-frames, i was not broadcasting. Somewhere I missed how you did that. I added broadcast_to <something> to my game model, trying :game, :watcher, etc. and that just produced an error. I finally figured out my missing piece in the `games/_watcher’ partial

= turbo_stream_from :watcher
= turbo_frame_tag "game_#{game.id}"
  div.bg-black.flex.gap-8 ...

The tag:watcher was defined in the game controller watch action:

 @game.broadcast_replace_to "watcher", partial: "games/watcher", locals: { game: @game }

Adding = turbo_stream_from :watcher fixed the broadcast. The naming of frame tags to ‘watcher’ was being ignored and was replace with dom_id in the log. So I set it to dom_id. Two lines of code!

Thanks, for all that looked at the post, but were probably confused. I’m still confused but getting close to understanding Turbo.

1 Like