1
1
# frozen_string_literal: true
2
2
3
+ # :markup: markdown
4
+
3
5
require "set"
4
6
require "active_support/rescuable"
5
7
require "active_support/parameter_filter"
6
8
7
9
module ActionCable
8
10
module Channel
9
- # = Action Cable \ Channel \ Base
11
+ # # Action Cable Channel Base
10
12
#
11
- # The channel provides the basic structure of grouping behavior into logical units when communicating over the WebSocket connection.
12
- # You can think of a channel like a form of controller, but one that's capable of pushing content to the subscriber in addition to simply
13
- # responding to the subscriber's direct requests.
13
+ # The channel provides the basic structure of grouping behavior into logical
14
+ # units when communicating over the WebSocket connection. You can think of a
15
+ # channel like a form of controller, but one that's capable of pushing content
16
+ # to the subscriber in addition to simply responding to the subscriber's direct
17
+ # requests.
14
18
#
15
- # Channel instances are long-lived. A channel object will be instantiated when the cable consumer becomes a subscriber, and then
16
- # lives until the consumer disconnects. This may be seconds, minutes, hours, or even days. That means you have to take special care
17
- # not to do anything silly in a channel that would balloon its memory footprint or whatever. The references are forever, so they won't be released
18
- # as is normally the case with a controller instance that gets thrown away after every request.
19
+ # Channel instances are long-lived. A channel object will be instantiated when
20
+ # the cable consumer becomes a subscriber, and then lives until the consumer
21
+ # disconnects. This may be seconds, minutes, hours, or even days. That means you
22
+ # have to take special care not to do anything silly in a channel that would
23
+ # balloon its memory footprint or whatever. The references are forever, so they
24
+ # won't be released as is normally the case with a controller instance that gets
25
+ # thrown away after every request.
19
26
#
20
- # Long-lived channels (and connections) also mean you're responsible for ensuring that the data is fresh. If you hold a reference to a user
21
- # record, but the name is changed while that reference is held, you may be sending stale data if you don't take precautions to avoid it.
27
+ # Long-lived channels (and connections) also mean you're responsible for
28
+ # ensuring that the data is fresh. If you hold a reference to a user record, but
29
+ # the name is changed while that reference is held, you may be sending stale
30
+ # data if you don't take precautions to avoid it.
22
31
#
23
- # The upside of long-lived channel instances is that you can use instance variables to keep reference to objects that future subscriber requests
24
- # can interact with. Here's a quick example:
32
+ # The upside of long-lived channel instances is that you can use instance
33
+ # variables to keep reference to objects that future subscriber requests can
34
+ # interact with. Here's a quick example:
25
35
#
26
- # class ChatChannel < ApplicationCable::Channel
27
- # def subscribed
28
- # @room = Chat::Room[params[:room_number]]
29
- # end
36
+ # class ChatChannel < ApplicationCable::Channel
37
+ # def subscribed
38
+ # @room = Chat::Room[params[:room_number]]
39
+ # end
30
40
#
31
- # def speak(data)
32
- # @room.speak data, user: current_user
41
+ # def speak(data)
42
+ # @room.speak data, user: current_user
43
+ # end
33
44
# end
34
- # end
35
45
#
36
- # The #speak action simply uses the Chat::Room object that was created when the channel was first subscribed to by the consumer when that
37
- # subscriber wants to say something in the room.
46
+ # The #speak action simply uses the Chat::Room object that was created when the
47
+ # channel was first subscribed to by the consumer when that subscriber wants to
48
+ # say something in the room.
38
49
#
39
- # == Action processing
50
+ # ## Action processing
40
51
#
41
52
# Unlike subclasses of ActionController::Base, channels do not follow a RESTful
42
53
# constraint form for their actions. Instead, Action Cable operates through a
43
- # remote-procedure call model. You can declare any public method on the
44
- # channel (optionally taking a <tt> data</tt> argument), and this method is
45
- # automatically exposed as callable to the client.
54
+ # remote-procedure call model. You can declare any public method on the channel
55
+ # (optionally taking a ` data` argument), and this method is automatically
56
+ # exposed as callable to the client.
46
57
#
47
58
# Example:
48
59
#
49
- # class AppearanceChannel < ApplicationCable::Channel
50
- # def subscribed
51
- # @connection_token = generate_connection_token
52
- # end
53
- #
54
- # def unsubscribed
55
- # current_user.disappear @connection_token
56
- # end
60
+ # class AppearanceChannel < ApplicationCable::Channel
61
+ # def subscribed
62
+ # @connection_token = generate_connection_token
63
+ # end
57
64
#
58
- # def appear(data)
59
- # current_user.appear @connection_token, on: data['appearing_on']
60
- # end
65
+ # def unsubscribed
66
+ # current_user.disappear @connection_token
67
+ # end
61
68
#
62
- # def away
63
- # current_user.away @connection_token
64
- # end
69
+ # def appear(data)
70
+ # current_user.appear @connection_token, on: data['appearing_on']
71
+ # end
65
72
#
66
- # private
67
- # def generate_connection_token
68
- # SecureRandom.hex(36)
73
+ # def away
74
+ # current_user.away @connection_token
69
75
# end
70
- # end
71
76
#
72
- # In this example, the subscribed and unsubscribed methods are not callable methods, as they
73
- # were already declared in ActionCable::Channel::Base, but <tt>#appear</tt>
74
- # and <tt>#away</tt> are. <tt>#generate_connection_token</tt> is also not
75
- # callable, since it's a private method. You'll see that appear accepts a data
76
- # parameter, which it then uses as part of its model call. <tt>#away</tt>
77
- # does not, since it's simply a trigger action.
77
+ # private
78
+ # def generate_connection_token
79
+ # SecureRandom.hex(36)
80
+ # end
81
+ # end
82
+ #
83
+ # In this example, the subscribed and unsubscribed methods are not callable
84
+ # methods, as they were already declared in ActionCable::Channel::Base, but
85
+ # `#appear` and `#away` are. `#generate_connection_token` is also not callable,
86
+ # since it's a private method. You'll see that appear accepts a data parameter,
87
+ # which it then uses as part of its model call. `#away` does not, since it's
88
+ # simply a trigger action.
78
89
#
79
- # Also note that in this example, <tt> current_user</tt> is available because
80
- # it was marked as an identifying attribute on the connection. All such
81
- # identifiers will automatically create a delegation method of the same name
82
- # on the channel instance.
90
+ # Also note that in this example, ` current_user` is available because it was
91
+ # marked as an identifying attribute on the connection. All such identifiers
92
+ # will automatically create a delegation method of the same name on the channel
93
+ # instance.
83
94
#
84
- # == Rejecting subscription requests
95
+ # ## Rejecting subscription requests
85
96
#
86
97
# A channel can reject a subscription request in the #subscribed callback by
87
98
# invoking the #reject method:
88
99
#
89
- # class ChatChannel < ApplicationCable::Channel
90
- # def subscribed
91
- # @room = Chat::Room[params[:room_number]]
92
- # reject unless current_user.can_access?(@room)
100
+ # class ChatChannel < ApplicationCable::Channel
101
+ # def subscribed
102
+ # @room = Chat::Room[params[:room_number]]
103
+ # reject unless current_user.can_access?(@room)
104
+ # end
93
105
# end
94
- # end
95
106
#
96
- # In this example, the subscription will be rejected if the
97
- # <tt>current_user</tt> does not have access to the chat room. On the
98
- # client-side, the <tt>Channel#rejected</tt> callback will get invoked when
99
- # the server rejects the subscription request.
107
+ # In this example, the subscription will be rejected if the `current_user` does
108
+ # not have access to the chat room. On the client-side, the `Channel#rejected`
109
+ # callback will get invoked when the server rejects the subscription request.
100
110
class Base
101
111
include Callbacks
102
112
include PeriodicTimers
@@ -109,14 +119,13 @@ class Base
109
119
delegate :logger , to : :connection
110
120
111
121
class << self
112
- # A list of method names that should be considered actions. This
113
- # includes all public instance methods on a channel, less
114
- # any internal methods (defined on Base), adding back in
115
- # any methods that are internal, but still exist on the class
116
- # itself.
122
+ # A list of method names that should be considered actions. This includes all
123
+ # public instance methods on a channel, less any internal methods (defined on
124
+ # Base), adding back in any methods that are internal, but still exist on the
125
+ # class itself.
117
126
#
118
- # ==== Returns
119
- # * <tt> Set</tt> - A set of all methods that should be considered actions.
127
+ # #### Returns
128
+ # * ` Set` - A set of all methods that should be considered actions.
120
129
def action_methods
121
130
@action_methods ||= begin
122
131
# All public instance methods of this class, including ancestors
@@ -130,9 +139,9 @@ def action_methods
130
139
end
131
140
132
141
private
133
- # action_methods are cached and there is sometimes need to refresh
134
- # them. ::clear_action_methods! allows you to do that, so next time
135
- # you run action_methods, they will be recalculated.
142
+ # action_methods are cached and there is sometimes need to refresh them.
143
+ # ::clear_action_methods! allows you to do that, so next time you run
144
+ # action_methods, they will be recalculated.
136
145
def clear_action_methods! # :doc:
137
146
@action_methods = nil
138
147
end
@@ -161,9 +170,9 @@ def initialize(connection, identifier, params = {})
161
170
delegate_connection_identifiers
162
171
end
163
172
164
- # Extract the action name from the passed data and process it via the channel. The process will ensure
165
- # that the action requested is a public method on the channel declared by the user (so not one of the callbacks
166
- # like #subscribed).
173
+ # Extract the action name from the passed data and process it via the channel.
174
+ # The process will ensure that the action requested is a public method on the
175
+ # channel declared by the user (so not one of the callbacks like #subscribed).
167
176
def perform_action ( data )
168
177
action = extract_action ( data )
169
178
@@ -177,8 +186,8 @@ def perform_action(data)
177
186
end
178
187
end
179
188
180
- # This method is called after subscription has been added to the connection
181
- # and confirms or rejects the subscription.
189
+ # This method is called after subscription has been added to the connection and
190
+ # confirms or rejects the subscription.
182
191
def subscribe_to_channel
183
192
run_callbacks :subscribe do
184
193
subscribed
@@ -188,29 +197,32 @@ def subscribe_to_channel
188
197
ensure_confirmation_sent
189
198
end
190
199
191
- # Called by the cable connection when it's cut, so the channel has a chance to cleanup with callbacks.
192
- # This method is not intended to be called directly by the user. Instead, override the #unsubscribed callback.
200
+ # Called by the cable connection when it's cut, so the channel has a chance to
201
+ # cleanup with callbacks. This method is not intended to be called directly by
202
+ # the user. Instead, override the #unsubscribed callback.
193
203
def unsubscribe_from_channel # :nodoc:
194
204
run_callbacks :unsubscribe do
195
205
unsubscribed
196
206
end
197
207
end
198
208
199
209
private
200
- # Called once a consumer has become a subscriber of the channel. Usually the place to set up any streams
201
- # you want this channel to be sending to the subscriber.
210
+ # Called once a consumer has become a subscriber of the channel. Usually the
211
+ # place to set up any streams you want this channel to be sending to the
212
+ # subscriber.
202
213
def subscribed # :doc:
203
214
# Override in subclasses
204
215
end
205
216
206
- # Called once a consumer has cut its cable connection. Can be used for cleaning up connections or marking
207
- # users as offline or the like.
217
+ # Called once a consumer has cut its cable connection. Can be used for cleaning
218
+ # up connections or marking users as offline or the like.
208
219
def unsubscribed # :doc:
209
220
# Override in subclasses
210
221
end
211
222
212
- # Transmit a hash of data to the subscriber. The hash will automatically be wrapped in a JSON envelope with
213
- # the proper channel identifier marked as the recipient.
223
+ # Transmit a hash of data to the subscriber. The hash will automatically be
224
+ # wrapped in a JSON envelope with the proper channel identifier marked as the
225
+ # recipient.
214
226
def transmit ( data , via : nil ) # :doc:
215
227
logger . debug do
216
228
status = "#{ self . class . name } transmitting #{ data . inspect . truncate ( 300 ) } "
0 commit comments