anycable-rails-core 1.5.6 โ†’ 1.6.0.rc.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 06a551d757afa07138babbf351024554d52b667ab35893bc995cc1fb56edd8af
4
- data.tar.gz: c630ecf586cfd0068b0d248c00dc1372ffd7d83a3abf0e76dc9ecd52ae960a7c
3
+ metadata.gz: 395f76c02adbd3f4878b55bb21bf4bd00969e4394e824fb7a23f086d8b221b1c
4
+ data.tar.gz: b300f5b2ab64aa893d462a22045587f65531bbaea8dce3a920c1fb1df67b211e
5
5
  SHA512:
6
- metadata.gz: 2d46a5b21269ef7cb9cfc562ecee2cfd9830e055dd18fb20bce468409f0a84bc3ca3dc1ba56d9d340202039de3ab93aeba2e600388b7130b99bff101cbf00448
7
- data.tar.gz: 64a083bf7d4046b5a1bfa57058f0aa1cedded844067b15f5f9ea660a8d5291bbe2c8f6db6c4a63768f2d17f67d40bb920f4abe8e55b9fbc9241f72c4a4d9c587
6
+ metadata.gz: f0a2f228649539c86b8e3f1a96f0c456b6ed0f933552fb93c90ef2d4a5c49261fdbdf9f3e7b650cd69b00ca38bddcf202bb6f1d730aa92ad07ec3f8504128904
7
+ data.tar.gz: dfce4029440d91a5a7f07abccca089b6b0bb759d152aa2fadad9ccb12ca0533a5d10cba6e200f398ce58c54c76c5b0fa5903c48aeacba2354dc8b5f9ad37d10f
data/README.md CHANGED
@@ -1,30 +1,18 @@
1
- [![Gem Version](https://badge.fury.io/rb/anycable-rails.svg)](https://rubygems.org/gems/anycable-rails)
1
+ [![Gem Version](https://badge.fury.io/rb/anycable.svg)](https://rubygems.org/gems/anycable)
2
2
  [![Build](https://github.com/anycable/anycable-rails/workflows/Build/badge.svg)](https://github.com/anycable/anycable-rails/actions)
3
3
  [![Documentation](https://img.shields.io/badge/docs-link-brightgreen.svg)](https://docs.anycable.io/rails/getting_started)
4
4
 
5
- # AnyCable Rails
5
+ # AnyCable SDK for Ruby on Rails
6
6
 
7
- AnyCable allows you to use any WebSocket server (written in any language) as a replacement for built-in Rails Action Cable server.
7
+ <img align="right" height="150" width="129"
8
+ title="AnyCable logo" src="https://docs.anycable.io/assets/images/logo.svg">
8
9
 
9
- With AnyCable you can use channels, client-side JS, broadcasting - (almost) all that you can do with Action Cable.
10
+ [AnyCable](https://github.com/anycable/anycable) is an open-source language-agnostic realtime server for reliable two-way communication over WebSockets and SSE.
11
+ This repository contains code for AnyCable Rails SKD that allows you to use AnyCable as a drop-in replacement for Action Cable.
10
12
 
11
- ๐Ÿ’พ [Example Application](https://github.com/anycable/anycable_rails_demo)
13
+ ๐ŸŒ [Website](https://anycable.io) ยท ๐Ÿ“š [Documentation](https://docs.anycable.io/rails/getting_started) ยท ๐Ÿ’พ [Example Rails Application](https://github.com/anycable/anycable_rails_demo)
12
14
 
13
- ๐Ÿ“‘ [Documentation](https://docs.anycable.io/rails/getting_started).
14
-
15
- > [AnyCable Pro](https://docs.anycable.io/pro) has been launched ๐Ÿš€
16
-
17
- <a href="https://evilmartians.com/">
18
- <img src="https://evilmartians.com/badges/sponsored-by-evil-martians.svg" alt="Sponsored by Evil Martians" width="236" height="54"></a>
19
-
20
- ## Requirements
21
-
22
- - Ruby >= 3.1
23
- - Rails >= 6.0\*
24
-
25
- \* Recent `anycable-rails` versions only work with Rails 8+; older versions compatible with Rails 6 and Rails 7 still receive fixes and minor updates (patch releases).
26
-
27
- ## Usage
15
+ ## Quick Start
28
16
 
29
17
  Add `anycable-rails` gem to your Gemfile:
30
18
 
@@ -32,62 +20,13 @@ Add `anycable-rails` gem to your Gemfile:
32
20
  gem "anycable-rails"
33
21
  ```
34
22
 
35
- ### Interactive set up
36
-
37
- After the gem was installed, you can run an interactive wizard to configure your Rails application for using with AnyCable by running a generator:
23
+ Then run our interactive setup command:
38
24
 
39
25
  ```sh
40
26
  bundle exec rails g anycable:setup
41
27
  ```
42
28
 
43
- ### Manual set up
44
-
45
- Specify AnyCable subscription adapter for Action Cable:
46
-
47
- ```yml
48
- # config/cable.yml
49
- development:
50
- adapter: any_cable # or anycable
51
-
52
- production:
53
- adapter: any_cable
54
- ```
55
-
56
- and specify AnyCable WebSocket server URL:
57
-
58
- ```ruby
59
- # For development it's likely the localhost
60
-
61
- # config/environments/development.rb
62
- config.action_cable.url = "ws://localhost:8080/cable"
63
-
64
- # For production it's likely to have a sub-domain and secure connection
65
-
66
- # config/environments/production.rb
67
- config.action_cable.url = "wss://ws.example.com/cable"
68
- ```
69
-
70
- Then, run AnyCable RPC server:
71
-
72
- ```sh
73
- $ bundle exec anycable
74
-
75
- # don't forget to provide Rails env
76
-
77
- $ RAILS_ENV=production bundle exec anycable
78
- ```
79
-
80
- And, finally, run AnyCable WebSocket server, e.g. [anycable-go](https://docs.anycable.io/anycable-go/getting_started):
81
-
82
- ```sh
83
- anycable-go --host=localhost --port=8080
84
- ```
85
-
86
- See [documentation](https://docs.anycable.io/rails/getting_started) for more information on AnyCable + Rails usage.
87
-
88
- ## Action Cable Compatibility
89
-
90
- See [documentation](https://docs.anycable.io/rails/compatibility).
29
+ Learn more [here](https://docs.anycable.io/rails/getting_started).
91
30
 
92
31
  ## Contributing
93
32
 
@@ -3,6 +3,17 @@
3
3
  require "action_cable"
4
4
 
5
5
  ActionCable::Channel::Base.prepend(Module.new do
6
+ def subscribe_to_channel
7
+ super unless anycabled? && !@__anycable_subscribing__
8
+ end
9
+
10
+ def handle_subscribe
11
+ @__anycable_subscribing__ = true
12
+ subscribe_to_channel
13
+ ensure
14
+ @__anycable_subscribing__ = false
15
+ end
16
+
6
17
  def start_periodic_timers
7
18
  super unless anycabled?
8
19
  end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AnyCable
4
+ module Rails
5
+ module Channel
6
+ # Presence API for Action Cable channels (backed by AnyCable)
7
+ module Presence
8
+ extend ActiveSupport::Concern
9
+
10
+ def join_presence(stream = nil, id: user_presence_id, info: user_presence_info)
11
+ return unless anycabled?
12
+
13
+ stream ||= connection.anycable_socket.streams[:start].first || raise(ArgumentError, "Provide a stream name for presence updates")
14
+
15
+ connection.anycable_socket.presence_join(stream, id, info)
16
+ end
17
+
18
+ def leave_presence(id = user_presence_id)
19
+ return unless anycabled?
20
+
21
+ connection.anycable_socket.presence_leave(id)
22
+ end
23
+
24
+ private
25
+
26
+ def user_presence_id
27
+ connection.connection_identifier
28
+ end
29
+
30
+ def user_presence_info
31
+ # nothing
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AnyCable
4
+ module Rails
5
+ module Channel
6
+ autoload :Presence, "anycable/rails/channel/presence"
7
+ end
8
+ end
9
+ end
@@ -31,65 +31,15 @@ module AnyCable
31
31
  end
32
32
 
33
33
  refine ActionCable::Connection::Subscriptions do
34
- # Override the original #execute_command to pre-initialize the channel for unsubscribe/message and
35
- # return true/false to indicate successful/unsuccessful subscription.
36
- # We also must not lose any exceptions raised in the process.
37
- def execute_rpc_command(data)
38
- # First, verify the channel name
39
- raise "Channel not found: #{ActiveSupport::JSON.decode(data["identifier"]).fetch("channel")}" unless subscription_class_from_identifier(data["identifier"])
40
-
41
- if data["command"] == "subscribe"
42
- add data
43
- subscription = subscriptions[data["identifier"]]
44
- return !(subscription.nil? || subscription.rejected?)
45
- end
46
-
47
- load(data["identifier"])
34
+ # Find or add a subscription to the list
35
+ def fetch(identifier)
36
+ add("identifier" => identifier) unless subscriptions[identifier]
48
37
 
49
- case data["command"]
50
- when "unsubscribe"
51
- remove data
52
- when "message"
53
- perform_action data
54
- when "whisper"
55
- whisper data
56
- else
57
- raise UnknownCommandError, data["command"]
38
+ unless subscriptions[identifier]
39
+ raise "Channel not found: #{ActiveSupport::JSON.decode(identifier).fetch("channel")}"
58
40
  end
59
41
 
60
- true
61
- end
62
-
63
- # Restore channels from the list of identifiers and the state
64
- def restore(subscriptions, istate)
65
- subscriptions.each do |id|
66
- channel = load(id)
67
- channel.__istate__ = ActiveSupport::JSON.decode(istate[id]) if istate[id]
68
- end
69
- end
70
-
71
- # Find or create a channel for a given identifier
72
- def load(identifier)
73
- return subscriptions[identifier] if subscriptions[identifier]
74
-
75
- subscription = subscription_from_identifier(identifier)
76
- raise "Channel not found: #{ActiveSupport::JSON.decode(identifier).fetch("channel")}" unless subscription
77
-
78
- subscriptions[identifier] = subscription
79
- end
80
-
81
- def subscription_class_from_identifier(id_key)
82
- id_options = ActiveSupport::JSON.decode(id_key).with_indifferent_access
83
- id_options[:channel].safe_constantize
84
- end
85
-
86
- def subscription_from_identifier(id_key)
87
- subscription_klass = subscription_class_from_identifier(id_key)
88
-
89
- if subscription_klass && subscription_klass < ActionCable::Channel::Base
90
- id_options = ActiveSupport::JSON.decode(id_key).with_indifferent_access
91
- subscription_klass.new(connection, id_key, id_options)
92
- end
42
+ subscriptions[identifier]
93
43
  end
94
44
  end
95
45
  end)
@@ -124,8 +74,15 @@ module AnyCable
124
74
  conn.cached_ids = {}
125
75
  conn.anycable_request_builder = self
126
76
 
77
+ return unless subscriptions
78
+
127
79
  # Pre-initialize channels (for disconnect)
128
- conn.subscriptions.restore(subscriptions, socket.istate) if subscriptions
80
+ subscriptions.each do |id|
81
+ channel = conn.subscriptions.fetch(id)
82
+ next unless socket.istate[id]
83
+
84
+ channel.__istate__ = ActiveSupport::JSON.decode(socket.istate[id])
85
+ end
129
86
  end
130
87
 
131
88
  def handle_open
@@ -154,8 +111,26 @@ module AnyCable
154
111
 
155
112
  def handle_channel_command(identifier, command, data)
156
113
  conn.run_callbacks :command do
157
- conn.subscriptions.execute_rpc_command({"command" => command, "identifier" => identifier, "data" => data})
114
+ # We cannot use subscriptions#execute_command here,
115
+ # since we MUST return true of false, depending on the status
116
+ # of execution
117
+ channel = conn.subscriptions.fetch(identifier)
118
+ case command
119
+ when "subscribe"
120
+ channel.handle_subscribe
121
+ !channel.rejected?
122
+ when "unsubscribe"
123
+ conn.subscriptions.remove_subscription(channel)
124
+ true
125
+ when "message"
126
+ channel.perform_action ActiveSupport::JSON.decode(data)
127
+ true
128
+ else
129
+ false
130
+ end
158
131
  end
132
+ # Support rescue_from
133
+ # https://github.com/rails/rails/commit/d2571e560c62116f60429c933d0c41a0e249b58b
159
134
  rescue Exception => e # rubocop:disable Lint/RescueException
160
135
  rescue_with_handler(e) || raise
161
136
  false
@@ -6,6 +6,20 @@ module AnyCable
6
6
  module SerializableIdentification
7
7
  extend ActiveSupport::Concern
8
8
 
9
+ module ConnectionGID
10
+ def connection_identifier
11
+ unless defined? @connection_identifier
12
+ @connection_identifier = connection_gid identifiers.filter_map { |id| instance_variable_get(:"@#{id}") || __send__(id) }
13
+ end
14
+
15
+ @connection_identifier
16
+ end
17
+ end
18
+
19
+ included do
20
+ prepend ConnectionGID
21
+ end
22
+
9
23
  class_methods do
10
24
  def identified_by(*identifiers)
11
25
  super
@@ -123,11 +123,12 @@ module AnyCable
123
123
 
124
124
  initializer "anycable.verify_pool_sizes" do
125
125
  next if AnyCable.config.disable_rpc_pool_size_warning?
126
- # Skip if non-gRPC server is used
127
- next unless AnyCable.config.respond_to?(:rpc_pool_size)
128
126
 
129
127
  # Log current db vs. gRPC pool sizes
130
128
  AnyCable.configure_server do
129
+ # Skip if non-gRPC server is used
130
+ next unless AnyCable.config.respond_to?(:rpc_pool_size)
131
+
131
132
  ActiveSupport.on_load(:active_record) do
132
133
  db_pool_size = ::ActiveRecord::Base.connection_pool.size
133
134
  rpc_pool_size = AnyCable.config.rpc_pool_size
@@ -2,6 +2,6 @@
2
2
 
3
3
  module AnyCable
4
4
  module Rails
5
- VERSION = "1.5.6"
5
+ VERSION = "1.6.0.rc.1"
6
6
  end
7
7
  end
@@ -9,6 +9,7 @@ require "globalid"
9
9
  require "active_support/core_ext/module/attribute_accessors_per_thread"
10
10
 
11
11
  require "anycable/rails/ext"
12
+ require "anycable/rails/channel"
12
13
 
13
14
  module AnyCable
14
15
  # Rails handler for AnyCable
metadata CHANGED
@@ -1,29 +1,35 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: anycable-rails-core
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.5.6
4
+ version: 1.6.0.rc.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - palkan
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-01-15 00:00:00.000000000 Z
11
+ date: 2025-01-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: anycable-core
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 1.6.0.rc.1
20
+ - - "<"
18
21
  - !ruby/object:Gem::Version
19
- version: 1.5.0
22
+ version: 1.7.0
20
23
  type: :runtime
21
24
  prerelease: false
22
25
  version_requirements: !ruby/object:Gem::Requirement
23
26
  requirements:
24
- - - "~>"
27
+ - - ">="
25
28
  - !ruby/object:Gem::Version
26
- version: 1.5.0
29
+ version: 1.6.0.rc.1
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: 1.7.0
27
33
  - !ruby/object:Gem::Dependency
28
34
  name: actioncable
29
35
  requirement: !ruby/object:Gem::Requirement
@@ -77,6 +83,8 @@ files:
77
83
  - lib/anycable/rails/action_cable_ext/channel.rb
78
84
  - lib/anycable/rails/action_cable_ext/connection.rb
79
85
  - lib/anycable/rails/action_cable_ext/remote_connections.rb
86
+ - lib/anycable/rails/channel.rb
87
+ - lib/anycable/rails/channel/presence.rb
80
88
  - lib/anycable/rails/channel_state.rb
81
89
  - lib/anycable/rails/compatibility.rb
82
90
  - lib/anycable/rails/compatibility/rubocop.rb
@@ -131,7 +139,7 @@ metadata:
131
139
  homepage_uri: https://anycable.io/
132
140
  source_code_uri: http://github.com/anycable/anycable-rails
133
141
  funding_uri: https://github.com/sponsors/anycable
134
- post_install_message:
142
+ post_install_message:
135
143
  rdoc_options: []
136
144
  require_paths:
137
145
  - lib
@@ -142,12 +150,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
142
150
  version: '2.7'
143
151
  required_rubygems_version: !ruby/object:Gem::Requirement
144
152
  requirements:
145
- - - ">="
153
+ - - ">"
146
154
  - !ruby/object:Gem::Version
147
- version: '0'
155
+ version: 1.3.1
148
156
  requirements: []
149
157
  rubygems_version: 3.4.19
150
- signing_key:
158
+ signing_key:
151
159
  specification_version: 4
152
160
  summary: AnyCable integration for Rails (w/o RPC dependencies)
153
161
  test_files: []