Using AnyCable without Rails
AnyCable can be used without Rails, thus allowing you to use ActionCable-like functionality in your app.
Learn how to use AnyCable with Hanami in the "AnyCable off Rails: connecting Twilio streams with Hanami" blog post.
Requirements
- Ruby >= 2.7
- Redis or NATS (see broadcast adapters)
Installation
Add anycable
gem to your Gemfile
:
gem "anycable", "~> 1.1"
# when using Redis broadcast adapter
gem "redis", ">= 4.0"
# when using NATS broadcast adapter
gem "nats-pure", "~> 2"
(and don't forget to run bundle install
).
Now you need to add channels layer to your application (anycable
gem only provides a CLI and a gRPC server).
You can use an existing solution (e.g., litecable
) or build your own.
Lite Cable
There is a ready-to-go framework – Lite Cable – which can be used for application logic. It also supports AnyCable out-of-the-box.
Resources:
Custom Ruby framework
You can build your own framework to use as logic-handler for AnyCable.
AnyCable initiates a connection object for every request using user-provided factory:
# Specify factory
AnyCable.connection_factory = MyConnectionFactory
# And then AnyCable calls .call method on your factory
connection = factory.call(socket, **options)
Where:
socket
– is an object, representing client's socket (say, socket stub) (see socket.rb)options
may contain:identifiers
: a JSON string returned byconnection.identifiers_json
on connection (see below)subscriptions
: a list of channels identifiers for the connection.
Connection interface:
class Connection
# Called on connection
def handle_open
end
# Called on disconnection
def handle_close
end
# Called on incoming message.
# Client send a JSON-encoded message of the form { "identifier": ..., "command": ..., "data" ... }.
# - identifier – channel identifier (e.g. `{"channel":"chat","id":1}`)
# - command – e.g. "subscribe", "unsubscribe", "message"
# - any additional data
def handle_channel_command(identifier, command, data)
# ...
end
# Returns any string which can be used later in .create function to initiate connection.
def identifiers_json
end
end
Connection#handle_channel_command
should return truthy value on success (i.e., when a subscription is confirmed, or action is called).
NOTE: connection instance is initiated on every request, so it should be stateless (except identifiers_json
).
To send a message to a client, you should call socket#transmit
.
For manipulating with streams use socket#subscribe
, socket#unsubscribe
and socket#unsubscribe_from_all
.
To persist client states between RPC calls you can use socket#cstate
(connection state) and socket#istate
(per-channel state), which are key-value stores (keys and values must both be strings).
See test factory for example.