Tutorial: Create a simple messaging system on Rails

I know some people learn by just playing with code and not reading/reading later, so if you’re one of those people, here is the repo:

https://github.com/danarmulder/simple_rails_messaging_tutorial

For our app, it was necessary to have a feature that allowed users to message each other.

I started out by trying to use a messaging gem I found online. This gem was overly complicated and not really what I was looking for, for these reasons:

  • it allowed for multiple conversations between two users and what we wanted was more of a Facebook-type messaging system, with only one conversation between users
  • it had extra features that were unnecessary (like a subject line, trash box, inbox)
  • there was no simple version of the whole app online for me to look at

So after a lot of wasted time trying to get the messaging gem to work in with our app and failing I decided to go down another road. At this point I found this tutorial. Its really well done but once again overly complicated for what I was looking for. So I read the basic logic for the messaging system and then decided to create my own messaging system from scratch.

And so the Simple Rails Messaging App Tutorial Starts Here.

Once again, the repo.

To start, I’ll explain the data system. We have users, you can use any user system you like. Besides the users we have two other data models, conversations and messages. Messages belong to a conversation and a conversation belongs to two users. In our model we’ll have a special method that checks to see if the conversation between two users exists already or not, if it does the new conversation will not be created, and instead the user will be redirected to the conversation’s page. So we’ll start with conversations. In your command line run:


rails g migration createConversations

Your migration will look like this:

class CreateConversations < ActiveRecord::Migration
def change
create_table :conversations do |t|
t.integer :sender_id
t.integer :recipient_id
   t.timestamps
end
end
end

So ultimately the sole responsibility of the conversation is to keep track of the two users involved. I’ve called these two users the sender and the receiver(the sender being the person that initialized the conversation) but both users will be sending and receiving messages during a conversation. We could call these users anything, such as user1 and user2 or userA and userB. Choose whatever makes the most sense to you. I’ve chosen sender/receiver because it gives a little more semantic meaning to the variable. I’ll also go ahead and show you the migration for the messages now, to help set the context of how these two will play together.

rails g migration createMessages
class CreateMessages < ActiveRecord::Migration
def change
create_table :messages do |t|
t.text :body
t.references :conversation, index: true
t.references :user, index: true
t.boolean :read, :default => false
   t.timestamps
end
end
end

The message has a body, a conversation id and user id and boolean value for whether is has been read yet, which allow us to have notifications and special displays for unread messages. Great, now we’ll talk about the conversation model.

class Conversation < ActiveRecord::Base
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

In the model we create a scope validation, “between,” this validation takes the sender_id and recipient_id for the conversation and checks whether a conversation exists between these two ids because we only want two users to have one conversation. Let’s say the user with id 5 is trying to create a conversation with a user whose id is 7, we’ll call them 5 and 7 respectively. 5 in this case is the sender or first user, 7 is the receiver or second user. Our scope method checks to see if a conversation exists with 5 as the sender id and 7 as the receiver but it also needs to check if a conversation exists with 7 being the sender and 5 being the receiver. In this way we can make sure that two users will only have one conversation created between them.

The model for the message will look like this:

class Message < ActiveRecord::Base
belongs_to :conversation
belongs_to :user
 validates_presence_of :body, :conversation_id, :user_id
 def message_time
created_at.strftime(“%m/%d/%y at %l:%M %p”)
end
end

Each message will have a conversation it belongs to, and also a user id it belongs to. In this way we can know who said what in our conversation. I’ve included a method here to display the time it was created at in a user friendly format. Moving on to the controllers, the conversation controller will look like this:

class ConversationsController < ApplicationController
before_action :authenticate_user
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

Here we use a scope method, between, when the create action is called, the between method checks to see if a conversation between these 2 users exists…only if one does not already exist is the conversation created. Our messages controller :

class MessagesController < ApplicationController
before_action do
@conversation = Conversation.find(params[:conversation_id])
end
def index
@messages = @conversation.messages
if @messages.length > 10
@over_ten = true
@messages = @messages[-10..-1]
end
if params[:m]
@over_ten = false
@messages = @conversation.messages
end
if @messages.last
if @messages.last.user_id != current_user.id
@messages.last.read = true;
end
end
@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

In the index I created a variable(over_ten) to allow me to only display 10 messages at a time and then I link to the rest of the messages using a query string. When the index action is called there is an if statement to check if the last message was created by the current user and if it wasn’t the message will become read. Though, in the example repo I don’t use this method to keep everything as straight forward as possible.

Moving on to routes. For the routes I have messages nested within conversations.

resources :conversations do
resources :messages
end

Lastly the for the views, I have a conversation index view, which is essentially a users inbox—it displays all the messages in which the user is apart of, and also a list of users to message.

<div class=”ui segment”>
<h3>Mailbox</h3>
 <div class=”ui list”>
<div class=”item”>
<% @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 %>
<%= link_to recipient.first_name, conversation_messages_path(conversation)%>
<% end %>
<% end %>
</div>
</div>
</div>
<div class=”ui segment”>
<h3>All Users</h3>
<div class=”ui list”>
<% @users.each do |user| %>
<% if user.id != current_user.id %>
<div class=”item”>
<%= user.first_name %> <%= link_to ‘Message me!’, conversations_path(sender_id: current_user.id, recipient_id: user.id), method: ‘post’%>
</div>
<% end %>
<% end %>
</div>
</div>

In the view I use some logic to display the conversation link as the other person’s name. This link sends them to the messages index for that conversation. Here is the view for the message index.

<% if @over_ten %>
<%= link_to ‘Show Previous’, ‘?m=all’ %>
<% end %>
<div class=”ui segment”>
<% @messages.each do |message| %>
<% if message.body %>
<% user = User.find(message.user_id) %>
<div class=”item”>
<div class=”content”>
<div class=”header”><strong><%= user.first_name %></strong> <%= message.message_time %></div>
<div class=”list”>
<div class=”item”>
<i class=”right triangle icon”></i>
<%= message.body %>
</div>
</div>
</div>
</div>
<% end %>
<% end %>
</div>
<%= form_for [@conversation, @message], html: {class: “ui reply form”} do |f| %>
<div class=”field”>
<%= f.text_area :body, class: “form-control” %>
</div>
<%= f.text_field :user_id, value: current_user.id, type: “hidden” %>
<div>
<%= f.submit “Add Reply”, class: “ui blue labeled submit icon button” %>
</div>
<% end %>

Here I use the boolean variable from the controller to display only ten messages and create a link to the rest of the messages, in which I use a query string to display all of the messages.

And that should conclude this tutorial. Please comment if you have any questions and happy coding!