Real Time Rails Chat Application (Part 2)
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' ...
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: