Chat App With Ruby on Rails, Devise, WebSockets, and Redis

If you want to jump to the good stuff: https://ruby-chat-room.herokuapp.com/

I’ve always liked the idea of making a site that does instantaneous communication. There’s something about an app that updates in real time that has always held my fascination. I remember a friend of mine made a chat room when we were in high school. We were all familiar with AOL Instant Messenger (AIM), and I remember him commenting that, because the page needed to refresh to show new messages, that it was more like the world’s fastest message board than a chat room. In this post I’ll outline the app itself, what technologies are used, and then I’ll go through what I had to do to get it hosted on Heroku.

I challenged myself to build a chat room app and I had some requirements: Ruby on Rails backend, asynchronous chat between 2 or more users, and chat history should be persisted between users. Also that fancy thing where the page automatically loads new content without having to refresh the page. I looked at a few tutorials before settling on this one by Iridakos. It was straightforward and concise, with good explanations. I have to mention how blown away I am that Iridakos wrote the whole thing in both English and Greek. That’s super cool. Anyway, in looking for a tutorial, I found quite a few that suffered from some pretty serious flaws: one site only had a part 1 of 2, another didn’t clearly explain what the final product would be, others didn’t have a final codebase, or didn’t state what version of Ruby, Rails, or other gems were required. Iridakos had all of these!

I had a few challenges executing the steps in the tutorial. I would say my main difficulty was managing different versions of the required software. With any tutorial, you have to take a leap of faith when downloading new libraries, gems, and other software. The first trouble I ran into was downloading and switching to the correct version of Ruby, because my Xcode was out of date. Then it turned out that I needed to install updates to my OS before I could do that, and then, once I got all that out of the way, I was able to get the correct Ruby version. After that, the tutorial mostly ran smoothly, until I got into the deployment/hosting stage, which I’ll talk about in a little bit. 

I’ve built apps before, but what was interesting with this one was it’s reliance on Devise, ActionCable and Redis. The app uses Devise to handle user authentication, and ActionCable with Redis to handle the immediate updates to the view. This gets a little bit technical, so I’ve tried to break it down and explain it, mostly for my own comprehension.

Devise is a handy gem to have. It simplifies the process to authenticate users; you run a few Rails commands, and Devise creates all the forms to register and log in, plus provides some other functions like storing cookies for sessions. Previously I’ve made login and signup forms from scratch, so comparatively, this is pretty easy. After including Devise in my gemfile and bundling, I generated the User object, required the username to be present and unique, and updated the routes to use Devise to handle users. After getting all that set up, users can register and log into the application, create chat rooms and send messages in them, but the site itself is pretty static. Even though you can post messages, you have to manually refresh the page to see them show up. How do we put the ‘instant’ in ‘instant message?’

Before talking specifics, it’s a good idea to go over the background. Typically, when something happens in the browser, such as visiting a page, or clicking a button, the browser sends a message to the web server, which looks at the message, and figures out what to do depending on the kind of message. If you submit a form, it might take the information from the form, save it in the database, and then display it in some fancy way in the browser. After the server does it’s thing, and the browser finishes loading instructions from the server, the connection between the two closes. If the information or the instructions change, you have to refresh the page to see anything different. In the case of Heroku, and the app I made, the production database is Postgresql, and that information is used to verify people who are logging in, and display any information on their landing page that we might want to incorporate. This is what’s known as the “request/response paradigm” of HTTP; In order to open a connection between browser and server, there has to be a request, followed by a response. That’s not what we want for our chat messages, however. We want to see them as soon as they’ve been sent. That’s where WebSockets and ActionCable come in.

From Heroku: “WebSockets are a protocol built on top of [Transmission Control Protocol] TCP. They hold the connection to the server open so that the server can send information to the client, even in the absence of a request from the client.” WebSockets create a persistent connection between client and server, and allow information to be sent both directions at any time. This is perfect for the chat application, and other low-latency apps, because it lacks the overhead of HTTP, but how do I integrate that into my Rails app?

ActionCable is a gem that integrates WebSockets with the rest of your rails app. It provides a client-side framework in JavaScript, and a Ruby framework server-side. The appeal is that it gives you the ability to write features in Ruby that match the style and form of the rest of your app. Here are some useful vocabulary terms to understand what is going on with the ActionCable gem:

  • Connections: a single ActionCable server can have multiple connection instances.
  • Consumers: in ActionCable, a consumer is created by the client-side JS framework.
  • Channels: each consumer can subscribe to multiple channels. Each channel encapsulates a logical unit of work.
  • Subscribers: A consumer that subscribes to a channel
  • Pub/Sub: publish/subscribe. ActionCable allows publishers to send data to an abstract class, i.e. subscriber.
  • Broadcastings: pub/sub link where publishers send information and it is broadcast to all channel subscribers who are streaming that named broadcasting.

When someone goes to a chat room, ActionCable creates a new channel, with connections between consumers and the server. Consumers can subscribe to a channel, and broadcast to all other subscribers. These relationships are all defined in the app’s routes and settings files, so you can tune them in precise ways depending on need and use. There’s a minor wrinkle with the server now, though. I’ve set the app up so that the Postgres database handles the registration and login validations, and database queries are notoriously slow. If the app has a connection that’s open all the time, it’ll be really slow, in addition to causing a lot of conflicts. That’s why we use Redis.

Technically speaking, Redis is “an open source, in-memory data structure store, used as a database, cache, and message broker.” In the case of this chat application, the main use is as a fast pub/sub message broker, which fits nicely with the ActionCable gem. Redis has a lot of reasons it’s the right choice for this project. It stores info in RAM, which makes it much faster than saving to a disk, it functions as a server, so that WebSockets isn’t monopolizing the PostgreSQL database for every single message sent and received, and it has the option to backup data onto the server, giving it the benefits of a traditional database, with the speed of in-memory database. So, with all these working together to create a smoothly-running app, the only thing left to do was to get it hosted on Heroku.

This was the more difficult part, because it wasn’t part of the tutorial. I had to use my experience with my Hypercube app to figure it out. It wasn’t too difficult, but it did require a lot of research into what versions of everything were required. The first problem was that Heroku didn’t like the version of Ruby I was using, so I had to upgrade it, along with the Rails version. I also had to make sure that the app would still work with a newer version of each of these libraries. The next thing was remembering to change the preferences to use Postgres, as I had been using SQLite3 on my local branch. Then after that, and when I’d pushed it to Heroku, it would work for a moment, and then crash. That’s when I started looking for instructions on how to get Redis running on Heroku, which (luckily) it supports. After all this, it finally booted, and was able to stay up and running. It’s still going! You can see it at https://ruby-chat-room.herokuapp.com/ 

There are some things that I’ve left undone on this project. I didn’t write any tests, and I’ve asked some friends to try and break it. They’ve had some moderate success, mostly just in messing with the way the CSS handles the page formatting. I also didn’t include a delete button for any of the chat rooms, so until someone reboots the site, the messages and rooms are gonna persist. As for addressing these odds and ends, I have started working on testing it with Cypress, and I might get around to adding that delete button. It’s been a lot of fun working on it, and I’m glad I took the time to understand it.

The main takeaway from this project is that I just had to keep using trial and error, and reason to figure out how to fix the problems and get it running. It’s such a satisfying feeling when you get every step on your to-do list done. Whether it’s diving into the documentation, or combing through the server logs to see what errors get thrown, nothing beats that sense of accomplishment when your project can take on a life of its own.

Bibliography:

https://redis.io/topics/introduction

https://blog.heroku.com/real_time_rails_implementing_websockets_in_rails_5_with_action_cable

https://github.com/corbettbw/chat-room

https://iridakos.com/programming/2019/04/04/creating-chat-application-rails-websockets

https://github.com/iridakos

https://rvm.io/rvm/basics

https://www.learnenough.com/ruby-on-rails-6th-edition-tutorial

https://devcenter.heroku.com/articles/heroku-redis

https://www.html5rocks.com/en/tutorials/websockets/basics/

Published by corbettbw

I am a Ruby developer in Phoenix, AZ. I'm interested in the intersection of technology and social justice, love weird science facts, and my dog, Coco; a cute black lab/pit bull mix, who won't stop eating rocks.

Leave a comment