Hướng dẫn tạo Real time notification with Action Cable Rails 5
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) Action Cable seamlessly integrates WebSockets with the rest of your Rails application. It allows ...
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)
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
rails generate model Notification event:string
rails db:migrate
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
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
- 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.
- 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