Web Sockets with ActionCable
A beginner’s guide to Web Sockets flow
Web Sockets are tough. Learning to use them is not fun. Understanding them can be an exercise in frustration. Even after having implemented a simple Web Sockets application (warning: there are still a number bugs to iron out), the finer points and full program flow remain elusive to me. Yet to help those who are taking a crack at Web Sockets, I decided to chronicle the difficulties I encountered and the lessons I learned while building the above single-page application.
There are a number of excellent guides that can get you started with a simple Web Socket application, and I highly suggest that you follow these guides before reading this post, which can be found here, here, and here. Keep in mind that these are guides through Rails’ implementation of Web Sockets known as Action Cable, though I’m sure there are many other guides to Web Sockets implementation in other languages.
Before you get started, there two questions that you should ask yourself. First off:
Do I need Web Sockets?
For those who don’t yet know, Web Sockets are a way to broadcast changes to a webpage to any clients (henceforth referred to as “subscribers”) that happen to be connected to that webpage. In other words, Web Sockets allow multiple subscribed users to see a live version of a webpage. For this reason, Web Sockets are especially useful for things that require users to have live interactions with each other like multiplayer games and chat rooms. For applications that merely display static information that only one user needs access to, having live webpages may be nice, but ultimately unnecessary. This leads to our second question:
If I do need Web Sockets, which features require them?
Once you have decided that you do need Web Sockets, the next decision should be which parts of your application need to be live. For example, a multiplayer game might need Web Sockets to display the same game screen to all users who are playing (e.g. a poker game might require everyone to see certain cards displayed on a common table), yet things like menu bars or friends lists should only be viewable to the user who is currently connected. Changes to those things (e.g. opening a menu, scrolling through a friends list, viewing your poker hand) are actions that are private to an individual user and need not (and should not) be displayed to everyone who is connected to a room. Making decisions about which changes need to be displayed to multiple users will be an important aspect of building out your application, as we will see later.
Preliminaries and Configuration
Before we get into the thick of things, it is important to define a two terms so that we’re on the same page.
1. Broadcasting
Broadcasting, in short, is the act of sending out information to subscribers (people who visit your wepbage). As we will see, broadcasting is the method by which ActionCable tells your front-end application what to do and what to render.
2. Channel
The channel is what does the broadcasting. In Rails, it can be generated with the following terminal command
$ rails g channel <Channel Name>
This creates three files: a cable.js file, a channel file, and a coffee script file. We will use the latter two.
I will not go too in depth in the setup as I am more concerned with the overall flow of a Web Sockets application. For an in-depth guide, you can jump here to see the few things that need to be configured in order to get started. In short, there are two things you have to do:
- Add
mount ActionCable.server, at: ‘/cable'
to your routes - In the generated channel file (located app/channel directory) comment out the the line under the “subscribed” function (conveniently written out for you)
Most of the work of implementing ActionCable will be done in the Coffeescript file (located in app/assets/javascripts/channels). For anyone who doesn’t want to learn Coffescript and would rather work in good ‘ol Javascript, you can convert the Coffeescript file to Javascript using this nifty tool.
You can then paste the code into the Coffeescript file, and change the .coffee
tag into a .js
tag. We will be working in the received
function.
The ActionCable Flow
For those of us who used to the normal flow of a Rails application, it is important to note some key differences in the application flow. Primarily, the controller takes on a modified role. Normally, the controller would be in charge of rendering HTML via views or redirecting to other routes. In ActionCable flow, Rails functions a bit more like an API: its main function is to return data. Requests are sent to a route within Rails as normal, but rather than rendering HTML, it passes data to the channel, which then uses that data to execute functions in Javascript.
In order to initiate a broadcast, requests are sent to a controller action via a route, as normal. The difference is that at the end of the end of the route, rather than rendering, a special line is added that will transmit data to your channel. It looks like this
ActionCable.server.broadcast "example_channel", content: {message: "hello"}
This line of code does a two major things:
- It overrides the normal render action of an action
- It broadcasts the second argument as a JSON object to your channel (in this case
content: {message: "hello"}
So where does this JSON object go? Here:
In the Coffeescript file that we converted to Javascript, the “data” argument of the “received” function will contain your JSON object. To access it, you might do something like the following:
data.content.message ==> "hello"
Cool, I have JSON data. Now what?
Any Javascript you run in the “received” function will be broadcast to all subscribers to a channel (anyone who is currently on your site). In your javascripts directory, there is an “application.js” file. Any functions and classes defined in this file or any files required by “application.js” will be available to be called in your channel file. A simple application of ActionCable uses the JSON data to tell your Javascript files which functions to execute and when. A received function might look like the following:
received(data) {
switch (data.content.action){
case "add message":
//Some function that uses data object to create and
display message
case "delete message":
//Some function that uses data object to find and delete
a displayed message
//... and so on
}
}
And now you should have live streaming code!
Quick Summary
The ActionCable flow is more or less as follows:
- Events (clicks, keystrokes, etc.) in your views submit POST requests to routes in your controller, sending any relevant data (e.g. form data, element information)
- The controller receives data from your view and uses that data to package information to send to the channel
- The channel then decides based on the information passed to it by the controller which Javascript functions to execute (e.g. create elements, remove elements, edit inner HTML of elements)
In making my Jeopardy game with my group, we used the executed Javascript to generate further forms and event listeners that would POST to other routes within our controller via form submission or fetch requests. These routes would then process sent data, pass data down to the channel, and the channel would then decide which Javascript to execute. Rinse, repeat.
There’s so much more
ActionCable is a monster. There is a lot of functionality that we never touched in making our Jeopardy game. If you noticed, we never touched the “subscribe” and “unsubscribe” functions within our channel, but in a full ActionCable implementation, these functions are important in controlling user flow (which users have access to what broadcasts). In addition, since my group was building a single page application, we never had to create more than one channel. I imagine that working across multiple models in multiple controllers would require the use of multiple channels to broadcast to the correct views. That sounds complicated.
For those of us who are beginners to ActionCable and WebSockets, I think the best first step to using them is to practice getting used to the flow of information from Event → Controller → Channel and back. There are plenty of advanced guides to follow, and I myself will definitely be taking a look at some of those.