Skip to content

Instantly share code, notes, and snippets.

@Joseph-N
Last active April 15, 2021 11:09
Show Gist options
  • Star 13 You must be signed in to star a gist
  • Fork 15 You must be signed in to fork a gist
  • Save Joseph-N/d1413cc5822efa416175 to your computer and use it in GitHub Desktop.
Save Joseph-N/d1413cc5822efa416175 to your computer and use it in GitHub Desktop.
Tutorial code snippets for chat application in rails. Tutorial link http://goo.gl/l3e8zN
<li class="<%= self_or_other(message) %>">
<div class="avatar">
<img src="http://placehold.it/50x50" />
</div>
<div class="chatboxmessagecontent">
<p><%= message.body %></p>
<time datetime="<%= message.created_at %>" title="<%= message.created_at.strftime("%d %b %Y at %I:%M%p") %>">
<%= message_interlocutor(message).name %> • <%= message.created_at.strftime("%H:%M %p") %>
</time>
</div>
</li>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="">
<meta name="author" content="">
<meta content='<%= user_signed_in? ? current_user.id : "" %>' name='user-id'/>
<title>Chatty</title>
<%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true %>
<%= stylesheet_link_tag '//maxcdn.bootstrapcdn.com/font-awesome/4.1.0/css/font-awesome.min.css' %>
<%= javascript_include_tag 'application', 'data-turbolinks-track' => true %>
<%= csrf_meta_tags %>
<!-- shiv here -->
</head>
<body>
<%= render 'layouts/nav' %>
<div class="container">
<!-- flash messages here -->
<%= yield %>
</div>
<audio id="chatAudio"><source src="/sounds/notification.mp3" type="audio/mpeg"></audio>
</body>
</html>
/**
* GMAIL Like chat css
* CREDITS: http://css-tricks.com/replicating-google-hangouts-chat/
*
*/
.chatbox {
position: fixed;
position: expression("absolute");
width: 280px;
display: none;
}
.chatboxhead {
background: #666;
color: white;
padding: 0.5rem;
overflow: hidden;
border-right: 1px solid rgba(85, 85, 85, 0.87);
border-left: 1px solid rgba(85, 85, 85, 0.87);
}
.chatboxhead h1 {
display: inline;
font-size: 17px;
font-weight: 700;
}
.chatboxblink {
background-color: #176689;
border-right: 1px solid #176689;
border-left: 1px solid #176689;
}
.chatboxcontent {
font-family: arial, sans-serif;
height: 280px;
width: 280px;
overflow-y: auto;
padding: 7px;
border-left: 1px solid #cccccc;
border-right: 1px solid #cccccc;
border-bottom: 1px solid #eeeeee;
background: #e5e5e5;
line-height: 1.3em;
list-style: none;
}
.chatboxcontent li {
padding: 0.5rem;
overflow: hidden;
display: flex;
}
.chatboxcontent .avatar {
width: 40px;
position: relative;
}
.chatboxcontent .avatar img {
display: block;
width: 100%;
}
.other .avatar:after {
content: "";
position: absolute;
top: 0;
right: 0;
width: 0;
height: 0;
border: 5px solid white;
border-left-color: transparent;
border-bottom-color: transparent;
}
.self {
justify-content: flex-end;
align-items: flex-end;
}
.self .chatboxmessagecontent {
order: 1;
border-bottom-right-radius: 0;
}
.self .avatar {
order: 2;
}
.self .avatar:after {
content: "";
position: absolute;
bottom: 0;
left: 0;
width: 0;
height: 0;
border: 5px solid white;
border-right-color: transparent;
border-top-color: transparent;
box-shadow: 1px 1px 2px rgba(0, 0, 0, 0.2);
}
.chatboxmessagecontent {
background: white;
padding: 10px;
border-radius: 2px;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
}
.chatboxmessagecontent p {
font-size: 12px;
margin: 0 0 0.2rem 0;
-ms-word-break: break-all;
/* Non standard for webkit */
word-break: break-word;
-webkit-hyphens: auto;
-moz-hyphens: auto;
hyphens: auto;
}
.chatboxmessagecontent time {
font-size: 9px;
color: #ccc;
}
.chatboxinput {
padding: 5px;
background-color: #ffffff;
border-left: 1px solid #cccccc;
border-right: 1px solid #cccccc;
border-bottom: 1px solid #cccccc;
}
.chatboxtextarea {
width: 262px;
height: 44px;
padding: 3px 0pt 3px 3px;
border: 1px solid #eeeeee;
margin: 1px;
overflow: hidden;
resize: none !important;
}
.chatboxtextareaselected {
border: 2px solid #878787;
margin: 0;
}
.chatboxmessage {
margin-left: 1em;
}
.chatboxinfo {
margin-left: -1em;
color: #666666;
}
.chatboxoptions {
float: right;
}
.chatboxoptions a {
text-decoration: none;
color: white;
font-weight: bold;
}
.chatboxtitle {
float: left;
}
/**
* Chat logic
*
* Most of the js functionality is inspired from anatgarg.com
* jQuery tag Module from the tutorial
* http://anantgarg.com/2009/05/13/gmail-facebook-style-jquery-chat/
*
*/
var chatboxFocus = new Array();
var chatBoxes = new Array();
var ready = function () {
chatBox = {
/**
* creates an inline chatbox on the page by calling the
* createChatBox function passing along the unique conversation_id
*
* @param conversation_id
*/
chatWith: function (conversation_id) {
chatBox.createChatBox(conversation_id);
$("#chatbox_" + conversation_id + " .chatboxtextarea").focus();
},
/**
* closes the chatbox by essentially hiding it from the page
*
* @param conversation_id
*/
close: function (conversation_id) {
$('#chatbox_' + conversation_id).css('display', 'none');
chatBox.restructure();
},
/**
* Plays a notification sound when a new chat message arrives
*/
notify: function () {
var audioplayer = $('#chatAudio')[0];
audioplayer.play();
},
/**
* Handles 'smart layouts' of the chatboxes. Like when new chatboxes are
* added or removed from the view, it restructures them so that they appear
* neatly aligned on the page
*/
restructure: function () {
align = 0;
for (x in chatBoxes) {
chatbox_id = chatBoxes[x];
if ($("#chatbox_" + chatbox_id).css('display') != 'none') {
if (align == 0) {
$("#chatbox_" + chatbox_id).css('right', '20px');
} else {
width = (align) * (280 + 7) + 20;
$("#chatbox_" + chatbox_id).css('right', width + 'px');
}
align++;
}
}
},
/**
* Takes in two parameters. It is responsible for fetching the specific conversation's
* html page and appending it to the body of our home page e.g if conversation_id = 1
*
* $.get("conversations/1, function(data){
* // rest of the logic here
* }, "html")
*
* @param conversation_id
* @param minimizeChatBox
*/
createChatBox: function (conversation_id, minimizeChatBox) {
if ($("#chatbox_" + conversation_id).length > 0) {
if ($("#chatbox_" + conversation_id).css('display') == 'none') {
$("#chatbox_" + conversation_id).css('display', 'block');
chatBox.restructure();
}
$("#chatbox_" + conversation_id + " .chatboxtextarea").focus();
return;
}
$("body").append('<div id="chatbox_' + conversation_id + '" class="chatbox"></div>')
$.get("conversations/" + conversation_id, function (data) {
$('#chatbox_' + conversation_id).html(data);
$("#chatbox_" + conversation_id + " .chatboxcontent").scrollTop($("#chatbox_" + conversation_id + " .chatboxcontent")[0].scrollHeight);
}, "html");
$("#chatbox_" + conversation_id).css('bottom', '0px');
chatBoxeslength = 0;
for (x in chatBoxes) {
if ($("#chatbox_" + chatBoxes[x]).css('display') != 'none') {
chatBoxeslength++;
}
}
if (chatBoxeslength == 0) {
$("#chatbox_" + conversation_id).css('right', '20px');
} else {
width = (chatBoxeslength) * (280 + 7) + 20;
$("#chatbox_" + conversation_id).css('right', width + 'px');
}
chatBoxes.push(conversation_id);
if (minimizeChatBox == 1) {
minimizedChatBoxes = new Array();
if ($.cookie('chatbox_minimized')) {
minimizedChatBoxes = $.cookie('chatbox_minimized').split(/\|/);
}
minimize = 0;
for (j = 0; j < minimizedChatBoxes.length; j++) {
if (minimizedChatBoxes[j] == conversation_id) {
minimize = 1;
}
}
if (minimize == 1) {
$('#chatbox_' + conversation_id + ' .chatboxcontent').css('display', 'none');
$('#chatbox_' + conversation_id + ' .chatboxinput').css('display', 'none');
}
}
chatboxFocus[conversation_id] = false;
$("#chatbox_" + conversation_id + " .chatboxtextarea").blur(function () {
chatboxFocus[conversation_id] = false;
$("#chatbox_" + conversation_id + " .chatboxtextarea").removeClass('chatboxtextareaselected');
}).focus(function () {
chatboxFocus[conversation_id] = true;
$('#chatbox_' + conversation_id + ' .chatboxhead').removeClass('chatboxblink');
$("#chatbox_" + conversation_id + " .chatboxtextarea").addClass('chatboxtextareaselected');
});
$("#chatbox_" + conversation_id).click(function () {
if ($('#chatbox_' + conversation_id + ' .chatboxcontent').css('display') != 'none') {
$("#chatbox_" + conversation_id + " .chatboxtextarea").focus();
}
});
$("#chatbox_" + conversation_id).show();
},
/**
* Responsible for listening to the keypresses when chatting. If the Enter button is pressed,
* we submit our conversation form to our rails app
*
* @param event
* @param chatboxtextarea
* @param conversation_id
*/
checkInputKey: function (event, chatboxtextarea, conversation_id) {
if (event.keyCode == 13 && event.shiftKey == 0) {
event.preventDefault();
message = chatboxtextarea.val();
message = message.replace(/^\s+|\s+$/g, "");
if (message != '') {
$('#conversation_form_' + conversation_id).submit();
$(chatboxtextarea).val('');
$(chatboxtextarea).focus();
$(chatboxtextarea).css('height', '44px');
}
}
var adjustedHeight = chatboxtextarea.clientHeight;
var maxHeight = 94;
if (maxHeight > adjustedHeight) {
adjustedHeight = Math.max(chatboxtextarea.scrollHeight, adjustedHeight);
if (maxHeight)
adjustedHeight = Math.min(maxHeight, adjustedHeight);
if (adjustedHeight > chatboxtextarea.clientHeight)
$(chatboxtextarea).css('height', adjustedHeight + 8 + 'px');
} else {
$(chatboxtextarea).css('overflow', 'auto');
}
},
/**
* Responsible for handling minimize and maximize of the chatbox
*
* @param conversation_id
*/
toggleChatBoxGrowth: function (conversation_id) {
if ($('#chatbox_' + conversation_id + ' .chatboxcontent').css('display') == 'none') {
var minimizedChatBoxes = new Array();
if ($.cookie('chatbox_minimized')) {
minimizedChatBoxes = $.cookie('chatbox_minimized').split(/\|/);
}
var newCookie = '';
for (i = 0; i < minimizedChatBoxes.length; i++) {
if (minimizedChatBoxes[i] != conversation_id) {
newCookie += conversation_id + '|';
}
}
newCookie = newCookie.slice(0, -1)
$.cookie('chatbox_minimized', newCookie);
$('#chatbox_' + conversation_id + ' .chatboxcontent').css('display', 'block');
$('#chatbox_' + conversation_id + ' .chatboxinput').css('display', 'block');
$("#chatbox_" + conversation_id + " .chatboxcontent").scrollTop($("#chatbox_" + conversation_id + " .chatboxcontent")[0].scrollHeight);
} else {
var newCookie = conversation_id;
if ($.cookie('chatbox_minimized')) {
newCookie += '|' + $.cookie('chatbox_minimized');
}
$.cookie('chatbox_minimized', newCookie);
$('#chatbox_' + conversation_id + ' .chatboxcontent').css('display', 'none');
$('#chatbox_' + conversation_id + ' .chatboxinput').css('display', 'none');
}
}
}
/**
* Cookie plugin
*
* Copyright (c) 2006 Klaus Hartl (stilbuero.de)
* Dual licensed under the MIT and GPL licenses:
* http://www.opensource.org/licenses/mit-license.php
* http://www.gnu.org/licenses/gpl.html
*
*/
jQuery.cookie = function (name, value, options) {
if (typeof value != 'undefined') { // name and value given, set cookie
options = options || {};
if (value === null) {
value = '';
options.expires = -1;
}
var expires = '';
if (options.expires && (typeof options.expires == 'number' || options.expires.toUTCString)) {
var date;
if (typeof options.expires == 'number') {
date = new Date();
date.setTime(date.getTime() + (options.expires * 24 * 60 * 60 * 1000));
} else {
date = options.expires;
}
expires = '; expires=' + date.toUTCString(); // use expires attribute, max-age is not supported by IE
}
// CAUTION: Needed to parenthesize options.path and options.domain
// in the following expressions, otherwise they evaluate to undefined
// in the packed version for some reason...
var path = options.path ? '; path=' + (options.path) : '';
var domain = options.domain ? '; domain=' + (options.domain) : '';
var secure = options.secure ? '; secure' : '';
document.cookie = [name, '=', encodeURIComponent(value), expires, path, domain, secure].join('');
} else { // only name given, get cookie
var cookieValue = null;
if (document.cookie && document.cookie != '') {
var cookies = document.cookie.split(';');
for (var i = 0; i < cookies.length; i++) {
var cookie = jQuery.trim(cookies[i]);
// Does this cookie string begin with the name we want?
if (cookie.substring(0, name.length + 1) == (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
};
}
$(document).ready(ready);
$(document).on("page:load", ready);
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 :involving, -> (user) do
where("conversations.sender_id =? OR conversations.recipient_id =?",user.id,user.id)
end
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
class ConversationsController < ApplicationController
before_filter :authenticate_user!
layout false
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
render json: { conversation_id: @conversation.id }
end
def show
@conversation = Conversation.find(params[:id])
@reciever = interlocutor(@conversation)
@messages = @conversation.messages
@message = Message.new
end
private
def conversation_params
params.permit(:sender_id, :recipient_id)
end
def interlocutor(conversation)
current_user == conversation.recipient ? conversation.sender : conversation.recipient
end
end
<% publish_to @path do %>
var id = "<%= @conversation.id %>";
var chatbox = $("#chatbox_" + id + " .chatboxcontent");
var sender_id = "<%= @message.user.id %>";
var reciever_id = $('meta[name=user-id]').attr("content");
chatbox.append("<%= j render( partial: @message ) %>");
chatbox.scrollTop(chatbox[0].scrollHeight);
if(sender_id != reciever_id){
chatBox.chatWith(id);
chatbox.children().last().removeClass("self").addClass("other");
chatbox.scrollTop(chatbox[0].scrollHeight);
chatBox.notify();
}
<% end %>
class CreateConversations < ActiveRecord::Migration
def change
create_table :conversations do |t|
t.integer :sender_id
t.integer :recipient_id
t.timestamps
end
add_index :conversations, :sender_id
add_index :conversations, :recipient_id
end
end
class Message < ActiveRecord::Base
belongs_to :conversation
belongs_to :user
validates_presence_of :body, :conversation_id, :user_id
end
class MessagesController < ApplicationController
before_filter :authenticate_user!
def create
@conversation = Conversation.find(params[:conversation_id])
@message = @conversation.messages.build(message_params)
@message.user_id = current_user.id
@message.save!
@path = conversation_path(@conversation)
end
private
def message_params
params.require(:message).permit(:body)
end
end
module MessagesHelper
def self_or_other(message)
message.user == current_user ? "self" : "other"
end
def message_interlocutor(message)
message.user == message.conversation.sender ? message.conversation.sender : message.conversation.recipient
end
end
<meta content='<%= user_signed_in? ? current_user.id : "" %>' name='user-id'/>
Rails.application.routes.draw do
devise_for :users
authenticated :user do
root 'users#index'
end
unauthenticated :user do
devise_scope :user do
get "/" => "devise/sessions#new"
end
end
resources :conversations do
resources :messages
end
end
<div class="chatboxhead">
<div class="chatboxtitle">
<i class="fa fa-comments"></i>
<h1><%= @reciever.name %> </h1>
</div>
<div class="chatboxoptions">
<%= link_to "<i class='fa fa-minus'></i> ".html_safe, "#", class: "toggleChatBox", "data-cid" => @conversation.id %>
&nbsp;&nbsp;
<%= link_to "<i class='fa fa-times'></i> ".html_safe, "#", class: "closeChat", "data-cid" => @conversation.id %>
</div>
<br clear="all"/>
</div>
<div class="chatboxcontent">
<% if @messages.any? %>
<%= render @messages %>
<% end %>
</div>
<div class="chatboxinput">
<%= form_for([@conversation, @message], :remote => true, :html => {id: "conversation_form_#{@conversation.id}"}) do |f| %>
<%= f.text_area :body, class: "chatboxtextarea", "data-cid" => @conversation.id %>
<% end %>
</div>
<%= subscribe_to conversation_path(@conversation) %>
<% @users.each_with_index do |user, index| %>
<tr>
<td><%= index +=1 %></td>
<td><%= user.name %></td>
<td>
<%= link_to "Send Message", "#", class: "btn btn-success btn-xs start-conversation",
"data-sid" => current_user.id, "data-rip" => user.id %>
</td>
</tr>
<% end %>
class User < ActiveRecord::Base
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
has_many :conversations, :foreign_key => :sender_id
end
var ready = function () {
/**
* When the send message link on our home page is clicked
* send an ajax request to our rails app with the sender_id and
* recipient_id
*/
$('.start-conversation').click(function (e) {
e.preventDefault();
var sender_id = $(this).data('sid');
var recipient_id = $(this).data('rip');
$.post("/conversations", { sender_id: sender_id, recipient_id: recipient_id }, function (data) {
chatBox.chatWith(data.conversation_id);
});
});
/**
* Used to minimize the chatbox
*/
$(document).on('click', '.toggleChatBox', function (e) {
e.preventDefault();
var id = $(this).data('cid');
chatBox.toggleChatBoxGrowth(id);
});
/**
* Used to close the chatbox
*/
$(document).on('click', '.closeChat', function (e) {
e.preventDefault();
var id = $(this).data('cid');
chatBox.close(id);
});
/**
* Listen on keypress' in our chat textarea and call the
* chatInputKey in chat.js for inspection
*/
$(document).on('keydown', '.chatboxtextarea', function (event) {
var id = $(this).data('cid');
chatBox.checkInputKey(event, $(this), id);
});
/**
* When a conversation link is clicked show up the respective
* conversation chatbox
*/
$('a.conversation').click(function (e) {
e.preventDefault();
var conversation_id = $(this).data('cid');
chatBox.chatWith(conversation_id);
});
}
$(document).ready(ready);
$(document).on("page:load", ready);
@charleswdickstein
Copy link

I got this application working for me before, but now when I press send message it has stopped working and I can't figure out why...

@vampiricbeef
Copy link

Because of the has_many foreign key relationship with sender and current user. only the current_user as a sender can list the conversations.. if you change the foreign key on the has_many to recipient_id then only the recipient can list the conversations.. Need both to be referenced, im not sure how to give both of them the foreign key. :/

@masudhossain
Copy link

Is there an update for this application? Pretty much the only one I found that supports facebook style web chatting.

If anyone knows of a better and new one (or even the github repo) please let me know!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment