diff options
-rw-r--r-- | guides/source/action_cable_overview.md | 151 |
1 files changed, 51 insertions, 100 deletions
diff --git a/guides/source/action_cable_overview.md b/guides/source/action_cable_overview.md index 7080720b04..047c163ee2 100644 --- a/guides/source/action_cable_overview.md +++ b/guides/source/action_cable_overview.md @@ -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 -------------------- @@ -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 @@ -69,26 +71,29 @@ 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 @@ -96,7 +101,8 @@ module ApplicationCable 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 @@ -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 @@ -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" } ``` @@ -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. @@ -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 @@ -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) @@ -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.' ``` @@ -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." } @@ -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 ``` @@ -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 @@ -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 @@ -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 ``` @@ -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 @@ -560,29 +537,6 @@ 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 @@ -590,8 +544,9 @@ 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 @@ -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: @@ -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). |