Giới thiệu
Realtime được hiểu đơn giản là thời gian thực, ví dụ như trong ứng dụng chat, khi bạn gõ tin nhắn trên máy của bạn thì nó hiện trực tiếp lên trên màn hình của người cùng chat. Thời gian thực ở đây nghĩa là thời gian chênh lệch có độ trễ nhỏ, tính bằng mili giây nên không đáng kể. Action Cable là một tính năng tích hợp WebSocket của Rails. nó cho phép bạn xây dựng được các tính năng Realtime.
Thực hiện
New app
Giờ ta sẽ thử tạo mới một project demo để gửi thông báo realtime đến User.
rails new demo_action_cable -d mysql
Ở đây mình dùng gem "devise" để xác thực User, bạn có thể tham khảo thêm ở đây devise
Connections
Tạo kết nối giữa Client-Server, hi server chấp nhận websocket thì một đối tượng connection sẽ được khởi tạo.
module ApplicationCable
class Connection < ActionCable::Connection::Base
identified_by :current_user
def connect
self.current_user = User.find_by id: cookies.signed[:user_id]
reject_unauthorized_connection unless current_admin.present?
end
end
end
Channel
Đăng ký channel, các thông báo sau khi tạo mới sẽ được chuyển đến các kênh này, vì là thông báo cá nhân mỗi user có một channel, ta có thể dựa vào current_user.id để phân biệt các channel này. Method subscriebed ở đây để thực hiện việc đăng ký channel để trao đổi dữ liệu với client
class NotificationChannel < ApplicationCable::Channel
def subscribed
stream_from "user_notification_#{current_user.id}_channel"
end
end
Sau khi đã đăng ký channel thì ta bước tiếp theo là code để tính năng hoạt động.
Tiếp theo ta viết 1 service để gửi thông báo đến User sau khi thông báo được tạo.
class PushNotificationService
def initialize args = {}
@notification = args[:notification]
@current_user = notification.user
end
def perform
ActionCable.server.broadcast "user_notification_#{notification.user.id}_channel", notification_attrs
rescue StandardError
nil
end
private
attr_reader :notification, :current_user
def render_notification
ApplicationController.renderer.render partial: partial_html_path, locals: {notification: notification}
end
def notification_attrs
{
id: notification.id,
message: render_notification,
count: @current_user.notifications.seen(false).size,
url: notification.display_url
}
end
def partial_html_path
"user/notifications/notification"
end
end
Ở model/notification.rb dùng callback để sau mỗi lần thông báo được tạo sẽ gửi đến cho User.
after_create :push_notify
private
def push_notify_to_user
PushNotificationService.new(notification: self).perform
end
Tiếp theo ở views:
để hiển thị nội dung thông báo.
views/user/notifications/_notification
<li>
<%= notification.content %>
</li>
views/user/notifications/_list_notification.html.slim
<ul class="js-list-notifications">
<%= render partial: "notification", collection: notifications, as: :notification %>
</ul>
Thêm vào js file channels/user/notification
>
Tạo kết nối với channel mà ta muốn. Ở đây là NotificationChannel. Dữ liệu nhận về sẽ thông qua biến data ở hàm received.
$(function() {
App.notifications = App.cable.subscriptions.create({
channel: 'NotificationChannel'
},
{
connected: function() {},
disconnected: function() {},
received: function(data) {
$('ul#js-list-notifications').prepend(data.message);
$('.js-notification-size').html(data.count);
}
});
});
Trong routes:
mount ActionCable.server => "/cable"
application.js
//= require ./channels/user/notification
//= require cable
Nguồn
https://guides.rubyonrails.org/action_cable_overview.html
https://blog.heroku.com/real_time_rails_implementing_websockets_in_rails_5_with_action_cable