anycable-rails 1.0.0.preview2 → 1.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (32) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +14 -125
  3. data/README.md +14 -34
  4. data/lib/anycable/rails.rb +36 -2
  5. data/lib/anycable/rails/actioncable/channel.rb +4 -0
  6. data/lib/anycable/rails/actioncable/connection.rb +24 -35
  7. data/lib/anycable/rails/actioncable/connection/serializable_identification.rb +42 -0
  8. data/lib/anycable/rails/actioncable/remote_connections.rb +11 -0
  9. data/lib/anycable/rails/channel_state.rb +48 -0
  10. data/lib/anycable/rails/compatibility.rb +4 -7
  11. data/lib/anycable/rails/compatibility/rubocop.rb +0 -1
  12. data/lib/anycable/rails/compatibility/rubocop/config/default.yml +3 -0
  13. data/lib/anycable/rails/compatibility/rubocop/cops/anycable/instance_vars.rb +1 -1
  14. data/lib/anycable/rails/compatibility/rubocop/cops/anycable/stream_from.rb +4 -4
  15. data/lib/anycable/rails/railtie.rb +10 -10
  16. data/lib/anycable/rails/refinements/subscriptions.rb +5 -0
  17. data/lib/anycable/rails/session_proxy.rb +19 -2
  18. data/lib/anycable/rails/version.rb +1 -1
  19. data/lib/generators/anycable/download/USAGE +14 -0
  20. data/lib/generators/anycable/download/download_generator.rb +77 -0
  21. data/lib/generators/anycable/setup/USAGE +2 -0
  22. data/lib/{rails/generators → generators}/anycable/setup/setup_generator.rb +67 -89
  23. data/lib/{rails/generators → generators}/anycable/setup/templates/Procfile.dev +0 -0
  24. data/lib/{rails/generators → generators}/anycable/setup/templates/config/anycable.yml.tt +12 -1
  25. data/lib/generators/anycable/setup/templates/config/cable.yml.tt +11 -0
  26. data/lib/{rails/generators/anycable/setup/templates/config/initializers/anycable.rb → generators/anycable/setup/templates/config/initializers/anycable.rb.tt} +1 -1
  27. data/lib/generators/anycable/with_os_helpers.rb +55 -0
  28. metadata +23 -47
  29. data/lib/anycable/rails/compatibility/rubocop/cops/anycable/remote_disconnect.rb +0 -31
  30. data/lib/rails/generators/anycable/setup/templates/Procfile +0 -2
  31. data/lib/rails/generators/anycable/setup/templates/bin/heroku-web +0 -7
  32. data/lib/rails/generators/anycable/setup/templates/config/cable.yml.tt +0 -11
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 93c88ec3ccf3eb9d145474e43aa8a851e32768de9b7c8ce1e6eb44cc0b24b0e9
4
- data.tar.gz: b12bc69aa43afc035038ee85c80bce57b9beabe36d9d0b93f261125f3af32876
3
+ metadata.gz: 36b6e47a498bfc2d1156e4635a5c30f60258c925142c59ad837c1b6708f1ff73
4
+ data.tar.gz: f27c94856df0db9d178537e8ac75598f8a355b338e725a80a5cb06a6e0ac610c
5
5
  SHA512:
6
- metadata.gz: 387d765e0d5f15c38d211e206eccc04982c4d20c4855539de49caefa58caffd0dd7a949f73817d576fc19b97a1e6359d0dd194520a3e9538b18aee987a5aa99e
7
- data.tar.gz: c75876c69f2655296cefd54b3ac9cba5fdefcdabbea206c526d9ddb502fcb61095e2855582d5ca17884fdadf70e6e4406a43b5e21470b7ade8b6027064903c1e
6
+ metadata.gz: 28b72e3f6a9950d3a95eb8d4f1c6a59f24a704ff63ac50384ffb2d22aa97c9f6b6e1f0c882a89cc6a0fd9ac22ea553a381514d56885f6f1cce41e7146153ab24
7
+ data.tar.gz: ac7c402d2b57a332ced9372a4546709d6b41a0fb082c823d292b87c8b11e4bf741047cbc10258f68acd228712f0ca974b6723246bd3ddc689920f656b1488974
@@ -1,6 +1,18 @@
1
1
  # Change log
2
2
 
3
- ## 🚧 1.0.0 (_coming soon_)
3
+ ## 1.0.0.rc1 (2020-06-10)
4
+
5
+ - Add `state_attr_accessor` for channels. ([@palkan][])
6
+
7
+ Just like `attr_accessor` but "persists" the state between RPC calls.
8
+
9
+ - Add `Channel#stop_stream_from` support. ([@palkan][])
10
+
11
+ - Add `RemoteConnections` support. ([@palkan][])
12
+
13
+ - Add `AnyCable::Rails.enabled?` method which returns true if Action Cable uses AnyCable adapter. ([@palkan][])
14
+
15
+ - Add `anycable:download` generator to download `anycable-go` binary. ([@palkan][])
4
16
 
5
17
  - **Ruby 2.5+ is required**. ([@palkan][])
6
18
 
@@ -23,130 +35,7 @@ You must turn this feature on by setting `persistent_session_enabled: true` in t
23
35
 
24
36
  - Add ability to develop the gem with Docker. ([@bibendi][])
25
37
 
26
- ## 0.6.4 (2019-06-26) 👶
27
-
28
- - Fix Compatibility bug when using with AnyCable. ([@palkan][])
29
-
30
- Compatibility patching (with `prepend` + `super`) conflicted with
31
- the `ActionCable::Channel::Base` patching in the core functionality (which uses `alias`).
32
-
33
- See [#95](https://github.com/anycable/anycable-rails/issues/95).
34
-
35
- ## 0.6.3 (2019-03-26)
36
-
37
- - Fix connection factory reloading for development sake. ([@sponomarev][])
38
-
39
- - Add `:anycable` subscription adapter alias. ([@sponomarev][])
40
-
41
- - Don't set AnyCable connection factory for incompatible adapter ([@sponomarev][])
42
-
43
- `anycable` server won't start with unpatched vanilla `ApplicationCable::Connection`.
44
-
45
- Motivation in [#74](https://github.com/anycable/anycable-rails/issues/74).
46
-
47
- - Fix instance detection inside complex cases in compatibility cops ([@sponomarev][])
48
-
49
- ## 0.6.2 (2019-01-10)
50
-
51
- - Fixed `anycable` 0.6.1 compatibility. ([@palkan][])
52
-
53
- - Broadcast logs to STDOUT in development only when server is running. ([@palkan][])
54
-
55
- Fixes #59.
56
-
57
- ## 0.6.1 (2018-11-15)
58
-
59
- - Fix regression introduced in [e64a366e](https://github.com/anycable/anycable-rails/commit/e64a366ea21293925e0c5c0b8e6595d65d5d0981#diff-fd0e56a6e825002eac978507c3581af7R14) ([@palkan][])
60
-
61
- `Connection` patch could be loaded after `identify_by` is called, thus breaking
62
- identifiers.
63
-
64
- ## 0.6.0 (2018-11-15)
65
-
66
- **NOTE**: this version has been yanked from RubyGems due to the regression bug. Use 0.6.1.
67
-
68
- - [PR #56](https://github.com/anycable/anycable-rails/pull/56) Request verification based on ActionCable config. ([@DmitryTsepelev][])
69
-
70
- - Add WS server session ID to log tags if present. ([@palkan][])
71
-
72
- - Support tagged logging. ([@palkan][])
73
-
74
- - Action Cable monkey-patches are only loaded in the context of AnyCable CLI. ([@palkan][])
75
-
76
- No need to think about `requie` and `group` for `anycable-rails`, just add it to Gemfile.
77
-
78
- - Add `:any_cable` subscription adapter for Action Cable. ([@palkan][])
79
-
80
- Use `:any_cable` adapter for Action Cable to broadcast data to AnyCable.
81
-
82
- No more `pubsub` monkey-patches 🎉.
83
-
84
- - Added Rails executor/reloader support. ([@palkan][])
85
-
86
- - **[Breaking]** No more generators. ([@palkan][])
87
-
88
- No need to generate AnyCable runner script since `anycable` gem ships with
89
- the CLI.
90
-
91
- - Add dynamic (`AnyCable::CompatibilityError`) compatibility checks. ([@DmitryTsepelev][])
92
-
93
- - Added static (RuboCop) compatibility checks. ([@DmitryTsepelev][])
94
-
95
- See https://github.com/anycable/anycable-rails/issues/52
96
-
97
- ## 0.5.4 (2018-06-13)
98
-
99
- - Fix duplicate logs in development. ([@palkan][])
100
-
101
- Fixes https://github.com/anycable/anycable_demo/issues/5.
102
-
103
- ## 0.5.3
104
-
105
- - Fix return value of `Connection#handle_close`. ([@palkan][])
106
-
107
- Should always be `true`, we do not expect a failure here.
108
-
109
- ## 0.5.2
110
-
111
- - Add config/anycable.yml to Rails generator. ([@alekseyl][])
112
-
113
- ## 0.5.1
114
-
115
- - Improve Rails integration. ([@palkan][])
116
-
117
- Log to STDOUT in development.
118
- Make order of initializers more deterministic.
119
- Show warning if AnyCable is loaded after application initialization.
120
-
121
- ## 0.5.0
122
-
123
- - [#17](https://github.com/anycable/anycable-rails/issues/17) Refactor logging. ([@palkan][])
124
-
125
- Use Rails logger everywhere.
126
-
127
- Add access logs ([anycable/anycable#20](https://github.com/anycable/anycable/issues/20)).
128
-
129
- ## 0.4.7
130
-
131
- - Minor fixes. ([@palkan][])
132
-
133
- ## 0.4.6
134
-
135
- - Disable mounting default Action Cable server when AnyCable is loaded. ([@palkan][])
136
-
137
- ## 0.4.5
138
-
139
- - Handle tagged logger. ([@palkan][])
140
-
141
- Ignore tagged logger features ('cause we do not have _persistent_ logger).
142
-
143
- ## 0.4.4
144
-
145
- - Fix bug with ActiveRecord connections (https://github.com/anycable/anycable/issues/9). ([@palkan][])
146
-
147
- ## 0.4.0
148
-
149
- - Initial version. ([@palkan][])
38
+ See [Changelog](https://github.com/anycable/anycable-rails/blob/0-6-stable/CHANGELOG.md) for versions <1.0.0.
150
39
 
151
40
  [@palkan]: https://github.com/palkan
152
41
  [@alekseyl]: https://github.com/alekseyl
data/README.md CHANGED
@@ -1,6 +1,8 @@
1
- [![GitPitch](https://gitpitch.com/assets/badge.svg)](https://gitpitch.com/anycable/anycable/master?grs=github) [![Gem Version](https://badge.fury.io/rb/anycable-rails.svg)](https://rubygems.org/gems/anycable-rails) [![Build Status](https://travis-ci.org/anycable/anycable-rails.svg?branch=master)](https://travis-ci.org/anycable/anycable-rails)
1
+ [![GitPitch](https://gitpitch.com/assets/badge.svg)](https://gitpitch.com/anycable/anycable/master?grs=github)
2
+ [![Gem Version](https://badge.fury.io/rb/anycable-rails.svg)](https://rubygems.org/gems/anycable-rails)
3
+ [![Build](https://github.com/anycable/anycable-rails/workflows/Build/badge.svg)](https://github.com/anycable/anycable-rails/actions)
2
4
  [![Gitter](https://img.shields.io/badge/gitter-join%20chat%20%E2%86%92-brightgreen.svg)](https://gitter.im/anycable/Lobby)
3
- [![Documentation](https://img.shields.io/badge/docs-link-brightgreen.svg)](https://docs.anycable.io/#/ruby/rails)
5
+ [![Documentation](https://img.shields.io/badge/docs-link-brightgreen.svg)](https://docs.anycable.io/v1/#/ruby/rails)
4
6
 
5
7
  # AnyCable Rails
6
8
 
@@ -10,10 +12,11 @@ With AnyCable you can use channels, client-side JS, broadcasting - (almost) all
10
12
 
11
13
  You can even use Action Cable in development and not be afraid of [compatibility issues](#compatibility).
12
14
 
13
- 💾 [Example Application](https://github.com/anycable/anycable_demo)
15
+ **Important** This is a readme for the upcoming v1.0 release. For v0.6.x see the readme from the [0-6-stable](https://github.com/anycable/anycable-rails/tree/0-6-stable) branch.
14
16
 
15
- 📑 [Documentation](https://docs.anycable.io).
17
+ <!-- 💾 [Example Application](https://github.com/anycable/anycable_demo) -->
16
18
 
19
+ 📑 [Documentation](https://docs.anycable.io/v1/#/ruby/rails).
17
20
 
18
21
  <a href="https://evilmartians.com/">
19
22
  <img src="https://evilmartians.com/badges/sponsored-by-evil-martians.svg" alt="Sponsored by Evil Martians" width="236" height="54"></a>
@@ -21,12 +24,8 @@ You can even use Action Cable in development and not be afraid of [compatibility
21
24
  ## Requirements
22
25
 
23
26
  - Ruby >= 2.5
24
- - Rails >= 5.0;
25
- - Redis (see [other options]() for broadcasting)
26
-
27
- ## How It Works?
28
-
29
- <img src="https://trello-attachments.s3.amazonaws.com/5781e0ed48e4679e302833d3/820x987/5b6a305417b04e20e75f49c5816e027c/Anycable_vs_ActionCable_copy.jpg" width="400" />
27
+ - Rails >= 5.2
28
+ - Redis (see [other options](https://github.com/anycable/anycable/issues/2) for broadcasting)
30
29
 
31
30
  ## Usage
32
31
 
@@ -39,14 +38,12 @@ gem "anycable-rails"
39
38
  gem "redis", ">= 4.0"
40
39
  ```
41
40
 
42
- (and don't forget to run `bundle install`).
43
-
44
41
  ### Interactive set up
45
42
 
46
43
  After the gem was installed, you can run an interactive wizard to configure your Rails application for using with AnyCable by running a generator:
47
44
 
48
45
  ```sh
49
- bin/rails g anycable:setup
46
+ bundle exec rails g anycable:setup
50
47
  ```
51
48
 
52
49
  ### Manual set up
@@ -86,38 +83,21 @@ $ bundle exec anycable
86
83
  $ RAILS_ENV=production bundle exec anycable
87
84
  ```
88
85
 
89
- And, finally, run AnyCable WebSocket server, e.g. [anycable-go](https://docs.anycable.io/#/anycable-go/getting_started):
86
+ And, finally, run AnyCable WebSocket server, e.g. [anycable-go](https://docs.anycable.io/v1/#/anycable-go/getting_started):
90
87
 
91
88
  ```sh
92
89
  anycable-go --host=localhost --port=3334
93
90
  ```
94
91
 
95
- See [documentation](https://docs.anycable.io/#/ruby/rails) for more information on AnyCable + Rails usage.
92
+ See [documentation](https://docs.anycable.io/v1/#/ruby/rails) for more information on AnyCable + Rails usage.
96
93
 
97
94
  ## Action Cable Compatibility
98
95
 
99
- See [documentation](https://docs.anycable.io/#/ruby/compatibility).
100
-
101
- ## Links
102
-
103
- - [AnyCable: Action Cable on steroids!](https://evilmartians.com/chronicles/anycable-actioncable-on-steroids)
104
-
105
- - [From Action to Any](https://medium.com/@leshchuk/from-action-to-any-1e8d863dd4cf) by [@alekseyl](https://github.com/alekseyl)
106
-
107
- ## Talks
108
-
109
- - One cable to rule them all, RubyKaigi 2018, [slides](https://speakerdeck.com/palkan/rubykaigi-2018-anycable-one-cable-to-rule-them-all) and [video](https://www.youtube.com/watch?v=jXCPuNICT8s) (EN)
110
-
111
- - Wroc_Love.rb 2018 [slides](https://speakerdeck.com/palkan/wroc-love-dot-rb-2018-cables-cables-cables) and [video](https://www.youtube.com/watch?v=AUxFFOehiy0) (EN)
112
-
113
- ## Compatible WebSocket servers
114
-
115
- - [AnyCable Go](https://github.com/anycable/anycable-go)
116
- - [ErlyCable](https://github.com/anycable/erlycable)
96
+ See [documentation](https://docs.anycable.io/v1/#/ruby/compatibility).
117
97
 
118
98
  ## Contributing
119
99
 
120
- Bug reports and pull requests are welcome on GitHub at https://github.com/anycable/anycable-rails.
100
+ Bug reports and pull requests are welcome on GitHub at [https://github.com/anycable/anycable-rails](https://github.com/anycable/anycable-rails).
121
101
 
122
102
  ## Development
123
103
 
@@ -5,6 +5,8 @@ require "anycable/rails/version"
5
5
  require "anycable/rails/config"
6
6
  require "anycable/rails/rack"
7
7
 
8
+ require "globalid"
9
+
8
10
  module AnyCable
9
11
  # Rails handler for AnyCable
10
12
  module Rails
@@ -12,8 +14,40 @@ module AnyCable
12
14
 
13
15
  ADAPTER_ALIASES = %w[any_cable anycable].freeze
14
16
 
15
- def self.compatible_adapter?(adapter)
16
- ADAPTER_ALIASES.include?(adapter)
17
+ class << self
18
+ def enabled?
19
+ adapter = ::ActionCable.server.config.cable&.fetch("adapter", nil)
20
+ compatible_adapter?(adapter)
21
+ end
22
+
23
+ def compatible_adapter?(adapter)
24
+ ADAPTER_ALIASES.include?(adapter)
25
+ end
26
+
27
+ # Serialize connection/channel state variable to string
28
+ # using GlobalID where possible or JSON (if json: true)
29
+ def serialize(obj, json: false)
30
+ obj.try(:to_gid_param) || (json ? obj.to_json : obj)
31
+ end
32
+
33
+ # Deserialize previously serialized value from string to
34
+ # Ruby object.
35
+ # If the resulting object is a Hash, make it indifferent
36
+ def deserialize(str, json: false)
37
+ str.yield_self do |val|
38
+ next unless val.is_a?(String)
39
+
40
+ gval = GlobalID::Locator.locate(val)
41
+ return gval if gval
42
+
43
+ next val unless json
44
+
45
+ JSON.parse(val)
46
+ end.yield_self do |val|
47
+ next val.with_indifferent_access if val.is_a?(Hash)
48
+ val
49
+ end
50
+ end
17
51
  end
18
52
  end
19
53
  end
@@ -28,6 +28,10 @@ module ActionCable
28
28
  connection.socket.subscribe identifier, broadcasting
29
29
  end
30
30
 
31
+ def stop_stream_from(broadcasting)
32
+ connection.socket.unsubscribe identifier, broadcasting
33
+ end
34
+
31
35
  def stop_all_streams
32
36
  connection.socket.unsubscribe_from_all identifier
33
37
  end
@@ -1,8 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "action_cable/connection"
4
+ require "anycable/rails/actioncable/connection/serializable_identification"
4
5
  require "anycable/rails/refinements/subscriptions"
5
6
  require "anycable/rails/actioncable/channel"
7
+ require "anycable/rails/actioncable/remote_connections"
6
8
  require "anycable/rails/session_proxy"
7
9
 
8
10
  module ActionCable
@@ -15,26 +17,26 @@ module ActionCable
15
17
 
16
18
  using AnyCable::Refinements::Subscriptions
17
19
 
20
+ include SerializableIdentification
21
+
18
22
  attr_reader :socket
19
23
 
20
24
  delegate :env, :session, to: :request
21
25
 
22
26
  class << self
23
27
  def call(socket, **options)
24
- new(socket, options)
28
+ new(socket, nil, options)
25
29
  end
30
+ end
26
31
 
27
- def identified_by(*identifiers)
28
- super
29
- Array(identifiers).each do |identifier|
30
- define_method(identifier) do
31
- instance_variable_get(:"@#{identifier}") || fetch_identifier(identifier)
32
- end
33
- end
32
+ def initialize(socket, env, identifiers: "{}", subscriptions: [])
33
+ if env
34
+ # If env is set, then somehow we're in the context of Action Cable
35
+ # Return and print a warning in #process
36
+ @request = ActionDispatch::Request.new(env)
37
+ return
34
38
  end
35
- end
36
39
 
37
- def initialize(socket, identifiers: "{}", subscriptions: [])
38
40
  @ids = ActiveSupport::JSON.decode(identifiers)
39
41
 
40
42
  @ltags = socket.cstate.read(LOG_TAGS_IDENTIFIER).yield_self do |raw_tags|
@@ -51,6 +53,18 @@ module ActionCable
51
53
  subscriptions.each { |id| @subscriptions.fetch(id) }
52
54
  end
53
55
 
56
+ def process
57
+ # Use Rails logger here to print to stdout in development
58
+ logger.error invalid_request_message
59
+ logger.info finished_request_message
60
+ [404, {"Content-Type" => "text/plain"}, ["Page not found"]]
61
+ end
62
+
63
+ def invalid_request_message
64
+ "You're trying to connect to Action Cable server while using AnyCable. " \
65
+ "See https://docs.anycable.io/v1/#/troubleshooting?id=server-raises-an-argumenterror-exception-when-client-tries-to-connect"
66
+ end
67
+
54
68
  def handle_open
55
69
  logger.info started_request_message if access_logs?
56
70
 
@@ -107,31 +121,6 @@ module ActionCable
107
121
  socket.transmit encode(cable_message)
108
122
  end
109
123
 
110
- # Generate identifiers info.
111
- # Converts GlobalID compatible vars to corresponding global IDs params.
112
- def identifiers_hash
113
- identifiers.each_with_object({}) do |id, acc|
114
- obj = instance_variable_get("@#{id}")
115
- next unless obj
116
-
117
- acc[id] = obj.try(:to_gid_param) || obj
118
- end.compact
119
- end
120
-
121
- def identifiers_json
122
- identifiers_hash.to_json
123
- end
124
-
125
- # Fetch identifier and deserialize if neccessary
126
- def fetch_identifier(name)
127
- @cached_ids[name] ||= @cached_ids.fetch(name) do
128
- val = ids[name.to_s]
129
- next val unless val.is_a?(String)
130
-
131
- GlobalID::Locator.locate(val) || val
132
- end
133
- end
134
-
135
124
  def logger
136
125
  @logger ||= TaggedLoggerProxy.new(AnyCable.logger, tags: ltags || [])
137
126
  end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionCable
4
+ module Connection
5
+ module SerializableIdentification
6
+ extend ActiveSupport::Concern
7
+
8
+ class_methods do
9
+ def identified_by(*identifiers)
10
+ super
11
+ Array(identifiers).each do |identifier|
12
+ define_method(identifier) do
13
+ instance_variable_get(:"@#{identifier}") || fetch_identifier(identifier)
14
+ end
15
+ end
16
+ end
17
+ end
18
+
19
+ # Generate identifiers info.
20
+ # Converts GlobalID compatible vars to corresponding global IDs params.
21
+ def identifiers_hash
22
+ identifiers.each_with_object({}) do |id, acc|
23
+ obj = instance_variable_get("@#{id}")
24
+ next unless obj
25
+
26
+ acc[id] = AnyCable::Rails.serialize(obj)
27
+ end.compact
28
+ end
29
+
30
+ def identifiers_json
31
+ identifiers_hash.to_json
32
+ end
33
+
34
+ # Fetch identifier and deserialize if neccessary
35
+ def fetch_identifier(name)
36
+ @cached_ids[name] ||= @cached_ids.fetch(name) do
37
+ AnyCable::Rails.deserialize(ids[name.to_s])
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end