Những điều cần biết về Action Cable trong Rails 5 - (Part 2)
Bài đăng này đã không được cập nhật trong 7 năm
Xây dựng một Chat App với Action Cable Đầu tiên tạo một project rails 5
rails new action-cable-demo
# Gemfile
gem "rail"', '~> 5.0.0'
gem "redis", '~> 3.0'
gem "puma", '~> 3.0'
Và chạy bundle install.
Ứng dụng sẽ bao gồm: 1 chat room có nhiều messages, 1 messages sẽ có nội dung và thuộc về một user trong một chat room. 1 user có username và có nhiều messages.
# app/models/chatroom.rb
class Chatroom < ApplicationRecord
has_many :messages, dependent: :destroy
has_many :users, through: :messages
validates :topic, presence: true, uniqueness: true, case_sensitive: false
end
# app/models/message.rb
class Message < ApplicationRecord
belongs_to :chatroom
belongs_to :user
end
# app/models/user.rb
class User < ApplicationRecord
has_many :messages
has_many :chatrooms, through: :messages
validates :username, presence: true, uniqueness: true
end
Ở đây chúng ta bỏ qua bước tạo routes và controllers, coi như user đã có thể đăng nhập bằng username và vào chatroom và gửi tin nhắn thông qua form trong chat room show page.
# app/controllers/chatrooms_controller.rb
class ChatroomsController < ApplicationController
...
def show
@chatroom = Chatroom.find_by slug: params[:slug]
@message = Message.new
end
end
Tiếp theo, tạo view cho chatroom
# app/views/chatrooms/show.html.erb
<div class="row col-md-8 col-md-offset-2">
<h1><%= @chatroom.topic %></h1>
<div class="panel panel-default">
<% if @chatroom.messages.any? %>
<div class="panel-body" id="messages">
<%= render partial: "messages/message", collection: @chatroom.messages%>
</div>
<% else %>
<div class="panel-body hidden" id="messages">
</div>
<% end %>
</div>
<%= render partial: "messages/message_form", locals: {message: @message, chatroom: @chatroom}%>
</div>
Và form để user post messages
# app/views/messages/_message_form.html.erb
<%=form_for message, remote: true, authenticity_token: true do |f|%>
<%= f.label :your_message%>:
<%= f.text_area :content, class: "form-control", data: {textarea: "message"}%>
<%= f.hidden_field :chatroom_id, value: chatroom.id %>
<%= f.submit "send", class: "btn btn-primary", style: "display: none", data: {send: "message"}%>
<% end %>
Viết code cho MessagesController để xử lý yêu cầu tạo message
class MessagesController < ApplicationController
def create
message = Message.new message_params
message.user = current_user
if message.save
# do some stuff
else
redirect_to chatrooms_path
end
end
private
def message_params
params.require(:message).permit(:content, :chatroom_id)
end
end
Đến lúc này ứng dụng chat về cở bản đã có thể chạy được, tuy nhiên chưa thể realtime. Bây giờ chúng ta sẽ tiếp tục xử lý real-time messages với Action Cable. Mô hình tổ chức của Action Cable như sau
├── app
├── channels
├── application_cable
├── channel.rb
└── connection.rb
Module ApplicationCable định nghĩa sẵn 2 class Channel và Connection
Connection class sẽ là nơi mà chúng ta thực hiện authorize với những kết nối đến.
Channel class sẽ là nơi chứa các shared logic giữa các channels mà chúng ta sẽ định nghĩa.
Thiết lập Websocket connection
Bưóc 1: Thiết lập socket connection phía server
Thêm vào file routes.rb
Rails.application.routes.draw do
mount ActionCable.server => "/cable"
resources :chatrooms, param: :slug
resources :messages
end
Bây giờ, Action Cable sẽ lắng nghe Websocket requests từ localhost:3000/cable. Khi ứng dụng chính của chúng ta đưọc khởi tạo thì một thực thể của Action Cable cũng được tạo ra. Action Cable sẽ thiết lập một socket connection trên localhost:3000/cable và bắt đầu lắng nghe socket requests.
Bước 2: Thiết lập socket connection phía client
Trong thư mục app/assets/javascripts/channels chúng ta sẽ tạo một file chatrooms.js để định nghĩa thực thể của websocket connection phía client
// app/assets/javascripts/channels/chatrooms.js
//= require cable
//= require_self
//= require_tree .
this.App = {};
App.cable = ActionCable.createConsumer();
Thêm require thư mục channels trong application.js file:
// app/assets/javascripts/application.js
//= require_tree ./channels
Bước 3: Tạo channel
Chúng ta đã thiết lập một persistent connection, lắng nghe mọi websocket requests đến ws://localhost:3000/cable. Bây giờ chúng ta cần tạo một channel để phát sóng và truyền tải tin nhắn.
Tạo một file app/channels/messages_channel và định nghĩa channel của chúng ta đưọc kế thừa từ class ApplicationCable::Channel
# app/channels/messages_channel.rb
class MessagesChannel < ApplicationCable::Channel
end
Messages Channel sẽ chứa một method subscribed, method này có trách nhiệm đăng ký và truyền tải thông điệp được broadcast trên channel này.
# app/channels/messages_channel.rb
class MessagesChannel < ApplicationCable::Channel
def subscribed
stream_from "messages"
end
end
Bước 4: Broadcast đến Channel
Khi có một tin nhắn mới nó sẽ được lưu xuống db và ngay lập tức được broadcast đến Message Channel, vì vậy chúng ta sẽ đặt phần code xử lý việc broadcast trong action create của Messages Channel.
# app/controllers/messages_controller.rb
class MessagesController < ApplicationController
def create
message = Message.new message_params
message.user = current_user
if message.save
ActionCable.server.broadcast "messages",
message: message.content,
user: message.user.username
head :ok
end
end
end
Chúng ta gọi đến method broadcast của Action Cable server và truyền kèm 1 vài tham số
mesages là tên của channel chúng ta đang thực hiện broadcast.
Kèm theo là nội dung sẽ được gửi qua channel dưới dạng JSON
message là nội dung của tin nhắn chúng ta vừa tạo.
user là username của user tạo ra tin nhắn.
Bước 5: Action Cable với Redis
Action Cable sử dụng Redis để gửi và nhận messages thông qua channel. Redis đóng vai trò lưu trữ dữ liệu và đảm bảo các messages sẽ được đồng bộ trong ứng dụng của chúng ta.
Action Cable sẽ tìm cấu hình của Redis trong file config/cable.yml đã đưọc tạo ra khi chúng ta tạo ứng dụng ban đầu.
development:
adapter: async
test:
adapter: async
production:
adapter: redis
url: redis://localhost:6379/1
Bây giờ, chúng ta cần thêm một subscription để đăng ký với Messages Channel.
Tạo file app/assets/javascripts/channels/messages.js để định nghĩa subscription:
// app/assets/javascripts/channels/messages.js
App.messages = App.cable.subscriptions.create('MessagesChannel', {
received: function(data) {
$("#messages").removeClass('hidden')
return $('#messages').append(this.renderMessage(data));
},
renderMessage: function(data) {
return "<p> <b>" + data.user + ": </b>" + data.message + "</p>";
}
});
Chúng ta thêm subscription cho client với App.cable.subscriptions.create kèm theo tên của channel muốn đăng ký, ở đây là Message Channel.
Khi function subscriptions.create được gọi, nó sẽ gọi callback đến method MessagesChannel#subscribed. MessagesChannel#subscribed có trách nhiệm truyền tải messages được broadcast trên Messages channel dưới dạng JSON đến phía client đã đăng ký channel.
Sau đó, khi phía client nhận được message mới dạng JSON nó sẽ gọi đến một helper function renderMessage với chức năng đơn giản là append message mới với DOM và message đưọc hiển thị trên chat room. Nguồn:
https://www.pluralsight.com/guides/ruby-ruby-on-rails/creating-a-chat-using-rails-action-cable
All rights reserved