actioncable-next 0.2.0 → 0.3.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f5831ed0f565c3bc58899a26ec3097bf47ea7cb518f380e0db05a8bad6fcc5dd
4
- data.tar.gz: 92aae3e36883530c9b51ee7acdb0e7df231aed27d60b5f8a4d31426c7158822e
3
+ metadata.gz: a3500aff266cab716b9cc6ef36748f32ecd4e6b5e1d24a1c8ce462c0e3e2dd9d
4
+ data.tar.gz: 74123e275958e25046b8a3a10ac7a3c2b5fa8066c63209baf591f3623100e867
5
5
  SHA512:
6
- metadata.gz: ef8e96d02d595e9004acc936acc8c116d1ac846664dbdf3455b0cfb8f872ba3dbbecfd3dfb3772a53b796505c02a592947bf838690d9c5f0b2c844a40e70e7a4
7
- data.tar.gz: bf36b4a97cd76f02d4162a70d27751ae15333d5c1c23654199bd59214dc061b0b779aa9dd28c73ca080f075fc5bc8972db86d6e98ce137c4b78f330bd3e5540c
6
+ metadata.gz: e6662409e60c2c0a2ac869771d05df9c620529a0cc92642e6bc3b5c953e1f78e3d64460953ecee186d713c8079e0b296fc25a8faadca646128a4c6b28f01c175
7
+ data.tar.gz: 588b659b2eddf199182e25bd7bec23f386621f97abdb169d33c81ac8022556cfa2ad8275cbaad3cf420c8f89306974843f47f851d7db3db0d37363f352d6e120
data/CHANGELOG.md CHANGED
@@ -2,6 +2,12 @@
2
2
 
3
3
  ## main
4
4
 
5
+ ## 0.3.0
6
+
7
+ - Sync with upstream (Rails 8.1.1).
8
+
9
+ - Do not call `#subscribed` when subscription is rejected in a `before_subscribe` callback.
10
+
5
11
  ## 0.2.0
6
12
 
7
13
  - Performance: store connections in a Hash, not an Array.
@@ -2,7 +2,6 @@
2
2
 
3
3
  # :markup: markdown
4
4
 
5
- require "set"
6
5
  require "active_support/rescuable"
7
6
  require "active_support/parameter_filter"
8
7
 
@@ -133,7 +132,10 @@ module ActionCable
133
132
  # Except for public instance methods of Base and its ancestors
134
133
  ActionCable::Channel::Base.public_instance_methods(true) +
135
134
  # Be sure to include shadowed public instance methods of this class
136
- public_instance_methods(false)).uniq.map(&:to_s)
135
+ public_instance_methods(false) -
136
+ # Except the internal methods
137
+ internal_methods).uniq
138
+ methods.map!(&:name)
137
139
  methods.to_set
138
140
  end
139
141
  end
@@ -146,6 +148,10 @@ module ActionCable
146
148
  @action_methods = nil
147
149
  end
148
150
 
151
+ def internal_methods
152
+ super
153
+ end
154
+
149
155
  # Refresh the cached action_methods when a new action_method is added.
150
156
  def method_added(name) # :doc:
151
157
  super
@@ -166,6 +172,7 @@ module ActionCable
166
172
 
167
173
  @reject_subscription = nil
168
174
  @subscription_confirmation_sent = nil
175
+ @unsubscribed = false
169
176
 
170
177
  delegate_connection_identifiers
171
178
  end
@@ -190,7 +197,7 @@ module ActionCable
190
197
  # confirms or rejects the subscription.
191
198
  def subscribe_to_channel
192
199
  run_callbacks :subscribe do
193
- subscribed
200
+ subscribed unless subscription_rejected?
194
201
  end
195
202
 
196
203
  reject_subscription if subscription_rejected?
@@ -201,11 +208,16 @@ module ActionCable
201
208
  # cleanup with callbacks. This method is not intended to be called directly by
202
209
  # the user. Instead, override the #unsubscribed callback.
203
210
  def unsubscribe_from_channel # :nodoc:
211
+ @unsubscribed = true
204
212
  run_callbacks :unsubscribe do
205
213
  unsubscribed
206
214
  end
207
215
  end
208
216
 
217
+ def unsubscribed? # :nodoc:
218
+ @unsubscribed
219
+ end
220
+
209
221
  private
210
222
  # Called once a consumer has become a subscriber of the channel. Usually the
211
223
  # place to set up any streams you want this channel to be sending to the
@@ -10,19 +10,19 @@ module ActionCable
10
10
  extend ActiveSupport::Concern
11
11
 
12
12
  module ClassMethods
13
- # Broadcast a hash to a unique broadcasting for this `model` in this channel.
14
- def broadcast_to(model, message)
15
- ActionCable.server.broadcast(broadcasting_for(model), message)
13
+ # Broadcast a hash to a unique broadcasting for this array of `broadcastables` in this channel.
14
+ def broadcast_to(broadcastables, message)
15
+ ActionCable.server.broadcast(broadcasting_for(broadcastables), message)
16
16
  end
17
17
 
18
18
  # Returns a unique broadcasting identifier for this `model` in this channel:
19
19
  #
20
20
  # CommentsChannel.broadcasting_for("all") # => "comments:all"
21
21
  #
22
- # You can pass any object as a target (e.g. Active Record model), and it would
22
+ # You can pass an array of objects as a target (e.g. Active Record model), and it would
23
23
  # be serialized into a string under the hood.
24
- def broadcasting_for(model)
25
- serialize_broadcasting([ channel_name, model ])
24
+ def broadcasting_for(broadcastables)
25
+ serialize_broadcasting([ channel_name ] + Array(broadcastables))
26
26
  end
27
27
 
28
28
  private
@@ -39,6 +39,8 @@ module ActionCable
39
39
  extend ActiveSupport::Concern
40
40
  include ActiveSupport::Callbacks
41
41
 
42
+ INTERNAL_METHODS = [:_run_subscribe_callbacks, :_run_unsubscribe_callbacks] # :nodoc:
43
+
42
44
  included do
43
45
  define_callbacks :subscribe
44
46
  define_callbacks :unsubscribe
@@ -70,6 +72,11 @@ module ActionCable
70
72
  set_callback(:unsubscribe, :after, *methods, &block)
71
73
  end
72
74
  alias_method :on_unsubscribe, :after_unsubscribe
75
+
76
+ private
77
+ def internal_methods
78
+ INTERNAL_METHODS
79
+ end
73
80
  end
74
81
  end
75
82
  end
@@ -88,6 +88,8 @@ module ActionCable
88
88
  # callback. Defaults to `coder: nil` which does no decoding, passes raw
89
89
  # messages.
90
90
  def stream_from(broadcasting, callback = nil, coder: nil, &block)
91
+ return if unsubscribed?
92
+
91
93
  broadcasting = String(broadcasting)
92
94
 
93
95
  # Don't send the confirmation until pubsub#subscribe is successful
@@ -104,15 +106,15 @@ module ActionCable
104
106
  end)
105
107
  end
106
108
 
107
- # Start streaming the pubsub queue for the `model` in this channel. Optionally,
109
+ # Start streaming the pubsub queue for the `broadcastables` in this channel. Optionally,
108
110
  # you can pass a `callback` that'll be used instead of the default of just
109
111
  # transmitting the updates straight to the subscriber.
110
112
  #
111
113
  # Pass `coder: ActiveSupport::JSON` to decode messages as JSON before passing to
112
114
  # the callback. Defaults to `coder: nil` which does no decoding, passes raw
113
115
  # messages.
114
- def stream_for(model, ...)
115
- stream_from(broadcasting_for(model), ...)
116
+ def stream_for(broadcastables, ...)
117
+ stream_from(broadcasting_for(broadcastables), ...)
116
118
  end
117
119
 
118
120
  # Unsubscribes streams from the named `broadcasting`.
@@ -2,8 +2,6 @@
2
2
 
3
3
  # :markup: markdown
4
4
 
5
- require "set"
6
-
7
5
  module ActionCable
8
6
  module Connection
9
7
  module Identification
@@ -40,7 +40,7 @@ module ActionCable
40
40
 
41
41
  class UnknownSubscription < Error
42
42
  def initialize(identifier)
43
- "Unable to find subscription with identifier: #{identifier}"
43
+ super "Unable to find subscription with identifier: #{identifier}"
44
44
  end
45
45
  end
46
46
 
@@ -142,11 +142,8 @@ module ActionCable
142
142
  #
143
143
  # ## Basic example
144
144
  #
145
- # Unit tests are written as follows:
146
- #
147
- # 1. Simulate a connection attempt by calling `connect`.
148
- # 2. Assert state, e.g. identifiers, has been assigned.
149
- #
145
+ # Unit tests are written by first simulating a connection attempt by calling
146
+ # `connect` and then asserting state, e.g. identifiers, have been assigned.
150
147
  #
151
148
  # class ApplicationCable::ConnectionTest < ActionCable::Connection::TestCase
152
149
  # def test_connects_with_proper_cookie
@@ -10,9 +10,9 @@ module ActionCable
10
10
 
11
11
  module VERSION
12
12
  MAJOR = 8
13
- MINOR = 0
14
- TINY = 0
15
- PRE = "alpha"
13
+ MINOR = 1
14
+ TINY = 1
15
+ PRE = nil
16
16
 
17
17
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
18
18
  end
@@ -10,7 +10,7 @@ module ActionCable
10
10
  class ThreadedExecutor # :nodoc:
11
11
  def initialize(max_size: 10)
12
12
  @executor = Concurrent::ThreadPoolExecutor.new(
13
- name: "ActionCable server",
13
+ name: "ActionCable-streamer",
14
14
  min_threads: 1,
15
15
  max_threads: max_size,
16
16
  max_queue: 0,
@@ -21,10 +21,12 @@ module ActionCable
21
21
  # ActionCable.server.broadcast \
22
22
  # "web_notifications_1", { title: "New things!", body: "All that's fit for print" }
23
23
  #
24
- # # Client-side CoffeeScript, which assumes you've already requested the right to send web notifications:
25
- # App.cable.subscriptions.create "WebNotificationsChannel",
26
- # received: (data) ->
27
- # new Notification data['title'], body: data['body']
24
+ # // Client-side JavaScript, which assumes you've already requested the right to send web notifications:
25
+ # App.cable.subscriptions.create("WebNotificationsChannel", {
26
+ # received: function(data) {
27
+ # new Notification(data['title'], { body: data['body'] });
28
+ # }
29
+ # });
28
30
  module Broadcasting
29
31
  # Broadcast a hash directly to a named `broadcasting`. This will later be JSON
30
32
  # encoded.
@@ -20,7 +20,7 @@ module ActionCable
20
20
 
21
21
  def initialize(max_size: 5)
22
22
  @executor = Concurrent::ThreadPoolExecutor.new(
23
- name: "ActionCable worker",
23
+ name: "ActionCable-server",
24
24
  min_threads: 1,
25
25
  max_threads: max_size,
26
26
  max_queue: 0,
@@ -32,7 +32,8 @@ module ActionCable
32
32
  end
33
33
 
34
34
  def identifier
35
- config.cable[:id] ||= "ActionCable-PID-#{$$}"
35
+ config.cable[:id] = "ActionCable-PID-#{$$}" unless config.cable.key?(:id)
36
+ config.cable[:id]
36
37
  end
37
38
  end
38
39
  end
@@ -36,18 +36,17 @@ module ActionCable
36
36
  end
37
37
 
38
38
  def with_subscriptions_connection(&block) # :nodoc:
39
- ar_conn = ActiveRecord::Base.connection_pool.checkout.tap do |conn|
40
- # Action Cable is taking ownership over this database connection, and will
41
- # perform the necessary cleanup tasks
42
- ActiveRecord::Base.connection_pool.remove(conn)
43
- end
39
+ # Action Cable is taking ownership over this database connection, and will
40
+ # perform the necessary cleanup tasks.
41
+ # We purposedly avoid #checkout to not end up with a pinned connection
42
+ ar_conn = ActiveRecord::Base.connection_pool.new_connection
44
43
  pg_conn = ar_conn.raw_connection
45
44
 
46
45
  verify!(pg_conn)
47
46
  pg_conn.exec("SET application_name = #{pg_conn.escape_identifier(identifier)}")
48
47
  yield pg_conn
49
48
  ensure
50
- ar_conn.disconnect!
49
+ ar_conn&.disconnect!
51
50
  end
52
51
 
53
52
  def with_broadcast_connection(&block) # :nodoc:
@@ -162,7 +162,7 @@ module ActionCable
162
162
  begin
163
163
  conn = @adapter.redis_connection_for_subscriptions
164
164
  listen conn
165
- rescue ConnectionError => e
165
+ rescue *CONNECTION_ERRORS => e
166
166
  reset
167
167
  if retry_connecting?
168
168
  logger&.warn "Redis connection failed: #{e.message}. Trying to reconnect..."
@@ -211,7 +211,7 @@ module ActionCable
211
211
  end
212
212
 
213
213
  if ::Redis::VERSION < "5"
214
- ConnectionError = ::Redis::BaseConnectionError
214
+ CONNECTION_ERRORS = [::Redis::BaseConnectionError].freeze
215
215
 
216
216
  class SubscribedClient
217
217
  def initialize(raw_client)
@@ -245,7 +245,12 @@ module ActionCable
245
245
  SubscribedClient.new(conn._client)
246
246
  end
247
247
  else
248
- ConnectionError = RedisClient::ConnectionError
248
+ CONNECTION_ERRORS = [
249
+ ::Redis::BaseConnectionError,
250
+
251
+ # Some older versions of redis-rb sometime leak underlying exceptions
252
+ RedisClient::ConnectionError,
253
+ ].freeze
249
254
 
250
255
  def extract_subscribed_client(conn)
251
256
  conn
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActionCableNext
4
- VERSION = "0.2.0"
4
+ VERSION = "0.3.0"
5
5
  end
@@ -59,17 +59,17 @@ module Rails
59
59
  def create_channel_javascript_file
60
60
  channel_js_path = File.join("app/javascript/channels", class_path, "#{file_name}_channel")
61
61
  js_template "javascript/channel", channel_js_path
62
- gsub_file "#{channel_js_path}.js", /\.\/consumer/, "channels/consumer" unless using_js_runtime?
62
+ gsub_file "#{channel_js_path}.js", /\.\/consumer/, "channels/consumer" if using_importmap?
63
63
  end
64
64
 
65
65
  def import_channels_in_javascript_entrypoint
66
66
  append_to_file "app/javascript/application.js",
67
- using_js_runtime? ? %(import "./channels"\n) : %(import "channels"\n)
67
+ using_importmap? ? %(import "channels"\n) : %(import "./channels"\n)
68
68
  end
69
69
 
70
70
  def import_channel_in_javascript_entrypoint
71
71
  append_to_file "app/javascript/channels/index.js",
72
- using_js_runtime? ? %(import "./#{file_name}_channel"\n) : %(import "channels/#{file_name}_channel"\n)
72
+ using_importmap? ? %(import "channels/#{file_name}_channel"\n) : %(import "./#{file_name}_channel"\n)
73
73
  end
74
74
 
75
75
  def install_javascript_dependencies
@@ -105,7 +105,7 @@ pin_all_from "app/javascript/channels", under: "channels"
105
105
  end
106
106
 
107
107
  def using_bun?
108
- # Cannot assume bun.lockb has been generated yet so we look for a file known to
108
+ # Cannot assume Bun lockfile has been generated yet so we look for a file known to
109
109
  # be generated by the jsbundling-rails gem
110
110
  @using_bun ||= using_js_runtime? && root.join("bun.config.js").exist?
111
111
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: actioncable-next
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Pratik Naik
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2025-02-04 00:00:00.000000000 Z
13
+ date: 2025-10-30 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: activesupport
@@ -167,7 +167,7 @@ licenses:
167
167
  - MIT
168
168
  metadata:
169
169
  bug_tracker_uri: https://github.com/anycable/actioncable-next
170
- changelog_uri: https://github.com/anycable/actioncable-next/blob/v0.2.0/CHANGELOG.md
170
+ changelog_uri: https://github.com/anycable/actioncable-next/blob/v0.3.0/CHANGELOG.md
171
171
  source_code_uri: https://github.com/anycable/actioncable-next
172
172
  rubygems_mfa_required: 'true'
173
173
  post_install_message:
@@ -178,7 +178,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
178
178
  requirements:
179
179
  - - ">="
180
180
  - !ruby/object:Gem::Version
181
- version: 3.1.0
181
+ version: 3.2.0
182
182
  required_rubygems_version: !ruby/object:Gem::Requirement
183
183
  requirements:
184
184
  - - ">="