actioncable 7.1.3.4 → 7.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +26 -129
  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 -52
  47. data/lib/action_cable/version.rb +3 -1
  48. data/lib/action_cable.rb +13 -7
  49. data/lib/rails/generators/channel/channel_generator.rb +4 -2
  50. data/lib/rails/generators/test_unit/channel_generator.rb +2 -0
  51. metadata +13 -13
  52. /data/lib/rails/generators/channel/templates/application_cable/{channel.rb → channel.rb.tt} +0 -0
  53. /data/lib/rails/generators/channel/templates/application_cable/{connection.rb → connection.rb.tt} +0 -0
@@ -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