Getting Started with AnyCable on Rails

AnyCable initially was designed for Rails applications only.

See also the demo of migrating from Action Cable to AnyCable.

Requirements

Installation

Add anycable-rails gem to your Gemfile:

gem "anycable-rails", "~> 1.1"

# when using Redis broadcast adapter
gem "redis", ">= 4.0"

(and don't forget to run bundle install).

Then, run the interactive configuration wizard via Rails generators:

bundle exec rails g anycable:setup

Configuration

Next, update your Action Cable configuration:

# config/cable.yml
production:
  # Set adapter to any_cable to activate AnyCable
  adapter: any_cable

Install WebSocket server and specify its URL in the configuration:

# For development it's likely the localhost

# config/environments/development.rb
config.action_cable.url = "ws://localhost:8080/cable"

# For production it's likely to have a sub-domain and secure connection

# config/environments/production.rb
config.action_cable.url = "wss://ws.example.com/cable"

Now you can start AnyCable RPC server for your application:

$ bundle exec anycable
#> Starting AnyCable gRPC server (pid: 48111)
#> Serving Rails application from ./config/environment.rb

# don't forget to provide Rails env in production
$ RAILS_ENV=production bundle exec anycable

NOTE: you don't need to specify -r option (see CLI docs), your application would be loaded from config/environment.rb.

And, finally, run AnyCable WebSocket server, e.g. anycable-go:

$ anycable-go --host=localhost --port=8080

INFO 2019-08-07T16:37:46.387Z context=main Starting AnyCable v0.6.2-13-gd421927 (with mruby 1.2.0 (2015-11-17)) (pid: 1362)
INFO 2019-08-07T16:37:46.387Z context=main Handle WebSocket connections at /cable
INFO 2019-08-07T16:37:46.388Z context=http Starting HTTP server at localhost:8080

You can store AnyCable-specific configuration in YAML file (similar to Action Cable one):

# config/anycable.yml
development:
  redis_url: redis://localhost:6379/1
production:
  redis_url: redis://my.redis.io:6379/1

Or you can use the environment variables (or anything else supported by anyway_config).

Server installation

You can install AnyCable-Go server using one of the multiple ways.

For your convenience, we have a generator task which could be used to download a binary from GitHub released for your platform:

$ bundle exec rails anycable:download

run  curl -L https://github.com/anycable/anycable-go/releases/download/...

You can specify the target bin path (--bin-path) or AnyCable-Go version (--version).

NOTE: This task uses cURL under the hood, so it must be available.

Access logs

Rails integration extends the base configuration by adding a special parameter–access_logs_disabled.

This parameter turn on/off access logging (Started <request data> / Finished <request data>) (disabled by default).

You can configure it via env var (ANYCABLE_ACCESS_LOGS_DISABLED=0 to enable) or config file:

# config/anycable.yml
production:
  access_logs_disabled: false

Forgery protection

AnyCable respects Action Cable configuration regarding forgery protection if and only if ORIGIN header is proxied by WebSocket server:

# with anycable-go
$ anycable-go --headers=cookie,origin --port=8080

Logging

AnyCable uses Rails.logger as AnyCable.logger by default, thus setting log level for AnyCable (e.g. ANYCABLE_LOG_LEVEL=debug) won't work, you should configure Rails logger instead, e.g.:

# in Rails configuration
config.logger = Logger.new($stdout)
config.log_level = :debug

# or
Rails.logger.level = :debug if AnyCable.config.debug?

Read more about logging.

Exceptions

AnyCable automatically integrates with Rails 7+ error reporting interface (Rails.error.report(...)), so you don't need to configure anything yourself.

For earlier Rails versions, see docs.

Development and test

AnyCable is compatible with the original Action Cable implementation; thus you can continue using Action Cable for development and tests.

Compatibility could be enforced by runtime checks or static checks (via RuboCop).

Use process manager (e.g. Hivemind or Overmind) to run AnyCable processes in development with the following Procfile:

web: bundle exec rails s
rpc: bundle exec anycable
ws:  anycable-go

Embedded mode

It is also possible to run RPC server within another Ruby process (Rails server or tests runner). We recommend using this option in development and test environments.

When using Rails 6.1, you can automatically start an RPC server every time you run rails s by specifying the following configuration parameter:

# config/anycable.yml
development:
  embedded: true

NOTE: Make sure you have Rails.application.load_server in your config.ru.

Testing with AnyCable

If you'd like to run AnyCable RPC server in tests (for example, in system tests), we recommend to start it manually only when necessary (i.e., when dependent tests are executed) and use the embedded mode. That's how we do it with RSpec:

# spec/support/anycable_setup.rb
RSpec.configure do |config|
  cli = nil

  config.before(:suite) do
    examples = RSpec.world.filtered_examples.values.flatten
    has_no_system_tests = examples.none? { |example| example.metadata[:type] == :system }

    # Only start RPC server if system tests are included into the run
    next if has_no_system_tests

    require "anycable/cli"

    $stdout.puts "\n⚡️  Starting AnyCable RPC server...\n"

    cli = AnyCable::CLI.new(embedded: true)
    cli.run
  end

  config.after(:suite) do
    cli&.shutdown
  end
end

To use :test Action Cable adapter along with AnyCable, you can extend it in the configuration:

# config/environments/test.rb
Rails.application.configure do
  config.after_initialize do
    # Don't forget to configure URL
    config.action_cable.url = ActionCable.server.config.url = ENV.fetch("CABLE_URL", "ws://localhost:8080/cable")

    # Make test adapter AnyCable-compatible
    AnyCable::Rails.extend_adapter!(ActionCable.server.pubsub)
  end

  # ...
end

Gradually migrating from Action Cable

In case switching from Action Cable to AnyCable requires updating the WebSocket url (e.g., when you have no control over a load balancer or ingress, so you can't just switch /cable traffic to a different service), you might want to pay additional attention to the migration.

First, you need to continue using your current pubsub adapter for Action Cable (say, redis). Clients could continue using the Rails endpoint (ws://<web>/cable) as well as connect via the AnyCable one (ws://<anycable-go>/cable).

To publish updates to both cables, we need to use the dual broadcast strategy. For that, you need to extend your pubsub adapter to send data to AnyCable:

# config/initializers/anycable.rb
AnyCable::Rails.extend_adapter!(ActionCable.server.pubsub)

NOTE: If you use graphql-anycable, things become more complicated. You will need to schemas with different subscriptions providers and a similar dual adapter to support both cables.