actioncable 7.0.8.7 → 7.2.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 (60) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +34 -180
  3. data/MIT-LICENSE +1 -1
  4. data/README.md +4 -4
  5. data/app/assets/javascripts/action_cable.js +30 -9
  6. data/app/assets/javascripts/actioncable.esm.js +30 -9
  7. data/app/assets/javascripts/actioncable.js +30 -9
  8. data/lib/action_cable/channel/base.rb +114 -90
  9. data/lib/action_cable/channel/broadcasting.rb +25 -16
  10. data/lib/action_cable/channel/callbacks.rb +39 -0
  11. data/lib/action_cable/channel/naming.rb +10 -7
  12. data/lib/action_cable/channel/periodic_timers.rb +7 -7
  13. data/lib/action_cable/channel/streams.rb +77 -62
  14. data/lib/action_cable/channel/test_case.rb +117 -86
  15. data/lib/action_cable/connection/authorization.rb +4 -1
  16. data/lib/action_cable/connection/base.rb +70 -42
  17. data/lib/action_cable/connection/callbacks.rb +57 -0
  18. data/lib/action_cable/connection/client_socket.rb +3 -1
  19. data/lib/action_cable/connection/identification.rb +9 -5
  20. data/lib/action_cable/connection/internal_channel.rb +7 -2
  21. data/lib/action_cable/connection/message_buffer.rb +4 -1
  22. data/lib/action_cable/connection/stream.rb +2 -2
  23. data/lib/action_cable/connection/stream_event_loop.rb +4 -4
  24. data/lib/action_cable/connection/subscriptions.rb +7 -2
  25. data/lib/action_cable/connection/tagged_logger_proxy.rb +12 -7
  26. data/lib/action_cable/connection/test_case.rb +67 -55
  27. data/lib/action_cable/connection/web_socket.rb +11 -7
  28. data/lib/action_cable/deprecator.rb +9 -0
  29. data/lib/action_cable/engine.rb +18 -8
  30. data/lib/action_cable/gem_version.rb +6 -4
  31. data/lib/action_cable/helpers/action_cable_helper.rb +21 -19
  32. data/lib/action_cable/remote_connections.rb +25 -13
  33. data/lib/action_cable/server/base.rb +29 -14
  34. data/lib/action_cable/server/broadcasting.rb +24 -16
  35. data/lib/action_cable/server/configuration.rb +27 -14
  36. data/lib/action_cable/server/connections.rb +13 -5
  37. data/lib/action_cable/server/worker/active_record_connection_management.rb +2 -0
  38. data/lib/action_cable/server/worker.rb +4 -3
  39. data/lib/action_cable/subscription_adapter/async.rb +1 -1
  40. data/lib/action_cable/subscription_adapter/base.rb +2 -0
  41. data/lib/action_cable/subscription_adapter/channel_prefix.rb +2 -0
  42. data/lib/action_cable/subscription_adapter/inline.rb +2 -0
  43. data/lib/action_cable/subscription_adapter/postgresql.rb +4 -3
  44. data/lib/action_cable/subscription_adapter/redis.rb +7 -7
  45. data/lib/action_cable/subscription_adapter/subscriber_map.rb +2 -0
  46. data/lib/action_cable/subscription_adapter/test.rb +6 -5
  47. data/lib/action_cable/test_case.rb +2 -0
  48. data/lib/action_cable/test_helper.rb +89 -59
  49. data/lib/action_cable/version.rb +3 -1
  50. data/lib/action_cable.rb +30 -12
  51. data/lib/rails/generators/channel/USAGE +14 -8
  52. data/lib/rails/generators/channel/channel_generator.rb +23 -7
  53. data/lib/rails/generators/test_unit/channel_generator.rb +2 -0
  54. metadata +27 -15
  55. data/lib/action_cable/channel.rb +0 -17
  56. data/lib/action_cable/connection.rb +0 -22
  57. data/lib/action_cable/server.rb +0 -16
  58. data/lib/action_cable/subscription_adapter.rb +0 -12
  59. /data/lib/rails/generators/channel/templates/application_cable/{channel.rb → channel.rb.tt} +0 -0
  60. /data/lib/rails/generators/channel/templates/application_cable/{connection.rb → connection.rb.tt} +0 -0
@@ -1,56 +1,68 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # :markup: markdown
4
+
3
5
  require "action_dispatch"
4
6
  require "active_support/rescuable"
5
7
 
6
8
  module ActionCable
7
9
  module Connection
8
- # For every WebSocket connection the Action Cable server accepts, a Connection object will be instantiated. This instance becomes the parent
9
- # of all of the channel subscriptions that are created from there on. Incoming messages are then routed to these channel subscriptions
10
- # based on an identifier sent by the Action Cable consumer. The Connection itself does not deal with any specific application logic beyond
11
- # authentication and authorization.
10
+ # # Action Cable Connection Base
12
11
  #
13
- # Here's a basic example:
12
+ # For every WebSocket connection the Action Cable server accepts, a Connection
13
+ # object will be instantiated. This instance becomes the parent of all of the
14
+ # channel subscriptions that are created from there on. Incoming messages are
15
+ # then routed to these channel subscriptions based on an identifier sent by the
16
+ # Action Cable consumer. The Connection itself does not deal with any specific
17
+ # application logic beyond authentication and authorization.
14
18
  #
15
- # module ApplicationCable
16
- # class Connection < ActionCable::Connection::Base
17
- # identified_by :current_user
19
+ # Here's a basic example:
18
20
  #
19
- # def connect
20
- # self.current_user = find_verified_user
21
- # logger.add_tags current_user.name
22
- # end
21
+ # module ApplicationCable
22
+ # class Connection < ActionCable::Connection::Base
23
+ # identified_by :current_user
23
24
  #
24
- # def disconnect
25
- # # Any cleanup work needed when the cable connection is cut.
26
- # end
25
+ # def connect
26
+ # self.current_user = find_verified_user
27
+ # logger.add_tags current_user.name
28
+ # end
27
29
  #
28
- # private
29
- # def find_verified_user
30
- # User.find_by_identity(cookies.encrypted[:identity_id]) ||
31
- # reject_unauthorized_connection
30
+ # def disconnect
31
+ # # Any cleanup work needed when the cable connection is cut.
32
32
  # end
33
+ #
34
+ # private
35
+ # def find_verified_user
36
+ # User.find_by_identity(cookies.encrypted[:identity_id]) ||
37
+ # reject_unauthorized_connection
38
+ # end
39
+ # end
33
40
  # end
34
- # end
35
41
  #
36
- # First, we declare that this connection can be identified by its current_user. This allows us to later be able to find all connections
37
- # established for that current_user (and potentially disconnect them). You can declare as many
38
- # identification indexes as you like. Declaring an identification means that an attr_accessor is automatically set for that key.
42
+ # First, we declare that this connection can be identified by its current_user.
43
+ # This allows us to later be able to find all connections established for that
44
+ # current_user (and potentially disconnect them). You can declare as many
45
+ # identification indexes as you like. Declaring an identification means that an
46
+ # attr_accessor is automatically set for that key.
39
47
  #
40
- # Second, we rely on the fact that the WebSocket connection is established with the cookies from the domain being sent along. This makes
41
- # it easy to use signed cookies that were set when logging in via a web interface to authorize the WebSocket connection.
48
+ # Second, we rely on the fact that the WebSocket connection is established with
49
+ # the cookies from the domain being sent along. This makes it easy to use signed
50
+ # cookies that were set when logging in via a web interface to authorize the
51
+ # WebSocket connection.
42
52
  #
43
- # Finally, we add a tag to the connection-specific logger with the name of the current user to easily distinguish their messages in the log.
53
+ # Finally, we add a tag to the connection-specific logger with the name of the
54
+ # current user to easily distinguish their messages in the log.
44
55
  #
45
56
  # Pretty simple, eh?
46
57
  class Base
47
58
  include Identification
48
59
  include InternalChannel
49
60
  include Authorization
61
+ include Callbacks
50
62
  include ActiveSupport::Rescuable
51
63
 
52
64
  attr_reader :server, :env, :subscriptions, :logger, :worker_pool, :protocol
53
- delegate :event_loop, :pubsub, to: :server
65
+ delegate :event_loop, :pubsub, :config, to: :server
54
66
 
55
67
  def initialize(server, env, coder: ActiveSupport::JSON)
56
68
  @server, @env, @coder = server, env, coder
@@ -66,8 +78,10 @@ module ActionCable
66
78
  @started_at = Time.now
67
79
  end
68
80
 
69
- # Called by the server when a new WebSocket connection is established. This configures the callbacks intended for overwriting by the user.
70
- # This method should not be called directly -- instead rely upon on the #connect (and #disconnect) callbacks.
81
+ # Called by the server when a new WebSocket connection is established. This
82
+ # configures the callbacks intended for overwriting by the user. This method
83
+ # should not be called directly -- instead rely upon on the #connect (and
84
+ # #disconnect) callbacks.
71
85
  def process # :nodoc:
72
86
  logger.info started_request_message
73
87
 
@@ -86,12 +100,18 @@ module ActionCable
86
100
 
87
101
  def dispatch_websocket_message(websocket_message) # :nodoc:
88
102
  if websocket.alive?
89
- subscriptions.execute_command decode(websocket_message)
103
+ handle_channel_command decode(websocket_message)
90
104
  else
91
105
  logger.error "Ignoring message processed after the WebSocket was closed: #{websocket_message.inspect})"
92
106
  end
93
107
  end
94
108
 
109
+ def handle_channel_command(payload)
110
+ run_callbacks :command do
111
+ subscriptions.execute_command payload
112
+ end
113
+ end
114
+
95
115
  def transmit(cable_message) # :nodoc:
96
116
  websocket.transmit encode(cable_message)
97
117
  end
@@ -106,13 +126,15 @@ module ActionCable
106
126
  websocket.close
107
127
  end
108
128
 
109
- # Invoke a method on the connection asynchronously through the pool of thread workers.
129
+ # Invoke a method on the connection asynchronously through the pool of thread
130
+ # workers.
110
131
  def send_async(method, *arguments)
111
132
  worker_pool.async_invoke(self, method, *arguments)
112
133
  end
113
134
 
114
- # Return a basic hash of statistics for the connection keyed with <tt>identifier</tt>, <tt>started_at</tt>, <tt>subscriptions</tt>, and <tt>request_id</tt>.
115
- # This can be returned by a health check against the connection.
135
+ # Return a basic hash of statistics for the connection keyed with `identifier`,
136
+ # `started_at`, `subscriptions`, and `request_id`. This can be returned by a
137
+ # health check against the connection.
116
138
  def statistics
117
139
  {
118
140
  identifier: connection_identifier,
@@ -143,11 +165,16 @@ module ActionCable
143
165
  send_async :handle_close
144
166
  end
145
167
 
168
+ def inspect # :nodoc:
169
+ "#<#{self.class.name}:#{'%#016x' % (object_id << 1)}>"
170
+ end
171
+
146
172
  private
147
173
  attr_reader :websocket
148
174
  attr_reader :message_buffer
149
175
 
150
- # The request that initiated the WebSocket connection is available here. This gives access to the environment, cookies, etc.
176
+ # The request that initiated the WebSocket connection is available here. This
177
+ # gives access to the environment, cookies, etc.
151
178
  def request # :doc:
152
179
  @request ||= begin
153
180
  environment = Rails.application.env_config.merge(env) if defined?(Rails.application) && Rails.application
@@ -155,7 +182,8 @@ module ActionCable
155
182
  end
156
183
  end
157
184
 
158
- # The cookies of the request that initiated the WebSocket connection. Useful for performing authorization checks.
185
+ # The cookies of the request that initiated the WebSocket connection. Useful for
186
+ # performing authorization checks.
159
187
  def cookies # :doc:
160
188
  request.cookie_jar
161
189
  end
@@ -192,9 +220,8 @@ module ActionCable
192
220
  end
193
221
 
194
222
  def send_welcome_message
195
- # Send welcome message to the internal connection monitor channel.
196
- # This ensures the connection monitor state is reset after a successful
197
- # websocket connection.
223
+ # Send welcome message to the internal connection monitor channel. This ensures
224
+ # the connection monitor state is reset after a successful websocket connection.
198
225
  transmit type: ActionCable::INTERNAL[:message_types][:welcome]
199
226
  end
200
227
 
@@ -222,10 +249,11 @@ module ActionCable
222
249
 
223
250
  logger.error invalid_request_message
224
251
  logger.info finished_request_message
225
- [ 404, { "Content-Type" => "text/plain" }, [ "Page not found" ] ]
252
+ [ 404, { Rack::CONTENT_TYPE => "text/plain; charset=utf-8" }, [ "Page not found" ] ]
226
253
  end
227
254
 
228
- # Tags are declared in the server but computed in the connection. This allows us per-connection tailored tags.
255
+ # Tags are declared in the server but computed in the connection. This allows us
256
+ # per-connection tailored tags.
229
257
  def new_tagged_logger
230
258
  TaggedLoggerProxy.new server.logger,
231
259
  tags: server.config.log_tags.map { |tag| tag.respond_to?(:call) ? tag.call(request) : tag.to_s.camelize }
@@ -237,7 +265,7 @@ module ActionCable
237
265
  request.filtered_path,
238
266
  websocket.possible? ? " [WebSocket]" : "[non-WebSocket]",
239
267
  request.ip,
240
- Time.now.to_default_s ]
268
+ Time.now.to_s ]
241
269
  end
242
270
 
243
271
  def finished_request_message
@@ -245,7 +273,7 @@ module ActionCable
245
273
  request.filtered_path,
246
274
  websocket.possible? ? " [WebSocket]" : "[non-WebSocket]",
247
275
  request.ip,
248
- Time.now.to_default_s ]
276
+ Time.now.to_s ]
249
277
  end
250
278
 
251
279
  def invalid_request_message
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ # :markup: markdown
4
+
5
+ require "active_support/callbacks"
6
+
7
+ module ActionCable
8
+ module Connection
9
+ # # Action Cable Connection Callbacks
10
+ #
11
+ # The [before_command](rdoc-ref:ClassMethods#before_command),
12
+ # [after_command](rdoc-ref:ClassMethods#after_command), and
13
+ # [around_command](rdoc-ref:ClassMethods#around_command) callbacks are invoked
14
+ # when sending commands to the client, such as when subscribing, unsubscribing,
15
+ # or performing an action.
16
+ #
17
+ # #### Example
18
+ #
19
+ # module ApplicationCable
20
+ # class Connection < ActionCable::Connection::Base
21
+ # identified_by :user
22
+ #
23
+ # around_command :set_current_account
24
+ #
25
+ # private
26
+ #
27
+ # def set_current_account
28
+ # # Now all channels could use Current.account
29
+ # Current.set(account: user.account) { yield }
30
+ # end
31
+ # end
32
+ # end
33
+ #
34
+ module Callbacks
35
+ extend ActiveSupport::Concern
36
+ include ActiveSupport::Callbacks
37
+
38
+ included do
39
+ define_callbacks :command
40
+ end
41
+
42
+ module ClassMethods
43
+ def before_command(*methods, &block)
44
+ set_callback(:command, :before, *methods, &block)
45
+ end
46
+
47
+ def after_command(*methods, &block)
48
+ set_callback(:command, :after, *methods, &block)
49
+ end
50
+
51
+ def around_command(*methods, &block)
52
+ set_callback(:command, :around, *methods, &block)
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # :markup: markdown
4
+
3
5
  require "websocket/driver"
4
6
 
5
7
  module ActionCable
@@ -43,7 +45,7 @@ module ActionCable
43
45
 
44
46
  @ready_state = CONNECTING
45
47
 
46
- # The driver calls +env+, +url+, and +write+
48
+ # The driver calls `env`, `url`, and `write`
47
49
  @driver = ::WebSocket::Driver.rack(self, protocols: protocols)
48
50
 
49
51
  @driver.on(:open) { |e| open }
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # :markup: markdown
4
+
3
5
  require "set"
4
6
 
5
7
  module ActionCable
@@ -12,18 +14,20 @@ module ActionCable
12
14
  end
13
15
 
14
16
  module ClassMethods
15
- # Mark a key as being a connection identifier index that can then be used to find the specific connection again later.
16
- # Common identifiers are current_user and current_account, but could be anything, really.
17
+ # Mark a key as being a connection identifier index that can then be used to
18
+ # find the specific connection again later. Common identifiers are current_user
19
+ # and current_account, but could be anything, really.
17
20
  #
18
- # Note that anything marked as an identifier will automatically create a delegate by the same name on any
19
- # channel instances created off the connection.
21
+ # Note that anything marked as an identifier will automatically create a
22
+ # delegate by the same name on any channel instances created off the connection.
20
23
  def identified_by(*identifiers)
21
24
  Array(identifiers).each { |identifier| attr_accessor identifier }
22
25
  self.identifiers += identifiers
23
26
  end
24
27
  end
25
28
 
26
- # Return a single connection identifier that combines the value of all the registered identifiers into a single gid.
29
+ # Return a single connection identifier that combines the value of all the
30
+ # registered identifiers into a single gid.
27
31
  def connection_identifier
28
32
  unless defined? @connection_identifier
29
33
  @connection_identifier = connection_gid identifiers.filter_map { |id| instance_variable_get("@#{id}") }
@@ -1,8 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # :markup: markdown
4
+
3
5
  module ActionCable
4
6
  module Connection
5
- # Makes it possible for the RemoteConnection to disconnect a specific connection.
7
+ # # Action Cable InternalChannel
8
+ #
9
+ # Makes it possible for the RemoteConnection to disconnect a specific
10
+ # connection.
6
11
  module InternalChannel
7
12
  extend ActiveSupport::Concern
8
13
 
@@ -32,7 +37,7 @@ module ActionCable
32
37
  case message["type"]
33
38
  when "disconnect"
34
39
  logger.info "Removing connection (#{connection_identifier})"
35
- websocket.close
40
+ close(reason: ActionCable::INTERNAL[:disconnect_reasons][:remote], reconnect: message.fetch("reconnect", true))
36
41
  end
37
42
  rescue Exception => e
38
43
  logger.error "There was an exception - #{e.class}(#{e.message})"
@@ -1,8 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # :markup: markdown
4
+
3
5
  module ActionCable
4
6
  module Connection
5
- # Allows us to buffer messages received from the WebSocket before the Connection has been fully initialized, and is ready to receive them.
7
+ # Allows us to buffer messages received from the WebSocket before the Connection
8
+ # has been fully initialized, and is ready to receive them.
6
9
  class MessageBuffer # :nodoc:
7
10
  def initialize(connection)
8
11
  @connection = connection
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "thread"
3
+ # :markup: markdown
4
4
 
5
5
  module ActionCable
6
6
  module Connection
@@ -100,7 +100,7 @@ module ActionCable
100
100
 
101
101
  # This should return the underlying io according to the SPEC:
102
102
  @rack_hijack_io = @socket_object.env["rack.hijack"].call
103
- # Retain existing behaviour if required:
103
+ # Retain existing behavior if required:
104
104
  @rack_hijack_io ||= @socket_object.env["rack.hijack_io"]
105
105
 
106
106
  @event_loop.attach(@rack_hijack_io, self)
@@ -1,7 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # :markup: markdown
4
+
3
5
  require "nio"
4
- require "thread"
5
6
 
6
7
  module ActionCable
7
8
  module Connection
@@ -117,9 +118,8 @@ module ActionCable
117
118
  stream.receive incoming
118
119
  end
119
120
  rescue
120
- # We expect one of EOFError or Errno::ECONNRESET in
121
- # normal operation (when the client goes away). But if
122
- # anything else goes wrong, this is still the best way
121
+ # We expect one of EOFError or Errno::ECONNRESET in normal operation (when the
122
+ # client goes away). But if anything else goes wrong, this is still the best way
123
123
  # to handle it.
124
124
  begin
125
125
  stream.close
@@ -1,11 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # :markup: markdown
4
+
3
5
  require "active_support/core_ext/hash/indifferent_access"
4
6
 
5
7
  module ActionCable
6
8
  module Connection
7
- # Collection class for all the channel subscriptions established on a given connection. Responsible for routing incoming commands that arrive on
8
- # the connection to the proper channel.
9
+ # # Action Cable Connection Subscriptions
10
+ #
11
+ # Collection class for all the channel subscriptions established on a given
12
+ # connection. Responsible for routing incoming commands that arrive on the
13
+ # connection to the proper channel.
9
14
  class Subscriptions # :nodoc:
10
15
  def initialize(connection)
11
16
  @connection = connection
@@ -1,10 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # :markup: markdown
4
+
3
5
  module ActionCable
4
6
  module Connection
5
- # Allows the use of per-connection tags against the server logger. This wouldn't work using the traditional
6
- # ActiveSupport::TaggedLogging enhanced Rails.logger, as that logger will reset the tags between requests.
7
- # The connection is long-lived, so it needs its own set of tags for its independent duration.
7
+ # # Action Cable Connection TaggedLoggerProxy
8
+ #
9
+ # Allows the use of per-connection tags against the server logger. This wouldn't
10
+ # work using the traditional ActiveSupport::TaggedLogging enhanced Rails.logger,
11
+ # as that logger will reset the tags between requests. The connection is
12
+ # long-lived, so it needs its own set of tags for its independent duration.
8
13
  class TaggedLoggerProxy
9
14
  attr_reader :tags
10
15
 
@@ -28,14 +33,14 @@ module ActionCable
28
33
  end
29
34
 
30
35
  %i( debug info warn error fatal unknown ).each do |severity|
31
- define_method(severity) do |message|
32
- log severity, message
36
+ define_method(severity) do |message = nil, &block|
37
+ log severity, message, &block
33
38
  end
34
39
  end
35
40
 
36
41
  private
37
- def log(type, message) # :doc:
38
- tag(@logger) { @logger.send type, message }
42
+ def log(type, message, &block) # :doc:
43
+ tag(@logger) { @logger.send type, message, &block }
39
44
  end
40
45
  end
41
46
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # :markup: markdown
4
+
3
5
  require "active_support"
4
6
  require "active_support/test_case"
5
7
  require "active_support/core_ext/hash/indifferent_access"
@@ -18,25 +20,33 @@ module ActionCable
18
20
  end
19
21
 
20
22
  module Assertions
21
- # Asserts that the connection is rejected (via +reject_unauthorized_connection+).
23
+ # Asserts that the connection is rejected (via
24
+ # `reject_unauthorized_connection`).
22
25
  #
23
- # # Asserts that connection without user_id fails
24
- # assert_reject_connection { connect params: { user_id: '' } }
26
+ # # Asserts that connection without user_id fails
27
+ # assert_reject_connection { connect params: { user_id: '' } }
25
28
  def assert_reject_connection(&block)
26
29
  assert_raises(Authorization::UnauthorizedError, "Expected to reject connection but no rejection was made", &block)
27
30
  end
28
31
  end
29
32
 
30
- # We don't want to use the whole "encryption stack" for connection
31
- # unit-tests, but we want to make sure that users test against the correct types
32
- # of cookies (i.e. signed or encrypted or plain)
33
- class TestCookieJar < ActiveSupport::HashWithIndifferentAccess
33
+ class TestCookies < ActiveSupport::HashWithIndifferentAccess # :nodoc:
34
+ def []=(name, options)
35
+ value = options.is_a?(Hash) ? options.symbolize_keys[:value] : options
36
+ super(name, value)
37
+ end
38
+ end
39
+
40
+ # We don't want to use the whole "encryption stack" for connection unit-tests,
41
+ # but we want to make sure that users test against the correct types of cookies
42
+ # (i.e. signed or encrypted or plain)
43
+ class TestCookieJar < TestCookies
34
44
  def signed
35
- self[:signed] ||= {}.with_indifferent_access
45
+ @signed ||= TestCookies.new
36
46
  end
37
47
 
38
48
  def encrypted
39
- self[:encrypted] ||= {}.with_indifferent_access
49
+ @encrypted ||= TestCookies.new
40
50
  end
41
51
  end
42
52
 
@@ -56,75 +66,77 @@ module ActionCable
56
66
  end
57
67
  end
58
68
 
69
+ # # Action Cable Connection TestCase
70
+ #
59
71
  # Unit test Action Cable connections.
60
72
  #
61
- # Useful to check whether a connection's +identified_by+ gets assigned properly
73
+ # Useful to check whether a connection's `identified_by` gets assigned properly
62
74
  # and that any improper connection requests are rejected.
63
75
  #
64
- # == Basic example
76
+ # ## Basic example
65
77
  #
66
78
  # Unit tests are written as follows:
67
79
  #
68
- # 1. Simulate a connection attempt by calling +connect+.
69
- # 2. Assert state, e.g. identifiers, has been assigned.
80
+ # 1. Simulate a connection attempt by calling `connect`.
81
+ # 2. Assert state, e.g. identifiers, has been assigned.
70
82
  #
71
83
  #
72
- # class ApplicationCable::ConnectionTest < ActionCable::Connection::TestCase
73
- # def test_connects_with_proper_cookie
74
- # # Simulate the connection request with a cookie.
75
- # cookies["user_id"] = users(:john).id
84
+ # class ApplicationCable::ConnectionTest < ActionCable::Connection::TestCase
85
+ # def test_connects_with_proper_cookie
86
+ # # Simulate the connection request with a cookie.
87
+ # cookies["user_id"] = users(:john).id
76
88
  #
77
- # connect
89
+ # connect
78
90
  #
79
- # # Assert the connection identifier matches the fixture.
80
- # assert_equal users(:john).id, connection.user.id
81
- # end
91
+ # # Assert the connection identifier matches the fixture.
92
+ # assert_equal users(:john).id, connection.user.id
93
+ # end
82
94
  #
83
- # def test_rejects_connection_without_proper_cookie
84
- # assert_reject_connection { connect }
95
+ # def test_rejects_connection_without_proper_cookie
96
+ # assert_reject_connection { connect }
97
+ # end
85
98
  # end
86
- # end
87
99
  #
88
- # +connect+ accepts additional information about the HTTP request with the
89
- # +params+, +headers+, +session+, and Rack +env+ options.
100
+ # `connect` accepts additional information about the HTTP request with the
101
+ # `params`, `headers`, `session`, and Rack `env` options.
90
102
  #
91
- # def test_connect_with_headers_and_query_string
92
- # connect params: { user_id: 1 }, headers: { "X-API-TOKEN" => "secret-my" }
103
+ # def test_connect_with_headers_and_query_string
104
+ # connect params: { user_id: 1 }, headers: { "X-API-TOKEN" => "secret-my" }
93
105
  #
94
- # assert_equal "1", connection.user.id
95
- # assert_equal "secret-my", connection.token
96
- # end
106
+ # assert_equal "1", connection.user.id
107
+ # assert_equal "secret-my", connection.token
108
+ # end
97
109
  #
98
- # def test_connect_with_params
99
- # connect params: { user_id: 1 }
110
+ # def test_connect_with_params
111
+ # connect params: { user_id: 1 }
100
112
  #
101
- # assert_equal "1", connection.user.id
102
- # end
113
+ # assert_equal "1", connection.user.id
114
+ # end
103
115
  #
104
116
  # You can also set up the correct cookies before the connection request:
105
117
  #
106
- # def test_connect_with_cookies
107
- # # Plain cookies:
108
- # cookies["user_id"] = 1
118
+ # def test_connect_with_cookies
119
+ # # Plain cookies:
120
+ # cookies["user_id"] = 1
109
121
  #
110
- # # Or signed/encrypted:
111
- # # cookies.signed["user_id"] = 1
112
- # # cookies.encrypted["user_id"] = 1
122
+ # # Or signed/encrypted:
123
+ # # cookies.signed["user_id"] = 1
124
+ # # cookies.encrypted["user_id"] = 1
113
125
  #
114
- # connect
126
+ # connect
115
127
  #
116
- # assert_equal "1", connection.user_id
117
- # end
128
+ # assert_equal "1", connection.user_id
129
+ # end
118
130
  #
119
- # == Connection is automatically inferred
131
+ # ## Connection is automatically inferred
120
132
  #
121
- # ActionCable::Connection::TestCase will automatically infer the connection under test
122
- # from the test class name. If the channel cannot be inferred from the test
123
- # class name, you can explicitly set it with +tests+.
133
+ # ActionCable::Connection::TestCase will automatically infer the connection
134
+ # under test from the test class name. If the channel cannot be inferred from
135
+ # the test class name, you can explicitly set it with `tests`.
124
136
  #
125
- # class ConnectionTest < ActionCable::Connection::TestCase
126
- # tests ApplicationCable::Connection
127
- # end
137
+ # class ConnectionTest < ActionCable::Connection::TestCase
138
+ # tests ApplicationCable::Connection
139
+ # end
128
140
  #
129
141
  class TestCase < ActiveSupport::TestCase
130
142
  module Behavior
@@ -176,10 +188,10 @@ module ActionCable
176
188
  #
177
189
  # Accepts request path as the first argument and the following request options:
178
190
  #
179
- # - params – URL parameters (Hash)
180
- # - headers – request headers (Hash)
181
- # - session – session data (Hash)
182
- # - env – additional Rack env configuration (Hash)
191
+ # * params – URL parameters (Hash)
192
+ # * headers – request headers (Hash)
193
+ # * session – session data (Hash)
194
+ # * env – additional Rack env configuration (Hash)
183
195
  def connect(path = ActionCable.server.config.mount_path, **request_params)
184
196
  path ||= DEFAULT_PATH
185
197