Action Cable
ActionCable seamlessly integrates WebSockets in Ruby on Rails. It allows for real-time communication between your clients and server. ActionCable and matestack can be combined to emit events using matestacks event hub from the server side, for example triggering a rerendering of a chat view if a new message was created on the server.
In this guide we will provide information on how to create channels, consumers and subscriptions to broadcast messages to all subscribed clients or target specific user via user authenticated connections.

Setup

Create a channel using the rails generator. Run the command rails generate channel MatestackUiCoreChannel.
This will create a app/javascript/channels/matestack_ui_core_channel.js file where you can setup your subscriptions.
It also generates the corresponding server side MatestackUiCoreChannel < ApplicationCable::Channel class.
The matestack_ui_core_channel.js is responsible to create a subscription to the "MatestackUiCoreChannel".
All we need to do is to tell this channel that it should trigger an event using the MatestackUiCore.eventHub with the received data.
app/javascript/channels/matestack_ui_core_channel.js
1
import MatestackUiCore from "matestack-ui-core"
2
import consumer from "./consumer"
3
4
consumer.subscriptions.create("MatestackUiCoreChannel", {
5
connected() {
6
// Called when the subscription is ready for use on the server
7
},
8
9
disconnected() {
10
// Called when the subscription has been terminated by the server
11
},
12
13
received(data) {
14
MatestackUiCore.eventHub.$emit(data.event, data)
15
}
16
});
Copied!
We expect the pushed data to include an event key with the name of the event that should be triggered. We also pass the data as event payload to the event emit, giving you the possibility to work with server side send data.
If you do not want to use the rails generator just create the matestack_ui_core_channel.js yourself in app/javascript/channels/ and paste the above code in it.

Usage

After setting up the client side JavaScript for our action cable we now take a look at how to create server side events to trigger for example rerenderings of async/isolated components or show/hide content with the toggle component. We will introduce two different types of creating server side events. First broadcasting events to all subscribed clients and secondly sending events to a user by authenticating a connection through a devise user.

Broadcast

If you've used the generator to setup your channels you already have a app/channels/matestack_ui_core_channel.rb. If not create it now. Inside it we define that every subscriber of this channel should stream from the "matestack-ui-core" channel, which means that anything transmitted by a publisher to this channel is direcetly routed to the channel subscribers.
1
class MatestackUiCoreChannel < ApplicationCable::Channel
2
def subscribed
3
stream_from "matestack_ui_core"
4
end
5
6
def unsubscribed
7
# Any cleanup needed when channel is unsubscribed
8
end
9
end
Copied!
Emitting events from controller actions or elsewhere in your Rails application can be done by calling:
1
ActionCable.server.broadcast('matestack_ui_core', {
2
event: 'update'
3
})
Copied!

User specific broadcast

You don't always want to broadcast messages to all other clients. You may only want to broadcast to a specific signed in user or multiple users. We now take a look at sending messages via websockets to an authenticated user using devise. Therefore we need to edit our ApplicationCable::Connection to identify connections by a current user.
1
# app/channels/application_cable/connection.rb
2
module ApplicationCable
3
class Connection < ActionCable::Connection::Base
4
identified_by :current_user
5
6
def connect
7
self.current_user = find_verified_user
8
end
9
10
protected
11
12
def find_verified_admin
13
if verified_user = env['warden'].user
14
verified_user
15
else
16
reject_unauthorized_connection
17
end
18
end
19
end
20
end
Copied!
Every websocket connection that gets established will be authorized by a current_user. We check if a user is signed in by accessing env['warden'].user which gets set when a user is successfully authenticated. If env['warden'].user is not set we reject the connection.
Now we can create a channel which streams for a specific user, enabling us to send broadcasts to all clients which are signed in with this user.
1
# app/channels/current_user_channel.rb
2
class CurrentUserChannel < ApplicationCable::Channel
3
def subscribed
4
stream_for current_user
5
end
6
7
def unsubscribed
8
# Any cleanup needed when channel is unsubscribed
9
end
10
end
Copied!
Emitting events for a user can now be done anywhere in your Rails application by calling:
1
# sending to the current user
2
CurrentUserChannel.broadcast_to(current_user, {
3
event: 'update'
4
})
5
6
# sending to a user
7
CurrentUserChannel.broadcast_to(User.first, {
8
event: 'new_message'
9
})
Copied!

General broadcast and user specific broadcast

With the above implemented connection authorization in place we will not be able to create connections from clients where no one is signed in. To be able to connect unauthenticated and authenticated users and only give access to authenticated users to specific channels we have to remove the reject_unauthorized_connection call from our connection.
1
# app/channels/application_cable/connection.rb
2
class Connection < ActionCable::Connection::Base
3
identified_by :current_user
4
5
def connect
6
self.current_user = find_verified_user
7
end
8
9
protected
10
11
def find_verified_admin
12
env['warden'].user
13
end
14
15
end
Copied!
We then can create two different channels. One allows all clients to subscribe to it and another which only allows signed in users to subscribe to it and handles user specific broadcasting.
First a public channel to which every user can subscribe.
1
# app/channels/public_channel.rb
2
class PublicChannel < ApplicationCable::Channel
3
def subscribe
4
stream_from 'public'
5
end
6
end
Copied!
Corresponding front end channel subscription.
1
// app/javascript/channels/public_channel.js
2
import MatestackUiCore from "matestack-ui-core"
3
import consumer from "./consumer"
4
5
consumer.subscriptions.create("PublicChannel", {
6
received(data) {
7
MatestackUiCore.eventHub.$emit(data.event, data)
8
}
9
});
Copied!
Second a channel only available for signed in users. We reject clients that try to subscribe but are not signed in by calling reject unless a current_user is present.
1
# app/channels/private_channel.rb
2
class PrivateChannel < ApplicationCable::Channel
3
def subscribe
4
return reject unless current_user
5
stream_for current_user
6
end
7
end
Copied!
Corresponding front end channel subscription.
1
// app/javascript/channels/private_channel.js
2
import MatestackUiCore from "matestack-ui-core"
3
import consumer from "./consumer"
4
5
consumer.subscriptions.create("PrivateChannel", {
6
received(data) {
7
MatestackUiCore.eventHub.$emit(data.event, data)
8
}
9
});
Copied!
Broadcasting messages
With these two channels in place and our connection authentication we can now broadcast messages either to a specific clients with a signed in user or to all clients.
Broadcasting to all clients can be achieved with:
1
ActionCable.server.broadcast('public', {
2
event: 'update'
3
})
Copied!
Broadcasting to specific clients with a logged in user can be achieved with:
1
# sending to a user
2
PrivateChannel.broadcast_to(user, {
3
event: 'new_message'
4
})
Copied!

Conclusion

Creating channels and connections can be done like you want. To learn more about all the possibilities read Rails Guide about ActionCable. Important for the use with matestack is to emit events in the JavaScript received(data) callback and have a clear structure to determine what the name of the event is which should be emitted. Like shown above we recommend using an :event key in your websocket broadcast, which represents the event name that gets emitted through the event hub. You optionally can pass all the received data as payload to that event or also use a specific key. As this is optional you don't need to pass any data to the event emit.
Last modified 6mo ago