Scope broadcast to instance

I’m having a hard time wrapping my head around how to broadcast via a model and where the Hotwire magic stops and where I have to step in. Apologies if this has been covered, but I can’t seem to find what must be a common use case. Throughout my app, I want to scope updates to a specific user – or in this case, to a specific client.

My case:

I have a Client model. A Client has_many :hospitalizations.

I have two index views for hospitalizations.

One lists all hospitalizations:

views/hospitalizations/index.html.erb
views/hospitalizations/_hospitalization.html.erb

One is nested under clients:

views/clients/hospitalizations/index.html.erb
views/clients/hospitalizations/_hospitalizations.html.erb
views/clients/hospitalizations/_hospitalization.html.erb

I’d like to be able to update both the unscoped hospitalizations/index and the clients/hospitalizations/index.

So models/hospitalization.rb:

class Hospitalization < ApplicationRecord
  belongs_to :client
  belongs_to :staff

  after_create_commit do
    broadcast_prepend_to  "hospitalizations",
                          partial: 'hospitalizations/hospitalization',
                          locals: { hospitalization: self },
                          target: 'hospitalizations'
    broadcast_prepend_to  :client,
                          partial: 'clients/hospitalizations/hospitalization',
                          locals: { hospitalization: self },
                          target: 'client_hospitalizations'
...

I wasn’t sure whether I’m supposed to do something with client.rb, so:

class Client < ApplicationRecord

  broadcasts
  ...

And then, views/clients/hospitalizations/index.html.erb:

<%= turbo_stream_from :client %>
<%= render 'hospitalizations' %>

With the code I have now, when I create a new Hospitalization:

  1. hospitalizations/hospitalization works great. The new row is prepended.
  2. If I create a new hospitalization for client 4689, Hotwire will prepend the turbo-frame for all clients, not just 4689.

How do I scope the broadcast so that it only updates the client to whom the hospitalization belongs?

OK, I think I have this working.

In model, for the broadcast_prepend_to, etc., helpers, I need to broadcast to the client instance, not a string or symbol. Also have to make sure I broadcasts_to :client.

# models/hospitalization.rb
class Hospitalization < ApplicationRecord
  belongs_to :client
  belongs_to :staff

  broadcasts_to :client

  after_create_commit -> (hospitalization) do
    broadcast_prepend_later_to  "hospitalizations"
    broadcast_prepend_later_to  hospitalization.client,
          partial: 'clients/hospitalizations/hospitalization',
          target: "client_#{hospitalization.client_id}_hospitalizations"
  end

  after_update_commit -> (hospitalization) do
    broadcast_replace_later_to "hospitalizations"
    broadcast_replace_later_to hospitalization.client,
          partial: 'clients/hospitalizations/hospitalization',
          target: "client_hospitalization_#{hospitalization.id}"
  end

  after_destroy_commit -> (hospitalization) do
    broadcast_remove_to "hospitalizations"
    broadcast_remove_to hospitalization.client,
          partial: 'clients/hospitalizations/hospitalization'
  end
...

Then, in client index, I need to stream from the client instance here, not a string or symbol:

# views/clients/hospitalizations/index.html.erb
<%= turbo_stream_from @client %>

Client collection partial needs a turbo frame ID that doesn’t match hospitalizations, which is already being used by views/hospitalizations/index.html.erb, so use unique ID like client_4689_hospitalizations as set above in target.

# views/clients/hospitalizations/_hospitalizations.html.erb
<%= turbo_frame_tag "client_#{@client.id}_hospitalizations" do %>
  <%= render @hospitalizations %>
<% end %>

Same for client instance partial. Prefix client_ to turbo frame ID, so it doesn’t interfere with dom_id of views/hospitalizations/_hospitalization.html.erb.

# views/clients/hospitalizations/_hospitalization.html.erb
<%= turbo_frame_tag "client_#{dom_id(hospitalization)}" do %>
<div class="span-eight tight">
    <h5><%= hospitalization.facility %></h5>
    <p><%= hospitalization.dates_to_s %></p>
    <%= simple_format hospitalization.comments %>  
</div>
<% end %>
1 Like