@@ -24,7 +24,8 @@ What is Pub/Sub
24
24
25
25
Pub/Sub, or Publish-Subscribe, refers to a message queue paradigm whereby senders
26
26
of information (publishers), send data to an abstract class of recipients (subscribers),
27
- without specifying individual recipients.
27
+ without specifying individual recipients. Action Cable uses this approach to communicate
28
+ between the server and many clients.
28
29
29
30
What is Action Cable
30
31
--------------------
@@ -35,19 +36,20 @@ client-server connection instance established per WebSocket connection.
35
36
## Server-Side Components
36
37
37
38
### Connections
39
+
38
40
Connections form the foundation of the client-server relationship. For every WebSocket
39
- the cable server is accepting, a Connection object will be instantiated. This instance
40
- becomes the parent of all the channel subscriptions that are created from there on.
41
+ the cable server is accepting, a Connection object will be instantiated on the server side.
42
+ This instance becomes the parent of all the channel subscriptions that are created from there on.
41
43
The Connection itself does not deal with any specific application logic beyond authentication
42
- and authorization. The client of a WebSocket connection is called the consumer.
43
- A single consumer may have multiple WebSockets open to your application if they
44
- use multiple browser tabs or devices.
44
+ and authorization. The client of a WebSocket connection is called a consumer. An individual
45
+ user will create one consumer-connection pair per browser tab, window, or device they have open.
45
46
46
47
Connections are instantiated via the ` ApplicationCable::Connection ` class in Ruby.
47
48
In this class, you authorize the incoming connection, and proceed to establish it
48
49
if the user can be identified.
49
50
50
51
#### Connection Setup
52
+
51
53
``` ruby
52
54
# app/channels/application_cable/connection.rb
53
55
module ApplicationCable
@@ -69,34 +71,38 @@ module ApplicationCable
69
71
end
70
72
end
71
73
```
74
+
72
75
Here ` identified_by ` is a connection identifier that can be used to find the
73
- specific connection again or later.
74
- Note that anything marked as an identifier will automatically create a delegate
75
- by the same name on any channel instances created off the connection.
76
+ specific connection later. Note that anything marked as an identifier will automatically
77
+ create a delegate by the same name on any channel instances created off the connection.
76
78
77
- This relies on the fact that you will already have handled authentication of the user,
78
- and that a successful authentication sets a signed cookie with the ` user_id ` .
79
- This cookie is then automatically sent to the connection instance when a new connection
79
+ This example relies on the fact that you will already have handled authentication of the user
80
+ somewhere else in your application, and that a successful authentication sets a signed
81
+ cookie with the ` user_id ` .
82
+
83
+ The cookie is then automatically sent to the connection instance when a new connection
80
84
is attempted, and you use that to set the ` current_user ` . By identifying the connection
81
85
by this same current_user, you're also ensuring that you can later retrieve all open
82
86
connections by a given user (and potentially disconnect them all if the user is deleted
83
87
or deauthorized).
84
88
85
89
### Channels
90
+
86
91
A channel encapsulates a logical unit of work, similar to what a controller does in a
87
- regular MVC setup.
88
- By default, Rails creates a parent ` ApplicationCable::Channel ` class for encapsulating
89
- shared logic between your channels.
92
+ regular MVC setup. By default, Rails creates a parent ` ApplicationCable::Channel ` class
93
+ for encapsulating shared logic between your channels.
90
94
91
95
#### Parent Channel Setup
96
+
92
97
``` ruby
93
98
# app/channels/application_cable/channel.rb
94
99
module ApplicationCable
95
100
class Channel < ActionCable ::Channel ::Base
96
101
end
97
102
end
98
103
```
99
- Then you would create child channel classes. For example, you could have a
104
+
105
+ Then you would create your own channel classes. For example, you could have a
100
106
** ChatChannel** and an ** AppearanceChannel** :
101
107
102
108
``` ruby
@@ -109,7 +115,7 @@ class AppearanceChannel < ApplicationCable::Channel
109
115
end
110
116
```
111
117
112
- A consumer could then be subscribed to either or both of these channels.
118
+ A consumer could then be subscribed to either or both of these channels.
113
119
114
120
#### Subscriptions
115
121
@@ -121,42 +127,47 @@ an identifier sent by the cable consumer.
121
127
``` ruby
122
128
# app/channels/application_cable/chat_channel.rb
123
129
class ChatChannel < ApplicationCable ::Channel
130
+ # Called when the consumer has successfully become a subscriber of this channel
124
131
def subscribed
125
132
end
126
133
end
127
134
```
128
135
129
136
## Client-Side Components
137
+
130
138
### Connections
139
+
131
140
Consumers require an instance of the connection on their side. This can be
132
- established using the following Javascript:
141
+ established using the following Javascript, which is generated by default in Rails :
133
142
134
143
#### Connect Consumer
144
+
135
145
``` coffeescript
136
146
# app/assets/javascripts/cable.coffee
137
147
# = require action_cable
138
148
139
149
@App = {}
140
- App .cable = ActionCable .createConsumer (" ws://cable.example.com " )
150
+ App .cable = ActionCable .createConsumer ()
141
151
```
142
- The ` ws://cable.example.com ` address must point to your set of Action Cable servers, and it
143
- must share a cookie namespace with the rest of the application (which may live under http://example.com ).
144
- This ensures that the signed cookie will be correctly sent.
152
+
153
+ This will ready a consumer that'll connect against /cable on your server by default.
154
+ The connection won't be established until you've also specified at least one subscription
155
+ you're interested in having.
145
156
146
157
#### Subscriber
158
+
147
159
When a consumer is subscribed to a channel, they act as a subscriber. A
148
160
consumer can act as a subscriber to a given channel any number of times.
149
161
For example, a consumer could subscribe to multiple chat rooms at the same time.
150
162
(remember that a physical user may have multiple consumers, one per tab/device open to your connection).
151
163
152
164
A consumer becomes a subscriber, by creating a subscription to a given channel:
165
+
153
166
``` coffeescript
154
167
# app/assets/javascripts/cable/subscriptions/chat.coffee
155
- # Assumes you've already requested the right to send web notifications
156
168
App .cable .subscriptions .create { channel : " ChatChannel" , room : " Best Room" }
157
169
158
170
# app/assets/javascripts/cable/subscriptions/appearance.coffee
159
- # Assumes you've already requested the right to send web notifications
160
171
App .cable .subscriptions .create { channel : " AppearanceChannel" }
161
172
```
162
173
@@ -166,6 +177,7 @@ received data will be described later on.
166
177
## Client-Server Interactions
167
178
168
179
### Streams
180
+
169
181
Streams provide the mechanism by which channels route published content
170
182
(broadcasts) to its subscribers.
171
183
@@ -190,8 +202,8 @@ class CommentsChannel < ApplicationCable::Channel
190
202
end
191
203
end
192
204
```
193
- You can then broadcast to this channel using:
194
- ` CommentsChannel.broadcast_to(@post, @comment) `
205
+
206
+ You can then broadcast to this channel using: ` CommentsChannel.broadcast_to(@post, @comment) `
195
207
196
208
### Broadcastings
197
209
@@ -261,7 +273,6 @@ will become your params hash in your cable channel. The keyword `channel` is req
261
273
262
274
``` coffeescript
263
275
# app/assets/javascripts/cable/subscriptions/chat.coffee
264
- # Client-side, which assumes you've already requested the right to send web notifications
265
276
App .cable .subscriptions .create { channel : " ChatChannel" , room : " Best Room" },
266
277
received : (data ) ->
267
278
@ appendLine (data)
@@ -281,7 +292,7 @@ App.cable.subscriptions.create { channel: "ChatChannel", room: "Best Room" },
281
292
282
293
``` ruby
283
294
# Somewhere in your app this is called, perhaps from a NewCommentJob
284
- ChatChannel .broadcast_to chat_# {room}, sent_by: 'Paul', body: 'This is a cool chat app.'
295
+ ChatChannel .broadcast_to " chat_#{ room } " , sent_by: ' Paul' , body: ' This is a cool chat app.'
285
296
```
286
297
287
298
305
316
306
317
``` coffeescript
307
318
# app/assets/javascripts/cable/subscriptions/chat.coffee
308
- # Client-side which assumes you've already requested the right to send web notifications
309
319
App .chatChannel = App .cable .subscriptions .create { channel : " ChatChannel" , room : " Best Room" },
310
320
received : (data ) ->
311
321
# data => { sent_by: "Paul", body: "This is a cool chat app." }
@@ -425,7 +435,7 @@ web notifications when you broadcast to the right streams:
425
435
# app/channels/web_notifications_channel.rb
426
436
class WebNotificationsChannel < ApplicationCable ::Channel
427
437
def subscribed
428
- stream_from " web_notifications_ #{ current_user.id } "
438
+ stream_for current_user
429
439
end
430
440
end
431
441
```
@@ -450,6 +460,7 @@ The `WebNotificationsChannel.broadcast_to` call places a message in the current
450
460
subscription adapter (Redis by default)'s pubsub queue under a separate
451
461
broadcasting name for each user. For a user with an ID of 1, the broadcasting
452
462
name would be ` web_notifications_1 ` .
463
+
453
464
The channel has been instructed to stream everything that arrives at
454
465
` web_notifications_1 ` directly to the client by invoking the ` #received(data) `
455
466
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
463
474
464
475
## Configuration
465
476
466
- Action Cable has three required configurations: a subscription adapter,
467
- allowed request origins, and the cable server URL.
477
+ Action Cable has two required configurations: a subscription adapter and allowed request origins.
468
478
469
479
### Subscription Adapter
470
480
@@ -475,10 +485,10 @@ additional information on adapters.
475
485
476
486
``` yaml
477
487
production : &production
478
- adapter : redis # Optional as default is redis
488
+ adapter : redis
479
489
url : redis://10.10.3.153:6381
480
490
development : &development
481
- url : redis://localhost:6379
491
+ adapter : async
482
492
test : *development
483
493
` ` `
484
494
@@ -512,41 +522,8 @@ in the development environment.
512
522
513
523
### Consumer Configuration
514
524
515
- Once you have decided how to run your cable server (see below), you must provide
516
- the server url (or path) to your client-side setup. There are two ways you can do this.
517
-
518
- The first is to simply pass it in when creating your consumer. For a standalone server,
519
- this would be something like: ` App.cable = ActionCable.createConsumer("ws://example.com:28080") ` ,
520
- and for an in-app server, something like: ` App.cable = ActionCable.createConsumer("/cable") ` .
521
-
522
- The second option is to pass the server url through the ` action_cable_meta_tag ` in your layout.
523
- This uses a url or path typically set via ` config.action_cable.url `
524
- in the environment configuration files, or defaults to "/cable".
525
-
526
- This method is especially useful if your WebSocket url might change
527
- between environments. If you host your production server via https,
528
- you will need to use the wss scheme for your ActionCable server, but
529
- development might remain http and use the ws scheme. You might use localhost
530
- in development and your domain in production.
531
-
532
- In any case, to vary the WebSocket url between environments, add the following
533
- configuration to each environment:
534
-
535
- ``` ruby
536
- config.action_cable.url = " ws://example.com:28080"
537
- ```
538
-
539
- Then add the following line to your layout before your JavaScript tag:
540
-
541
- ``` erb
542
- <%= action_cable_meta_tag %>
543
- ```
544
-
545
- And finally, create your consumer like so:
546
-
547
- ``` coffeescript
548
- App .cable = ActionCable .createConsumer ()
549
- ```
525
+ To configure the URL, add a call to ` action_cable_meta_tag ` in your HTML layout HEAD.
526
+ This uses a url or path typically set via ` config.action_cable.url ` in the environment configuration files.
550
527
551
528
### Other Configurations
552
529
@@ -560,38 +537,16 @@ Rails.application.config.action_cable.log_tags = [
560
537
]
561
538
```
562
539
563
- Your WebSocket URL might change between environments. If you host your
564
- production server via https, you will need to use the wss scheme
565
- for your ActionCable server, but development might remain http and
566
- use the ws scheme. You might use localhost in development and your
567
- domain in production. In any case, to vary the WebSocket URL between
568
- environments, add the following configuration to each environment:
569
-
570
- ``` ruby
571
- config.action_cable.url = " ws://example.com:28080"
572
- ```
573
-
574
- Then add the following line to your layout before your JavaScript tag:
575
-
576
- ``` erb
577
- <%= action_cable_meta_tag %>
578
- ```
579
-
580
- And finally, create your consumer like so:
581
-
582
- ``` coffeescript
583
- App .cable = Cable .createConsumer ()
584
- ```
585
-
586
540
For a full list of all configuration options, see the ` ActionCable::Server::Configuration ` class.
587
541
588
542
Also note that your server must provide at least the same number of
589
543
database connections as you have workers. The default worker pool is
590
544
set to 100, so that means you have to make at least that available.
591
545
You can change that in ` config/database.yml ` through the ` pool ` attribute.
592
546
593
- ## Running the cable server
547
+ ## Running standalone cable servers
594
548
549
+ <<<<<<< HEAD
595
550
### In App
596
551
597
552
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.
615
570
616
571
### Standalone
617
572
The cable server(s) can be separated from your normal application server.
573
+ =======
574
+ The cable servers can be separated from your normal application server.
575
+ >>>>>>> Further cleanup of the cable guide
618
576
It's still a Rack application, but it is its own Rack application.
619
577
The recommended basic setup is as follows:
620
578
@@ -636,13 +594,6 @@ The above will start a cable server on port 28080.
636
594
637
595
### Notes
638
596
639
- Beware that currently the cable server will _ not_ auto-reload any
640
- changes in the framework. As we've discussed, long-running cable
641
- connections mean long-running objects. We don't yet have a way of
642
- reloading the classes of those objects in a safe manner. So when
643
- you change your channels, or the model your channels use, you must
644
- restart the cable server.
645
-
646
597
The WebSocket server doesn't have access to the session, but it has
647
598
access to the cookies. This can be used when you need to handle
648
599
authentication. You can see one way of doing that with Devise in this [ article] ( http://www.rubytutorial.io/actioncable-devise-authentication ) .
0 commit comments