Tạo chat room đơn giản sử dụng Rails Action Cable
Bài đăng này đã không được cập nhật trong 3 năm
Ở bài viết này chúng ta sẽ khám phá 2 tính năng mới của Ruby on Rails - Action Cable và Active Job. Với Action Cable ta không còn nghi ngờ gì nữa, nó cho phép tích hợp giao thức WebSocket communication mà từ đó có thể mở một tương tác communication session giữa user browser và server. Với những công nghệ mà việc xây dựng ứng dụng realtime trở nên dễ dàng hơn bao giờ hết .
1. HTTP and Websockets
Đối với HTTP, kết nối giữa server và client là một kết nối ngắn hạn. Client gửi request tới server, khi đó một kết nối sẽ được thiết lập để yêu cầu resource (có thể là JSON, HTML, XML, ...) trả về cho client và ngay sau đó sẽ kết nối sẽ kết thúc. Câu hỏi được đặt ra ở đây là làm sao client biết được server có sử thay đổi data để gửi về client hay không?
Nào, đã đến lúc tìm hiểu về WebSocket rồi. Giao thức WebSocket nhằm duy trì kết nối giữa client và server, từ đây có thể stream data cho nhau. Các client đăng kí một kết nối tới server và khi có thông tin mới server sẽ broadcasts dữ liệu đến tất cả client đã đăng kí. Bằng cách này cả server và client đều biết được trạng thái của data để có thể dễ dàng đồng bộ khi xuất hiện sự thay đổi.
2. ActionCable function
Từ khi Rails Controller phát triển để xử lý nhiều requests, Rails đã đưa ra một ý tưởng để tích hợp WebSockets. Rails 5 có một thư mục trong app directory là channels. Channels hoạt động như là controllers để đóng gói các websocket request thành các unit work, ví dụ điển hình ở đây có thể là chat messages hay notifications. Các kênh có thể đăng ký client-side để có thể truyền dữ liệu tới một hay nhiều kênh.
3. Small chat app
Để hiểu rõ hơn về Rails Action Cable mình sẽ xây dựng một ứng dụng đơn giản step by step dưới @@
Requirement: Rails 5 (and higher) Ruby 2.2.4 (and higher)
Đối với action cable thì nó yêu cầu adapter PostgresSQL do đó bạn cần phải cài đặt nó trước nhé ) Tiếp theo bắt đầu init rails project với database postgres nào:
rails new chatapp --database=postgresql cd chatapp rake db:create
- Ý tưởng
- Tạo một server-side websocket channel gọi là room_channel chứa một vài method khi mà user subcribes, unsubcribes và gửi data tới client.
- Tạo một client-side sử dụng javascript (app này mình sẽ viết coffeescript) là RoomChannel. Nó sẽ đăng ký server-side channel và một số method connection/disconnecting với server để gửi nhận dữ liệu.
- RailsJob đặt các messages vào hàng đợi để lưu vào database và broadcast trở lại qua websocket channel sau khi đã được tạo.
Đầu tiên chúng ta generate controller cho chatroom ha:
rails g controller rooms show
Tiếp đến là config routes theo chuẩn sách giáo khoa nha
#config/routes.rb
Rails.application.routes.draw do
root to: 'rooms#show'
Tạo model message để lưu trữ nội dung tin nhắn:
rails g model message content:text rails db:migrate
Viết code để list all message nào: app/controllers/rooms_controller.rb
class RoomsController < ApplicationController
def show
@messages = Message.all
end
end
Tiếp đến view nè: app/views/rooms/show.html.erb
<h1>Chat room</h1>
<div id="messages"> <%= render @messages %> </div>
<form>
<label>Say something:</label><br>
<input type="text" data-behavior="room_speaker">
</form>
Tạo partial _message để render new message: app/views/messages/_message.html.erb
<div class="message">
<p><%= message.content %></p>
</div>
render @messages sẽ tự động tìm _message.html.erb trong views/messages
Kiểm trả application js và chắc rằng nội dung như sau nhé: app/assets/javascripts/application.js
//= require jquery
//= require jquery_ujs
- Creating a channel Đầu tiên để kích hoạt sử dụng action cable ta config như sau: config/routes.rb
mount ActionCable.server => '/cable'
init app/asset/javascipt/cable.js
//= require action_cable
//= require_self
//= require_tree ./channels
(function() {
this.App || (this.App = {});
App.cable = ActionCable.createConsumer();
}).call(this);
generate room channel
rails g channel room speak
Lệnh này sẽ generate 2 file: app/assets/javascripts/channels/room.coffee và app/channels/room_channel.rb.
class RoomChannel < ApplicationCable::Channel
def subscribed
# stream_from "some_channel"
end
def unsubscribed
# Any cleanup needed when channel is unsubscribed
end
def speak
end
end
App.room = App.cable.subscriptions.create "RoomChannel",
connected: ->
# Called when the subscription is ready for use on the server
disconnected: ->
# Called when the subscription has been terminated by the server
received: (data) ->
# Called when there's incoming data on the websocket for this channel
speak: ->
@perform 'speak'
Với javascipt file, client đăng ký server qua App.room = App.cable.subscriptions.create "RoomChannel". Có 3 method mặc định: connected, disconnected, received. Method speak gửi data tới server-side Chạy sever rails s và kiểm tra javascript console
- Trasmitting data Bây giờ chúng ta bắt đầu create và transmit data nhờ ActionCable. Chúng ta sửa lại speak function như sau: #app/assets/javascripts/channels/room.coffee
App.room = App.cable.subscriptions.create "RoomChannel",
#rest of the code
speak: (message) ->
@perform 'speak', message: message
Function này sẽ gửi message object tới server-side speak method ở RoomChannel. Sửa lại một chút method để nó accept parameters nào =))
#app/channels/room_channel.rb
def speak data
ActionCable.server.broadcast "room_channel", message: data['message']
end
Bây giờ speak method đã lấy được message và broadcast tới room_channel. Để get broadcast, chúng ta chỉ định subscribers lắng nghe sử dụng stream_from ở method subcribed
class RoomChannel < ApplicationCable::Channel
def subscribed
stream_from "room_channel"
end
def unsubscribed
# Any cleanup needed when channel is unsubscribed
end
def speak data
ActionCable.server.broadcast "room_channel", message: data['message']
end
end
- Lưu message vào database #app/channels/room_channel.rb
def speak data
Message.create! content: data['message']
end
Tiếp đến sử dụng RailsJob để đưa các messages vào queue từ đó server có thể thực thi nhiều tin nhắc trong cùng 1 thời điểm #app/models/message.rb
class Message < ApplicationRecord
after_create_commit {MessageBroadcastJob.perform_later self}
end
after_create_commit cho phép job thực thi lưu message vào database. perform_later: job sẽ thực thi khi queue rỗng Generate MessageBroadCastJob:
rails g job MessageBroadcast
class MessageBroadcastJob < ApplicationJob
queue_as :default
def perform message
ActionCable.server.broadcast "room_channel",
message: render_message(message)
end
private
def render_message message
ApplicationController.renderer.render(
partial: "messages/message",
locals: {message: message}
)
end
end
Method perform sẽ nhận message và render nó vào _message.html.erb partial. Với rails 5 bạn có thể render template ngoài phạm vi controller, sử dụng renderer của ApplicationController. Bước cuối cùng ta append message vào chatroom #app/assets/javascripts/channels/room.coffee
received: (data) ->
$('#messages').append data['message']
4. Source code và tham khảo
Github: Chatapp Tham khảo: creating-a-chat-using-rails-action-cable
All rights reserved