Channel state

Channel objects are ephemeral when using AnyCable (see Architecture) compared to Action Cable. Thus, the following example wouldn't work in AnyCable as expected:

class RoomChannel < ApplicationCable::Channel
  def subscribed
    @room = Room.find(params["room_id"])
    stream_for @room
  end

  def speak(data)
    broadcast_to @room, message: data["text"]
  end
end

The instance variable @room lives only during the #subscribed call, it's not set when the #speak action is performed, 'cause it happens in the context of the new RoomChannel instance.

The are two ways to fix this: using params or using channel state accessors.

Subscription params

Subscription parameters are included into the subscription identifier, and thus accessible to all RPC requests.

NOTE: params are read-only.

We can refactor our channel to rely on params instead of instance variables:

class RoomChannel < ApplicationCable::Channel
  def subscribed
    stream_for params["room_id"]
  end

  def speak(data)
    broadcast_to params["room_id"], message: data["text"]
  end
end

Using state_attr_accessor

In case params is not enough and you want to mutate the channel state or store non-primitive values, you can use state accessors.

State accessor behaves like attr_accessor but also persists the data in AnyCable for subsequent calls:

class RoomChannel < ApplicationCable::Channel
  state_attr_accessor :room

  def subscribed
    self.room = Room.find(params["room_id"])
    stream_for room
  end

  def speak(data)
    broadcast_to room, message: data["text"]
  end
end

Read more about how the state is passed from a WebSocket server and restored in an RPC server in the architecture overview.

Connection state

In addition to persisting channel states, we provide an ability to store data in the connection itself using a similar .state_attr_accessor interface:

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

    state_attr_accessor :url

    def connect
      self.current_user = verify_user
      # URL is accesible in subsequent RPC calls just like current_user
      self.url = request.url if current_user
      logger.add_tags "ActionCable", current_user.name
    end
  end
end

Why having a separate storage when we already have identifiers? Identifiers are meant what they say: they should be used to identify the connection (e.g., when used for remote disconnects). If you want to store some additional information in the connection, use state accessors instead.