9

I want to stream model updates using Hotwire in Rails. In the documentation they mention creating a stream using a model but I want to create a dynamic stream per user so that changes to models done by a user are only streamed to that user.

I have a VideoCall model which has and belongs to many users via a joining table so the model looks like this and I have a broadcast as such

class VideoCall < ApplicationRecord
  has_and_belongs_to_many :users

  broadcasts_to ->(video_call) {
    video_call.users.map{|u| "video_calls:user_#{u.id}"}
  }, inserts_by: :prepend

  # more code here
end

On the ERB template where I want to get the updates to the model I have added

<%= turbo_stream_from "video_calls:user_#{current_user.id}" %>

This works if I have one user in a users_video_calls table. But as soon as there are multiple users in a video_call it does not work and I want to broadcast to the stream of all of those users.

Is the approach I am taking correct and how can I achieve broadcasting to multiple users streams dynamically.

Encore PTL
  • 8,084
  • 10
  • 43
  • 78

1 Answers1

14

So I solved this the following way. Looking at the source code I realized that it makes sense to override the three callbacks that get called on create, update and destroy. So I removed this code

  broadcasts_to ->(video_call) {
    video_call.users.map{|u| "video_calls:user_#{u.id}"}
  }, inserts_by: :prepend

and replaced it with this

  after_create_commit  -> {
    self.users.each do |u|
      broadcast_action_later_to "video_calls:users_#{u.id}", action: :prepend, target: broadcast_target_default
    end
  }

  after_update_commit  -> {
    self.users.each do |u|
      broadcast_replace_later_to "video_calls:users_#{u.id}"
    end
  }

  after_destroy_commit -> {
    self.users.each do |u|
      broadcast_remove_to "video_calls:users_#{u.id}", action: :prepend, target: broadcast_target_default
    end
  }

In the code above now I can loop through each associated model which in my case is many users per video call and broadcast to the channel name that contains user id. So all the users subscribed to the video call will only get the update and not everyone.

Encore PTL
  • 8,084
  • 10
  • 43
  • 78
  • Looks good. But I don't think you need `action: :prepend` in your `broadcast_remove_to` statement. – moveson Feb 04 '21 at 17:35
  • 1
    I've been wanting to do the same thing -- use Hotwire to manage a message list scoped to the logged-in user. Thanks for following up with your solution. – dwayne Jun 10 '21 at 01:33
  • Thanks, nice solution. However the after_destroy_commit does not work if you want to update the view of the same user (let's say the website is open in 2 different browsers). Reason is that self.users does not contain the user that just deleted whatever self is (in my case it's links). I am not sure how to do that. Any idea? – imarg Feb 14 '22 at 00:15
  • You can extract the users before calling the destroy. – Stephane Paquet Jun 23 '22 at 06:56