Websockets on Rails

Websockets on Rails

Real time applications, quick response time, and high reliable applications are the most common requirements nowadays. Fortunately, Rails is a top-notch framework that allows this and much more in a simple perspective.

ActionCable was introduced in 2015 as one of the solutions that Rails implemented to be on the path for a continuous increase of the modern web application. It was one of the most attractive features for the recent Rails upgrade which is why in this article we are going to explore how to implement a basic functionality taking advantage of the Websocket protocol that was implemented in Rails.

The intention of this article is to go straight to the technical details which developers care most about when trying to learn a new technology. More than a tutorial, recipe or just some code that works it will be a guide on how the protocol is being implemented in Rails.

Let’s begin with creating a new Rails project.

rails new websockets --skip-active-record

This project is skipping the default database config because we’re going to add MongoDB to it. After this is done let’s be sure that these two gems are added in order to support MongoDB.

gem 'mongoid' gem 'bson_ext'

Let's make sure that in our Gemfile, the following are also added. We need Puma because ActionCable needs a threaded server and we’re going to process messages in the middleware with Redis.

gem 'puma' gem 'redis'

After all of this is set do a bundle install

Let’s configure the DB by configuring MongoDB.

rails g mongoid:config

We are going to send simple messages from the console to a simple webpage. So, let’s create a model to get those messages. For this, we’re taking advantage of scaffolding.

rails g scaffold message message:text title:string

Rails scaffolding is pretty solid when the goal is to save time. We have created all the necessary routes, controllers, views and specs for our Message model. This will help us see ActionCable in action.

Note that we have a folder in the root path called channels. Inside this folder we’ll find Connection.rb and Channel.rb . These will be our base classes to define the logic that will be used for authorizing a connection and specifically to take care of all the communication inside a channel.

For Connection.rb we must include all the necessary logic to authorize the connection in the first place. For Channel.rb we’re including sharing code among all necessary channels needed in the app. For this exercise we don’t need to address any more complexity in these classes.

Now, we need to mount the ActionCable server in a separate uri. To do this we just add this to the routes.rb file.

mount ActionCable.server => '/cable'

Finally,  we’re going to establish the connection on the client side by including a separate JS file with the ActionCable consumer.

You will notice that the `channels` folder inside the assets pipeline pretty much includes all the client side for the consumers. What we are doing is just creating a consumer.

//= require cable //= require_self //= require_tree . this.App = {}; App.cable = ActionCable.createConsumer();

Now, we need to specify where the consumer must connect to establish the connection. This is made on each env config since we may use different architecture to support this functionality on production. For this example I’m just setting the url in `development` environment.

Rails.application.configure do ... config.action_cable.url = "ws://localhost:3000/cable" ...

Along with this, we must include `action_cable_metatag` in order to be able to allow the consumer to access the configuration in the different environments. Usually this is included in the `application.html.erb` layout.

<%= action_cable_meta_tag %>

We have built the connection for ActionCable. Now, we need to define how the channel of messages will behave on each event in the channel. Let’s create a new message channel for this matter. Let’s create it under /channels.

class MessagesChannel < ApplicationCable::Channel def subscribed stream_from 'messages' end end

We have established a channel to interact with the consumers. Now let’s feed this channel with a broadcast when creating a Message.

def create @message = Message.new(message_params) if @message.save ActionCable.server.broadcast 'messages', message: message.message head :ok end end

Note that this would send a broadcast message to all consumers listening to the message channel. This is our intention when trying to refresh all the pages using this Message. Now, the last step will handle that broadcast on the client side. See below:

App.messages = App.cable.subscriptions.create('MessagesChannel', { received: function(data) { $(".message-text").html(data.message); } });

For this specific example, we are setting the message text to be available for all views. Therefore, if the message is changed in the edit view, then the index view will reflect the changes.

It’s important to notice that when a broadcast is made the entire channel is handled by Redis. So basically when you send a message through a channel you’re telling Redis to process that message through that channel.

After all of the previous work is done we should be able to interact with the creation of different messages and be able to see the changes in real time when any of the messages is updated. By running the local Rails server we should be able to see the connections being established.

Puma starting in single mode... * Version 3.12.4 (ruby 2.4.1-p111), codename: Llamas in Pajamas * Min threads: 5, max threads: 5 * Environment: development * Listening on tcp://localhost:3000 Use Ctrl-C to stop Started GET "/cable" for ::1 at 2020-05-01 09:33:08 -0500 Started GET "/cable/" [WebSocket] for ::1 at 2020-05-01 09:33:08 -0500 Successfully upgraded to WebSocket (REQUEST_METHOD: GET, HTTP_CONNECTION: Upgrade, HTTP_UPGRADE: websocket) MessagesChannel is transmitting the subscription confirmation MessagesChannel is streaming from messages Started GET "/cable" for ::1 at 2020-05-01 09:33:10 -0500 Started GET "/cable/" [WebSocket] for ::1 at 2020-05-01 09:33:10 -0500 Successfully upgraded to WebSocket (REQUEST_METHOD: GET, HTTP_CONNECTION: Upgrade, HTTP_UPGRADE: websocket) MessagesChannel is transmitting the subscription confirmation MessagesChannel is streaming from messages

And then you can start creating messages and you’ll see that in the index page all the messages should be the broadcasted one!

Natively Expiring Records In AWS DynamoDB
CTO Cheat Sheet: CMS + SSG

Suscribe to our newsletter

Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.