Action Cable in Rails
Rails 5 được hỗ trợ WebSocket qua Action Cable giúp cho việc xây dựng các ứng dụng realtime trở nên dễ dàng hơn. Tuy nhiên, bên cạnh những điểm mạnh thì luôn tồn tại song song những điểm yếu của nó. Sau đây chúng ta cùng tìm hiểu qua về tính năng ActionCable của Rails 5 I. HTTP và Websockets ...
Rails 5 được hỗ trợ WebSocket qua Action Cable giúp cho việc xây dựng các ứng dụng realtime trở nên dễ dàng hơn. Tuy nhiên, bên cạnh những điểm mạnh thì luôn tồn tại song song những điểm yếu của nó. Sau đây chúng ta cùng tìm hiểu qua về tính năng ActionCable của Rails 5
I. HTTP và Websockets
1. HTTP
Kết nối giữa client và server ngắn. Khi client request một resource trên server thì một kết nối đến server sẽ được thiết lập và resource được yêu cầu (có thể là JSON, HTML, XML,…) sẽ được truyền về cho client như là một response. Sau đó, kết nối sẽ bị đóng. Client muốn biết được phía server cơ sự thay đổi data thì phải gửi yêu cầu đến server.
2. Websockets
WebSockets là giao thức cho phép các client và server giữ một kết nối mở cho phép truyền dữ liệu trực tiếp. Các client đăng kí một kết nối mở với server và khi có những thông tin mới thì server sẽ broadcasts dữ liệu đó đến tất cả các client đã đăng kí. Bằng cách này, cả server và client đều biết về trạng thái của dữ liệu và có thể dễ dàng đồng bộ hóa các xuất hiện các thay đổi.
II. Action Cable
Là "full-stack offering": Nó cung cấp cả client-side JavaScript framework, và Ruby server-side framework.
Action Cable có thể chạy như một server riêng rẽ, hoặc chúng ta có thể thiết lập để nó chạy trên bên trong server của ứng dụng Rails.
ActionCable sử dụng Redis để lưu trữ dữ liệu, đồng bộ nội dung thông qua các instances của ứng dụng.
Chat app với Action Cable
Tạo một project rails 5
rails new action-cable-demo --database=postgresql
Sau khi tạo xong project,cần config lại database.yml cho chính xác với PostgreSQL và Gemfile
gem 'rails', '~> 5.0.0' gem 'redis', '~> 3.0' gem 'puma', '~> 3.0'
Rồi chạy bundle install.
Để xây dựng Chat app chúng ta có 3 models sau:
app/models/user.rb
class User < ApplicationRecord has_many :messages has_many :chatrooms, through: :messages validates :username, presence: true, uniqueness: true end
app/models/message.rb
class Message < ApplicationRecord belongs_to :chatroom belongs_to :user end
app/models/chatroom.rb
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
Xem như user đã có thể login vào chatroom và tạo message
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 và form để user post messages
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>
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 %>
MessagesController xử lý yêu cầu tạo message
lass 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
Tiếp tục chúng ta sẽ xử lý real-time messages với Action Cable.
Module ApplicationCable định nghĩa sẵn 2 class Channel và Connection
Connection class là nơi mà chúng ta thực hiện authorize với những kết nối đến.
Channel class 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 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
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:
//= require_tree ./channels
Tạo channel
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.
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
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.
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ố, messages là tên của channel chúng ta đang thực hiện broadcast.
Nội dung được gửi qua channel dưới dạng JSON với message là nội dung của tin nhắn và user là username của user tạo ra tin nhắn.
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
Tạo file messages.js định nghĩa subscription để đăng ký với Messages Channel.
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.
Tham khảo
http://tutorials.pluralsight.com/ruby-ruby-on-rails/creating-a-chat-using-rails-action-cable
https://techmaster.vn/posts/33939/lap-trinh-socket-trong-rails5