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

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 by connection.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.