Skip to main content

53 Rails 5's ActionCable and Websockets Introduction

Episode 75 · July 11, 2015

Learn how Rails 5's new ActionCable and Websocket feature works with Redis in realtime

ActionCable Rails 5.0


What's up guys, I'm really excited for this episode because we're going to be talking about ActionCable, now ActionCable is a Rails 5 piece of functionality that is going to ship pretty soon, it just got released the Alpha version on GitHub the other day, and that's what we're going to talk about today.

ActionCable is a WebSocket framework for Rails, and ruby sort of specifically, but with Rails's mentality behind it, what does that mean? It means that you're going to be able to build application in Rails with ActionCable that can communicate to and from the server in real time, DHH just posted an example of how to use it, we're going to dive into that today.

I've cloned the ActionCable examples directory to my computer, I've installed Redis, made sure that's set up and then I have run the bin command which basically sets up all your gems on your machine. The difference with ActionCable is that you're going to need to run two servers on your machine from now on, not including Redis or MySQL or PostgreSQL or your database, but you'll need to run two separate servers, so you could run your Rails application on unicorn or passenger, puma or whatever you're used to, but you'll want to run ActionCable on something like Puma that's multithreaded, and that is because you're going to have all of these open connections between your server and the client. Now normally when Rails works and someone requests a page, Rails just says: "Cool, I'll do that for ya", it does a little bit of work, sends the response back and it's done. With WebSockets it keeps an open connection so if you have a thousand-people asking for things, that means that there's going to be a thousand open connections, rather than something like unicorn opening up like four threads and saying: "Cool, we'll do these four things, we'll give you guys that back, we'll do yours" and like going down the line, so with WebSockets you need a lot more concurrency, flexibility there and that's why it's designed the way it is. All we need to do to test out this example is run the ./bin/cable server in one terminal, and then we want to do the regular old ./bin/rails server in the other one. We're also running everything here on GitHub master branches for Rails and ActiveRecord, and all of those pieces like sprockets everything you're going to be using here is the latest and greatest which could possibly break.

Now that we've got the page loaded, we can open up two of these, so I'm going to open up just the incognito mode for this, we'll open up localhost:3000 here as well, and this page sessions/new is just like logging in with any authentication system normally you would type in a username and password or whatever, but this is just going to set the cookie in the browser just because it's an example and we don't need to have all tat functionality, so on the left here, let's log in as Snoop Dog, and on the right, let's log in as Ice Cube, and basically, were now on examples, and you can click on "Messages with live comments", clearly this is designed so that in the future you'll be able to have more examples once you've logged in, but the one we've got right now is messages with live comments, so if we go into one of these messages, you'll see there's a few comments in the past that I've tested out and this is great, and if we make a post, this basically just puts it on the page immediately, that's just happening with regular old JavaScript form submits, nothing special about that, but once we have another user in here, and they post something, you'll notice there's an instantaneous update on both sides. This one (Ice Cube) added the comment through just the form JavaScript response, but this one added the comment to the page through ActionCable.

This means we can add real-time comments into our Rails Application, and they'll function without a hitch. And also when we go and navigate between messages, the updates are not going to override each other. If I comment here, you don't see an update in messages/1, you don't see an update in messages/2, and the same vice-versa, but navigating to messages/2 will be functional again. There's a handful of things going on here, let's dive into the Rails code and take a look at what's going on to make this all happen.

There's a couple things to take a look at in the Rails app that have changed with ActionCable. First is the bin/cable file which basically says let's run Puma with the config rack up file for cable, and cable/ is just the same as the Rails one, we load Rails, we load ActionCable, and run the server. Nothing special there, but the real magic of all of this is the Redis connection. We're using Redis and the config/redis/cable.yml, we're using that to create the publish and subscribe functionality here. so if you're not familiar with this, I definitely recommend checking out Redis pub sub and you basically create these channels, you can give them names and they're separated usually by namespaces so you have like messages:id_number:comments, and you would subscribe to a channel like that, and if you ad another one you would add messages:2:comments. All of this functionality is built on top of Redis's pub/sub functionality, so you'll notice that bleed through into your ActionCable a litle bit as we go through here so there's two things in our Rails app that have changed, or have been added, we have an app/channels folder and this is really the serve side stuff so it's the connection to Redis, and we have a channel which you always inherit from this ActionCable channel. it's the base, like ActiveRecord::Base, and then you have your connection which is going to say "when the WebSocket loads, let's verify the cookie in there, make sure the user is logged in" because we want to only do this for logged in users.

That's pretty straight forward, you just find the user and do that as you normally would. So inside the app/channels/comments_channel.rb file, we have two interesting methods here, now these are methods that can be called from the JavaScript through the WebSocket and they will be executed server side, this is really nifty. When the page loads, we want to start following this thread, like the current message that we're looking at, in the browser, so when we visit messages/2 we want to say: "Hey, my current user wants to subscribe to the messages/2 comment stream" and this follow method basically tells Redis that, so it says: "Hey Redis, let's stop listening to everything, and then let's make sure we listen to message thread one. We want to get all the comments for that" So that's what this does, and then when you want to navigate away and you want to unfollow that just kills your subscription to that Redis pub/sub channel. It's really really simple, there's no real magic here with how that works. And then, the other interesting piece, you have this channel, and the browser side is setting up all this stuff. So inside app/assets/javascripts/channels, the first thing that it does is creates this App.cable variable, and that's a new WebSocket connection, so you have the server side, and it connects to Redis and talks back and forth, and then you have client side in the browser and that talks to the WebSocket which then talks to this Redis channel, and then vice-versa, so you really have four things.

  1. The client side browser
  2. ActionCable, server side
  3. Redis
  4. Rails app

The Rails app can send a message to Redis through ActionCable, then ActionCable send it across the WebSocket to the client side browser, so there's a lot going on here, but it's not as complicated as you might think. So is the file where most of the magic happens, because most of the magic here is all in the browser. So this basically says: "Let's create a subscription, and we're going to go to the comments channel" Now that should look very familiar, "CommentsChannel" is exactly the same name as the channels/application_cable/comments_channel.rb in our Rails app, so it's the same thing as the server side ruby that we're trying to access. Now we have a collection, and this is basically saying: "Hey on the page there's a set of comments, we're looking for that", that's where we're going to display all the comments, and then we have an event when our WebSocket connection is connected and authenticated and everything, then we need to follow the current message, so we need to tell the server we want to subscribe to that, and then we have to add a change call back onto the page so that if we navigate away we can unfollow that. So let's look at how that works.

So we want to follow this current message comment thread, we look for the id on the page, and then we just tell the server in the comments channel, to perform the "follow" action for this ID, and we can pull up that code, because this basically says: Let’s hit the CommentsChannel method, and then pass in some data as a hash, which is the message id, and then that's it. So it's going to inject that message id from the JavaScript straight to the ActionCable's server and start listening to that Redis channel. So this is all handled for you, all the communications, everything is taken care of, which is pretty nifty.

So then, once whe have that follow, we're done, we set up this @installPageChangeCallback() next which basically just says: hey, when the page changes (so if you're using Turbolinks), is you go back a page, we want to make sure that we stop following that, and we don't want to get more updates and have weird stuff where those comments are still getting added to the wrong pages. So when we navigate away, we need to make sure we unfollow that thread, and we call the same followCurrentMessage method, but because the page's html and ID's have changed, the URL has changed as well, we're not going to have the same message id, so we're either going to follow a new one, or we're going to unfollow all of them. So maybe if we just go from messages, and we hit back and we came here to localhost:3000/messages, and there is no ID. In that case, we're not following any threads, so we don't need to be having that subscription in Redis to that. That's really, really simple and there's not a whole lot else to that. So we have a received: (data) method that says: when we get a new comment that's been saved in the database, and we send it out to all the WebSockets, all those clients of those browsers are going to receive it, and then they're going to make sure that they just append it to the page, unless you're the current user. And the reason you're doing that unless @userIsCurrentUser(data.comment) is because they submit the form, and the form has JavaScript response that immediately puts the comment on the page, so that there's no delay between that, and that's it. We really just check to see if the current user matches and ignore that in that case. So client side it's really simple, you set up the page, you subscribe to the channel, and then you handle updates on the channel. That's all you're doing.

Now let's talk about how we get those changes across Redis. So if we open this up again, and we type into one of these, this is just a regular old Rails remote form, so when we post the comment, it's some JavaScript going to the server, and that's saving it to the database, and then sending us a JavaScript response back to inject it on the page. It's a normal remote form that you're used to, but with one added thing. If we open up the comment model, there's an after_commit hook that creates a background job to broadcast this out to every single user that's subscribed to that Redis channel for that current message. So the comment's relay job is the one that broadcasts everything out, and it happens in the background. Which is why ActiveJob has been so important in Rails 5 and the Rails community is because we need sort of the standard to process background jobs if we're going to start using WebSockets and Rails, because you don't ever want to block those responses back to the server. This is defined in the jobs folder, and we have the comment_relay_job.rb which just talks straight to ActionCable server, and I broadcast a message to the channel that you're looking for, and you can send some stuff over. And notably here, comments controller renders this partial, and that means that we're sending HTML to Redis, and ActionCable is sending that across the WebSocket into the browser, and that makes the browser really just have to do no processing, and it can straight up inject it on a page and be done with it. There's something really awesome about that, because the server should be in theory the fastest at generating HTML. It should be faster than phones or any of the possible devices that are running JavaScript in the browser these days. Ideally, it's going to be faster for the server to generate the HTML rather than using a front end framework, so you're kind of pushing that idea where servers are always going to be faster in theory, and you can use that to transfer HTML rather than sending JSON. You could totally use JSON, or send something like that over, and then use React to reupdate the page on the Front End. But you totally don't have to, you could just send HTML and handle it the way this example does. So that's cool, we can define the way the 'comment' html is rendered, one place server side, and we don't have to duplicate it on both server and front end. And that's pretty nifty. Another interesting thing to point out before we're done here is that ActionCable server broadcast is really simple. Basically just says: Hey redis, on this channel, send this message, and take care of it. So that is available to you pretty much anywhere you could want to use it. It makes a lot of sense to use this and background jobs, but that means that you can send messages pretty much from anywhere that you would like across Redis's pub/sub into a WebSockets on the client side as long as they are subscribed. So channels are awesome, you could use a channel for notifications in the navigation, so you can send out real-time notifications to all your users, you name it, there's a lot of possibilities for this. And comments are of course, you know, just the ideal one because you're able to do that so quickly and everybody seems to like to use comments or real-time messaging as their first example for anything real-time. But yeah

That's it, that's ActionCable, it's not too bad, it's got a long ways to go, it's definitely still in Alpha, it sounds like they're going to be making a lot of changes to how it works internally, but it works, and it's really not to hard once you wrap your head around, you have the browser, talking to the WebSocket which listens to Redis and then is the one communicating back and forth with your Rails application. So that's it, that is the whirlwind tour of ActionCable, I hope you liked it, if you have comments, questions, anything, definitely leave a comment below, give it a like if you liked this episode, and you want to see more about ActionCable, let me know what you'd like to see built with ActionCable, I plan on doing many more screencasts with it, kind of experimenting what we can do in Rails and add in some really easy real-time communication. So that is it, I will talk to you next episode. Peace v

Transcript written by Miguel