Benito Serna Tips and tools for Ruby on Rails developers

Broadcast multiple actions with broadcast_render

September 19, 2022

Imagine that you are creating a record (let’s say a tweet), and on creation, you want to add the tweet to the page and also update the tweets count in the page…

But you want to update the content, not just for the current user, but for all the users in the page, you want to broadcast the changes.

How would you do it?

Well, here I want to show you how you can use broadcast_render or its _later variations, to do it.

Example video

You will be able to do something like this:

Use broadcast_render_later_to

This method will help you render a turbo stream template with the current model as the local variable, so you can send something like this:

<%= turbo_stream.replace "new_tweet" do %>
  <%= render "tweets/form", tweet: Tweet.new %>
<% end %>

<%= turbo_stream.prepend "tweets", tweet %>

<%= turbo_stream.replace "tweets_count" do %>
  <%= render "tweets/count", tweets: Tweet.all %>
<% end %>

If you do:

tweet.broadcast_render_later_to :tweets, partial: "tweets/on_create"

Why you should prefer broadcast_render_later_to over broadcast_render_to?

As normally these methods will render some templates and the send them, if you use broadcast_render_to as a reaction to a controller action, you will block the response of the controller action until the rendering is done.

Instead if you use broadcast_render_later_to the rendering and broadcast would be executed on a background job, and won’t block the controller’s response.

When you are destroying a record (as far as I understand) you won’t be able to use broadcast_render_later_to because as the record is already destroyed, it won’t be able to fetch the record on the job. So, after destroy, you could use broadcast_render_to.

Let’s see the full example

For the example of the video…

You can have a tweets/index like this:

<%= turbo_stream_from :tweets %>

<header>
  <%= render "count", tweets: @tweets %>
  <h1>Tweets</h1>
  <%= render "form", tweet: Tweet.new %>
</header>

<section id="tweets">
  <%= render @tweets %>
</section>

It needs the turbo_stream_from :tweets to listen to the broadcasts that you will produce.

The form could be a “standard” form like:

<%= form_with(model: tweet, id: dom_id(tweet)) do |form| %>
  <div>
    <%= form.text_area :body %>
  </div>

  <div>
    <%= form.submit %>
  </div>
<% end %>

Just look that in this case I am using dom_id(tweet) as the form id. In that way, for non persisted records, the id will be new_tweet and it could be replaced with turbo_stream.replace "new_tweet".

Then in the controller you can create the tweet and do the broadcast there, without any other response.

def create
  @tweet = Tweet.new(tweet_params)
  @tweet.save
  @tweet.broadcast_render_later_to :tweets, partial: "tweets/on_create"
end

The partial tweets/on_create.turbo_stream.erb could look something like this:

<%= turbo_stream.replace "new_tweet" do %>
  <%= render "tweets/form", tweet: Tweet.new %>
<% end %>

<%= turbo_stream.prepend "tweets", tweet %>

<%= turbo_stream.replace "tweets_count" do %>
  <%= render "tweets/count", tweets: Tweet.all %>
<% end %>

It will:

If you think that is better for your use case to render from the model, you can do it on an after_create_commit:

class Tweet < ApplicationRecord
  after_create_commit do
    broadcast_render_later_to :tweets, partial: "tweets/on_create"
  end
end

Play with the code

Here is code of the example app that I used for the video and the examples, go and play with it.

github.com/bhserna/broadcast_render_example

Related articles

Subscribe to get future posts via email

Here I try to share knowledge and fixes to common problems and struggles for ruby on rails developers, like How to fetch the latest-N-of-each record or How to test that an specific mail was sent or a Capybara cheatsheet. You can see more examples on Most recent posts or all posts.