Guide to Implementing Live Data Updates in a Rails 5 Application with ActionCable
This is going to be an exciting guide. With all of the configuration items in place we're ready to finish building out the ActionCable channel and fully implement live updates of comments on our blog pages.
Guide Tasks
  • Read Tutorial
  • Watch Guide Video
Video locked
This video is viewable to users with a Bottega Bootcamp license

In the last guide, we talked about what we had to do in order to create a broadcast and I used the analogy of understanding the way that a television system works and how a broadcast has channels.

Now in this guide, we are going to walk through how to configure those channels. If you go to app/channels you can right click, create a new file and we're going to call this blogs_channel.rb...

Inside of this we will create a new class called BlogsChannel and this is going to inherit from ApplicationCable::Channel. Just make sure that you have it spelled out exactly like that. Now that we have this in place, let's come and let's create the methods that we need to make this work.

First, is going to be a subscribed method. We're also going to have an unsubscribed method, and then we're going to have a custom method. These first two(subscribed and unsubscribed), these are ones that channels have and they're looking for this.(I notice I have a little spelling mistake, not Chanel it's channel).

What this is, is a channel has the ability to be subscribed and unsubscribed. You have to put these inside of any channel that you create. That's something that ActionCable looks for. Now though, we have to create an actual custom action. This one is going to be send_comment and this is going to handle the data.

We're going to leave our unsubscribed method alone because we're not going to worry about what is going to happen when we're unsubscribed. Essentially what this means is that the user left the page where our channel was live. Imagine a user just turning off the TV if they're watching something, that is like unsubscribing from this blog's channel.

class BlogsChannel < ApplicationCable::Channel
  def subscribed
  end

  def unsubscribed
  end

  def send_comments(data)

  end
end

In the subscribed method we do have to do some things. What do you watch on TV? You watch channels, but the channel itself is a stream. We need to call a method called stream_from. Let's create the actual streams. We're going to say that we're going to stream from blogs_, pass in #{params}, and then grab the blog_id. Make sure to put it inside of a string. After that put _channel.

def subscribed
  stream_from "blogs_#{params{'blog_id']}_channel"
end

What this is going to do is it's going to look inside of the params and this is provided via our connection and it's going to say. Okay, what is the blog_id? Then it's going to create a channel. In other words, it's going to create a channel that may look something like this...

medium

If the id is 4, it's going to say blogs_4_channel and it's going to have that and that's what the connection is going to be. That's all we have to do inside of the subscribe method is point to what the stream is pointing to.

Inside of our send_comment, this is where we start to get a little bit more custom. Here we're going to grab the current_user, then we're going to say .comments and then .create!. Inside of this, we're going to grab the attribute, we're going to say content: data and then use the hash selector syntax and say data['comment']. We're going to also grab the blog_id:, then using our same data hash(data['']). We're going to say blog_id.

I like this line of code right here because it is getting us a little bit closer back in our familiar Rails territory. This looks like something that you would use and you would run inside of the Rails console. So, you could call current_user and then say current_user.comments.create. Then pass in the comment data, pass in the blog_id and then you would end up with something like you just did something like this where you said...

large

Comment.create and put all the content creation mechanism and data all inside of it.

That is the first part. This is all we have to do to have our channel and the send_comment if you notice if you go back to our Javascript if you go to blogs.coffee. You may notice that we have this same method in a few different spots and this is exactly what it's doing. Notice here how we have comment and blog_id as arguments? send_comment right here for this App.global_chat.send_comment. Notice how it's getting the textarea and the value of the textarea? Right here, this is where all the magic is happening...

large

Because by itself this doesn't do anything. This has to call the actual application and that's what it's doing here. When it says App.global_chat.send_comment it's saying that I am in this channel and I want to send the comment, passing in the data and then it goes and grabs all of that information. This is something that is very helpful. I like understanding the way the data flow works, from that perspective. That is our BlogsChannel. Now inside of our application_cable directory, we have a channel and by default, this just is a class that inherits from ActionCable::Channel::Base. We're not going to make any changes to this but we also have this Connection class that is inside the ApplicationCable module(connection.rb) and we are going to have to do quite a bit in here. This is what is going to provide a lot of the information to our channel and its required especially for being able to supply a current user. This is one of the more important things that you'll learn in this section. I want you to understand this part of it and that is that ActionCable and websockets do not have the same access points as our HTTP connections they have access to a completely different set of parameters. It's because how the data flow works and how it works with websockets as opposed to how it works with HTTP. What we have to do here is we actually have to recreate our current user method because, say we left this part of it out then websockets wouldn't know what a current user is, and our entire system relies on being able to find the current user in order to pass that in to create the comment.

identified_by :current_user

def connect
  self.current_user = find_verified_user
  logger.add_tags 'ActionCable', current_user.email
  logger.add_tags 'ActionCable', current_user.id
end

protected

def find_verified_user
  if verified_user = env['warden'].user
    verified_user
  end
end

We are going to have to implement a method called identified_by and it is going to have :current user. Inside of this, we are going to have a number of methods the first one is going to be our connect method. This is the one that it's looking for. Here we're going to set up our current_user. I'm going to say self.current_user =. Then we'll say find_verified_user. I'm also going to add a view logger and say logger. add_tags. What this is going to do is this is going to give us the ability to see what is happening in the terminal because if we don't put this we're not going to have any visibility, and if we have any errors then it is going to be very frustrating to track them down. Right here we have the ability to say when the connection occurs I want the logger to start printing this information to the terminal and then it will make it a lot easier for us to track it down. Inside of this, we can add in a new method because if you notice this find_verified_user that is a method we have to create. I'm going to say def find_verified_user and right here we're just going to check and say if verified_user = and this is where we're going to go and use something provided by Devise called warden. Warden is a tool that you can use in order to call and kind of bypass what happens with the current_user method because we don't have access to all the cool current_user functionality that we have in the rest of our app with Devise. So, here what we're essentially doing is we're recreating it and we're saying hey Devise we're not really able to ask you in our normal way. So, can you let us know if this user is logged in or not, and if so then we're just going to return the verified_user. This syntax may look a little bit different. What I'm essentially doing is I am assigning verified_user and I'm only returning it if it is true. If this(env['warden'].user) parts true, I'm assigning the value inside of the variable and then I'm returning it. We're kind of skipping a few steps just to make it possible, but I definitely like that implementation. This kind of works but you're going to get an error, because we have this concept of guest users and this current implementation only works if users are signed in because it's going to throw an error whenever it can't find a current_user. We need to utilize our Guest User class, but we're going to have to make one change(guest_user.rb) and that is that Warden is going to be looking for an id. All we have to do is come to GuestUser, add an id, close it off...

large

Now we can come up here and we can create our own guest user that is specifically created for ActionCable. We're not going to use the same one we use in the rest of our app. We can just create a new one so I'm going to say...

def guest_user
  guest = GuestUser.new
  guest.id = guest.object_id
  guest.name = "Guest User"
  guest.first_name = "Guest"
  guest.last_name = "User"
  guest.email = "guest@user.com"
  guest
end

guest = GuestUser.new. Now we can duplicate a few of these. The first one we're going to do is assign guest.id to guest.object_id. I may want to come back and refactor this at some point. object_id is an object in memory. This is an identifier in memory that is very quick and easy to access and it should also be unique. I personally am going to run it by a few of the other senior devs I've worked with and see if they can come up with any reasons why this would be a bad idea, if they have any objections, when we talk through it and there may be something that might be better but I've gone through a few different implementations and tested this out a few different times and I never ran into any conflicts by calling object_id. It's very fast and I never ran into a time where it was not unique. What this will do essentially is we can open up the terminal. Say that I open this up in pry, and I'll just create a regular string or something. So I'll say

"asdfasdf".object_id

you know here's a string I can call object_id and you can see it returns this large number and this is the value, this is the identifier in memory for this object. The same object if I create it and call it again notice how it's now a different number. If I run it again it is a different number again. If I do it over and over and over again it is going to be a different number. I've used it before as a way to be able to mimic an identifier when I don't really care about it. But Devise and Warden are going to be looking for an id. That's why we had to add it there. That's the first thing. Next, we need to add a name so here we're just going to go with our same Guest User and now we can come here and say guest.first_name set this =guest. Come here and we'll do guest.last_name set this =user and guest.email set this =guest@user.com. Lastly, we need to just return this. Yes, now we just return the guest and what we can do is we can say find_verified_user or just call the guest_user.

large

That should all work and we shouldn't have any issues there. Now with all of this in place, I think that may work. I have a checklist on my notes and I believe that we have crossed everything off the list. So let's start up the server and see if this is working or not.

I'm going to leave it here in the terminal for a second just to make sure that everything loads up properly and we don't have any crazy bugs or syntax errors or anything like that. So far it looks pretty good. Now let's open up a blog page, oh actually. OK good. Notice what we're doing right here.

large

Remember our logging statement inside of our connect? We said ActionCable and print out the user's e-mail address and id. Well, I have a page open right now and I'm on a blog page and look at this. It already is capturing that I am there and it prints out my email and my id. Let's go and open this up and take a look see how I am right here. I'm going to refresh the page just to make sure I load all of our changes in. Now if I say "My first comment" and hit post comment that worked perfectly. Look at that. It added it, notice there was no page refresh, anything like that. That all worked beautifully. That's fine, that could have been accomplished very easily though that is not magical. That's just a basic commenting function.

Let's see the magic happen now. I am going to shrink this down and open up another window in incognito mode with just a guest user who is accessing the site. If I come here a grab the same URL. This is the moment of truth for us right here. You can see as a guest user I can't see that content, but I can see the comments, but I can't see the form.

Let's also take a look at the terminal. Let's see if this is working. Yes look at this, we have access and we see that a guest user is now watching this, so we have a guest user and look at our id, looks good.

Now if I switch back and add another comment. If everything works it's going to automatically update the user page without us doing anything. If I hit this, it worked. That is working gorgeously. That is where more of the magic is happening. What we've essentially done is we've opened up a broadcast we've opened up a websocket connection for our blog pages. Now anytime someone comes to one of these pages it's kind of like it's a TV show and any changes that get made are automatically going to populate almost like they're watching something. This completely changes the traditional process of you know going to a page and having to hit refresh. This also changes the concept of if you had to do this in the past in Rails you'd need to implement something called polling and what polling is, is where you set a timer like one second or five seconds or 10 seconds and then you would have a query run and then you would have Jquery update the page. That was very server intensive, but that's the way that you would have to do it in Rails before Rail's 5 came out. Now what happens is we have an open data connection between not just this page and this page. We have it between this page and anyone who is watching it. A good example is let's pull up another browser. Let's shrink this and have this viewed here. I'm going to copy the same URL, bring over Safari and shrink this down as well so we can see all three pages. If I scroll down you can see the comments. If I say "third comment", hit post comment. All of them get updated automatically. This is exactly the same kind of functionality that you see on sites like Facebook or Twitter when you're on them and something happens such as a new tweet getting published and all of a sudden the page updates without you having to actually hit refresh or anything like that. I think that's really exciting. I really hope that you can see why there is so much excitement around ActionCable and Rails 5. Because this is functionality you simply were not able to easily implement years ago and even within recent history Rails. They spent a lot of time on building this out and I really think they did an excellent job with it.

With all that being said, I think we did a fantastic job with building all of this out. You should be very proud of yourself. If we do git status. We have all of that added and now we can say "implemented actioncable for comments". Let's push it up.

In the next guide, we are going to go through and we're going to start styling these. Because they look ok, but still not exactly perfect. They're budding up against the button and against each other so we're going to go apply some CSS styles. We're going to go see how we can add an image and how to do some cool things like that. So see you in that guide.

Resources