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.