In the last episode, we actually built the foundation for our chat application, built all the database models, we installed devise, we used devise-bootstrapped and that gem in order to add user names to log in with, but the next piece we need is actually to build the layout of our page, we want the sidebar to show the channels that you have joined, and then we want the main content of the page to be all the messages in that active channel, so we're going to be building the UI for that just on a basic level, and then adding in the ability for you to join and leave channel, so if you join a channel, it should show up in the sidebar, if you leave a channel it should remove it from the sidebar and that will give it a field like IRC or Slack or any of those chat applications like that. So without further ado, let's get started.
If we hop back into application.html.erb, we can add a container around the main content of the pager
<div class="fluid-container"> <div class="row"> <div class="col-sm-2"></div> <div class="col-sm-10"> <%= yield %> </div> </div> </div>
Once we have all these divs in here we should see things move around a little bit, and what we'll do is put the chatroom stuff on the left side once the user is signed in, we'll display that stuff there, so here we can just simply say
<% if user_signed_in? %> <h5>Chatrooms</h5>
This will be all the chatrooms that the user is connected to, so we'll have the chatrooms here in the middle, let's remove rm app/assets/stylesheets/scaffold.css because that conflicts with bootstrap a little bit. So we'll have these in, and we'll have our chatrooms here but let's also display them on the left side, we're going to want to display
<% current_user.chatrooms.each do |chatroom| %> <li><%= link_to chatroom.name, "#" %></li> <% end %>
Now we need to build the link to actually go join this, so the link is really just going to create the association between the user and the chatroom that join table record, and we'll be off to the races, so what I'm going to do here is I'm going to go to the routes file.
resources :chatrooms do resource :chatroom_users end
This is actually the chatroom_users join table model which we'll be interacting with directly, and the reason for that is because this is going to be the easiest way to grab those by id, so we'll be able to associate those by id instead of having to look them up every time, but this should be pretty easy, so we can go into our
class ChatroomUsersController < ApplicationController before_action :authenticate_user! before_action :set_chatroom def create @chatroom_user = @chatroom.chatroom_users.where(user_id: current_user.id).first_or_create redirect_to @chatroom end def destroy @chatroom_user = @chatroom.chatroom_users.where(user_id: current_user.id).destroy_all redirect_to chatrooms_path end private def set_chatroom @chatroom = Chatroom.find(params[:chatroom_id]) end end
If for some reason there happen to be multiple in there we'll just destroy all of them just for the safety of that, so there should never be any more than one record, and if there ever was, we just destroy all of them. If there were two records and you left and it deleted one but not the other, then it would look like you left but you're still in the channel and you'd be confused as a user, so this is kind of a little safety net of that situation in case you ever had that. Of course you can also set your validations on the join table and unique index in the database table across those two columns so you could never insert those records as well. How you want to go about that, you could either go this route, you can do kind of a mixture, you can approach that in a couple ways, just for the safety and quickness of this example, we're going to do it this way, but I would recommend the database indexes for unique across two columns for the safety of your data anyways, so we'll go to the chatroom's index, and this just really needs <%= link_to "Join", chatroom_chatroom_users_path(chatroom), method: :post %> the reason for that is we want to hit the create action, and so if you click join here, and we might also need to not use that instance variable. Now you should be able to see "Join" here, and you see that "General" shows up on the left side which is awesome, it very very good. It works exactly as we would expect. We could put an X on the chatrooms on the left side, or we could also replace this "Join" with a "Leave" function, and so I'm just going to make a "Leave" action here for simplicity sake, but we'll be able to make that link and move it around when we have icons, so we can put a pretty looking X on the left side or whatever so that it shows up visually a little bit more nicely, so we'll do that later but for now we're just going to put a "Leave" link, and the leave is really simple, and this POST is going to get changed to a DELETE, and so you should be able to leave General--
This accidentally crashed as you saw because my routes were configured incorrectly, so what we want here is resource as a singular route, because this is the chatroom user record just for yourself, so the CREATE and the DESTROY actions are not actually scoped to an individual record, and really we shouldn't care about, you should never be able to create and assign someone else to a channel for you, so the reason why we want to change this back to resource as a singular one is that his should control your connection to and from that chat room by itself, so think of this controller is specifically for the current user, and it really determines weather or not the user is joining or leaving that chat room, so it's nested, but it only really does joining and leaving, and that's really it, so this resource is a little bit different than what you might be normally used to, but it allows us for moving all that join and leave functionality into two actions in its own controller. It can be customized on it's own and it just kind of works independently of the chatrooms themselves, which I think is really nice, so if we refresh, we should now be able to join General, we can go back and we also should be able to leave General, we can click "Leave" on rando, but it's not going to do anything, we should be able to join both of these, and we can. We can leave random, we can leave General, and if we try to leave any of the chatrooms that we're not already in, it doesn't crash, and that's the benefit of the chatroom_users controller that we did. So the destroy_all will just destroy all but if there's no record found, it will just not crash, so it's kind of a nicety of this approach. If you didn't find anything it's going to crash, so this kind of eases over those exceptions or those errors that would come up, and you can take care of it just by modifying your ActiveRecord query a little bit differently. So if that's useful to you, that's a little tip that you might be able to use in other places, it's just a habit I've gotten into because it allows for kind of a seamless experience, if for some reason a user tries to leave the channel that they're not in already the app doesn't crash where it normally would have previously in different approaches of coding. Now where we're at is that we have bootstrap set up, we have our users, we have our channels, we have the ability to join and leave channels, but we don't have anything of the channels themselves, so if you go click on the channel, you don't get to see any of the messages, it doesn't really feel like a chat app yet, but we're making progress, so this is where I'm going to leave this episode off, and we'll dive into actually building the message functionality in the next episode. So this is it for now, and next piece we'll dive into setting up the messages, and then we'll start connecting ActionCable in order to make that stuff real time, so that's it for now, I will talk to you in the next one. Peace ✌️
Transcript written by Miguel
Join 20,000+ developers who get early access to new screencasts, articles, guides, updates, and more.