actioncable 7.1.5 → 7.2.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +15 -161
  3. data/app/assets/javascripts/action_cable.js +3 -3
  4. data/app/assets/javascripts/actioncable.esm.js +3 -3
  5. data/app/assets/javascripts/actioncable.js +3 -3
  6. data/lib/action_cable/channel/base.rb +98 -86
  7. data/lib/action_cable/channel/broadcasting.rb +25 -18
  8. data/lib/action_cable/channel/callbacks.rb +27 -25
  9. data/lib/action_cable/channel/naming.rb +9 -8
  10. data/lib/action_cable/channel/periodic_timers.rb +7 -7
  11. data/lib/action_cable/channel/streams.rb +77 -64
  12. data/lib/action_cable/channel/test_case.rb +112 -86
  13. data/lib/action_cable/connection/authorization.rb +4 -1
  14. data/lib/action_cable/connection/base.rb +53 -38
  15. data/lib/action_cable/connection/callbacks.rb +20 -18
  16. data/lib/action_cable/connection/client_socket.rb +3 -1
  17. data/lib/action_cable/connection/identification.rb +9 -5
  18. data/lib/action_cable/connection/internal_channel.rb +5 -2
  19. data/lib/action_cable/connection/message_buffer.rb +4 -1
  20. data/lib/action_cable/connection/stream.rb +2 -0
  21. data/lib/action_cable/connection/stream_event_loop.rb +4 -3
  22. data/lib/action_cable/connection/subscriptions.rb +6 -3
  23. data/lib/action_cable/connection/tagged_logger_proxy.rb +7 -4
  24. data/lib/action_cable/connection/test_case.rb +66 -56
  25. data/lib/action_cable/connection/web_socket.rb +10 -8
  26. data/lib/action_cable/deprecator.rb +2 -0
  27. data/lib/action_cable/engine.rb +5 -3
  28. data/lib/action_cable/gem_version.rb +6 -4
  29. data/lib/action_cable/helpers/action_cable_helper.rb +21 -19
  30. data/lib/action_cable/remote_connections.rb +19 -16
  31. data/lib/action_cable/server/base.rb +27 -15
  32. data/lib/action_cable/server/broadcasting.rb +23 -17
  33. data/lib/action_cable/server/configuration.rb +17 -14
  34. data/lib/action_cable/server/connections.rb +11 -5
  35. data/lib/action_cable/server/worker/active_record_connection_management.rb +2 -0
  36. data/lib/action_cable/server/worker.rb +4 -2
  37. data/lib/action_cable/subscription_adapter/async.rb +2 -0
  38. data/lib/action_cable/subscription_adapter/base.rb +2 -0
  39. data/lib/action_cable/subscription_adapter/channel_prefix.rb +2 -0
  40. data/lib/action_cable/subscription_adapter/inline.rb +2 -0
  41. data/lib/action_cable/subscription_adapter/postgresql.rb +4 -2
  42. data/lib/action_cable/subscription_adapter/redis.rb +5 -2
  43. data/lib/action_cable/subscription_adapter/subscriber_map.rb +2 -0
  44. data/lib/action_cable/subscription_adapter/test.rb +8 -5
  45. data/lib/action_cable/test_case.rb +2 -0
  46. data/lib/action_cable/test_helper.rb +51 -48
  47. data/lib/action_cable/version.rb +3 -1
  48. data/lib/action_cable.rb +1 -0
  49. data/lib/rails/generators/channel/channel_generator.rb +4 -2
  50. data/lib/rails/generators/channel/templates/application_cable/channel.rb +2 -0
  51. data/lib/rails/generators/channel/templates/application_cable/connection.rb +2 -0
  52. data/lib/rails/generators/test_unit/channel_generator.rb +2 -0
  53. metadata +14 -14
@@ -1,36 +1,39 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # :markup: markdown
4
+
3
5
  require "active_support/callbacks"
4
6
 
5
7
  module ActionCable
6
8
  module Channel
7
- # = Action Cable \Channel \Callbacks
9
+ # # Action Cable Channel Callbacks
10
+ #
11
+ # Action Cable Channel provides callback hooks that are invoked during the life
12
+ # cycle of a channel:
8
13
  #
9
- # Action Cable Channel provides callback hooks that are invoked during the
10
- # life cycle of a channel:
14
+ # * [before_subscribe](rdoc-ref:ClassMethods#before_subscribe)
15
+ # * [after_subscribe](rdoc-ref:ClassMethods#after_subscribe) (aliased as
16
+ # [on_subscribe](rdoc-ref:ClassMethods#on_subscribe))
17
+ # * [before_unsubscribe](rdoc-ref:ClassMethods#before_unsubscribe)
18
+ # * [after_unsubscribe](rdoc-ref:ClassMethods#after_unsubscribe) (aliased as
19
+ # [on_unsubscribe](rdoc-ref:ClassMethods#on_unsubscribe))
11
20
  #
12
- # * {before_subscribe}[rdoc-ref:ClassMethods#before_subscribe]
13
- # * {after_subscribe}[rdoc-ref:ClassMethods#after_subscribe] (aliased as
14
- # {on_subscribe}[rdoc-ref:ClassMethods#on_subscribe])
15
- # * {before_unsubscribe}[rdoc-ref:ClassMethods#before_unsubscribe]
16
- # * {after_unsubscribe}[rdoc-ref:ClassMethods#after_unsubscribe] (aliased as
17
- # {on_unsubscribe}[rdoc-ref:ClassMethods#on_unsubscribe])
18
21
  #
19
- # ==== Example
22
+ # #### Example
20
23
  #
21
- # class ChatChannel < ApplicationCable::Channel
22
- # after_subscribe :send_welcome_message, unless: :subscription_rejected?
23
- # after_subscribe :track_subscription
24
+ # class ChatChannel < ApplicationCable::Channel
25
+ # after_subscribe :send_welcome_message, unless: :subscription_rejected?
26
+ # after_subscribe :track_subscription
24
27
  #
25
- # private
26
- # def send_welcome_message
27
- # broadcast_to(...)
28
- # end
28
+ # private
29
+ # def send_welcome_message
30
+ # broadcast_to(...)
31
+ # end
29
32
  #
30
- # def track_subscription
31
- # # ...
32
- # end
33
- # end
33
+ # def track_subscription
34
+ # # ...
35
+ # end
36
+ # end
34
37
  #
35
38
  module Callbacks
36
39
  extend ActiveSupport::Concern
@@ -46,14 +49,13 @@ module ActionCable
46
49
  set_callback(:subscribe, :before, *methods, &block)
47
50
  end
48
51
 
49
- # This callback will be triggered after the Base#subscribed method is
50
- # called, even if the subscription was rejected with the Base#reject
51
- # method.
52
+ # This callback will be triggered after the Base#subscribed method is called,
53
+ # even if the subscription was rejected with the Base#reject method.
52
54
  #
53
55
  # To trigger the callback only on successful subscriptions, use the
54
56
  # Base#subscription_rejected? method:
55
57
  #
56
- # after_subscribe :my_method, unless: :subscription_rejected?
58
+ # after_subscribe :my_method, unless: :subscription_rejected?
57
59
  #
58
60
  def after_subscribe(*methods, &block)
59
61
  set_callback(:subscribe, :after, *methods, &block)
@@ -1,26 +1,27 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # :markup: markdown
4
+
3
5
  module ActionCable
4
6
  module Channel
5
7
  module Naming
6
8
  extend ActiveSupport::Concern
7
9
 
8
10
  module ClassMethods
9
- # Returns the name of the channel, underscored, without the <tt>Channel</tt> ending.
10
- # If the channel is in a namespace, then the namespaces are represented by single
11
+ # Returns the name of the channel, underscored, without the `Channel` ending. If
12
+ # the channel is in a namespace, then the namespaces are represented by single
11
13
  # colon separators in the channel name.
12
14
  #
13
- # ChatChannel.channel_name # => 'chat'
14
- # Chats::AppearancesChannel.channel_name # => 'chats:appearances'
15
- # FooChats::BarAppearancesChannel.channel_name # => 'foo_chats:bar_appearances'
15
+ # ChatChannel.channel_name # => 'chat'
16
+ # Chats::AppearancesChannel.channel_name # => 'chats:appearances'
17
+ # FooChats::BarAppearancesChannel.channel_name # => 'foo_chats:bar_appearances'
16
18
  def channel_name
17
19
  @channel_name ||= name.delete_suffix("Channel").gsub("::", ":").underscore
18
20
  end
19
21
  end
20
22
 
21
- included do
22
- # Delegates to the class's ::channel_name.
23
- delegate :channel_name, to: :class
23
+ def channel_name
24
+ self.class.channel_name
24
25
  end
25
26
  end
26
27
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # :markup: markdown
4
+
3
5
  module ActionCable
4
6
  module Channel
5
7
  module PeriodicTimers
@@ -13,14 +15,12 @@ module ActionCable
13
15
  end
14
16
 
15
17
  module ClassMethods
16
- # Periodically performs a task on the channel, like updating an online
17
- # user counter, polling a backend for new status messages, sending
18
- # regular "heartbeat" messages, or doing some internal work and giving
19
- # progress updates.
18
+ # Periodically performs a task on the channel, like updating an online user
19
+ # counter, polling a backend for new status messages, sending regular
20
+ # "heartbeat" messages, or doing some internal work and giving progress updates.
20
21
  #
21
- # Pass a method name or lambda argument or provide a block to call.
22
- # Specify the calling period in seconds using the <tt>every:</tt>
23
- # keyword argument.
22
+ # Pass a method name or lambda argument or provide a block to call. Specify the
23
+ # calling period in seconds using the `every:` keyword argument.
24
24
  #
25
25
  # periodically :transmit_progress, every: 5.seconds
26
26
  #
@@ -1,67 +1,77 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # :markup: markdown
4
+
3
5
  module ActionCable
4
6
  module Channel
5
- # = Action Cable \Channel \Streams
6
- #
7
- # Streams allow channels to route broadcastings to the subscriber. A broadcasting is, as discussed elsewhere, a pubsub queue where any data
8
- # placed into it is automatically sent to the clients that are connected at that time. It's purely an online queue, though. If you're not
9
- # streaming a broadcasting at the very moment it sends out an update, you will not get that update, even if you connect after it has been sent.
10
- #
11
- # Most commonly, the streamed broadcast is sent straight to the subscriber on the client-side. The channel just acts as a connector between
12
- # the two parties (the broadcaster and the channel subscriber). Here's an example of a channel that allows subscribers to get all new
13
- # comments on a given page:
14
- #
15
- # class CommentsChannel < ApplicationCable::Channel
16
- # def follow(data)
17
- # stream_from "comments_for_#{data['recording_id']}"
18
- # end
7
+ # # Action Cable Channel Streams
8
+ #
9
+ # Streams allow channels to route broadcastings to the subscriber. A
10
+ # broadcasting is, as discussed elsewhere, a pubsub queue where any data placed
11
+ # into it is automatically sent to the clients that are connected at that time.
12
+ # It's purely an online queue, though. If you're not streaming a broadcasting at
13
+ # the very moment it sends out an update, you will not get that update, even if
14
+ # you connect after it has been sent.
15
+ #
16
+ # Most commonly, the streamed broadcast is sent straight to the subscriber on
17
+ # the client-side. The channel just acts as a connector between the two parties
18
+ # (the broadcaster and the channel subscriber). Here's an example of a channel
19
+ # that allows subscribers to get all new comments on a given page:
20
+ #
21
+ # class CommentsChannel < ApplicationCable::Channel
22
+ # def follow(data)
23
+ # stream_from "comments_for_#{data['recording_id']}"
24
+ # end
19
25
  #
20
- # def unfollow
21
- # stop_all_streams
26
+ # def unfollow
27
+ # stop_all_streams
28
+ # end
22
29
  # end
23
- # end
24
30
  #
25
- # Based on the above example, the subscribers of this channel will get whatever data is put into the,
26
- # let's say, <tt>comments_for_45</tt> broadcasting as soon as it's put there.
31
+ # Based on the above example, the subscribers of this channel will get whatever
32
+ # data is put into the, let's say, `comments_for_45` broadcasting as soon as
33
+ # it's put there.
27
34
  #
28
35
  # An example broadcasting for this channel looks like so:
29
36
  #
30
- # ActionCable.server.broadcast "comments_for_45", { author: 'DHH', content: 'Rails is just swell' }
37
+ # ActionCable.server.broadcast "comments_for_45", { author: 'DHH', content: 'Rails is just swell' }
31
38
  #
32
- # If you have a stream that is related to a model, then the broadcasting used can be generated from the model and channel.
33
- # The following example would subscribe to a broadcasting like <tt>comments:Z2lkOi8vVGVzdEFwcC9Qb3N0LzE</tt>.
39
+ # If you have a stream that is related to a model, then the broadcasting used
40
+ # can be generated from the model and channel. The following example would
41
+ # subscribe to a broadcasting like `comments:Z2lkOi8vVGVzdEFwcC9Qb3N0LzE`.
34
42
  #
35
- # class CommentsChannel < ApplicationCable::Channel
36
- # def subscribed
37
- # post = Post.find(params[:id])
38
- # stream_for post
43
+ # class CommentsChannel < ApplicationCable::Channel
44
+ # def subscribed
45
+ # post = Post.find(params[:id])
46
+ # stream_for post
47
+ # end
39
48
  # end
40
- # end
41
49
  #
42
50
  # You can then broadcast to this channel using:
43
51
  #
44
- # CommentsChannel.broadcast_to(@post, @comment)
52
+ # CommentsChannel.broadcast_to(@post, @comment)
45
53
  #
46
- # If you don't just want to parlay the broadcast unfiltered to the subscriber, you can also supply a callback that lets you alter what is sent out.
47
- # The below example shows how you can use this to provide performance introspection in the process:
54
+ # If you don't just want to parlay the broadcast unfiltered to the subscriber,
55
+ # you can also supply a callback that lets you alter what is sent out. The below
56
+ # example shows how you can use this to provide performance introspection in the
57
+ # process:
48
58
  #
49
- # class ChatChannel < ApplicationCable::Channel
50
- # def subscribed
51
- # @room = Chat::Room[params[:room_number]]
59
+ # class ChatChannel < ApplicationCable::Channel
60
+ # def subscribed
61
+ # @room = Chat::Room[params[:room_number]]
52
62
  #
53
- # stream_for @room, coder: ActiveSupport::JSON do |message|
54
- # if message['originated_at'].present?
55
- # elapsed_time = (Time.now.to_f - message['originated_at']).round(2)
63
+ # stream_for @room, coder: ActiveSupport::JSON do |message|
64
+ # if message['originated_at'].present?
65
+ # elapsed_time = (Time.now.to_f - message['originated_at']).round(2)
56
66
  #
57
- # ActiveSupport::Notifications.instrument :performance, measurement: 'Chat.message_delay', value: elapsed_time, action: :timing
58
- # logger.info "Message took #{elapsed_time}s to arrive"
59
- # end
67
+ # ActiveSupport::Notifications.instrument :performance, measurement: 'Chat.message_delay', value: elapsed_time, action: :timing
68
+ # logger.info "Message took #{elapsed_time}s to arrive"
69
+ # end
60
70
  #
61
- # transmit message
71
+ # transmit message
72
+ # end
62
73
  # end
63
74
  # end
64
- # end
65
75
  #
66
76
  # You can stop streaming from all broadcasts by calling #stop_all_streams.
67
77
  module Streams
@@ -71,18 +81,20 @@ module ActionCable
71
81
  on_unsubscribe :stop_all_streams
72
82
  end
73
83
 
74
- # Start streaming from the named <tt>broadcasting</tt> pubsub queue. Optionally, you can pass a <tt>callback</tt> that'll be used
75
- # instead of the default of just transmitting the updates straight to the subscriber.
76
- # Pass <tt>coder: ActiveSupport::JSON</tt> to decode messages as JSON before passing to the callback.
77
- # Defaults to <tt>coder: nil</tt> which does no decoding, passes raw messages.
84
+ # Start streaming from the named `broadcasting` pubsub queue. Optionally, you
85
+ # can pass a `callback` that'll be used instead of the default of just
86
+ # transmitting the updates straight to the subscriber. Pass `coder:
87
+ # ActiveSupport::JSON` to decode messages as JSON before passing to the
88
+ # callback. Defaults to `coder: nil` which does no decoding, passes raw
89
+ # messages.
78
90
  def stream_from(broadcasting, callback = nil, coder: nil, &block)
79
91
  broadcasting = String(broadcasting)
80
92
 
81
93
  # Don't send the confirmation until pubsub#subscribe is successful
82
94
  defer_subscription_confirmation!
83
95
 
84
- # Build a stream handler by wrapping the user-provided callback with
85
- # a decoder or defaulting to a JSON-decoding retransmitter.
96
+ # Build a stream handler by wrapping the user-provided callback with a decoder
97
+ # or defaulting to a JSON-decoding retransmitter.
86
98
  handler = worker_pool_stream_handler(broadcasting, callback || block, coder: coder)
87
99
  streams[broadcasting] = handler
88
100
 
@@ -94,17 +106,18 @@ module ActionCable
94
106
  end
95
107
  end
96
108
 
97
- # Start streaming the pubsub queue for the <tt>model</tt> in this channel. Optionally, you can pass a
98
- # <tt>callback</tt> that'll be used instead of the default of just transmitting the updates straight
99
- # to the subscriber.
109
+ # Start streaming the pubsub queue for the `model` in this channel. Optionally,
110
+ # you can pass a `callback` that'll be used instead of the default of just
111
+ # transmitting the updates straight to the subscriber.
100
112
  #
101
- # Pass <tt>coder: ActiveSupport::JSON</tt> to decode messages as JSON before passing to the callback.
102
- # Defaults to <tt>coder: nil</tt> which does no decoding, passes raw messages.
113
+ # Pass `coder: ActiveSupport::JSON` to decode messages as JSON before passing to
114
+ # the callback. Defaults to `coder: nil` which does no decoding, passes raw
115
+ # messages.
103
116
  def stream_for(model, callback = nil, coder: nil, &block)
104
117
  stream_from(broadcasting_for(model), callback || block, coder: coder)
105
118
  end
106
119
 
107
- # Unsubscribes streams from the named <tt>broadcasting</tt>.
120
+ # Unsubscribes streams from the named `broadcasting`.
108
121
  def stop_stream_from(broadcasting)
109
122
  callback = streams.delete(broadcasting)
110
123
  if callback
@@ -113,7 +126,7 @@ module ActionCable
113
126
  end
114
127
  end
115
128
 
116
- # Unsubscribes streams for the <tt>model</tt>.
129
+ # Unsubscribes streams for the `model`.
117
130
  def stop_stream_for(model)
118
131
  stop_stream_from(broadcasting_for(model))
119
132
  end
@@ -126,7 +139,7 @@ module ActionCable
126
139
  end.clear
127
140
  end
128
141
 
129
- # Calls stream_for with the given <tt>model</tt> if it's present to start streaming,
142
+ # Calls stream_for with the given `model` if it's present to start streaming,
130
143
  # otherwise rejects the subscription.
131
144
  def stream_or_reject_for(model)
132
145
  if model
@@ -143,8 +156,8 @@ module ActionCable
143
156
  @_streams ||= {}
144
157
  end
145
158
 
146
- # Always wrap the outermost handler to invoke the user handler on the
147
- # worker pool rather than blocking the event loop.
159
+ # Always wrap the outermost handler to invoke the user handler on the worker
160
+ # pool rather than blocking the event loop.
148
161
  def worker_pool_stream_handler(broadcasting, user_handler, coder: nil)
149
162
  handler = stream_handler(broadcasting, user_handler, coder: coder)
150
163
 
@@ -153,8 +166,8 @@ module ActionCable
153
166
  end
154
167
  end
155
168
 
156
- # May be overridden to add instrumentation, logging, specialized error
157
- # handling, or other forms of handler decoration.
169
+ # May be overridden to add instrumentation, logging, specialized error handling,
170
+ # or other forms of handler decoration.
158
171
  #
159
172
  # TODO: Tests demonstrating this.
160
173
  def stream_handler(broadcasting, user_handler, coder: nil)
@@ -165,14 +178,14 @@ module ActionCable
165
178
  end
166
179
  end
167
180
 
168
- # May be overridden to change the default stream handling behavior
169
- # which decodes JSON and transmits to the client.
181
+ # May be overridden to change the default stream handling behavior which decodes
182
+ # JSON and transmits to the client.
170
183
  #
171
184
  # TODO: Tests demonstrating this.
172
185
  #
173
- # TODO: Room for optimization. Update transmit API to be coder-aware
174
- # so we can no-op when pubsub and connection are both JSON-encoded.
175
- # Then we can skip decode+encode if we're just proxying messages.
186
+ # TODO: Room for optimization. Update transmit API to be coder-aware so we can
187
+ # no-op when pubsub and connection are both JSON-encoded. Then we can skip
188
+ # decode+encode if we're just proxying messages.
176
189
  def default_stream_handler(broadcasting, coder:)
177
190
  coder ||= ActiveSupport::JSON
178
191
  stream_transmitter stream_decoder(coder: coder), broadcasting: broadcasting