I’ve implemented a very simple chat application and I would like to display how many people are connected to it. Also, this number should be updated upon new access (subscribed) and when user closes the tab (unsubscribed the channel).
Is there any way I can count how many subscribers have subscribed from a turbo_stream without using ActionCableadapter methods, i.e., agnostic of Redis/Async/etc. ?
I have a feeling I should be using ActionCable methods as Turbo Stream descend from it, but I am not sure
Thanks. I’ve been scouring the documentation but haven’t been able to find a clear solution.
It’d be really useful to count the number of connected subscribers - I don’t want to bother rendering a complex stream if there are 0 connected subscribers.
I’ve explored all these answers and couldn’t get any of them to work. I’m wondering if perhaps I’m misunderstanding something.
My use case is that my web app has an Chat Inbox, where admins can see and send messages to users. But we have hundreds of messages coming in every minute, and I want to avoid eating up server resources by rendering and sending partials for these messages, unless an admin is online and subscribed to the Chat’s turbostream.
So in my .html, I have
<%= turbo_stream_from @chat %>
and it connects perfectly. But in my message.rb, can’t figure out how to check if anyone is subscribed to the @chat, when a new message comes in and I’m trying to determine whether to broadcast it over the stream.
The best way to track the presence of subscribed users to a channel, like a @chat channel, is to use a regular action cable channel and record user when they subscribe to the channel and un-record them when they unsubcribe.
I will share you some code but take it carefully may I answered uncorrectly.
but I copied the code used by the new magic stream to not duplicate redis connection… “await window.cable.subscribeTo”
# models/chat.rb
class Chat < ApplicationRecord
kredis_unique_list :online_user_ids
def add_online_user(user_id)
online_user_ids << user_id
end
def remove_online_user(user_id)
online_user_ids.remove(user_id)
end
def online_users_count
online_user_ids.elements.count
end
def online_users
User.where(id: online_user_ids.elements)
end
# etc...
end
# channels/chat_channel.rb
class ChatChannel < ApplicationCable::Channel
before_subscribe :set_chat
before_unsubscribe :set_chat
def subscribed
stream_for(@chat)
@chat.add_online_user(current_user.id)
# broadcast online_users_count or whatever
end
def unsubscribed
stop_all_streams
@chat.remove_online_user(current_user.id)
# broadcast online_users_count or whatever
end
private
def set_chat
@chat = Chat.find(params[:id])
end
end
@culov, reading again your question, I answer a bit more.
So, you want to broadcast chat’s messages only when one, or more, admins are online which mean connected to the chat’s channel.
# models/message.rb
class Message < ApplicationRecord
include Turbo::Streams::ActionHelper
belongs_to :chat
# NOTE: we use turbo_stream thru a channel but we send it by our own
after_create_commit :broadcast_append, if :admins_online?
after_update_commit :broadcast_update, if :admins_online?
after_destroy_commit :broadcast_remove, if :admins_online?
private
# NOTE: do each for multiple admin... up to you
def admins_online?
ADMIN_USER_ID = "123"
chat.online_user_ids.elements.include?(ADMIN_USER_ID)
end
# NOTE: we can move this code in a worker; normally, hotwire do it without writing a worker
def broadcast_append
html = ApplicationController.renderer.render partial: 'messages/message', locals: { message: self }
html = turbo_stream_action_tag("append", target: "message_list", template: html)
ChatChannel.broadcast_to(chat, html)
end
def broadcast_update
html = ApplicationController.renderer.render partial: 'messages/message', locals: { message: self }
html = turbo_stream_action_tag("replace", target: "message_#{id}", template: html)
ChatChannel.broadcast_to(chat, html)
end
def broadcast_remove
html = turbo_stream_action_tag("remove", target: "message_#{id}")
ChatChannel.broadcast_to(chat, html)
end
end