Skip to main content
Realtime Group Chat With ActionCable:

Group Chat with ActionCable: Part 3

Episode 131 · August 2, 2016

In this series, we're building a clone of Slack using Rails 5 and ActionCable



Earn a free month

What's up guys? This episode we're going to be taking where we left off and starting into the messaging aspects of the chatroom, so this chat is going to have multiple chatrooms, and we should be able to click on these and go to the chatroom, so the first thing I want to do is modify our sidebar, so instead of linking to an empty hash, we can link to the chatroom itself. Now the reason I originally did this was because I was going to have JavaScript handle that click, and we can definitely do that as well but it's kind of nice to have turbolinks handle that, I realized, and so if we refresh the page and we click on this and have turbolinks do the navigation for us, and actually that will keep the websocket open because that is already running on the previous page and because this is not a browser transition to a new page, the websocket will continue to be uninterrupted, which is super nice, so that means that we don't have to rerun all the websocket setup stuff, and you won't miss out on any messages which is a good thing. This is going to allow us to navigate to the chatroom itself, and we're going to need to modify that view so that we can see what's going on here. So really we probably want some sort of header thing at the top but that header thing probably should be somewhat fixed and this is where our CSS from bootstrap is kind of going to break down a little bit, we're going to need to actually build a fair amount of this stuff and kind of tweak the way that it works with bootstrap, so there's a lot of things that bootstrap is good at, but it's not really designed for building a really custom application like this where we probably want the main content being scrollable, a static sidebar, a footer that's static, that sort of thing. They support a marginal amount of this stuff, so we're going to dive in to a little bit of tweaks on right now, so let's actually go and say: What if we created this chatroom thing? Let's get rid of these links, and what if we created a div in here that had data-behavior of messages, and let's imagine this is where the messages will show up, so we'll have a navbar that will show the channel that you're currently in, and we'll have the messages div, and what if this div had like 1000 fake messages in it. So that's quite a lot, so we'll do this, and we'll just say the username and the message, so we'll just have some example in here, we'll see what this looks like and how the UI works.

First thing is first, now our chatroom sidebar is disappearing as soon as you start to scroll, which isn't ideal, and then the navbar itself is disappearing as well and the name of the channel is disappearing too, so we kind of want to position those three fixed on the page, so let's go do some tweaks to that now that we know that we need to do that. One thing that we could do is go into application.html.erb, and we can go up to our navbar here and add in which is a bootstrap class that will make the navbar on the top fixed of course, so that will position that so that it's always there and is always available. Of course that means that all of our content gets pushed up and the navbar sits on top of some of it, so you can see that you can't really see the top of the page anymore when you scroll to the very top, so this means that our fluid container needs to have

so that it pushes the main content down because the navbar itself is a certain number of pixels tall, it is 51 pixels tall, and then it has a 20 pixel margin or something, so what that's going to say is like: Well, it's about 71 pixels tall so if we put a margin on top of our content, then it kind of evens it out and we can see all of our stuff again, so the next thing that we want to do is say
and if we add this in there, we should see that while the main content shifts over, the chatroom stuff stays there as we scroll, which means that we just need to bump over the main content by the same amount of space which is two columns in bootstrap columns. These are all kind of hacks to the bootstrap CSS, which aren't ideal, and of course you can take the CSS that we did inline and pull them out into classes, but I would really encourage you to go the extra distance if you actually want to make this a real application because all the CSS is just really hacks on top of bootstrap to get it working well enough for a usable UI for now just for this example, so I'm not building out the full UI, and if I did, I would probably not use bootstrap for a lot of this layout stuff, I'd build it all custom because we're wanting to collapse all this in different ways, and chances are you would want these chatrooms to be like a button in the navigation if you're on mobile, so while you have this menu thing on the right side, you'd probably want the chatrooms to go into a menu on the left side and all of that is just kind of extra stuff that we're not worried about in this series, but if you are you can go take a look at a lot of the CSS tutorials online, they'll show you how to do that and build this out responsively whereas we're definitely not doing a good job building this responsively, but it's not too hard if you go look up some tutorials on that. So skipping on, we want to be able to now add a form at the bottom here for creating a new message in our chatroom so when we go to it, of course the only way that we can interact with the chatroom is to add a message, so what we'll want to do is go into our chatroom


<%= form_for [@chatroom,] do |f| %>
  <%= f.text_area :body, rows: 1, class: "form-control", autofocus: true %>
<% end %>

We also need to add


resources :messages 

We'll refresh the page and now at the bottom we should have a text area that only has one like of text which is good. We could also add autofocus= true to it, so that when you load the page it automatically scrolls you down to the bottom and focuses that form field, so once you load that page you can just immediately start typing, which is super cool. We're making some progress, but as you might have noticed, the autofocus doesn't actually scroll you down if you switch chatrooms. Maybe this is working, maybe it's not, you might also need to add in some JavaScript in order to do the scroll to the bottom if you don't add the autofocus in or if it's not working appropriately, because you have to remember, we're using turbolinks for the navigation, which doesn't necessarily mean all of those things that the browser would do on a brand new page will work exactly the same way, so that's one of those gotchas that you might run into, with little things here and there because turbolinks is faking the new page and your browser is not actually doing everything that it normally would, so you might get little differences there, but it looks like it's working so that's cool.

We want it to be able to say "Hello" and we want to be able to capture the Enter on that form and automatically submit that message, so we'll need some JavaScript to intercept the Enter key when you hit that, and so let's go and do that in our JavaScript. So this is really simple, basically all you need to do is say app/assets/javascripts/ and inside this file we're simply going to say:

$(document).on "turbolinks:load", ->
  $("#new_message").on "keypress", (e) ->
    if e && e.keyCode == 13

This did submit it, now we have to build out our messages controller in order to create that message


class MessagesController < ApplicationController
  before_action :authenticate_user!
  before_action :set_chatroom

  def create
    message =
    message.user = current_user
    redirect_to @chatroom


    def set_chatroom
      @chatroom = Chatroom.find(params[:chatroom_id])

    def message_params

Let's just make sure that this works and redirects us back to the chatroom, now we don't have it being displayed there, so if we go to chatrooms show, we can remove this fake loop that we had, and instead we can say


<div data-behavior='messagees'>
    <% @chatroom.messagees.limit(100).each do |messages| %>
    <div><strong><%= message.user.username %></strong> <%= message.body %></div>
    <% end %>

That should take us now back to the Gorails users, and if we log in to localhost:3000 with a different user and we join the general channel, we can go to that channel and we should be able to see that message here but we did not. Let's go back to the he page, let's go to random, and we see the message there, and I should be able to type in "Hi", and I don't have a username so let's go set that, because I've hadn't updated that user from when I created it before we added usernames, so now if we go in there, you can see that user said "Hi" but it hasn't updated in real time because we need to add ActionCable for that but if we refresh the page we do ge that message, so we have a basic chat going on right now, and you can jump between the channels, and the other user should be able to navigate to it and see it, and all that should be working, and the messages are also in order by default because the normal query pattern is that you have the id numbers in your database and you start from one and go up to whatever number you have and it will always query those in order where it should, but you're going to want to make sure that you set the order on your messages, and really you probably want to say created_at is the timestamp that you want to order by, so you want them to be descending, so you get the most recent ones, but you're going to want to reverse them, so what will happen is you want to grab the most recent one hundred, and then that will be the most recent messages at the top, but when you insert them onto the page you actually want to insert them in reverse order, so you wan the last one to go first and then the next one to go under that, so you kind of want to flip this array around and then print it out in the html on the page, so you have to query with the newest items first and grab those hundred, otherwise you won't get the right ones, and then you have to reverse that array to get the right ones to print it out on the page correctly, so the way we're going to do that is we're going to add the reverse method here, and this will take the results that we got from ActiveRecord, and then reverse them before we do each, so if you see in your query, you should still get these SELECT messages with the order created_at as descending, and that shouldn't affect the results that you get, so this should display now.

If you spell it right, when you see the messages, you should get the most recent at the very bottom, and if we were to go to the same chatroom on both and we went to limit that number to one, we should see that the most recent message is the one that says "recent", so that way you can know that you got it right by the filtering and the reverse by seeing the most recent when you limit down to one. If you didn't have the order(created_at.desc) here and you removed it, and you refreshed the random channel, you would see that the gorails message that was first actually shows up as if it were the last one, and that's not correct, so you have to make sure you have your ordering down, you have your limiting down to the correct number that you want, and then you reverse it so it shows up on the page appropriately. We have all those pieces in place, you can move this out into a helper method inside your model if you want to say: recent_messages instead of adding the order and the limits here. I'd recommend doing that because if you don't, you're going to have to make sure you always have that order and the limit in as the appropriate and you might forget to do that sometimes, or you could also set this as messages inside of your controller, and you could use something like chatrooms controller, and when you do your show, you have @messages, and you do this instead. This could be one way of doing that and it really doesn't need to go in your model as a helper method, you could absolutely do it here. The thing is if you end up doing this a few more places it's probably better to pull it out into its own method on the chatroom model so that you don't have to duplicate this code all around your application. Now we only reference this one time here, so I'm going to use this instance variable, and we'll have @messages here and inside the controller, and that will take care of any sort of the complexity that would be putting in the view that it doesn't really belong in the views. So with that said, the next piece of course is that we want to be able to type into these and have them update in real time, so we want to submit this form with JavaScript so it doesn't navigate away, we can keep our websockets live and running, and then when a message comes across, we want to be able to see it injected into the page in real-time using ActionCable, so the next step for us is to dive into all of this and the JavaScript required to make that happen. So in the next episode we'll do all that, and I will talk to you then. Peace ✌️

Transcript written by Miguel


Subscribe to the newsletter

Join 31,152+ developers who get early access to new screencasts, articles, guides, updates, and more.

    By clicking this button, you agree to the GoRails Terms of Service and Privacy Policy.

    More of a social being? We're also on Twitter and YouTube.