Building a Real Time Scoreboard with Ruby on Rails and CableReady

The release of Hotwire in late 2020 brought attention to a growing interest within the Rails community in building modern, reactive Rails applications without needing the complexity of an API + SPA.

Although Hotwire’s Turbo library garnered a lot of attention, the Rails community has been working for years to improve the toolset we have to build modern, full-stack Rails applications. Turbo isn’t the first attempt at giving Rails developers the tools they need.

One of the most important of these projects is CableReady, which powers StimulusReflex and Optimism, along with standing on its own as a tool to:

Create great real-time user experiences by triggering client-side DOM changes, events and notifications over ActionCable web sockets

Source: CableReady Welcome

Today we’re going to explore CableReady by using Rails, CableReady, and Stimulus to build a scoreboard that updates for viewers in real-time, with just a few lines of Ruby and JavaScript.

When we’re finished, our scoreboard will look like this:

A screen recording of a user with a web page and a terminal window open. On the page are the scores for two teams, Miami and Dallas. The user types a command in the terminal to update the home team's score to 95 and, after the command runs, the score on the web page for Miami updates to 95.

This article assumes that you’re comfortable working with Rails but you won’t need any prior knowledge of CableReady or ActionCable to follow along. If you’ve never used Rails before, this article isn’t the best place to start.

Let’s dive in!

Read the rest 19-minute read

Conditional Rendering With Turbo Stream Broadcasts

A very common pattern in Rails development is for a view to contain checks for things like current_user.can?(:take_some_action). These types of checks are common, especially in B2B applications that implement role-based permissions powered by a solution like Pundit.

So, naturally, when a Rails developer begins working with Turbo Stream broadcasts, they wonder how to access the current_user or other session-level variables. The short answer? You can’t.

Partials rendered via a stream broadcast have to be free of global variables or they’ll throw an error because they are not rendered within the context of a specific request. Without that request context variables like current_user will always be undefined.

While we can’t access global variables from a broadcast, we can, with creative use of Turbo Frames, still deliver real-time broadcasts that retain access to key variables like current_user.

To demonstrate the concept today we’re going to build a simple application that displays a list of Spies.

Each spy will have a mission; however, not everyone will be able to see the spy’s mission. Only visitors with secret clearance can see the mission field. Everyone else will see a “Classified” string in place of the mission.

When a new spy is created, we’ll broadcast an update to any viewers of the spy list. Viewers with clearance will see the mission for the newly created spy, viewers without won’t.

The end result will work like this:

A screen recording of a user with two windows open to the same webpage. The webpage displays a list of names, with a header that reads Spies. In one window the user sees a message that they have secret clearance, in the other they don't. The user creates a new spy by entering a name and the new spy ges added to the list of spies already on the page. In the window with secret clearance, the user sees newly created spy's mission, in the other, they don't.

This article will be most useful for folks who are already comfortable with Ruby on Rails and who have a little experience with Turbo already. If you’re comfortable with Rails but you’ve never used Turbo before, an article like this might be a better introduction.

As usual, you’ll find the full code for this article on GitHub.

Ready? Let’s get started.

Read the rest 18-minute read

Turbo Streams on Rails

Updated March 17th, 2024 to account for the release of refresh and morph Stream actions in Turbo 8

Turbo, part of the Hotwire tripartite, helps you build modern, performant web applications with dramatically less custom JavaScript.

Turbo is composed of Turbo DriveTurbo FramesTurbo Streams, and Strada. Each is a valuable piece of the puzzle but today we’re going to focus on Turbo Streams. Turbo Streams “deliver page changes over WebSocket, Server-Sent Events, or in response to form submissions” and they might be the most exciting part of the Turbo package.

Streams allow you to deliver fast and efficient user experiences with minimal effort and, for Rails developers, you’ll find that streams fit comfortably into your existing toolkit. With just a few lines of code, you can send tiny snippets of HTML over the wire and and use those snippets to update the DOM with no custom JavaScript.

For fancier use cases, you can broadcast changes over a WebSocket connection to keep every interested user updated in real-time. No custom JavaScript to write, just HTML and a little bit of Ruby.

Let’s dive in.

What’s a Turbo Stream?

At their core, streams allow you to send snippets of HTML to the browser to make small changes to the DOM in response to events that happen on the server.

The basic structure of a Turbo Stream is an HTML snippet that looks like this:

<turbo-stream action="action_to_take" target="element_to_update">
    <div id="element_to_update">
      <!-- Some more html -->

The <turbo-stream> element always includes an action, from the following possibilities:

  • append
  • prepend
  • before
  • after
  • replace
  • update
  • remove
  • morph
  • refresh

In addition to an action, a target (or targets) must be provided for all actions except for refresh. Refreshing and morphing are special cases introduced in Turbo 8, and you can read more about morphing and Turbo Stream refresh broadcasts in this article.

Inside of the stream element, the HTML to render gets wrapped in a <template> tag. Turbo’s JavaScript processes these HTML snippets by reading the action and target from the stream tag and inserting (or removing) the wrapped HTML as appropriate.

Turbo Streams can be sent in response to a direct request from a browser (like submitting a form) or broadcast to subscribers over a WebSocket connection.

With turbo-rails, we have the tools we need to take either path, and we’ll look at both methods in detail next.

Turbo Streams in controllers

The simplest way to get started with Turbo Streams is to have a controller respond to turbo_stream requests and render a turbo_stream response in return.

This approach looks something like this:

# players_controller.rb
def create
  @player =

  respond_to do |format|
      format.html { redirect_to @player, notice: "Player was successfully created." }
      format.html { render :new, status: :unprocessable_entity }

# app/views/players/create.turbo_stream.erb
<%= turbo_stream.replace "players_form" do %>
  <%= render partial: "new_player" %>
<% end %>

In this example, a user is attempting to save a new player record in the database.

When the save is successful and the request format is turbo_stream, we use Rails’ implicit rendering to respond with create.turbo_stream.erb which renders a Turbo Stream response with a replace action targeting the players_form element.

The turbo_stream method in the create view generates the necessary <turbo-stream> tag and wraps the content within the turbo_stream block in a <template> tag.

The response back to the browser will look like this:

<turbo-stream action="replace" target="players_form">
    <!-- Content of new_player.html.erb partial -->
    <turbo-frame id="players_form">
      <a href="/players/new">New Player</a>

Turbo’s JavaScript then steps in, processes the response, and updates the DOM. We do not need to make any adjustments to the failure path in our controller above. Even though the form submission is a turbo_stream request, Rails falls back to responding with HTML when a turbo_stream format to respond with does not exist.

Our example demonstrates handling a POST request with Turbo Streams, but we can use the same approach with a GET request, add data-turbo-stream to the link or GET form you want to send as a Turbo Stream request and Turbo will handle the rest.

Rendering inline

For simple responses we do not always need dedicated .erb files. Instead we can render the turbo_stream response inline in the controller, like this:

def create
  @player =

  respond_to do |format|
      format.html { redirect_to @player, notice: "Player was successfully created." }
      format.turbo_stream { render turbo_stream: turbo_stream.replace('players_form', partial: 'new_player') }

      format.html { render :new, status: :unprocessable_entity }

There is no functional difference between inline rendering and rendering from a template, for simple actions you can use whichever feels better.

Updating multiple elements at once

The create.turbo_stream.erb file we saw earlier renders a single <turbo-stream> tag, but we aren’t limited to updating a single stream per response. We can update multiple elements at once by simply adding them to our turbo_stream.erb file:

<%= turbo_stream.replace "players_form" do %>
  <%= render partial: "new_player" %>
<% end %>
<%= turbo_stream.append "players" do %>
  <%= render partial: "player", locals: { player: @player } %>
<% end %>

Now after a successful form POST, we will send back a response with two <turbo-stream> tags, each of which will be processed by Turbo’s JavaScript on the front end.

If you really dislike creating turbo_stream templates, you can also render multiple Streams inline in the controller like this:

format.turbo_stream do
  # Pass an array of turbo_streams to the render call
  render turbo_stream: 
      turbo_stream.replace('players_form', partial: 'new_player'),
      turbo_stream.append('players', partial: 'player', locals: { player: @player })

Should you do this? Probably not, but it works.

Broadcasting changes

So far, we have seen rendering streams from controller actions. This method works great for dealing with form submissions made by individual users; however, there is another method of broadcasting updates to a wider audience that you can call on when needed.

As we saw in the Hotwire introduction video, stream broadcasts sent from the controller only update the page that made the original request. If other people are viewing the same page, they won’t see the update until they refresh the page.

If your use case requires every interested user to see changes as they happen, you can use broadcasts from the turbo-rails gem to send streams to every subscriber at once, in real time.

The basic syntax for these updates looks like this, in your model:

# app/models/player.rb
after_create_commit { broadcast_append_to('players') }

Here we’re using a callback, fired each time a new player is created, to broadcast the newly created player to the players channel.

Broadcast options

By default, the Stream target will be set to the plural name of the model; however you can override the target as needed by passing in a target to the method, like this:

after_create_commit { broadcast_append_to('players', target: 'players_list') }

Broadcasting from the model will attempt to use to_partial_path to guess the name of the partial that should be rendered.

In our examples so far, if we have a partial in app/views/players named _player.html.erb that partial will be used. As with targeting, you can override the partial like this:

after_create_commit { broadcast_append_to('players', partial: 'a_special_player') }

When streaming in this manner, you must create a WebSocket connection to the channel you’re broadcasting updates on.

To do that, you can add <%= turbo_stream_from "some_channel" %> to the view. The name of the channel passed to turbo_stream_from must match the name of the channel passed to broadcast_action_to , otherwise your updates will get lost in space.

broadcast_action_to vs. broadcast_action_later_to

In the source code for broadcastable, a comment near the top of the file advises us to use broadcast_action_later_to instead of broadcast_action_to .

Using _later methods moves the stream broadcast into a background job, which means that broadcasts can happen asynchronously, moving the potentially expensive rendering work out of the web request.

Since we want everything to be fast, not slow, we’ll use broadcast_action_later_to, like this:

after_create_commit { broadcast_append_later_to('players') }

The only exception to the later rule is delete actions. broadcast_remove_to simply removes an element from the DOM without rendering a template and so does not need to be moved into a background job. To reinforce this exception, broadcast_remove_later_to is not defined and calling it will throw an undefined method error.

Broadcasting from a controller

So far we’ve seen that we can render a Turbo Stream response from a controller action and broadcast from a model.

Another, less common, path is to call broadcast_append|remove|replace on an instance of a model from within a controller (or a service, or anywhere else you like), like this:


This does the same thing as calling this method in a callback in the model, meaning the append will broadcast to all users subscribed to the players channel.

More magical methods

So far we’ve been adding _to at the end of our broadcast methods and explicitly passing in the channel name. If you like magic, you can omit the _to from your broadcasts and just use broadcast_append|remove|replace.

Using this form requires a channel streaming from the model instance, like the below.

# player.rb
after_update_commit { broadcast_replace }

# _player.html.erb
<%= turbo_stream_from @player %>

To type even less, you can add broadcasts_to to your model, like this:

# player.rb
broadcasts_to ->(_) { 'players' }

Here we’re using a non-standard stream name of players. If we’re using the model instance as the stream name, we can get down to a single magic word:

# player.rb

Both broadcasts and broadcasts_to automatically add broadcasts on createupdate, and delete commits to the model, as seen here

In practice, these methods often do not add much value since they rely so much on magical naming convention.

The magic fails when attempting to, for example, append a newly created record to an index list. In that scenario, our stream won’t be tied to an instance of the class, and so we’ll need to use the long form version of broadcast_action_to, like we saw above.

When in doubt, just use the longer, slightly less magical methods. I’m sharing these magic methods because they’re commonly used in guides and feature prominently in the announcement video so you’ll likely find them in the wild for years to come. If your use case allows you to use them, go for it, but the longer versions work just fine too.


Scope your channels!

In most of the examples I’ve shown so far, we are broadcasting to streams with hardcoded strings for names (“players”). This works fine; however, in most web applications resources aren’t globally available. Account 123 should only see updates on their account, not every account. You can avoid streaming updates to the wrong user by ensuring you’re broadcasting to properly scoped channels and subscribing users to those scoped channels.

In the players example code we’ve used so far, we can imagine that a player belongs to a team, and updates to those players should be broadcast on the team channel.

To implement this in our code, it might look like this:

# teams/show.html.erb
<%= turbo_stream_from @team %>

# player.rb
belongs_to :team

after_create_commit { broadcast_append_later_to(team) }
after_destroy_commit { broadcast_remove_to(team) }

With this in place, the team show page subscribes to updates related to that specific team, keeping data from leaking between different teams. The same concept can be applied to scope channels by user or account, as needed.

Note that this scoping is only necessary for broadcasts sent over WebSocket. If your controller renders a turbo_stream response, only the client that made the initial request will receive the update. There is no risk of data leaking in this manner when responding to a form submission with a turbo stream template.

Error handling

When you render a turbo_stream from a controller action and the DOM doesn’t have a matching target element to update… nothing happens. There’s no error anywhere, the stack trace indicates that the partial was rendered, and the DOM doesn’t update.

This is normal, expected behavior. There was not an error, there just wasn’t anything to change in the DOM - but it can be confusing when you’re getting started and the lack of errors can make troubleshooting more difficult.

If you are not getting errors, the turbo_stream response is showing in the server logs, and nothing is updating the page, the problem is (almost) always that the target passed to <turbo-stream> isn’t matching any elements in your page’s markup. Start by checking what you are rendering on the page and compare that to the target passed to the stream.

Wrapping up

Turbo is an incredibly powerful tool, and the tight integration between Rails and Turbo means that forward-looking Rails developers should be exploring ways to bring Turbo into their applications.

Using streams with turbo-rails opens up a world of possible user experiences for Rails developers that previously meant more work, more effort, and more code to maintain.

Hopefully what I’ve shared here helps you feel a little more comfortable thinking about streams, and how streams can fit into your existing code base. As always, thanks for reading! More resources Ready to dive deeper?

  • Spend some time with turbo-rails source code
  • Spend some time with the turbo source code
  • Dive into the docs
  • Read some practical guides, like this guide on building a live search interface, or this one on handling modal form submissions
  • GoRails has published a variety of Hotwire-specific guides

Finally, if you want to learn about implementing Turbo Streams in a complex Rails application, I wrote a book on building a modern Ruby on Rails application with Hotwire. The book is written in this same, step-by-step tutorial style that you find throughout this blog and aims to offer solutions for a variety of common, real-world web application problems without reaching for the weight of a full frontend framework.

Read the rest 16-minute read

Instant search with Rails and Hotwire

Last year I wrote an article on building an instant search form with Rails and Stimulus. Since then, Turbo, the other half of Hotwire for the web has been released and the Hotwire stack is now the default for new applications in Rails 7.

Turbo opens the door for an even simpler, cleaner implementation of an instant search form.

So, today we’re taking another look at how to build a search-as-you-type interface that allows us to query our database for matches and update the UI with those matches (almost) instantly.

When we’re done, our code will look almost identical to what you’re used to writing to build Rails applications, and the only JavaScript we’ll need will be to trigger form submissions as the user types.

The finished product will work like this:

A screen recording of a user typing in a word to search a list of Dallas Mavericks basketball players. As they type, the list of players updates automatically.

I’m writing this assuming that you’re comfortable with Ruby on Rails - you won’t need any knowledge of the Hotwire stack and you’ll find the most value from this article if you’re new to Turbo. While this article uses Stimulus, its core focus is on Turbo, and specifically how to use Turbo Frames in a Rails application.

You can find the complete code for this guide on Github. Follow along with this guide from the main branch, or see the complete implementation in the instant search branch.

Let’s get started.

Read the rest 19-minute read

Handling modal forms with Rails, Tailwind CSS, and Hotwire

September 2022 update: Since this article was written, Rails 7 and Turbo 7.2 have been released. There are new, simpler techniques to building modal forms with Turbo now. I published an updated version of this article after the release of Turbo 7.2, I recommend reading that article instead.

The release of the Hotwire package of Stimulus and Turbo has brought a tremendously powerful set of tools to Rails developers who want to bring the speed of a single page application to their users without sacrificing the development speed and happiness that Rails is known for.

Today we are using Hotwire, Rails, and Tailwind CSS to build a modal form submission flow.

With just a little bit of Stimulus and Turbo, we will create a user experience that opens a modal on click, retrieves modal content remotely, and cleanly handles successful form submission and form errors.

This modal flow will help us learn a few of the core concepts of Hotwire while demonstrating how to implement a very common user experience in modern web development.

The finished product will look something like this:

A screen recording of a user on a webpage clicking a link to create a comment, typing a message in the form that opens in a modal, and submitting the form successfully. After submission, the modal closes and the user's message appears in a list of messages on the page.

I am writing this guide assuming that you are comfortable with Ruby on Rails and that you have some level of familiarity with Stimulus. You do not need to be an expert on either.

While we use Tailwind CSS to make our modal implementation simple, you do not need experience with Tailwind. If you prefer, you can implement your own, non-Tailwind modal and still get value from this guide.

You can find the complete code for this project on Github.

Let’s get started.

Read the rest 24-minute read

Building a collapsible sidebar with Stimulus and Tailwind CSS

Today we’re building one of the most common elements in web design - a lefthand sidebar for navigation - using Tailwind CSS and Stimulus.js.

Our sidebar will always take up 100% of the height of the page and we’ll be able to expand and collapse the sidebar by clicking a button. The whole thing will be accomplished with just a bit of Stimulus for the expand/collapse action and a small amount of Tailwind in our markup.

Here’s what it will look like when we’re finished.

A screen recording of a user clicking an icon to collapse and expand a sidebar on a web page

To accomplish this, we’ll start with a plain HTML file, pull in Tailwind CSS to make things look nice, and use Stimulus for interactivity.

I’m writing this assuming a solid understanding of HTML and CSS, and some level of comfort with JavaScript.

If you’ve never seen Tailwind before, some of the classes we add for styling might feel a little odd. You don’t need any knowledge of how Stimulus works, but if you’re brand new you might want to read the Stimulus Handbook to help solidify some concepts as we go.

You can find the complete code for this project on Github.

Let’s dive in.

Read the rest 19-minute read

Remotely loading tabbed content with Ruby on Rails and Hotwire

Hotwire is a set of tools for building web applications by sending HTML instead of JSON over the wire. Hotwire gives us a framework for making DOM updates without writing much, or any, JavaScript while delivering fast, modern-feeling web applications.

In today’s example, we’re building a interface that remotely retrieves a portion of the page content from an endpoint and replaces a targeted portion of the DOM with the response from the endpoint. We’ll build this without writing any JavaScript and with only minor additions to the standard Rails code you already know how to write.

Here is what it will look like when we are finished. We won’t be focused on styling today but our “tabs” will be fully functional and ready for you to add a nice looking Bootstrap or Tailwind skin.

A screen recording of a user toggling between an Awards and Credits tab on a web page, with the content changing on each click

To accomplish this, we will start with a new Rails 6.1 application, install Hotwire in the application, and then walk through the basics of adding Turbo Drive to our views.

I’m writing this assuming that you are comfortable with the basics of Ruby on Rails development and that you’ve never used Hotwire before.

You can find the complete source code for this tutorial on Github.

Let’s dive in.

Read the rest 11-minute read

Using Hotwire and Rails to build a live commenting system

Today we’re exploring Hotwire, a new-old way of building web applications by sending HTML instead of JSON over the wire.

We’ll learn how Hotwire works with Ruby on Rails by building a simple application that allows users to leave comments on a project and see those comments in real-time, without requiring full page turns.

This guide presents an alternative approach to a guide I wrote last year which guides you through creating a similar experience using Stimulus. Either approach is valid and works well. The approach presented today presents a way of delivering the experience without writing any JavaScript code, and should feel very natural for any Ruby on Rails developer.

Here is what it will look like when we are finished. It won’t be pretty, but it will work and we won’t write a single line of JavaScript.

A screen recording of a user typing in a comment in a text input and the comment being added to a list of comments

To accomplish this, we will start with a new Rails 6.1 application, install Hotwire in the application, and then walk through the basics of adding Hotwire to our views and controllers.

I’m writing this assuming that you are comfortable with the basics of Ruby on Rails development and that you’ve never used Hotwire before.

You can find the complete source code for this tutorial on Github.

Let’s dive in.

Read the rest 15-minute read

Building a horizontal slider with Stimulus and Tailwind CSS

Today we’re building a component that is common but deceptively tricky to get right - a horizontal slider with a position indicator and navigation buttons.

We’ll have a list of items of an arbitrary length, and our slider will allow folks to scroll to see every item in the list. As they scroll, indicators below the slider will update to show which items are visible on the screen. Clicking on the indicators will scroll the corresponding item into view. The whole thing is pretty fancy.

Here’s what it will look like when we’re finished.

A screen recording of a user scrolling a web page horizontally across an image gallery with identical images of red shoes

To accomplish this, we’ll start with a plain HTML file, pull in Tailwind CSS to make things look nice, and use Stimulus to build interactivity for our position indicators and navigation buttons.

I’m writing this assuming a solid understanding of HTML and CSS, and some level of comfort with JavaScript. If you’ve never seen Tailwind before, some of the classes we add for styling might feel a little odd. You don’t need any knowledge of how Stimulus works, but if you’re brand new you might want to read the Stimulus Handbook to help solidify some concepts.

Let’s dive in.

Read the rest 20-minute read

Building a video converter with Rails 6 and FFmpeg

Today’s project pulls together a number of built-in Rails tools, plus a couple of incredibly useful open source projects to build a web application that can convert user-uploaded video files to MP4.

To demonstrate some of the modern parts of the standard Rail stack, we’re going to go further than just building a converter. We will enhance the user experience during the upload and conversion process with Active Storage to handle direct file uploads, Action Cable for real-time updates on the video conversion, and Stimulus to dynamically update the DOM without needing a heavy Javascript framework.

Read the rest 33-minute read