ActionCable và websockets trên Rails 5

Mở đầu

Hiện nay việc sử dùng Realtime trong các ứng dụng ngày càng phổ biến. Đơn cử như những ứng dụng Chat, việc trao đổi thông tin qua lại giữa những người Chat với nhau đòi hỏi thông tin phải được cập nhật liên tục và tự động gửi đến cho người nhận hoặc đưa ra những thông báo ngay sau khi có sự thay đổi dữ liệu trong hệ thống. Đương nhiên với một Framework hỗ trợ xây dựng các ứng dụng Web như Ruby on Rails thì việc hỗ trợ tinh năng này là điều sớm muộn. Đó là lí do Rails 5 ra mắt tính năng ActionCable.

Nhiệm vụ cả ActionCable là giúp cho việc xây dựng những chức năng Realtime bên trong các ứng dụng Rails trở nên đơn giản và thuận tiện hơn.

Chức năng của ActionCable

ActionCable sử dụng cơ chế Publish/Subscribe của Redis để đăng kí chanel và truyền tải dữ liệu vào chanel đó. Cụ thể là mỗi khi có thông tin truyền tới, hệ thống Redis sẽ tự động phát sinh sự kiện Publish, dựa vào sự kiện này để cập nhật hay truyền tải dữ liệu đến người dùng.

ActionCable tích hợp với Websocket hỗ trợ giao tiếp hai chiều giữa client và server.

Xây dựng ứng dụng Realtime đơn giản dùng ActionCable

Một chú ý cho bạn khi muốn tạo ứng dụng Realtime dùng ActionCable đó là cơ chế này chỉ được Rails cung cấp cho phiên bản Rails 5 và ruby 2.2.2 trở lên. Vậy nên hãy bắt đầu bằng việc cài đặt Ruby 2.2.2 và Rails 5.

1. Tạo các trang views

Chúng ta sẽ hiển thị nội dung chat trên trang messages#index và yêu cầu người dùng phải nhập username trước khi vào phòng Chat.

# app/controllers/sessions_controller.rb
class SessionsController < ApplicationController
  def create
    cookies.signed[:username] = params[:session][:username]
    redirect_to messages_path
  end
end

Form cho người dùng điền thông tin trước khi vào Chat Room

  <%= form_for :session, url: sessions_path do |f| %>
    <%= f.label :username, 'Enter a username' %><br/>
    <%= f.text_field :username %><br/>
    <%= f.submit 'Comment' %>
  <% end %>

Tạo Messages controller và action hiển thị nội dung chat

# app/controllers/messages_controller.rb
class MessagesController < ApplicationController
  def index
  end
end

Trang index.html.erb chỉ có nhiệm vụ hiển thị nội dung chat, vậy nên không cần can thiệp gì thêm vào action này.

Tiếp theo là phần hiển thị thông tin chat

  <-- messages/index.html.erb -->
  <p>Signed in as @<%= cookies.signed[:username] %>.</p>
    <div id='messages'></div>
  <br/><br/>

  <%= text_field_tag :body, '', id: 'chat-discuss' %>

2. Generate Channel

Phần việc tiếp theo là tạo ra các kênh giao tiếp giữa server và client

rails generate channel Chat discuss

Câu lệnh này sẽ sinh ra đối tượng ChatChannel và action discuss Sau khi tạo kênh, cần bật kết nối cable

@App ||= {}
App.cable = ActionCable.createConsumer()

Để ActionCable chạy mỗi khi rails server running ta cần set config trong routes

  Rails.application.routes.draw do
    # other routes

    mount ActionCable.server => '/cable'
  end

3. Thiết lập cho kênh vừa tạo

Như cơ chế Pub/Sub đã nêu bên trên, khi người dùng kết nối vào kênh, action subcribe được gọi, phía client chính là các messages. Mỗi khi có thay đổi phía messages thì nó cũng sẽ được gửi tới người dùng.

Mặt khác action discuss phía client, mỗi khi người dùng gõ messages và ấn Enter, chúng ta sẽ gọi App.chat.discuss để gửi dữ liệu đến server.

# app/channels/chat_channel.rb
class ChatChannel < ApplicationCable::Channel
  def subscribed
    stream_from 'messages'
  end

  def discuss(data)
    ActionCable.server.broadcast('messages',
      message: render_message(data['message']))
  end

  private

  def render_message(message)
    ApplicationController.render(partial: 'messages/message',
                                 locals: { message: message })
  end
end

Khi nhận được tin nhắn từ phía discuss, chúng ta sẽ render nội dung tin nhắn đó ra HTML

<--app/views/messages/_message.html.erb -->
<p><%= message %></p>

Khi chúng ta gửi message, tất cả người dùng đang kết nối trên cùng kênh sẽ nhận được dữ liệu thông qua phương thức App.chat.received từ cliend side.

4 Client side

Bắt sự kiện nhấn phím Enter khi chat:

#app/assets/javascripts/channels/chat.coffee:
$(document).on 'keypress', '#chat-discuss', (event) ->
  if event.keyCode is 13
    App.chat.discuss(event.target.value)
    event.target.value = ""
    event.preventDefault()

Khi người dùng nhấn phím Enter, nội dung chat sẽ được truyền vào channel thông qua method App.chat.discuss rồi gửi đến cable server. Thiết lập 2 action discuss và received message trong App.chat

# app/assets/javascripts/channels/chat.coffee
App.chat = App.cable.subscriptions.create "ChatChannel",
  received: (data) ->
    $('#messages').append(data.message)

  discuss: (msg) ->
    @perform 'discuss', message: msg

Và như vậy ứng dụng chat đã sẵn sàng sử dụng. Người dùng soạn message trong textbox rồi gửi đi, nội dung message sẽ được hiển thị trên trang message trong box chat.

Trên đây là một ví dụ đơn giản giới thiệu về sử dụng ActionCable - cơ chế được Rails 5 hỗ trợ nhằm đẩy nhanh việc xây dựng một ứng dụng Realtime trong các ứng dụng Web dùng Framework Rails. Công nghệ này không chỉ bó gọn trong việc áp dụng Rails thuần với JavaScript thông thường mà bạn hoàn toàn có thể tích hợp công nghệ này với các Frame JavaScript khác như Nodejs, Reactjs nhằm tối ưu hóa ứng dụng của mình.