Getting more comfortable with Action Cable

A few months ago, I learned how to use Rails’ Action Cable for the first time. I was taking the Craft Academy bootcamp, which I happen to be teaching now, and we were building an application for delivering pub quizzes electronically. It was great fun and everyone in the class and also the coaches in Craft Academy learned a lot! Thomas, the head coach at Craft Academy, wrote up a long blog post detailing our work in setting up the application and testing it with Cucumber and Capybara.

Building the Second Project

Of course, in the midst of trying a thousand things and breaking stuff and getting frustrated and finally — it works! — you forget some of the steps you took along the way. You mean to write a long, technical post about all of the solutions you encountered and pitfalls along the way but, seriously, you have a bootcamp to teach and your own things to build and then it all just flows out of your head.

But this week, I was tasked with adding a feature to an existing code base: push notifications. Here we go again with Action Cable. Thomas keeps telling me I’m the “resident expert” on the technology, so I headed back into my “domain”. This time I feel like I’m actually getting a handle on how all the pieces fit together. And so, dear Medium:

The components of Action Cable, explained

What you have out of the box

  • A channels folder, inside of javascripts. You can put a .js file in here, which will subscribe every page in your application to the channel mentioned. More on that later.
  • A controllers folder, which contains channel.rb and connection.rb. I’m sure they do important stuff — I won’t be talking about them because I didn’t touch them.
  • app/assets/javascripts/cable.js which contains code for creating the cable and running the code that will establish connections. We won’t be touching it.

Mounting the Action Cable server

First things first, get this line into your config/routes.rb:

mount ActionCable.server => '/cable'

This line will be where your cables are sent to and from.

Go ahead and use whatever url you like, but keep in mind that some of the libraries for associated technologies may be expecting you to stick to the default. If you change it, just be aware of that when you are troubleshooting with logs and such.

Generating the channel

rails g channel Dinosaur will create a new file for you: app/channels/dinosaur_channel.rb. It looks like this:

class DinosaurChannel < ApplicationCable::Channel
def subscribed
stream_from "dinosaur"
end

def
unsubscribed
# Any cleanup needed when channel is unsubscribed
end
end

Into the subscribed method we will put anything that happens when a page / application joins the channel. This is not where we put actions related to sending information across the cable.

For my part, stream_from is a very confusing wording for what is happening here. In your logs, you’ll see stream_from dinosaur → this indicates that someone has subscribed, and has nothing at all to do with information being sent.

Be careful about naming here — everything is talking to everything else. You can’t call something DinosaursChannel or it won’t talk to the right scripts.

Creating the broadcast job

Next we will need to create a job that will send a message over the cable. We don’t have to contain this information inside a job, but it does DRY our code up. It makes it more difficult to follow the information through our application, but hopefully this very post will clear some of that up.

We generate a broadcast job: rails g job BroadcastDinosaur which creates a file app/jobs/broadcast_dinosaur_job.rb along with a file to unit test the job. It looks like this:

class BroadcastDinosaurJob < ApplicationJob
queue_as :default

def perform(data)
ActionCable.server.broadcast "dinosaur", data
end
end

Now we will be able to call on this job from our controller, with BroadcastDinosaurJob.perform_now(data). We can also use perform_later but that’s outside the scope of this blog post.

How you might use this

I’m going to take a short break and just give a quick example of how this would work.

Say you have a bunch of people looking at a page on a website, perhaps while playing pub quiz (say). If you wanted to send everyone a message, you’d have some kind of admin interface and a textbox. You type in your message, hit send and then everybody’s screen now shows the message. This is what we’re after.

In our example above, you’d have that send button route you through a controller action that contained our broadcast job. We’d get the input from the text box into the controller, then pass it into the job with data.

Our cable Javascript

Almost there. Next we need to write the Javascript language that will allow a page to subscribe. We have two options for this code: we can put it inside our javascripts/channels folder, where it will be called when any page in the application loads. Or we can put it inside of another javascript file in the main javascripts folder and call it specifically on a certain page or after a certain action. You could tie this script to a DOM action, such that users don’t subscribe until they are ready. Here’s the code we need:

function cableSubscribe() {
App.cable.subscriptions.create({channel: 'DinosaurChannel', {
connected: function (data) {
// Called when the subscription is ready for use on the server
console.log(data);

},

disconnected: function () {
// Called when the subscription has been terminated by the server
},

received: function (data) {
// Called when there's incoming data on the websocket for this channel. You could put plain jQuery into here to replace a div with html, for example.
}
});
}

We have a console.log(data) in there so that we can quickly see when all of our hard work has started paying off. Once we’re displaying the data we got from the text box, we’re almost there!

Make sure you call the cableSubscribe() function from your website (or put this function, without the function wrapper, into the channels folder so it is called when your app loads anywhere).

Figuring out what to do with the cable

The last step is figuring out what to do with the message received from the cable. One thing you can do is simply adjust jQuery elements on the page, for instance:

[...]
received: function (data) {
// Called when there's incoming data on the websocket for this channel
$('.dinosaur_flash').html("Your message '" + data.note + "' was sent at " + data.time);
}
[...]

On the page where we want to receive the message, we create a div with id dinosaur-flash, now we’re simply replacing whatever is currently inside that div with a text message. We can also send more complicated data into this element, such as a partial rendered in HTML or really anything we could possibly want. You’ll find lots of tutorials online details how to make a chat app which is rendering a partial.

Scoping down the channels

What we have built so far will message anyone who is looking at the web page where we have our empty div waiting. But we can also configure pages or our application to subscribe to particular channels, and broadcast only to them.

Broadcast to a particular channel

First we’ll need to scope down our message. Head back to our broadcast job, and adjust our perform method to send only to a particular channel:

def perform(data)
ActionCable.server.broadcast "dinosaur_#{data[:id]}", data
end

This will create a broadcast to a channel dinosaur_1 or dinosaur_2, or whatever id you are passing in from the controller. Easy, right?

Receive a particular channel

Subscribing to a particular channel is only slightly more tricky. First, we’ll head back to our channels/dinosaur_channel.rb and fix up our subscribed method:

def subscribed
stream_from "dinosaur_#{params[:id]}"
end

Those params are coming from our cable subscription script. We’ll have to adjust the create function:

App.cable.subscriptions.create({channel: 'DinosaurChannel', 
id: parseInt(id, 10) } },
[...]

You’ll get that id variable from your page (my script looks like var id = document.querySelector('.dinosaur_id').id;. If you’re having every page in your website subscribe to the channel, you probably don’t need to scope it down. If you do, you’ll have to somehow get that variable into the subscription script — calling it a second time from somewhere else in your application (rather than $(document).ready()).

And that is it! There are seemingly a lot of moving pieces in Action Cable, but once you start working with it (or, in my case, once you build an entire application and then come back to it months later and build a second one), you’ll start to get a feel for how everything fits together and where changes need to be made. When things are finally working, your logs will tell you explicitly how the subscription is working and what is being sent on what channel.

Good luck!

Like what you read? Give Amber Wilkie a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.