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.
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/config.ru 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.
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.
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 comments.coffee 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 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
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
Transcript written by Miguel
[ActiveJob] [CommentBroadcastJob] [29558acd-658a-4a3a-b870-f447881740b8] Error performing CommentBroadcastJob (Job ID: 29558acd-658a-4a3a-b870-f447881740b8) from Async(default) in 34.14ms: ActionView::Template::Error (Devise could not find the
Warden::Proxyinstance on your request environment.
Make sure that your application is loading Devise and Warden as expected and that the
Warden::Managermiddleware is present in your middleware stack.
If you are seeing this on one of your tests, ensure that your tests are either executing the Rails middleware stack or that your tests are using the
Devise::Test::ControllerHelpersmodule to inject the
request.env['warden']object for you.):