How to Create a Broadcast with ActionCable in Rails 5
With our jQuery configured, now we're ready to build the broadcast that will manage the data workflow for the live commenting feature. Additionally, we're going to add an ActionCable routing interface as well as update our Comment model to have a callback that communicates with a background job.
Guide Tasks
  • Read Tutorial
  • Watch Guide Video
Video locked
This video is viewable to users with a Bottega Bootcamp license

I know that was a long guide and I know that if you're not that familiar with Jquery and Coffeescript that, that may have been a little bit confusing but I have good news for you and that is that we pretty much done in writing the Javascript that we're going to need to make this work and that's one of the beautiful things about how Rails 5 works with ActionCable is it's going to perform the rest of the job for us by being able to leverage a number of built-in Rails classes.

One of the first things we're going to do is we're going to open up routes. Inside of our routes file, we're going to create a mounting route. We're going to say mount ActionCable and we're going to say .server and then pass this to the route of cable.

mount ActionCable.server => '/cable'

If you for some reason have decided to use a different route, then you're going to have to make the change here. I'm just going to go with the default that is available and what this is going to do is, it is going to allow us to create this web socket connection.

If I come to the top of the file where we have our resources added. I can actually get rid of our resources comments...

large

Now if that seems a little confusing, that's good because you may wonder how can we create comments without having some type of route to pass the data. That is the beauty of websockets. Websockets are much different than how traditional HTTP protocol works. With HTTP we need to have routes like this...

large

You know we need to have our resource routes to do things, like doing sorting or in order to get an item to create a get or to perform any of these like to say toggle_status.

There's all kinds of routes and every time you make an action like we've talked about in our data flow guides, we need a route that can capture it. Well, here what we're going to do is we're going to wrap our entire route into ActionCable server. This is going to give us a direct feed into our Redis database, and this is going to open up a websocket connection that we can use in order to connect in, send data and receive data. This isn't all we have to do. This is just the start of it, but that is definitely one important thing. The next thing we're going to do is I'm going to open up our comment.rb file and right below the validation we need to add a callback. The callback we're going to use is after_create_commit and pass this a block...

after_create_commit { CommentBroadcastJob.perform_later(self) }

Now what this is going to do is we need a process that is going to run right after a creation process has occurred. So by default what would happen is we would send a route and then we would get routed through either via Javascript or traditional Rails routing we're going to get routed to a page on the site.

With our websocket connection, this is going to be different. We're not going to do that. Instead, we are going to pass this to a background job so I'm going to say, CommentBroadcastJob and spelled exactly like that with this capitalization structure and then say perform_later, then pass itself. What this is going to do is it is going to call this CommentBroadcastJob(which is going to be a background job) because part of the way that web sockets work.

You wouldn't want to simply say I want to immediately invoke this. In most cases that's exactly what's going to happen. But imagine that you have a huge load on your server and you have 50,000 people trying to make comments at the same time. Obviously, you're probably aren't going to have that. You would need a much larger server setup in order to make that kind of thing happen. But, imagine a situation where that's the case. If you tried to run just an immediate create statement right away, right when someone hits return then you could end up with a nasty backlog, a lot of failures, a lot of collisions. That is not what you want. What Rail's does in perform_later is it doesn't mean later as in a 'to be determined' kind of time what that means is I want you to perform this whenever you have a chance, but it doesn't have to be right at this very second. What that can do is, imagine a situation where you have a large number of requests coming in on that web socket. So a lot of people trying to make comments then you're going to run into fewer errors and fewer collisions because you're giving it time to build up in the list of background jobs. Now that we have that. One of your very first thoughts maybe; Where is this? This 'CommentBackgroundJob' is something we don't have. So that's the next step is we're going to create it.

Let's come into jobs new file. Hit save and this one is going to be spelled out exactly like this(CommentBroadcastJob). You can put it in here and then let's just go change the case. So it's going to be comment_broadcast_job.rb from here...

class CommentBroadcastJob < ApplicationJob
  queue_as :default

  def perform(comment)
    ActionCable.server.broadcast "blogs"
  end

  private

  def render_comment(comment)
    CommentsController.render partial: 'comments/comment', locals: { comment: comment }
  end
end

I can create a class called CommentBroadcastJob, this is going to inherit from ApplicationJob and let's end the tag. If you come into the jobs directory here(application_job.rb) you can see ApplicationJob inherits from ActiveJob: :Base you don't have to worry about it right now, just know that with Rails 5 we have this built-in concept of performing jobs and you can think of this as having a set of tasks that you can send your application that it can go and perform so that it's kind of a form of delegation where you can say hey I as an application need to do all kinds of things. I want to hand you this job. You can go when you have time and go and perform the action. The very first thing we're going to put in here is the concept of a queue. A queue here, when it comes to web sockets, is essentially saying that we want to have a list. This is a background job so there is a definite chance that we could have a backlog. We could have 50 people who are trying to comment at the same time. We want to make sure that we take these in, in the right order so we're going to just use the default queue. We're going to say queue as :default. Now, do you remember that one perform method? If we go to our blogs.coffee you remember this @perform? Well, this is something that is going to speak exactly to what we need to do. We need to be able to create a perform method now. We're going to say def perform and we'll pass in a comment as an argument so we can pass in arguments to our jobs so that it knows exactly what job to perform. Now, I'm going to say ActionCable.server.broadcast. If you have a difficult time thinking about the concept of what ActionCable is I really like the terminology that Rails used in order to implement it. Because I think it fits in very nicely with understanding how TV and broadcast work. Here we have a broadcast job, we are going to perform this job, we are going to tie into ActionCable(it is a server connection) and we're going to perform a broadcast. This broadcast just like a broadcast on a TV show. This is going to be something that we pass in. What do you watch on TV? You watch a channel. So we're going to now create a channel. So I'm going to say "blogs_". We're going to now perform some string interpellation. Say comment we're getting 'comment' from our argument here. You're going to say comment.blog.id. This is going to know what blog it's associated with. Then from here, we're going to say _channel. This is the channel we're creating. Think of it as the channel we're creating to watch on TV. Now we're going to with the comment we need to tell it what to do now. We're going to say render_comment. Your first question when you see something like this is; Where are we getting this? What is render_comment? If I come down here, we need to create it. Say def render_comment and this is going to as you can see it takes a comment as an argument as well. Inside of this we are going to now communicate with the rails app. I'm going to say CommentsController. Then say .render partial. This is going to be the partial that we created, put it in a string. Say comments/comment and then pass in locals: and pass it in as a hash and say comment: which is going to be our variable and comment. Which just so you know the difference. This 'comment' that we're getting here...

large

is the value of comment that we sent here in the perform action...

large

This is a lot of code. Well, it's not a lot of code in terms of you know it's only a few lines, but this is a lot of functionality. Let's hit pause really quick.(We're watching tv with the broadcast, so let's hit pause.)

We have a comment that we are broadcasting a job on. We're creating a queue. This is our list, this is where the comments are going to go into a list. If there is only one comment that comes in, then their going to be the only people there, and that comment is going to get the priority. If there are 50 then as they come in they're going to be served in order. From there we have our perform method which takes a comment. This gives us our direct connection where we say I want you to start a broadcast on ActionCable and we want the channel to be a blog's_"whatever the blog_id channel" is. So, imagine a situation where you have 50 people on your site at the same time on different blogs. They are going to have different channels that they're watching the same way if you go and you watch different channels on your TV. We need the ability to know which channel they're on. Then from there, we are saying that included in this is we want the process of rendering the comment. Now that drops us down to our private method here of render_comment which takes in that same comment value and now this is where we can get back into some more familiar territory. This is simply going to call the CommentsController. We're going to say render the partial and this is a partial that we created('comments/comment'). So, if you go into views/comments this is this file right here(_comment.html.erb) and then it passes in the variable(locals: { comment: comment }) so it knows exactly what to render. This is how we're going to have the ability to dynamically show comments without the user having to refresh the page.

So this is a decent amount of code, but this is one thing I love about how Rails 5 built their channels and how they built their broadcasts and the entire ActionCable setup is I think it makes for a pretty logical flow when it comes to what you need to do in order to build this type of functionality. I have built other node based kinds of web-socket things and they are not quite as intuitive as this so I'm definitely a fan of how this is implemented.

That was a decent amount of code. Let's hit git status to see everything that we built out and let's just save this where we're at right now. When we come back we can continue building it out. Here we are going to say that we "Created a comment broadcast, configured the websocket route and updated the comment model with a callback".

That was a decent amount of work for one guide. Let's push this up and in the next guide we're going to continue building this out. We are going to see what we need to do in order to implement our channel.

Resources