How to build a simple chat messaging system in Rails

Carly L
4 min readJul 14, 2020

--

This system will rely on has many and belongs to relationships, validations, generators, self join models, params usage, and/or bootstrap so I recommend reviewing these concepts before following these steps.

Step 1: Create the rails application

Navigate into the directory you wish to create your new rails application. In your terminal, type rails new ‘insertAppName’. In this example, the rails app will be named ‘chat’.

rails new chat

Step 2 (Optional): Use devise gem for quick authentication

In your Gemfile, add the gem gem 'devise' and run bundle install.

Runrails generate devise user to create the User model and automatically require an email and password authorization attributes.

Run rails g devise:views — g is short for generate. If this errors out, run rails g devise:install. If this still causes errors, go to config/routes.rb and comment out # devise_for: users with the hashtag (#) and try again.

Step 3: Set up Model View Controller (MVC) framework

If you skipped step 2, run rails g resource user to create the User model, controller, and view files.

Run rails g resource conversation sender_id:integer recipient_id:integer. This will create columns for sender_id and recipient_id in the conversations data table.

Run rails g resource message.

In the latest migration file within the db/migrate directory, the migration should look like the following:

class CreateMessages < ActiveRecord::Migration[6.0]
def change
create_table :messages do |t|
t.text :body
t.references :conversations, index: true
t.references :users, index: true
t.timestamps
end
end
end

Run rails db:migrate.

Step 3: Set up Model relationships

In the Conversation model, app/models/conversation.rb,

class Conversation < ApplicationRecord
belongs_to :sender, foreign_key: :sender_id, class_name: 'User'
belongs_to :recipient, foreign_key: :recipient_id, class_name: 'User'
has_many :messages, dependent: :destroy
validates_uniqueness_of :sender_id, scope: :recipient_id
scope :between, -> (sender_id, recipient_id) do
where("(conversations.sender_id = ? AND conversations.recipient_id =?) OR (conversations.sender_id = ? AND conversations.recipient_id =?)", sender_id, recipient_id, recipient_id, sender_id)
end
end

belongs_to relationship — sets up a self join within the Conversation model for the User model. This allows for a user to be both the sender and recipient of messages. The Conversation model has a has_many and belongs_to relationship with the Message model; a conversation has many messages and messages belongs to a conversation.

dependent: :destroy — messages are dependent on conversations and if a conversation is deleted, its associated messages will be deleted as well.

scope — this defines that a conversation is unique for sender based on the recipient, aka a sender and recipient can’t have multiple conversations.

In the Message model, app/models/message.rb:

class Message < ApplicationRecord
belongs_to :conversation
belongs_to :user
validates_presence_of :body, :conversation_id, :user_id
end

Step 4: Set up Controller actions

In the Conversations controller, app/controllers/conversations_controller.rb:

class ConversationsController < ApplicationController
before_action :authenticate_user! # if followed step 2

def index
@users = User.all
@conversations = Conversation.all
end
def create
if Conversation.between(params[:sender_id], params[:recipient_id]).present?
@conversation = Conversation.between(params[:sender_id], params[:recipient_id]).first
else
@conversation = Conversation.create!(conversation_params)
end
redirect_to conversation_messages_path(@conversation)
end
private
def conversation_params
params.permit(:sender_id, :recipient_id)
end
end

create— this action checks if a conversation between a sender and recipient already exists and set an instance variable to this conversation, else, create a new conversation.

In the Message controller, app/controllers/messages_controller.rb:

class MessagesController < ApplicationController
before_action do
@conversation = Conversation.find(params[:conversation_id])
end

def index
@messages = @conversation.messages
@message = @conversation.messages.new
end
def new
@message = @conversation.messages.new
end
def create
@message = @conversation.messages.new(message_params)
if @message.save
redirect_to conversation_messages_path(@conversation)
end
end
private
def message_params
params.require(:message).permit(:body, :user_id)
end
end

index— this action allows the index view to have access to all current messages in a conversation and the capacity for creating new messages.

create — this action is validating that any new messages have correct params and redirects back to the conversation if it is so

Step 5: Set up Views

In app/views/conversations/index.html.erb

<h1>My Inbox</h1><h1>All Conversations:</h1>
<% @conversations.each do |conversation| %>
<% if conversation.sender_id == current_user.id || conversation.recipient_id == current_user.id %>
<% if conversation.sender_id == current_user.id %>
<% recipient = User.find(conversation.recipient_id) %>
<% else %>
<% recipient = User.find(conversation.sender_id) %>
<% end %>
<h3><%= link_to recipient.email, conversation_messages_path(conversation)%></h3>
<% end %>
<% end %>
<h1>All Users:</h1><% @users.each do |user| %>
<% if user.id != current_user.id %><h3>
<%= link_to user.email, conversations_path(sender_id: current_user.id, recipient_id: user.id), method: "post"%></h3>
<% end %>
<% end %>

In app/views/messages/index.html.erb

<% @messages.each do |message| %>
<% if message.body %>
<% user = User.find(message.user_id) %>
<%= user.email %><%= message.message_time %>
<%= message.body %>
<% end %>
<% end %>
<%= form_for [@conversation, @message] do |f| %>
<%= f.text_area :body %>
<%= f.text_field :user_id, value: current_user.id, type: "hidden" %>
<%= f.submit "Send Reply" %>
<% end %>

For your view files, feel free to add bootstrap/css to style this! The above code will be the basis of viewing every message in the conversation and a form_for to create new messages.

Voilà you can now chat!

Limitations

This is a simple messaging function that uses only Ruby. It does not run in real time so you will need to refresh the page after every message to view new messages. Each message is stored in a Message instance so runtimes may slow as more messages are stored in the database.

Don’t feel that you should follow this verbatim! These steps are easily tailored to fit any Rails application you are working on!

--

--