~tsvallender

My name is Trevor, I’m a software engineer specialising in Ruby on Rails. I’m also a dad, geek and tabletop gamer.

Profile photo of Trevor Vallender

Broadcasting with Turbo Streams—WebSockets made simple.

14 June 2022

I love Ruby on Rails. I love the way it takes a set of assumptions about how web applications should be made and uses those to make developing web applications easier and more joyful.

I love Turbo. Turbo takes this same philosophy and applies it to JavaScript. It makes a set of assumptions about the things you are most likely to be doing (essentially, updating a page based on standard CRUD functionality) and makes those things really simple.

One aspect of Turbo, and more specifically Turbo Streams, I only recently discovered is Broadcasts. In this short post I just want to briefly highlight how powerful they can be. There’s not a huge amount of documentation out there currently. One of the best places to learn more is the Broadcastable concern source code itself.

The problem to solve

I’m currently working on an application for tabletop roleplayers. It has a page called combat, with a list of zones. When a player clicks on a zone, their character should be moved there and—key to this demonstration—all the other players should have the data update on their view too.

On the combat page we have some basic HTML:

<ul id="zone-1">
  <li id="character-1">Bungo</li>
  <li id="character-2">Bilbo</li>
  <li><a href="…">Move to zone 1</a></li>
</ul>

<ul id="zone-2">
  <li id="character-3">Gandalf</li>
  <li id="character-4">Radagast</li>
  <li><a href="…">Move to zone 1</a></li>
</ul>

Obviously the HTML is being generated dynamically, with the integers being the IDs of the zone or character. The link, which I have omitted for brevity, makes a PATCH request to the zone, which updates a given characters zone. So far, so standard, but this is where the magic happens.

First, we need to subscribe to updates on a given object. This does some magic in the background created appropriately named channels in Action Cable (Rails’ implementation of WebSockets) which we can then broadcast updates over. In this example, we want to subscribe to combat, as the zones are all attributes of this.

<%= turbo_stream_from combat %>

Now, we add a small amount of code to the Character class so that whenever it is updated, it uses ActionCable (i.e. a WebSocket connection) to broadcast the change and update everyone viewing the page.

class Character < ApplicationRecord
  belongs_to :zone
  after_commit :update_zones
  
  private
  
  def update_zones
	broadcast_remove_to(zone.combat, target: "character-#{id}")
	broadcast_append_later_to(zone.combat, target: "zone-#{id}")
  end
end

And that, unbelievably, is all there is to it. We now have a page on which players can move their character between zones, and any other players can see in real-time which zone each character is in.

A quick explanation of the code above:

Obviously this approach limits you to some basic CRUD-based functionality, but that is often all or most of what you need. Even if you may need to grow beyond that in the future, there’s no reason not to start here and then add more advanced Action Cable functionality as needed, similar to the natural flow from Turbo Frames to Streams to Stimulus.

To see a full list of the methods available, see the documentation or the source linked above.

Have at it!