Action Cable in Rails
Hôm nay chúng ta sẽ build 1 app chat sử dụng ActionCable websocket, rails và postgreql
Http và Websockets
Với Http việc kết nối giữa client server có vòng đời ngắn. Client request đến server, kết nối được hình thành và dữ liệu được máy chủ trả về cho client gọi là response. Sau đó kết nối được đóng lại. Nhưng làm thế nào để client biết server có thay đổi. Thông thường, http sử dụng long pulling. Client sẽ hỏi server xem có gì thay đổi không trong khoảng thời gian nhất định Không giống như http, websocket là phương thức cho phép client và server giữ kết nối, client và server có thể trao đổi qua lại. Client subscribes đến channel trên server và khi có thay đổi, server sẽ phát tín hiệu và client nhận nó.
ActionCable hoạt động như thế nào?
Trong Rails, controller được xây dựng với mục đích xử lí các http request. Để xử lí các kết nối websocket, rails đã tạo ra thư mục mới gọi là channels. Channels hoạt động giống controller để xử lí các Websocket request. Các channel có thể được client subscribe để truyền dữ liệu qua lại.
Cài đặt
ActionCable là tín năng mới được tích hợp trên rails 5. Databse: PostgreSQL
rails _5.0.0.beta3_ new chatapp --database=postgresql
Sau đó cấu hình ở trong file database.yml xong thì chạy
cd chatapp
rake db:create
Các bước cần làm:
- Tạo channel websocket phía server gọi là room_channel, nó sẽ có các các method để xử lí việc subscribe, unsubscribe và gửi data đến client
- Sử dụng javascript ở client đế gọi subscribe, unsubcribe, xử lí việc gửi mà nhận dữ liệu.
rails g controller rooms show
#config/routes.rb
Rails.application.routes.draw do
root to: 'rooms#show'
Tiếp theo chúng ta sẽ tạo model mesage
rails g model message content:text
rồi tạo bảng message trong db
rails db:migrate
List tất cả các message
#app/controllers/rooms_controller.rb
class RoomsController < ApplicationController
def show
@messages = Message.all
end
end
view rooms#show
<h1>Chat room</h1>
<div id="messages">
<%= render @messages %>
</div>
<form>
<label>Say something:</label><br>
<input type="text" data-behavior="room_speaker">
</form>
# app/view/messages/_message.html.erb
<div class=“message”>
<p><%= message.content %></p>
</div>
// app/assets/javascripts/application.js
//= require jquery
//= require jquery_ujs
Tạo channel
routes.
#config/routes.rb
# Serve websocket cable requests in-process
mount ActionCable.server => '/cable'
cable.coffee
#= require action_cable
#= require_self
#= require_tree ./channels
#
@App ||= {}
App.cable = ActionCable.createConsumer()
Đặt <%= action_cable_meta_tag %> trong head của app/views/layouts/application.html.erb
Tạo channel
rails g channel room speak
#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
subscribed method sẽ được gọi khi client kết nối đến channel, và nó thường được sử dụng để bật lắng nghe những thay đổi cho client. speak method nhận data từ client.
#app/assets/javascripts/channels/room.coffee
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'
Ở đây, client subscribes đến server thông qua App.room = App.cable.subscriptions.create "RoomChannel"
. connected và disconnected dùng để xử lí trạng thái kết nối, received xử lí data nhận được từ server. speak method có nhiệm vụ gửi data lên server.
Truyền dữ liệu
Thêm tham số vào speak method
#app/assets/javascripts/channels/room.coffee
App.room = App.cable.subscriptions.create "RoomChannel",
#rest of the code
speak: (message) ->
@perform 'speak', message: message
speak sẽ gửi 1 message Json object đến speak method trong class RoomChannel.
#app/channels/room_channel.rb
def speak(data)
ActionCable.server.broadcast "room_channel", message: data['message']
end
Bây giờ, speak method sẽ phát tin nhắn lên room_channel. Nhưng làm thế nào để chúng ta nhận được? Để làm điều này ta chỉ định tất cả các subscriber nhận nó sử dụng stream_from trong subscribed method.
#app/channels/room_channel.rb
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
Về bản chất, room_channel là môi trường trong actioncable server, nơi mà dữ liệu đến và đổ về những client nhất định. Chúng ra có thể nhận dữ liệu từ subscribed method sử dụng received method trong room.coffee
#app/assets/javascripts/channels/room.coffee
received: (data) ->
alert(data['message'])
#speak function
$(document).on 'keypress', '[data-behavior~=room_speaker]', (event) ->
if event.keyCode is 13 # return/enter = send
App.room.speak event.target.value
event.target.value = ''
event.preventDefault()
Một event listener được thêm phía dưới file cho textbox ở trong template. Khi chúng ta viết gì đó và nhấn enter, nó sẽ gọi speak method trong room.coffee và gửi text đã nhập lên server.
Xử lí database
Khi nhận data từ client gửi lên, thay vì phát nó lên channel thì bây giờ lưu vào database.
#app/channels/room_channel.rb
#the rest of the methods
def speak(data)
Message.create! content: data['message']
end
Để giảm thời gian chờ server xử lí request, ta sử dụng background job để phát sóng message lên channel
#app/models/message.rb
class Message < ApplicationRecord
after_create_commit { MessageBroadcastJob.perform_later self }
end
#app/jobs/message_broadcast_job.rb
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
#app/assets/javascripts/channels/room.coffee
received: (data) ->
$('#messages').append data['message']
Hoàn thành
Bây giờ, bạn có thể start server và nhập vào textbox và tin nhắn của mình xuất hiện trong cuộc trò chuyện.