12/08/2018, 14:49

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:

  1. Table để lưu notification
  2. View để hiển thị notification
  3. 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

  1. 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);
}
  1. 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:

  1. đầ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
  1. Việc bắn notifcation bạn có thể tùy chỉnh khi setup ở fileJS
  2. 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.

  1. page tạo notification: https://demo-notification-viblo.herokuapp.com/notifications/new
  2. page nhận notification: https://demo-notification-viblo.herokuapp.com/
  3. git demo: https://github.com/dattx1/notification
0