How To Implement Real-time Messaging In Rails 6 Using Action Cable

Bashir
8 min readJun 24, 2020

--

In this article, I will show you how to add a realtime messaging feature to your rails application using Action Cable. Under the hood, action cable is simply a wrapper around WebSocket. For those who are unfamiliar with WebSocket, it is a TCP communication protocol that allows for a bi-directional free flow of data between a server and a client. This is different from an HTTP protocol, where a client has to send a request to the server in order to receive data. You can read this article if you need to get a general idea of how WebSockets work.

There are a few different ways to implement Action Cable in your application; the differences arise from the stack of your application (ie. whether React is used in the front-end or not) to different programming styles. My goal here is to show you the basics of Action Cable and how the information freely flows between the server and the front-end. We will build a simple web chat application, which will help highlight the essential components of Action Cable. Below is the functionality that we will aim to implement by the end of this article.

First, let’s create an app called “realtime-messaging-demo”. You can follow the steps below to create the starter code which will display a chatbox and the list of users you are messaging. You can alternatively clone the starter code from this repository. If you chose to clone the starter code, you can jump to the section titled, “Incorporating Action Cable”. For the sake of simplicity, we will set up a basic schema with a user and a message model. A user can have many messages and a message belongs to two users (the sender and the receiver). This is a self-referential relationship, where two users are related through a message. If you don’t understand self-referential relationships, I highly recommend reading this article by Jack Hilscher.

Create a Starter Code

Step 1: Create a rails app by running the following command:

rails new realtime-messaging-demo

Step 2: CD into the created directory and generate a model, controller along with the database migration file for the users by running the following commands:

rails g resource User name 

For the sake of simplicity, our user model will only have a name attribute in the migration file. We could run a similar command to build the model, controller, and migration file for the messages.

rails g resource Message sender_id:integer receiver_id:integer text

Aside from the model and controller, the command above will generate a migration file with a messages table containing the columns sender_id, receiver_id, and text (which corresponds to the text content of the message).

Step 3: Now that we have all the models, controllers, and migration files setup, let’s add the proper associations to our models. We will add the following to our user and message models. Again, this is a self-referential relationship where users are related to one another through a message.

Step 4: With all the associations up and running, let’s go ahead and migrate our database followed by seeding the database. You can seed your own data or use the data we have in the repository.

rails db:migrate  #to migrate our database and generate the schema 
rails db:seed #to seed the database

Step 5: Let’s create a page that lists all the users which allow us to select which user we want to massage. Add the following code to the user's index view.

Now let’s add a page that displays the chat box where users can communicate. Add the following code to the users’ view show file.

Let’s update our user controller to handle the display of the index and show files.

We can also update our messages controller to handle the text submission and persist the text in the database (as well as broadcasting the message to the WebSocket as we shall see in the next section).

We have our basic application setup. If you run it you should see the list of users that you can message. Run an additional instance of your application in an incognito mode and try to send a message to one user and see if the user is able to see that message. You will notice that the user has to refresh the page in order to see the new message. We can now go ahead and implement WebSocket to allow the new messages to show up instantly without a need for the page to refresh. Clone this repository if you want to be sure that we are both working on the same code base.

Incorporate Action Cable

Action cable uses Redis to temporarily store data and to efficiently transmit data across the application. Let’s enable Redis by going to the gem file and uncomment the “gem redis” line. Run “bundle” and we should be good to go.

This is an overview of the flow of information that we will accomplish with action cable:

  1. A text message is sent from the front end to an action controller in the backend
  2. The action controller will store the text in the database followed by broadcasting the message to a WebSocket channel.
  3. WebSocket will send the information to users who are connected to that particular WebSocket channel.

Let’s add action cable to our application by following the steps outlined above. The first step is taken care of by our initial setup. We can see that the message is sent from the front-end to the “create” action of the message controller, where it gets stored in the database. But we don’t want to store the message in the database only, we want to also send it to the WebSocket where it will then broadcast the message to the subscribed user (a user who is connected to the channel). After storing the message in the database, we can then simply add the following line to our action “create” of the messages controller in order to broadcast the message to the WebSocket:

ActionCable.server.broadcast("hypothetical_channel", message: message)

This line will broadcast the message to a WebSocket channel named, “hypothetical_channel”. Broadcasting the messages directly from the controller limits the scalability of our application. It is best practice to run the broadcasting in an ActiveJob, which allows our broadcasting to be held in a queue if resources are not available. To create a job, simply run the following commands:

rails g job  send_message

This will generate a send_message_job file located in app/jobs. Modify the perform method to match the following:

Our perform method is expecting a message parameter. This method will be called from the messages_controller create action. We can simply replace the redirect_back with the following line of code:

SendMessageJob.perform_later(message)

We are generating an HTML that will be sent to the DOM by the WebSocket. This HTML is usually placed as a partial in one of the views.

You may be wondering what is the chat_id variable. It’s simply the sorted user ids of the receiver and sender. It is a way for us to create a unique channel for a pair of users. For example, When user “A” is chatting with user “B”, the messages they sent to each other should be broadcasted(transmitted) to channel “AB”. If user “A” is messaging with user “C”, we don’t want to broadcast their message to channel “AB” (doing so will cause user B to see the message). Instead, we will have to broadcast it to a channel that is unique to both users, which we can hypothetically call “AC”.

On line 9, we are broadcasting the “html” we created on line 7 to the channel
message_channel_UniqueNumberHere. The channel is made unique to each user pair by the addition of the chat_id to the channel name. Creating channel names this way has some limitations; for example, it will be more complicated to know which user is online or which user is typing. In a typical application, you may want to have a conversation model that links two users together and use the id of the conversation as part of the channel name. Then each user can have his own channel which you can track if they’re online or typing. For now, our focus is to understand the basics of action cable, but this way will work for group messaging as well as many other instances.

We are broadcasting to the message_channel, but we haven’t created a channel yet. We can create a channel by running the following commands:

rails g channel message

This will generate a few files for us, let’s go through each file by following the flow of information within our application. Our send_message_job has the following line of code:

ActionCable.server.broadcast("message_channel_#{chat_id}", html: html)

This code is broadcasting our “html” to a message_channel which we just generated in the above command. Go to our message_channel file and modify it to match the following:

In line 5, we are simply saying don’t run the code in this file until the page is fully loaded. We are doing this because the code in line 6 is retrieving the chat_id from the front-end. If the page is not loaded this line of code will give us an error. Also, on line 8, the user is being subscribed (aka connected) to the MessageChannel. We changed the first argument to a hash and included the key chat_id with its value (note that we are getting the value from the front-end).

The subscription is re-routed to a specific channel through the subscribe method found:

All we are doing in this file is specifying the unique channel we are connecting to (streaming from). The params[:chat_id] is coming from the first argument of consumer.subscriptions.create:

consumer.subscriptions.create({channel: "MessageChannel", chat_id: chat_id},...)

This subscribed method cannot be called directly, it’s a callback method that will get called when trying to subscribe a consumer to a channel. It will reroute our user to the proper channel, but it can also be used to verify if the user is allowed to access the channel if not, the connection can be rejected.

A very special method to pay particular attention to in message_channel.js is the received function:

This function receives the “data” as an argument from the broadcasting function in the job file:

ActionCable.server.broadcast("message_channel_#{chat_id}", html: html)

So in the received(data) function, we can access the html value broadcasted by calling data.html. This will give us the html “p” tag we created in the job file. All we have to do is add this tag to the dom.

In our user show page, we have a div with a “messages” id. This div contains all our text messages. In the received function above, all we are doing is grabbing that div by its id and adding our data.html which contains the message.

This is a brief overview of how action cable works. As a recap, a message is sent from the front end to the controller action in the backend. The controller action will save the message in the database followed by calling the job performed method, which will broadcast the message to a specific channel. The received method will take the broadcasted message and add it to the dom.

Common Issues

  1. Sometimes your application may not run successfully if your Redis server is not manually started. You can start your Redis server by running the following command on a separate terminal:
redis-serve

2. If you are still having errors related to Redis, then change your development key found in config/cable.yml to the following:

development:
adapter: redis
url: redis://localhost:6379/1

Recap

Action cable can be used to implement real-time messaging, but it’s important to be aware that you can use action cable in any application that requires real-time data transfer between users. I hope you find this article informative.

--

--