Skip to content

Commit 5d0ade5

Browse files
committed
feat: add command callbacks support and backport
1 parent 641cea7 commit 5d0ade5

6 files changed

Lines changed: 68 additions & 16 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
## master
44

5+
- Add support and backport for Connection command callbacks. ([@palkan][])
6+
57
## 1.3.3 (2022-04-20)
68

79
- Added `sid` (unique connection identifier) field to the `welcome` message if present. ([@palkan][])

docs/compatibility.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,14 @@ Streaming | ✅
1717
Broadcasting | ✅
1818
Periodical timers | 🚫
1919
Disconnect remote clients | ✅
20+
Command callbacks | ✅ \*\*\*
2021

2122
\* See [restoring state objects](../architecture.md#restoring-state-objects) for more information on how identifiers work.
2223

2324
\*\* See [channel state](./channels_state.md) for more information on subscription instance variables support.
2425

26+
\*\*\* AnyCable (via `anycable-rails`) also supports [command callbacks](https://github.com/rails/rails/pull/44696) (`before_command`, `after_command`, `around_command`) for older Rails versions (event when not using AnyCable).
27+
2528
## Runtime checks
2629

2730
AnyCable provides a way to enforce compatibility through runtime checks.

lib/anycable/rails/action_cable_ext/connection.rb

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,30 @@ def request
2828
@request ||= anycable_request_builder.build_rack_request(@env)
2929
end
3030
end)
31+
32+
# Backport command callbacks: https://github.com/rails/rails/pull/44696
33+
unless ActionCable::Connection::Base.respond_to?(:before_command)
34+
ActionCable::Connection::Base.include ActiveSupport::Callbacks
35+
ActionCable::Connection::Base.define_callbacks :command
36+
ActionCable::Connection::Base.extend(Module.new do
37+
def before_command(*methods, &block)
38+
set_callback(:command, :before, *methods, &block)
39+
end
40+
41+
def after_command(*methods, &block)
42+
set_callback(:command, :after, *methods, &block)
43+
end
44+
45+
def around_command(*methods, &block)
46+
set_callback(:command, :around, *methods, &block)
47+
end
48+
end)
49+
50+
ActionCable::Connection::Base.prepend(Module.new do
51+
def dispatch_websocket_message(*)
52+
return super unless websocket.alive?
53+
54+
run_callbacks(:command) { super }
55+
end
56+
end)
57+
end

lib/anycable/rails/connection.rb

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -110,22 +110,24 @@ def handle_close
110110
end
111111

112112
def handle_channel_command(identifier, command, data)
113-
# We cannot use subscriptions#execute_command here,
114-
# since we MUST return true of false, depending on the status
115-
# of execution
116-
channel = conn.subscriptions.fetch(identifier)
117-
case command
118-
when "subscribe"
119-
channel.handle_subscribe
120-
!channel.rejected?
121-
when "unsubscribe"
122-
conn.subscriptions.remove_subscription(channel)
123-
true
124-
when "message"
125-
channel.perform_action ActiveSupport::JSON.decode(data)
126-
true
127-
else
128-
false
113+
conn.run_callbacks :command do
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
129131
end
130132
# Support rescue_from
131133
# https://github.com/rails/rails/commit/d2571e560c62116f60429c933d0c41a0e249b58b

spec/dummy/app/channels/application_cable/connection.rb

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ def log_event(source, data)
2222

2323
state_attr_accessor :token
2424

25+
around_command :log_command_execution
26+
2527
def connect
2628
self.current_user = verify_user
2729
self.url = request.url if current_user
@@ -34,6 +36,12 @@ def disconnect
3436

3537
private
3638

39+
def log_command_execution
40+
start = Time.now
41+
yield
42+
self.class.log_event("command", time: Time.now - start)
43+
end
44+
3745
def verify_user
3846
return env["warden"]&.user if env["warden"]&.user
3947

spec/integrations/rpc/perform_spec.rb

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,16 @@
5151
end
5252
end
5353

54+
context "with arround_command" do
55+
let(:log) { ApplicationCable::Connection.events_log }
56+
57+
it "executes command callbacks" do
58+
expect { subject }.to change { log.size }.by(1)
59+
60+
expect(log.last[:source]).to eq "command"
61+
end
62+
end
63+
5464
describe "#stop_stream_from" do
5565
let(:data) { {action: "unfollow_all"} }
5666

0 commit comments

Comments
 (0)