12/08/2018, 15:08

Những điều cần biết về Action Cable trong Rails 5 - (Part 2)

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ó ...

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

https://github.com/rails/actioncable-examples

https://github.com/rails/rails/tree/master/actioncable

0