Hướng dẫn tạo Real time notification with Action Cable Rails 5
Bài đăng này đã không được cập nhật trong 7 năm
Mình là 1 người mới học Rail và thực hành về bài sử dụng Action Cable để xử lý notification nên viết bài này dựa trên những cái mình đc học xem còn thiếu chỗ nào thì nhờ mọi người chỉnh giúp ạ (bow)
Introduction
Action Cable seamlessly integrates WebSockets with the rest of your Rails application. It allows for real-time features to be written in Ruby in the same style and form as the rest of your Rails application, while still being performant and scalable. It's a full-stack offering that provides both a client-side JavaScript framework and a server-side Ruby framework. You have access to your full domain model written with Active Record or your ORM of choice.
over view Action Cable Rails: http://edgeguides.rubyonrails.org/action_cable_overview.html
Mình sẽ thiết notification như sau:
- Table để lưu notification
- View để hiển thị notification
- Action Cable để real time notifcation
Setup table Notification
rails generate model Notification event:string
rails db:migrate
Create Controller and View
Create View
#app/views/notifications/_notification_center.html.erb
<ul class="notificator">
<div id="notificationContainer">
<div id="notificationTitle">Notifications</div>
<div id="notificationsBody" class="notifications">
<ul id="notificationList">
<%= render notifications %>
</ul>
</div>
<div id="notificationFooter"></div>
</div>
</ul>
Partial View khi có Notification mới
#app/views/notifications/_notification.html.erb
<li>
<%= notification.event %>
<span> <%= notification.created_at.strftime("%d %b. %Y") %></span>
</li>
Partial View để đếm số lượng Notifcation
#app/views/notifications/_counter.html.erb
<li>
<%= image_tag("notification_bell.svg") %>
<span id="notification-counter"><%= counter %></span>
</li>
#app/views/notifications/index.html.erb
<%= render "notifications/notification_center", notifications: @notifications %>
#app/views/notifications/new.html.erb
<%= form_for(@new_notification) do |f| %>
<div class="field">
<%= f.label :event %>
<%= f.text_field :event %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
class NotificationsController < ApplicationController
def index
@notifications = Notification.all.reverse
end
def new
@new_notification = Notification.new
render layout: false
end
def create
@notification = Notification.new event_params
if @notification.save
flash[:success] = "Create success."
redirect_to new_notification_path
end
end
private
def event_params
params.require(:notification).permit :event
end
end
Xong phấn setup cho notification data
Say hello to ActionCable!
ActionCable sẽ cho phép bạn mở chanel và duy trì chanel kết nối của chanel tới server mà ko cần phải refresh page. đầu tiên chúng ta sẽ khởi tạo chanel cho project với cú pháp
rails g channel notifications
Running via Spring preloader in process 21629
create app/channels/notifications_channel.rb
identical app/assets/javascripts/cable.js
create app/assets/javascripts/channels/notifications.coffee
Rails sẽ render chanel với cú pháp bên dưới, tạm thời trong bài này chúng ta sẽ thao tác với function subscribed
ở đây chúng ta sẽ khởi tạo kênh chanel bằng cách đặt tên cho nó.
khi server bắn notification thì sẽ dựa vào tên này để client có thể nhận notification
ngoài ra chúng ta có thể đặt tên cho nó tùy ý chúng ta bằng cách truyền parameter từ client lên
ví dụ như "stream_from "notification_channel_#{params[:user_id]}"", còn ở client khi setup file JS thì truyền userid vào
App.notifications = App.cable.subscriptions.create({
channel: 'NotificationsChannel',
user_id: user_id
}
/app/channels/notificationschannel.rb
class NotificationsChannel < ApplicationCable::Channel
def subscribed
stream_from "notification_channel"
end
def unsubscribed
# Any cleanup needed when channel is unsubscribed
end
end
để server có thể hiểu được chanel tiếp tục chúng ta config Router
config/routes.rb
Rails.application.routes.draw do
.
.
.
mount ActionCable.server => '/cable'
end
Chúng ta đã thiếp lập xong cho chanel giờ việc tiếp theo là chúng ta sẽ xử lý ở client khi nhận được notifcation
ở đây có 2 option cho các bạn, 1 là các bạn viết Jquery, 2 là sử dụng coffeJs
- Jquery /app/assets/javascripts/notificationscenter.js
$(document).ready(function()
{
(function() {
App.notifications = App.cable.subscriptions.create({
channel: 'NotificationsChannel'
},
{
connected: function() {},
disconnected: function() {},
received: function(data) {
$('#notificationList').prepend('' + data.notification);
$('#notificationList li').click(function(){
window.location.href = $(this).find('a').first().attr('href');
});
return this.update_counter(data.counter);
},
update_counter: function(counter) {
var $counter, val;
$counter = $('#notification-counter');
val = parseInt($counter.text());
val++;
return $counter.css({
opacity: 0
}).text(val)
.css({
top: '-10px'
})
.transition({
top: '10px',
opacity: 1
});
}
});
}).call(this);
}
- JsCoffee /app/assets/javascripts/channels/notifications.coffee
App.notifications = App.cable.subscriptions.create "NotificationsChannel",
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
this.update_counter(data.counter)
update_counter: (counter) ->
$counter = $('#notification-counter')
val = parseInt $counter.text()
val++
$counter
.text(val)
.css({top: '-10px'})
Chúng ta đã thêm chức năng update_counter , được kích hoạt bất cứ khi nào nhận dữ liệu từ chanel. chức năng update_counter sẽ update biến counter để đếm notification.
Tiếp theo chúng ta sẽ xử lý khi nào thì bắn notification:
- đầu tiên chúng ta sẽ tạo 1 broadcast
rails generate job NotificationBroadcast
notificator/app/jobs/notificationbroadcastjob.rb
Ruby
class NotificationBroadcastJob < ApplicationJob
queue_as :default
def perform(counter,notification)
ActionCable.server.broadcast 'notification_channel', counter: render_counter(counter), notification: render_notification(notification)
end
private
def render_counter(counter)
ApplicationController.renderer.render(partial: 'notifications/counter', locals: { counter: counter })
end
def render_notification(notification)
ApplicationController.renderer.render(partial: 'notifications/notification', locals: { notification: notification })
end
end
OK, vậy là đã setup xong cho cả ở client và server, giờ chúng ta sẽ bắt đầu bắn notification. Việc bắn notification thì mình sẽ setup ở model, sau khi notification đc create
class Notification < ApplicationRecord
after_create_commit {
NotificationBroadcastJob.perform_later(Notification.count, self)}
validates :event, presence: true
end
Note:
- Việc bắn notifcation bạn có thể tùy chỉnh khi setup ở fileJS
- Việc bắn notification có thể sử dụng 1 số gem khác thay vì để ở chế độ Async ví dụ như "redis"
Như vậy app của chúng ta đã có realtime notifcation rồi.
Demo
- page tạo notification: https://demo-notification-viblo.herokuapp.com/notifications/new
- page nhận notification: https://demo-notification-viblo.herokuapp.com/
- git demo: https://github.com/dattx1/notification
All rights reserved