Skip to content

Commit

Permalink
chore: bring back backward compatibility
Browse files Browse the repository at this point in the history
  • Loading branch information
palkan committed Sep 30, 2024
1 parent ddb981a commit 3bf488a
Show file tree
Hide file tree
Showing 21 changed files with 763 additions and 250 deletions.
96 changes: 47 additions & 49 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name: Build
on:
push:
branches:
- master
- master
pull_request:
workflow_dispatch:

Expand All @@ -28,37 +28,35 @@ jobs:
fail-fast: false
matrix:
ruby: ["3.3"]
gemfile: ["railsmaster"]
# include:
# - ruby: "3.3"
# gemfile: "railsmaster"
# - ruby: "3.2"
# gemfile: "anycablemaster"
# - ruby: "3.2"
# gemfile: "rails8"
gemfile: ["rails7", "rails8"]
next: ["0", "1"]
include:
- ruby: "3.2"
gemfile: "anycablemaster"
next: "0"
steps:
- uses: actions/checkout@v4
- name: Install system deps
run: |
sudo apt-get update
sudo apt-get install libsqlite3-dev
- uses: ruby/setup-ruby@v1
with:
ruby-version: ${{ matrix.ruby }}
bundler-cache: true
- name: Install Coveralls reporter
run: |
curl -L https://github.com/coverallsapp/coverage-reporter/releases/latest/download/coveralls-linux.tar.gz | tar zxv
- name: Run RSpec
continue-on-error: ${{ matrix.allow_failure }}
run: |
bundle exec rake spec
./coveralls -p --job-flag=ruby-${{ matrix.ruby }}-${{ matrix.gemfile }}
- name: Run compatibility specs
continue-on-error: ${{ matrix.allow_failure }}
run: |
bundle exec rake spec:compatibility
./coveralls -p --job-flag=compatibility-${{ matrix.ruby }}-${{ matrix.gemfile }}
- uses: actions/checkout@v4
- name: Install system deps
run: |
sudo apt-get update
sudo apt-get install libsqlite3-dev
- uses: ruby/setup-ruby@v1
with:
ruby-version: ${{ matrix.ruby }}
bundler-cache: true
- name: Install Coveralls reporter
run: |
curl -L https://github.com/coverallsapp/coverage-reporter/releases/latest/download/coveralls-linux.tar.gz | tar zxv
- name: Run RSpec
continue-on-error: ${{ matrix.allow_failure }}
run: |
bundle exec rake spec
./coveralls -p --job-flag=ruby-${{ matrix.ruby }}-${{ matrix.gemfile }}
- name: Run compatibility specs
continue-on-error: ${{ matrix.allow_failure }}
run: |
bundle exec rake spec:compatibility
./coveralls -p --job-flag=compatibility-${{ matrix.ruby }}-${{ matrix.gemfile }}
anyt:
if: ${{ !contains(github.event.head_commit.message, '[ci skip tests]') }}
Expand All @@ -75,28 +73,28 @@ jobs:
ports: ["6379:6379"]
options: --health-cmd="redis-cli ping" --health-interval 1s --health-timeout 3s --health-retries 30
steps:
- uses: actions/checkout@v4
- name: Install system deps
run: |
sudo apt-get update
sudo apt-get install libsqlite3-dev
- uses: ruby/setup-ruby@v1
with:
ruby-version: 3.3
bundler-cache: true
- name: Run conformance tests with Anyt
run: |
bundle exec anyt --self-check --require=etc/anyt/*.rb
- uses: actions/checkout@v4
- name: Install system deps
run: |
sudo apt-get update
sudo apt-get install libsqlite3-dev
- uses: ruby/setup-ruby@v1
with:
ruby-version: 3.3
bundler-cache: true
- name: Run conformance tests with Anyt
run: |
bundle exec anyt --self-check --require=etc/anyt/*.rb
coverage:
needs: rspec
runs-on: ubuntu-latest
env:
COVERALLS_REPO_TOKEN: ${{ secrets.github_token }}
steps:
- name: Install Coveralls reporter
run: |
curl -L https://github.com/coverallsapp/coverage-reporter/releases/latest/download/coveralls-linux.tar.gz | tar zxv
- name: Finilize Coveralls build
run: |
./coveralls -d
- name: Install Coveralls reporter
run: |
curl -L https://github.com/coverallsapp/coverage-reporter/releases/latest/download/coveralls-linux.tar.gz | tar zxv
- name: Finilize Coveralls build
run: |
./coveralls -d
8 changes: 8 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@ gemspec name: "anycable-rails"

gem "debug", platform: :mri

if ENV["NEXT_ACTION_CABLE"] == "1"
if File.directory?(File.join(__dir__, "..", "actioncable-next"))
gem "actioncable-next", path: "../actioncable-next", require: false
else
gem "actioncable-next", require: false
end
end

local_gemfile = "#{File.dirname(__FILE__)}/Gemfile.local"

eval_gemfile "gemfiles/rubocop.gemfile"
Expand Down
17 changes: 17 additions & 0 deletions Gemfile._local
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
path "../../rails" do
gem 'activerecord'
gem 'actioncable'
gem 'activejob'
gem 'activesupport'
gem 'railties'
end
gem 'anycable', path: '../anycable'
path '../../rspec' do
gem 'rspec-core'
gem 'rspec-support'
gem 'rspec-expectations'
gem 'rspec-rails'
end

gem "anyt", path: "../anyt"
gem "puma"
2 changes: 1 addition & 1 deletion anycable-rails-core.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,6 @@ Gem::Specification.new do |spec|
spec.required_ruby_version = ">= 2.7"

spec.add_dependency "anycable-core", "~> 1.5.0"
spec.add_dependency "actioncable", "> 7.2"
spec.add_dependency "actioncable", ">= 7.0", "< 9.0"
spec.add_dependency "globalid"
end
9 changes: 9 additions & 0 deletions gemfiles/rails7.gemfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
source "https://rubygems.org"

gem "actioncable", "~> 7.0"
gem "activerecord"
gem "activejob"
gem "rspec-rails"
gem "sqlite3"

gemspec path: "..", name: "anycable-rails"
2 changes: 1 addition & 1 deletion gemfiles/rails8.gemfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
source "https://rubygems.org"

gem "actioncable", "~> 8.0"
gem "actioncable", "~> 8.0.0.beta1"
gem "activerecord"
gem "activejob"
gem "rspec-rails"
Expand Down
10 changes: 2 additions & 8 deletions gemfiles/railsmaster.gemfile
Original file line number Diff line number Diff line change
@@ -1,13 +1,7 @@
source "https://rubygems.org"

gem "rails", git: "https://github.com/palkan/rails.git", branch: "refactor/action-cable-server-adapterization"
gem "rspec-rails", git: "https://github.com/palkan/rspec-rails.git", branch: "feat/actioncable-v8"

gem "rspec-core", git: "https://github.com/rspec/rspec-core.git"
gem "rspec-support", git: "https://github.com/rspec/rspec-support.git"
gem "rspec-expectations", git: "https://github.com/rspec/rspec-expectations.git"
gem "rspec-mocks", git: "https://github.com/rspec/rspec-mocks.git"

gem "rails", git: "https://github.com/rails/rails.git", branch: "mail"
gem "rspec-rails"
gem "sqlite3"

gemspec path: "..", name: "anycable-rails"
79 changes: 61 additions & 18 deletions lib/anycable/rails/action_cable_ext/channel.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,34 +3,77 @@
require "action_cable"

ActionCable::Channel::Base.prepend(Module.new do
# Whispering support
def whispers_to(broadcasting)
def subscribe_to_channel
super unless anycabled? && !@__anycable_subscribing__
end

def handle_subscribe
@__anycable_subscribing__ = true
subscribe_to_channel
ensure
@__anycable_subscribing__ = false
end

def start_periodic_timers
super unless anycabled?
end

def stop_periodic_timers
super unless anycabled?
end

def stream_from(broadcasting, _callback = nil, **opts)
whispering = opts.delete(:whisper)
return super unless anycabled?

connection.anycable_socket.whisper identifier, broadcasting
broadcasting = String(broadcasting)

connection.anycable_socket.subscribe identifier, broadcasting
if whispering
connection.anycable_socket.whisper identifier, broadcasting
end
end

def stream_for(model, callback = nil, **opts, &block)
stream_from(broadcasting_for(model), callback || block, **opts)
end

# Unsubscribing relies on the channel state (which is not persistent in AnyCable).
# Thus, we pretend that the stream is registered to make Action Cable do its unsubscribing job.
def stop_stream_from(broadcasting)
streams[broadcasting] = true if anycabled?
super
return super unless anycabled?

connection.anycable_socket.unsubscribe identifier, broadcasting
end

# For AnyCable, unsubscribing from all streams is a separate operation,
# so we use a special constant to indicate it.
def stop_all_streams
if anycabled?
streams.clear
streams[AnyCable::Rails::Server::PubSub::ALL_STREAMS] = true
end
super
end
return super unless anycabled?

# Make rejected status accessible from outside
def rejected? = subscription_rejected?
connection.anycable_socket.unsubscribe_from_all identifier
end

private

def anycabled? = connection.anycabled?
def anycabled?
# Use instance variable check here for testing compatibility
connection.instance_variable_defined?(:@anycable_socket)
end
end)

# Handle $pubsub channel in Subscriptions
ActionCable::Connection::Subscriptions.prepend(Module.new do
# The contents are mostly copied from the original,
# there is no good way to configure channels mapping due to #safe_constantize
# and the layers of JSON
# https://github.com/rails/rails/blob/main/actioncable/lib/action_cable/connection/subscriptions.rb
def add(data)
id_key = data["identifier"]
id_options = ActiveSupport::JSON.decode(id_key).with_indifferent_access

return if subscriptions.key?(id_key)

return super unless id_options[:channel] == "$pubsub"

subscription = AnyCable::Rails::PubSubChannel.new(connection, id_key, id_options)
subscriptions[id_key] = subscription
subscription.subscribe_to_channel
end
end)
91 changes: 76 additions & 15 deletions lib/anycable/rails/action_cable_ext/connection.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,86 @@

ActionCable::Connection::Base.include(AnyCable::Rails::Connections::SerializableIdentification)
ActionCable::Connection::Base.prepend(Module.new do
def anycabled?
anycable_socket
end
attr_reader :anycable_socket
attr_accessor :anycable_request_builder

# Allow overriding #subscriptions to use a custom implementation
attr_writer :subscriptions
# In AnyCable, we lazily populate env by passing it through the middleware chain,
# so we access it via #request
def env
return super unless anycabled?

# Alias for the #socket which is only set within AnyCable RPC context
attr_accessor :anycable_socket
request.env
end

# Enhance #send_welcome_message to include sid if present
def send_welcome_message
transmit({
type: ActionCable::INTERNAL[:message_types][:welcome],
sid: env["anycable.sid"]
}.compact)
def anycabled?
@anycable_socket
end

def subscribe_to_internal_channel
super unless anycabled?
private

def request
return super unless anycabled?

@request ||= anycable_request_builder.build_rack_request(@env)
end
end)

# Backport command callbacks: https://github.com/rails/rails/pull/44696
unless ActionCable::Connection::Base.respond_to?(:before_command)
ActionCable::Connection::Base.include ActiveSupport::Callbacks
ActionCable::Connection::Base.define_callbacks :command
ActionCable::Connection::Base.extend(Module.new do
def before_command(*methods, &block)
set_callback(:command, :before, *methods, &block)
end

def after_command(*methods, &block)
set_callback(:command, :after, *methods, &block)
end

def around_command(*methods, &block)
set_callback(:command, :around, *methods, &block)
end
end)

ActionCable::Connection::Base.prepend(Module.new do
def dispatch_websocket_message(websocket_message)
return super unless websocket.alive?

handle_channel_command(decode(websocket_message))
end

def handle_channel_command(payload)
run_callbacks :command do
subscriptions.execute_command payload
end
end
end)
end

# Trigger autoload
test_case_defined = false

begin
ActionCable::Connection::TestCase # rubocop:disable Lint/Void
test_case_defined = true
rescue NameError
end

# Backport: https://github.com/rails/rails/pull/45445
if test_case_defined && !ActionCable::Connection::TestConnection.method_defined?(:transmissions)
ActionCable::Connection::TestConnection.prepend(Module.new do
attr_reader :transmissions

def initialize(*)
super

@transmissions = []
@subscriptions = ActionCable::Connection::Subscriptions.new(self)
end

def transmit(cable_message)
transmissions << cable_message.with_indifferent_access
end
end)
end
Loading

0 comments on commit 3bf488a

Please sign in to comment.