22

I'm trying to create a simple chat-like application (planning poker app) with Action Cable. I'm a little bit confused by the terminology, files hierarchy and how the callbacks work.

This is the action that creates user session:

class SessionsController < ApplicationController
  def create
    cookies.signed[:username] = params[:session][:username]
    redirect_to votes_path
  end
end

A user can then post a vote that should be broadcasted to everyone:

class VotesController < ApplicationController
  def create
    ActionCable.server.broadcast 'poker',
                                 vote: params[:vote][:body],
                                 username: cookies.signed[:username]
    head :ok
  end
end

Up to this point everything is clear for me and works fine. The problem is - how do I display the number of connected users? Is there a callback that fires in JS when a user (consumer?) connects? What I want is when I open 3 tabs in 3 different browsers in incognito mode I would like to display "3". When a new user connects, I would like the number to increment. If any user disconnects, the number should decrement.

My PokerChannel:

class PokerChannel < ApplicationCable::Channel
  def subscribed
    stream_from 'poker'
  end
end

app/assets/javascripts/poker.coffee:

App.poker = App.cable.subscriptions.create 'PokerChannel',

  received: (data) ->
    $('#votes').append @renderMessage(data)

  renderMessage: (data) ->
    "<p><b>[#{data.username}]:</b> #{data.vote}</p>"
pmichna
  • 4,800
  • 13
  • 53
  • 90

5 Answers5

19

Seems that one way is to use

ActionCable.server.connections.length

(See caveats in the comments)

Jonathan Allard
  • 18,429
  • 11
  • 54
  • 75
pmichna
  • 4,800
  • 13
  • 53
  • 90
  • 7
    Do note that this is the current number of connection for the *specific thread* this is called in, not the number of connections over various processes and threads – edwardmp Dec 31 '16 at 07:47
  • 3
    It's also the number of connections to the server, not to a specific channel. – Matt Jan 04 '17 at 13:58
3

For a quick (and probably not ideal) solution you can write a module that tracks subscription counts (using Redis to store data):

#app/lib/subscriber_tracker.rb
module SubscriberTracker
  #add a subscriber to a Chat rooms channel 
  def self.add_sub(room)
    count = sub_count(room)
    $redis.set(room, count + 1)
  end

  def self.remove_sub(room)
    count = sub_count(room)
    if count == 1
      $redis.del(room)
    else
      $redis.set(room, count - 1)
    end
  end

  def self.sub_count(room)
    $redis.get(room).to_i
  end
end

And update your subscribed and unsubscribed methods in the channel class:

class ChatRoomsChannel < ApplicationCable::Channel  
  def subscribed
     SubscriberTracker.add_sub params['room_id']
  end

  def unsubscribed
     SubscriberTracker.remove_sub params['chat_room_id'] 
  end
end
T1000
  • 423
  • 9
  • 17
3

In a related question on who is connected, there was an answer for those who uses redis:

Redis.new.pubsub("channels", "action_cable/*")

and if you just want number of connections:

Redis.new.pubsub("NUMPAT", "action_cable/*")

This will summarize connections from all your servers.

All the magic covered inside RemoteConnections class and InternalChannel module.

TL;DR all connections subscibed on special channels with a prefix action_cable/* with only purpose of disconnecting sockets from main rails app.

0

I think i found a answer for you. Try this:

ActionCable.server.connections.select { |con| con.current_room == room }.length?

I can use it everywhere in my code and check amount of connected users to selected stream :)

in my connection.rb I have something like this:

module ApplicationCable
  class Connection < ActionCable::Connection::Base
    identified_by :current_room

    def connect
      self.current_room = find_room
    end

    private

    def find_room
      .....
    end
  end
end

Hope that helps anyone.

Garbus Uchiha
  • 68
  • 1
  • 6
-1

With

ActionCable.server.pubsub.send(:listener).instance_variable_get("@subscribers")

you can get map with subscription identifier in the key and array of procedures which will be executed on the broadcast. All procedures accept message as argument and have memoized connection.