Skip to content

Commit

Permalink
Further cleanup of the cable guide
Browse files Browse the repository at this point in the history
  • Loading branch information
dhh committed Feb 28, 2016
1 parent 4c43a10 commit 2b5d784
Showing 1 changed file with 51 additions and 100 deletions.
151 changes: 51 additions & 100 deletions guides/source/action_cable_overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ What is Pub/Sub

Pub/Sub, or Publish-Subscribe, refers to a message queue paradigm whereby senders
of information (publishers), send data to an abstract class of recipients (subscribers),
without specifying individual recipients.
without specifying individual recipients. Action Cable uses this approach to communicate
between the server and many clients.

What is Action Cable
--------------------
Expand All @@ -35,19 +36,20 @@ client-server connection instance established per WebSocket connection.
## Server-Side Components

### Connections

Connections form the foundation of the client-server relationship. For every WebSocket
the cable server is accepting, a Connection object will be instantiated. This instance
becomes the parent of all the channel subscriptions that are created from there on.
the cable server is accepting, a Connection object will be instantiated on the server side.
This instance becomes the parent of all the channel subscriptions that are created from there on.
The Connection itself does not deal with any specific application logic beyond authentication
and authorization. The client of a WebSocket connection is called the consumer.
A single consumer may have multiple WebSockets open to your application if they
use multiple browser tabs or devices.
and authorization. The client of a WebSocket connection is called a consumer. An individual
user will create one consumer-connection pair per browser tab, window, or device they have open.

Connections are instantiated via the `ApplicationCable::Connection` class in Ruby.
In this class, you authorize the incoming connection, and proceed to establish it
if the user can be identified.

#### Connection Setup

```ruby
# app/channels/application_cable/connection.rb
module ApplicationCable
Expand All @@ -69,34 +71,38 @@ module ApplicationCable
end
end
```

Here `identified_by` is a connection identifier that can be used to find the
specific connection again or later.
Note that anything marked as an identifier will automatically create a delegate
by the same name on any channel instances created off the connection.
specific connection later. Note that anything marked as an identifier will automatically
create a delegate by the same name on any channel instances created off the connection.

This relies on the fact that you will already have handled authentication of the user,
and that a successful authentication sets a signed cookie with the `user_id`.
This cookie is then automatically sent to the connection instance when a new connection
This example relies on the fact that you will already have handled authentication of the user
somewhere else in your application, and that a successful authentication sets a signed
cookie with the `user_id`.

The cookie is then automatically sent to the connection instance when a new connection
is attempted, and you use that to set the `current_user`. By identifying the connection
by this same current_user, you're also ensuring that you can later retrieve all open
connections by a given user (and potentially disconnect them all if the user is deleted
or deauthorized).

### Channels

A channel encapsulates a logical unit of work, similar to what a controller does in a
regular MVC setup.
By default, Rails creates a parent `ApplicationCable::Channel` class for encapsulating
shared logic between your channels.
regular MVC setup. By default, Rails creates a parent `ApplicationCable::Channel` class
for encapsulating shared logic between your channels.

#### Parent Channel Setup

```ruby
# app/channels/application_cable/channel.rb
module ApplicationCable
class Channel < ActionCable::Channel::Base
end
end
```
Then you would create child channel classes. For example, you could have a

Then you would create your own channel classes. For example, you could have a
**ChatChannel** and an **AppearanceChannel**:

```ruby
Expand All @@ -109,7 +115,7 @@ class AppearanceChannel < ApplicationCable::Channel
end
```

A consumer could then be subscribed to either or both of these channels.
A consumer could then be subscribed to either or both of these channels.

#### Subscriptions

Expand All @@ -121,42 +127,47 @@ an identifier sent by the cable consumer.
```ruby
# app/channels/application_cable/chat_channel.rb
class ChatChannel < ApplicationCable::Channel
# Called when the consumer has successfully become a subscriber of this channel
def subscribed
end
end
```

## Client-Side Components

### Connections

Consumers require an instance of the connection on their side. This can be
established using the following Javascript:
established using the following Javascript, which is generated by default in Rails:

#### Connect Consumer

```coffeescript
# app/assets/javascripts/cable.coffee
#= require action_cable

@App = {}
App.cable = ActionCable.createConsumer("ws://cable.example.com")
App.cable = ActionCable.createConsumer()
```
The `ws://cable.example.com` address must point to your set of Action Cable servers, and it
must share a cookie namespace with the rest of the application (which may live under http://example.com).
This ensures that the signed cookie will be correctly sent.

This will ready a consumer that'll connect against /cable on your server by default.
The connection won't be established until you've also specified at least one subscription
you're interested in having.

#### Subscriber

When a consumer is subscribed to a channel, they act as a subscriber. A
consumer can act as a subscriber to a given channel any number of times.
For example, a consumer could subscribe to multiple chat rooms at the same time.
(remember that a physical user may have multiple consumers, one per tab/device open to your connection).

A consumer becomes a subscriber, by creating a subscription to a given channel:

```coffeescript
# app/assets/javascripts/cable/subscriptions/chat.coffee
# Assumes you've already requested the right to send web notifications
App.cable.subscriptions.create { channel: "ChatChannel", room: "Best Room" }

# app/assets/javascripts/cable/subscriptions/appearance.coffee
# Assumes you've already requested the right to send web notifications
App.cable.subscriptions.create { channel: "AppearanceChannel" }
```

Expand All @@ -166,6 +177,7 @@ received data will be described later on.
## Client-Server Interactions

### Streams

Streams provide the mechanism by which channels route published content
(broadcasts) to its subscribers.

Expand All @@ -190,8 +202,8 @@ class CommentsChannel < ApplicationCable::Channel
end
end
```
You can then broadcast to this channel using:
`CommentsChannel.broadcast_to(@post, @comment)`

You can then broadcast to this channel using: `CommentsChannel.broadcast_to(@post, @comment)`

### Broadcastings

Expand Down Expand Up @@ -261,7 +273,6 @@ will become your params hash in your cable channel. The keyword `channel` is req

```coffeescript
# app/assets/javascripts/cable/subscriptions/chat.coffee
# Client-side, which assumes you've already requested the right to send web notifications
App.cable.subscriptions.create { channel: "ChatChannel", room: "Best Room" },
received: (data) ->
@appendLine(data)
Expand All @@ -281,7 +292,7 @@ App.cable.subscriptions.create { channel: "ChatChannel", room: "Best Room" },

```ruby
# Somewhere in your app this is called, perhaps from a NewCommentJob
ChatChannel.broadcast_to chat_#{room}, sent_by: 'Paul', body: 'This is a cool chat app.'
ChatChannel.broadcast_to "chat_#{room}", sent_by: 'Paul', body: 'This is a cool chat app.'
```


Expand All @@ -305,7 +316,6 @@ end

```coffeescript
# app/assets/javascripts/cable/subscriptions/chat.coffee
# Client-side which assumes you've already requested the right to send web notifications
App.chatChannel = App.cable.subscriptions.create { channel: "ChatChannel", room: "Best Room" },
received: (data) ->
# data => { sent_by: "Paul", body: "This is a cool chat app." }
Expand Down Expand Up @@ -425,7 +435,7 @@ web notifications when you broadcast to the right streams:
# app/channels/web_notifications_channel.rb
class WebNotificationsChannel < ApplicationCable::Channel
def subscribed
stream_from "web_notifications_#{current_user.id}"
stream_for current_user
end
end
```
Expand All @@ -450,6 +460,7 @@ The `WebNotificationsChannel.broadcast_to` call places a message in the current
subscription adapter (Redis by default)'s pubsub queue under a separate
broadcasting name for each user. For a user with an ID of 1, the broadcasting
name would be `web_notifications_1`.

The channel has been instructed to stream everything that arrives at
`web_notifications_1` directly to the client by invoking the `#received(data)`
callback. The data is the hash sent as the second parameter to the server-side
Expand All @@ -463,8 +474,7 @@ repository for a full example of how to setup Action Cable in a Rails app and ad

## Configuration

Action Cable has three required configurations: a subscription adapter,
allowed request origins, and the cable server URL.
Action Cable has two required configurations: a subscription adapter and allowed request origins.

### Subscription Adapter

Expand All @@ -475,10 +485,10 @@ additional information on adapters.

```yaml
production: &production
adapter: redis # Optional as default is redis
adapter: redis
url: redis://10.10.3.153:6381
development: &development
url: redis://localhost:6379
adapter: async
test: *development
```
Expand Down Expand Up @@ -512,41 +522,8 @@ in the development environment.

### Consumer Configuration

Once you have decided how to run your cable server (see below), you must provide
the server url (or path) to your client-side setup. There are two ways you can do this.

The first is to simply pass it in when creating your consumer. For a standalone server,
this would be something like: `App.cable = ActionCable.createConsumer("ws://example.com:28080")`,
and for an in-app server, something like: `App.cable = ActionCable.createConsumer("/cable")`.

The second option is to pass the server url through the `action_cable_meta_tag` in your layout.
This uses a url or path typically set via `config.action_cable.url`
in the environment configuration files, or defaults to "/cable".

This method is especially useful if your WebSocket url might change
between environments. If you host your production server via https,
you will need to use the wss scheme for your ActionCable server, but
development might remain http and use the ws scheme. You might use localhost
in development and your domain in production.

In any case, to vary the WebSocket url between environments, add the following
configuration to each environment:

```ruby
config.action_cable.url = "ws://example.com:28080"
```

Then add the following line to your layout before your JavaScript tag:

```erb
<%= action_cable_meta_tag %>
```

And finally, create your consumer like so:

```coffeescript
App.cable = ActionCable.createConsumer()
```
To configure the URL, add a call to `action_cable_meta_tag` in your HTML layout HEAD.
This uses a url or path typically set via `config.action_cable.url` in the environment configuration files.

### Other Configurations

Expand All @@ -560,38 +537,16 @@ Rails.application.config.action_cable.log_tags = [
]
```

Your WebSocket URL might change between environments. If you host your
production server via https, you will need to use the wss scheme
for your ActionCable server, but development might remain http and
use the ws scheme. You might use localhost in development and your
domain in production. In any case, to vary the WebSocket URL between
environments, add the following configuration to each environment:

```ruby
config.action_cable.url = "ws://example.com:28080"
```

Then add the following line to your layout before your JavaScript tag:

```erb
<%= action_cable_meta_tag %>
```

And finally, create your consumer like so:

```coffeescript
App.cable = Cable.createConsumer()
```

For a full list of all configuration options, see the `ActionCable::Server::Configuration` class.

Also note that your server must provide at least the same number of
database connections as you have workers. The default worker pool is
set to 100, so that means you have to make at least that available.
You can change that in `config/database.yml` through the `pool` attribute.

## Running the cable server
## Running standalone cable servers

<<<<<<< HEAD
### In App

Action Cable can run alongside your Rails application. For example, to
Expand All @@ -615,6 +570,9 @@ but the use of Redis keeps messages synced across connections.

### Standalone
The cable server(s) can be separated from your normal application server.
=======
The cable servers can be separated from your normal application server.
>>>>>>> Further cleanup of the cable guide
It's still a Rack application, but it is its own Rack application.
The recommended basic setup is as follows:

Expand All @@ -636,13 +594,6 @@ The above will start a cable server on port 28080.

### Notes

Beware that currently the cable server will _not_ auto-reload any
changes in the framework. As we've discussed, long-running cable
connections mean long-running objects. We don't yet have a way of
reloading the classes of those objects in a safe manner. So when
you change your channels, or the model your channels use, you must
restart the cable server.

The WebSocket server doesn't have access to the session, but it has
access to the cookies. This can be used when you need to handle
authentication. You can see one way of doing that with Devise in this [article](http://www.rubytutorial.io/actioncable-devise-authentication).
Expand Down

0 comments on commit 2b5d784

Please sign in to comment.