Real Time Rails Chat Application (Part 2)
Bài đăng này đã không được cập nhật trong 3 năm
Link phần 1: https://viblo.asia/raincatcher/posts/oOVlYEzal8W Trong phần này, chúng ta sẽ bắt đầu tạo conversation để gửi tin nhắn, kèm theo các chức năng close, minimum, ...
Bắt đầu 1 conversation
Thêm vào routes.rb
Rails.application.routes.draw do
root 'home#index'
devise_for :users
resources :conversations, only: [:create]
end
Update conversation.rb
class Conversation < ApplicationRecord
has_many :messages, dependent: :destroy
belongs_to :sender, foreign_key: :sender_id, class_name: User
belongs_to :recipient, foreign_key: :recipient_id, class_name: User
validates :sender_id, uniqueness: { scope: :recipient_id }
scope :between, -> (sender_id, recipient_id) do
where(sender_id: sender_id, recipient_id: recipient_id).or(
where(sender_id: recipient_id, recipient_id: sender_id)
)
end
def self.get(sender_id, recipient_id)
conversation = between(sender_id, recipient_id).first
return conversation if conversation.present?
create(sender_id: sender_id, recipient_id: recipient_id)
end
def opposed_user(user)
user == recipient ? sender : recipient
end
end
Chúng ta đã thêm 1 scope để trả về cuộc nói chuyện giữa 2 request user. Nếu cả 2 đã từng có cuộc trò chuyện, chúng ta sẽ lấy nó. Nếu không có sẽ tạo mới.
Thêm vào ConversationsController
:
class ConversationsController < ApplicationController
def create
@conversation = Conversation.get(current_user.id, params[:user_id])
add_to_conversations unless conversated?
respond_to do |format|
format.js
end
end
private
def add_to_conversations
session[:conversations] ||= []
session[:conversations] << @conversation.id
end
def conversated?
session[:conversations].include?(@conversation.id)
end
end
Trong create method, chúng ta lấy conversation giữa current user và requested user. Nếu trong session không được add conversation_id, chúng ta sẽ thêm nó vào, nếu có, ta respond với 1 file js
Update home.index.html
dòng 18 từ:
<li><%= user.email %></li>
thành:
<li><%= link_to user.email, conversations_path(user_id: user), remote: true, method: :post %></li>
Tạo file conversations/create.js.erb
:
var conversations = $('#conversations-list');
var conversation = conversations.find("[data-conversation-id='" + "<%= @conversation.id %>" + "']");
if (conversation.length !== 1) {
conversations.append("<%= j(render 'conversations/conversation', conversation: @conversation, user: current_user) %>");
conversation = conversations.find("[data-conversation-id='" + "<%= @conversation.id %>" + "']");
}
conversation.find('.panel-body').show();
var messages_list = conversation.find('.messages-list');
var height = messages_list[0].scrollHeight;
messages_list.scrollTop(height);
Đầu tiên, chúng ta tìm kiếm existing conversation window trong thẻ div ‘#conversations-list’. Nếu nó không tồn tại (length !== 1), chúng ta thêm 1 conversation mới vào ‘#conversations-list’. Sau đó, chúng ta gọi method .show()
để hiển thị conversation’s window.
Cuối cùng, chúng ta kiểm tra height of the window và scroll xuống dưới cùng. Nếu chúng ta send message mới, chúng ta sẽ luôn ở dưới cùng của cuộc trò chuyện
Đây là kết quả:
Closing và minimizing 1 conversation
Update file routes.rb
:
Rails.application.routes.draw do
root 'home#index'
devise_for :users
resources :conversations, only: [:create] do
member do
post :close
end
end
end
Thay dòng thứ 5 trong file _converastion.html.erb
thành:
<%= link_to "x", close_conversation_path(conversation), class: "btn btn-default btn-xs pull-right", remote: true, method: :post %>
Thêm vào Conversations controller:
class ConversationsController < ApplicationController
...
def close
@conversation = Conversation.find(params[:id])
session[:conversations].delete(@conversation.id)
respond_to do |format|
format.js
end
end
...
end
Add file close.js.erb
$('#conversations-list').find("[data-conversation-id='" + "<%= @conversation.id %>" + "']").parent().remove();
Thêm method để minimizing 1 window:
//= require jquery
//= require jquery_ujs
//= require_tree .
(function() {
$(document).on('click', '.toggle-window', function(e) {
e.preventDefault();
var panel = $(this).parent().parent();
var messages_list = panel.find('.messages-list');
panel.find('.panel-body').toggle();
panel.attr('class', 'panel panel-default');
if (panel.find('.panel-body').is(':visible')) {
var height = messages_list[0].scrollHeight;
messages_list.scrollTop(height);
}
});
})();
Gửi 1 tin nhắn
Edit file routes.rb
resources :conversations, only: [:create] do
...
resources :messages, only: [:create]
end
Thêm form vào _converastion.html.erb
sau ‘.message-list’
<li>
<div class="panel panel-default" data-conversation-id="<%= conversation.id %>">
<div class="panel-heading">
<%= link_to conversation.opposed_user(user).email, '', class: 'toggle-window' %>
<%= link_to "x", close_conversation_path(conversation), class: "btn btn-default btn-xs pull-right", remote: true, method: :post %>
</div>
<div class="panel-body" style="display: none;">
<div class="messages-list">
<ul>
<%= render 'conversations/conversation_content', messages: conversation.messages, user: user %>
</ul>
</div>
<%= form_for [conversation, conversation.messages.new], remote: true do |f| %>
<%= f.hidden_field :user_id, value: user.id %>
<%= f.text_area :body, class: "form-control" %>
<%= f.submit "Send", class: "btn btn-success" %>
<% end %>
</div>
</div>
</li>
Thêm MessagesController
class MessagesController < ApplicationController
def create
@conversation = Conversation.includes(:recipient).find(params[:conversation_id])
@message = @conversation.messages.create(message_params)
respond_to do |format|
format.js
end
end
private
def message_params
params.require(:message).permit(:user_id, :body)
end
end
Tạo file create.js.erb
var conversation = $('#conversations-list').find("[data-conversation-id='" + "<%= @conversation.id %>" + "']");
conversation.find('.messages-list').find('ul').append("<%= j(render 'messages/message', message: @message, user: current_user) %>");
conversation.find('textarea').val('');
Thêm css vào application.scss
.messages-list {
max-height: 200px;
overflow-y: auto;
overflow-x: hidden;
}
.message-sent {
position: relative;
background-color: #D9EDF7;
border-color: #BCE8F1;
margin: 5px 20px;
padding: 10px;
float: right;
}
.message-received {
background-color: #F1F0F0;
border-color: #EEEEEE;
margin: 5px 20px;
padding: 10px;
float: left;
}
Và đây là sản phẩm:
All rights reserved