Chat Application Development using Rails

Chat Application Development

Chat Application Development using Rails

In this blog, we demonstrate how Action Cable can be used in a real-world Rails application to build a chat application. And connect this chat application with SQLite database.

 

We will initially build a traditional Rails application and then change the app to feature a live message list where the page is immediately updated with new messages as they are posted.

 

Application Specification

To properly demonstrate, the supporting application will be a simple Rails application with two models:

  1. User
  2. Message

 

In this blog, we demonstrate how Action Cable can be used in a real-world Rails application to build a chat application. And connect this chat application with SQLite database.

 

The message model will store all the chat message content, along with the user_id of the corresponding author of each message. Again, we will use SQLite to store this to keep the code as simple as possible in demonstrating the Action Cable features.

 

By storing all the users and messages in a database table, when a new user comes to the site, they will be presented with all the messages that have been posted up to that point.

 

In order to simplify the application, I won’t build any authentication, just a simple sign-in page that either creates a new user record or updates an existing one, indexed on the given username. 

 

The initial version of the app will not use Action Cable, once we have the working traditional application, we will modify it to make use of WebSocket.

 

Create the Basic Application

Although Action Cable is delivered as a separate gem, it is included by default for new Rails 5 applications. Create the application using the normal Rails application generator:

$ rails new livechat

If you look at the freshly created application, you notice some additions to the usual Rails project skeleton.

 

First, there is a new directory called channels within the app/. This directory contains your Ruby code for the server-side component of your Action Cable feature, such as accepting and authenticating requests, processing incoming messages, and specifying what data to send out to the clients.

 

There is a new config file within config/ called cable.yml. As you can likely guess, this contains the configuration for the Action Cable back end, currently Redis or PostgreSQL.

 

There is also a new directory within assets/javascript/, called cable. This contains the JavaScript client-side component of your code, which contains the WebSocket connection code, along with detailing what channels to subscribe to and what actions to take on incoming messages on those channels. By default, Rails generates CoffeeScript files rather than JavaScript.

 

Before we crack on with the Action Cable code, let’s quickly create the simplest possible Rails app to support our features.

 

Users and Messages Resources

Messages Resources

Create User and Message resources; a user just has a name string, messages belong to a user and have a text message body:

$ rails g resource user name:string:uniq
invoke active_record
create db/migrate/20160108213846_create_users.rb
create app/models/user.rb
...
route resources :users
And now messages:
$ rails g resource message user:references body:text
invoke active_record
create db/migrate/20160108213918_create_messages.rb
create app/models/message.rb
...
route resources :messages
Now let’s add the message relationship to the user model, app/models/user.rb:
class User < ApplicationRecord
has_many :messages
end

 

The reciprocal belongs_to relationship in the message.RB file is already created by the generator so there is no need to add that manually.

Run the migrations. (Don’t forget that we now use the rails command rather than rake!) Since we’re just using the default SQLite driver for the database, we don’t need to run a command to create the database.

$ rails db:migrate
== 20160108213846 CreateUsers: migrating
-- create_table(:users)
-> 0.0011s
-- add_index(:users, :name, {:unique=>true})
-> 0.0006s
== 20160108213846 CreateUsers: migrated (0.0017s)
== 20160108213918 CreateMessages: migrating
-- create_table(:messages) -> 0.0011s
== 20160108213918 CreateMessages: migrated (0.0012s)

 

Now let’s add a couple of simple controller methods to allow us to log in or create a user

Edit the app/controllers/users_controller.rb file, as shown in Listing.

 

Listing The Users Controller File

class UsersController < ApplicationController def new
@user = User.new
end
def create
@user = User.find_or_initialize_by(name: params[:user][:name]) if @user.save
cookies.signed[:user_id] = @http://user.id
redirect_to messages_path
else
render :new
end
end
end

This simply presents a user login/sign up form and, on submission of a user’s name, either create the user record if the user doesn’t already exists, or finds the user record by name.

 

We then set a signed cookie to be the id of this user. Using a signed cookie ensures that the value cannot be tampered with on the client’s machine. Now create the associated view file, the log in/sign up form as app/views/users/ new.html.erb. Create the form.

 

Listing The User Created and Login Form

<h1>Sign up or Log in</h1>
<%=form_for @user do |form| %>
<%=form.text_field :name %>
<%=form.submit 'Log in' %>
<% end %>

Before we add the code for the messages controller, we should add some support methods for authenticating the user from the cookie we set in the login process.

Edit app/controllers/application_controller.rb and add the methods.

 

Listing  The Application Controller File

class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
helper_method :current_user
def require_user
redirect_to new_user_path unless current_user end
def current_user
@current_user = User.find_by id: cookies.signed[:user_id] end
end

 

Now, we can use these methods to build our messages controller. This just has two methods: index, to list all messages and create, to add a new message.

Edit the file app/controllers/messages_controller.rb and add the before_action and the index and create methods.

 

Listing The Messages Controller File

class MessagesController < ApplicationController before_action :require_user
def index
@messages = Message.all
end
def create
@message = current_user.messages.create! body: params[:message][:body] redirect_to messages_path
end
end

 

As discussed earlier, this initial version doesn’t make use of WebSocket, just traditional HTTP POST request to create messages, which in turn redirects the user’s browser back to the messages index page, such as how a typical web page would implement a form to create a record.

 

Now add the corresponding view, app/views/messages/index.html.erb.

 

Listing. The Basic Messages Index View

<h1>Chat room</h1>
<p>Your name: <%=current_user.name %></p>
<h2>Messages</h2>
<%=form_for :message do |form| %>
<%=form.text_field :body %>
<%=form.submit 'Send Message' %>
<% end %>
<ul id="messages">
<% @messages.each do |message| %>
<%=render partial: 'message', object: message %>
<% end %>
</ul>

The message partial file, app/views/messages/_message.html.erb.

Listing The Message Partial View

<li> <%= http://message.user.name %>: <%= message.body %> </li>

 

Finally, let’s set the application’s default route to point to the login page. Edit config/routes.rb and add the root route before the final end statement: root 'users#new'.

 

Obviously, this is a woefully incomplete application but it demonstrates how a Rails application would perform the business of presenting a form and accepting HTTP POST requests to create objects.

 

To check that this is all working, start the application with rails server and open two different browser applications, for example, Safari and Chrome, and opening http://localhost:3000 on each.

 

Log in to one browser as “Fred” and log in to the other as “Julie”, and then enter some messages in both browser windows. Immediately, we can see a problem: when other users enter new messages, they don’t show in our browser until we either reload the page or send a message of our own, which causes the page to reload.

 

Obviously, this is a limitation of web pages that we’ve come to know and work around as necessary. The most common way around this in Rails applications is to use polling, in which each browser repeatedly sends requests to the server asking if there are any new messages.

 

Obviously, the vast majority of the time there will be no new messages, but the client must keep polling every few seconds to make sure there is nothing new.

 

Clearly, this is a suboptimal state of affairs. Not only does it mean that the server is going to have to be able to withstand the constant barrage of polling requests from client browsers, but there is naturally going to be a delay in the message being posted and the client polling the server to discover it.

 

This naturally depends on the frequency of polling, but depending on the trade-off made by the developers, this will likely be, at best, a few seconds. This might not be too important for our little chat application, but it precludes certain types of apps being developed if they require more immediate updates.

 

WebSocket allows us to massively improve our chat application and provide immediate delivery of messages to all connected clients and completely does away with the need for polling. Action Cable makes this incredibly easy to do in Rails. Let’s get to work.

 

Set Up Action Cable

Set Up Action Cable

As discussed earlier, Action Cable requires a back-end service. A number of different subscription server adapters ship with Action Cable. By default, Rails now uses the Async adapter for development and testing environments, and the Redis adapter for production environments.

 

Async and Inline adapters allow the rails server itself to work as the subscription back-end server, meaning you don’t have to use an external server such as Redis.

 

However, since this is only recommended in development mode and you will require either Redis or PostgreSQL for production mode, I will demonstrate here how to use Redis as the back-end server.

 

[Note: You can free download the complete Office 365 and Office 2019 com setup Guide.]

 

Redis Installation

Redis Installation

Redis is very easy to install. Since the Redis source code doesn’t depend on any libraries or frameworks (apart from the standard libc), it is very simple to build from source. If you would like to do this, follow the instructions at http://Redis.io/topics/quickstart.

 

For the sake of simplicity, I will show how to install on Linux and OS X using the package management tools apt and brew.

 

Redis is not officially supported on Windows, but there is a fork maintained by Microsoft. You can find out more about this at https://github.com/MSOpenTech/Redis. If using Windows, you may find it simpler to make use of the PostgreSQL pub/sub adapter as that may be simpler to set up.

 

OS X Installation

To install Redis on OS X, we will use Homebrew, a package manager for OS X. If you don’t already use Homebrew, you should read about it and follow the install instructions at http://brew.sh.

 

After Homebrew is installed and set up, install Redis with the command:

$ brew install Redis

Downloading https://homebrew.bintray.com/bottles/Redis-3.0.7.el_ capitan.bottle.1.tar.gz

Already downloaded: /Library/Caches/Homebrew/Redis-3.0.7.el_capitan. bottle.1.tar.gz

Pouring Redis-3.0.7.el_capitan.bottle.1.tar.gz ==> Caveats

 

To have launched start Redis at login:

ln -sfv /usr/local/opt/Redis/*.plist ~/Library/LaunchAgents

 

Then to load Redis now:

launchctl load ~/Library/LaunchAgents/homebrew.mxcl.Redis.plist

Or, if you don't want/need launchctl, you can just run: Redis-server /usr/local/etc/Redis.conf

 

 Summary

/usr/local/Cellar/Redis/3.0.7: 9 files, 876.3K

Follow the instructions and either set up Redis to start automatically, or just run with this:

$ Redis-server /usr/local/etc/Redis.conf

 

After successfully starting the server, you are presented with the message:

5620:M 18 Jan 23:04:25.805 # Server started, Redis version 3.0.7

5620:M 18 Jan 23:04:25.807 * DB loaded from disk: 0.003 seconds

5620:M 18 Jan 23:04:25.807 * The server is now ready to accept connections on port 6379

 

Linux Installation

Linux Installation

To install Redis on Linux, you can either follow the instructions at http://Redis.io/ topics/quickstart to install from source or install the apt package using

$ sudo apt-get -y install Redis-server
Reading package lists... Done
Building dependency tree
...
Setting up Redis-server (2:1.2.0-1) ...
Starting Redis-server: Redis-server.
This installs and automatically starts the Redis server.

 

Action Cable Configuration

The Action Cable back-end adapter configuration is set in the config/cable.yml config file.

If you look at this file, you see that it follows the Rails convention of specifying the different configurations for the application environments development, test, and production.

 

Listing The Default Action Cable Back-end Configuration

# Action Cable uses Redis by default to administer connections, channels and sending/receiving messages over the WebSocket.

production: adapter: Redis
url: http://Redis://localhost:6379/1
development:
adapter: async
test:
adapter: async

 

By default, Rails uses the Redis adapter for production and Async for development and test. As discussed earlier, we will change the development adapter to Redis to demonstrate how it is configured and used.

 

Change the development section of config/cable.yml to use Redis, as follows:

development:

adapter: Redis

url: http://Redis://localhost:6379/2

 

Action Cable Connection Setup

Action Cable Connection Setup

When each browser (or client application) first connects to our Action Cable server, they perform an HTTP request, which, in turn, opens a socket connection between the application running in the browser and our Action Cable server.

 

When your Rails application is created, a skeleton Connection class is defined in app/channels/application_cable/connection.rb. If you open this, you will see that it currently contains no methods.

 

Listing The Generated ApplicationCable Class

# Be sure to restart your server when you modify this file. Action Cable runs in a loop that does not support auto-reloading.

module ApplicationCable

class Connection < ActionCable::Connection::Base end

end

 

As mentioned earlier, in this connection class, we deal with any authentication logic to identify which user is initiating a WebSocket connection. 

 

When the client application opens a WebSocket connection, as part of the handshake, it sends the same cookies that are used for an HTTP request, meaning that we receive any cookies that we have set elsewhere in our application.

 

As it is common Rails practice to save the user’s id as a signed cookie on the client’s machine, we can simply use this to identify the currently logged-in user and load the user object in the same way as we would in a normal Rails authentication code.

 

Although the Action Cable code lives within the Rails app and has access to the model and view code, since it doesn’t live within the usual Rails request lifecycle, none of the application’s before_filters are executed.

 

Instead, Action Cable Connection and Channel classes are expected to follow a set of conventions to match certain events, similar to the Rails callbacks that you already know.

 

When a connection attempt is made by the client, the method connect in the Connection class in the app/channels/application_cable/connection.rb file will be run. 

Similarly, the disconnect method will be executed when the WebSocket connection is severed (for instance, if the browser window is closed or the computer is powered down).

 

Within the connect method, we should perform any authentication and authorization that is necessary for any WebSocket channel. You also declare how this connection should be identified. Since we will be identifying a user by their id, we will use current_user.

 

So, let’s go ahead and add the connection code to the Connection class

Listing Connection Class to Authenticate a User

module ApplicationCable
class Connection < ActionCable::Connection::Base identified_by :current_user
def connect
self.current_user = find_current_user end
def disconnect
end
protected
def find_current_user
if current_user = User.find_by(id: cookies.signed[:user_id])
current_user
else
reject_unauthorized_connection
end
end
end
end

 

As discussed earlier, as the WebSocket connection is initiated by an HTTP request, any relevant cookies are passed to us. With these, we can find the User model for the signed-in user. If no user is found, the connection is rejected.

 

The statement identified_by :current_user marks current_user as an identifier for this specific connection

It is also available as a delegate for all channels created for this connection. This is necessary so that we can refer to the connection made to a user within the channel objects.

 

We should now enable Action Cable in the client-side JavaScript code. The Rails application generator created a file app/assets/javascript/cable.coffee. If you take a look at this, you will see the Asset Pipeline directives to include the Action Cable library and to load and files within the channels directory.

 

Listing The Generated cable. coffee File
# Action Cable provides the framework to deal with WebSockets in Rails.
# You can generate new channels where WebSocket features live using the rails generate channel command.
#
#= require action_cable #= require_self
#= require_tree ./channels
(function() {
this.App || (this.App = {});
App.cable = ActionCable.createConsumer();
}).call(this);

The ActionCable.createConsumer() command can take a parameter to specify the URL of the Action Cable server, 

however, it is best to omit this parameter and configure the cable server URL in the Rails environment setting, as we will see later in this blog.

 

Finally, to complete the setup we need to enable the Action Cable server to be accessed via the rails server. To do this, you need to add a configuration setting. Edit the config/application.RB file to include the mount_point configuration.

 

Listing Specifying a Mount Point for the Action Cable Server

require_relative 'boot'
require 'rails/all'
# Require the gems listed in Gemfile, including any gems
# you've limited to :test, :development, or :production. Bundler.require(*Rails.groups)
module Livechat
class Application < Rails::Application
# Settings in config/environments/* take precedence over those specified here.
# Application configuration should go into files in config/initializers
# -- all .rb files in that directory are automatically loaded. config.action_cable.mount_path = '/cable'
end
end

 

Creating a Messaging Channel

Creating a Messaging Channel

Now that we have a connection configured, we can begin creating channels. Each Action Cable connection can subscribe to one or more Action Cable channels. Once subscribed to a channel, the browser code can listen and transmit messages back and forth to the server at will.

 

To enable real-time messaging, we will create a channel called messages. There is a Rails generator to create the stub files for us. Run this now.

$ rails generate channel Messages

create app/channels/messages_channel.rb

create app/assets/javascripts/channels/messages.coffee

 

Server-Side Code

First, let’s write the server-side code for this channel. Open the generated messages_ channel.RB file. You will see the generated class.

ListingThe generated Messages Channel file

># Be sure to restart your server when you modify this file. Action Cable runs in a loop that does not support auto reloading.
class MessagesChannel < ApplicationCable::Channel def subscribed
# stream_from "some_channel"
end
def unsubscribed
# Any cleanup needed when channel is unsubscribed
end
end

 

Unsurprisingly, the subscribed method is executed whenever a client subscribes to a channel. The most common action to be included here is to initiated streaming from a Pub/Sub queue to the client’s subscribers. This is done using the stream_from command.

 

Change the subscribed method to the following:

def subscribed

stream_from "messages"

end

 

This means that any messages that are broadcast via the Pub/Sub queue called messages will be directly relayed to any subscribing clients. In turn, the client will then process or display the broadcast message.

 

In this case, since we only have one massage room, we only need a single channel called messages. If we were to have different messages boards when the client subscribes to a channel, it would pass a parameter that we could use as part of the queue name, for example:

 

stream_from "messages_room_#{params[:id]}"

 

We currently don’t need to perform any actions when a client unsubscribes from a channel, so we can leave the unsubscribe method empty.

 

We now need to write the code to actually specify the message to send via the channel and to initiate the broadcast. This is done using the command ActionCable. server.broadcast, specifying the channel name and the data to broadcast. Since we want to initiate the broadcast when a new message is created, we can do this in MessageController create method.

 

As for what data to send - we have two options here. Either we can send just the text of the chat message sent by the user or we can send an HTML fragment.

 

In the case of just sending the text of the message, the client code would then be responsible for how the message is presented on the page. The issue with this is that the HTML would then be duplicated in two places - the Rails partial view file _message.html.erb, and the JavaScript client-side code.

 

To solve this, we can use the existing HTML partial view file to create the HTML which in-turn is then broadcast to the clients, simplifying the client-side code and meaning that you only have to maintain one set of HTML templates.

 

Of course, which method you use depends entirely what your requirements are and you should use whichever makes the most sense for your application.

 

Go back to your messages_controller.rb file and modify the create method to broadcast the message partial view rather than performing a redirect.

 

Listing Changes to the Messages Controller to support Action Cable

def create
@message = current_user.messages.create! body: params[:message][:body] ActionCable.server.broadcast "messages", render(
partial: 'messages/message',
object: @message
)
end

 

This conveniently makes use of the new Rails 5 feature that allows partials to be rendered from anywhere. In a production application, you may prefer to perform this broadcast in an asynchronous job using Active Job.

 

Client-Side Code

Client-Side Code

We now need to write the client-side code for the live chat feature. But first, we need to make one small change to the new message form. Open app/views/messages/index.html.erb and change the form_for statement to add remote: true, as shown in Listing.

 

Listing Messages Index View changed to a remote form

<h2>Messages</h2>
<%=form_for :message, remote: true do |form| %> <%=form.text_field :body %>
<%=form.submit 'Send Message' %>
<% end %>

This change means that the message creates request is done as a JavaScript AJAX request, so the browser won’t automatically reload the page after the form is submitted.

 

Now edit the generated messages channel CoffeeScript file, app/assets/ javascript/channels/messages.coffee. As you can see, the default callbacks for connected, disconnected, and received are stubbed out for you. Since we simply want to append any message HTML partials that are received via the channel.

 

Listing The client-side Messages Channel code

App.messages = App.cable.subscriptions.create "MessagesChannel", connected: ->
# Called when the subscription is ready for use on the server
disconnected: ->
# Called when the subscription has been terminated by the server
received: (data) ->
# Called when there's incoming data on the websocket for this
# channel
$('#messages').append(data)

 

Running the Application

Running the Application

Before trying out the application, you need to ensure that Redis is running. If you haven’t already started it, you can start it up in a new terminal window using Redis-server.

 

Since we have added the Action Cable mount to the routes.rb file, we only need to run a single Rails process, so start as usual:

$ rails s

 

As before, open two different browsers, for example, Safari and Chrome. Go to http://localhost:3000 on each and log in as different people.

 

Now, with both windows open side by side, enter a chat message into the form. The message will immediately appear in both windows. If you look at the Rails server log, you will see the broadcast message being sent to all the subscribed browsers when a new message is created.

 

Running in Production

As previously mentioned, you have the option of running Action Cable as an in-app server or as a standalone process. When you go running your Action Cable application in production you may wish to run the Action Cable server separate from your normal Rails app to allow you to scale them separately as necessary.

 

However, this obviously complicates the setup and configuration of your deployment. You may wish to start simple and switch when necessary. We will briefly look at the configuration options available when setting up your application for something other than the default in-app development mode.

 

Action Cable URL

If you are running a standalone Action Cable server, you should set the URL where the Action Cable server can be accessed. This is likely to be a subdomain of your site, a different port on your main site or as a directory on your domain (such as /cable).

 

You should set this in your production.rb environment configuration file as config. action_cable.url .

Simply pass in the URL of your Action Cable server as a string.

If you are using SSL, you must use the prefix http://wss://, if not, http://ws://.

 

For example:

config.action_cable.url = 'http://wss://ThesisScientist.com:2000/cable'

 

When you set this configuration variable, Rails uses it for the action_cable_meta_ tag, which is used in the layout app/views/layouts/application.html.erb. Adding the following helper to the header of your layout file allows you to customize the URL of you Action Cable server.

 

<%= action_cable_meta_tag %>

 

If you look in the source HTML of your development application, you see this expands to the following:

<meta name="action-cable-url" content="/cable" />

 

This obviously shows your production action_cable.url in production mode rather than the relative URL /cable.

 

Allowed Request Origins

Action Cable only accepts a request from whitelisted domains, meaning that you must pass in the domain of your site as a configuration option.

 

You do this by setting config.action_cable.allowed_request_origins in your production.rb environment file. You pass in any domains that your application will be run on as an array. You can also use regular expressions in this array. For example:

 

config.action_cable.allowed_request_origins = [ 'http://ThesisScientist.com', /http:\/\/ThesisScientist.*/ ]

 

Standalone Mode

The Action Cable server is just a Rack application, so if you add a simple Rack configuration file, you can start the Action Cable server using puma or thin.

 

For example, add the Rack config file cable_config.ru:

require ::File.expand_path('../config/environment', __FILE__) Rails.application.eager_load!

run Action Cable.server

 

This can then be started using Puma as an application server:

bundle exec puma -p 3010 cable_config.ru

 

Then, by setting the config.action_cable.url configuration option as described, your application connects to this cable server.

In production, this would be made available through a web server such as NGINX, allowing you to load balance and route to multiple Action Cable servers if necessary.

Recommend