notification and activity

I.Activity

Thêm gem** 'public_activity'** sau đó bundle Tiếp tục tạo model nhé

+ rails g public_activity:migration
+ rake db:migrate

Xem trong db vừa tạo gồm những trường gì class CreateActivities < ActiveRecord::Migration

  def self.up
    create_table :activities do |t|
      t.belongs_to :trackable, :polymorphic => true
      t.belongs_to :owner, :polymorphic => true
      t.string  :key
      t.text    :parameters
      t.belongs_to :recipient, :polymorphic => true
      t.boolean :read, default: false # trường này mình thêm vào
      t.boolean :enable, default: true # trường này mình thêm vào
      t.integer :activity_type, default: 0 # trường này mình thêm vào
      t.references :user # trường này mình thêm vào để tạo quan hệ với bảng user
      t.timestamps
    end

    add_index :activities, [:trackable_id, :trackable_type]
    add_index :activities, [:owner_id, :owner_type]
    add_index :activities, [:recipient_id, :recipient_type]
  end
  # Drop table
  def self.down
    drop_table :activities
  end
end

Nếu bạn muốn thêm 1 số trường thì đơn giản chỉ tạo 1 model mới rồi kế thừa nó

class Activity < PublicActivity::Activity

  belongs_to :user
end

Bây giờ các bạn có thể gọi Activity.new xem nó có những trường nào nhé

Activity.new
#<Activity id: nil, trackable_type: nil, trackable_id: nil, owner_type: nil, owner_id: nil, key: nil, parameters: {}, recipient_type: nil,
    recipient_id: nil, read: false, enable: true, activity_type: "active", user_id: nil, created_at: nil, updated_at: nil>

Trước tiên muốn sử dụng được thì các bạn làm như sau:

  • include "include PublicActivity::Model" vào ApplicationRecord
class ApplicationRecord < ActiveRecord::Base
  self.abstract_class = true

  include PublicActivity::Model
end
  • Sau đó gọi "tracked" vào model nào bạn muốn luôn tất cả hành động của nó
class User < ApplicationRecord
    tracked
end

Như vậy là xong, ngoài ra bạn có thể override lại nó

II.Notification

Tiếp tục nhé, mình tạo 1 model Notification cũng kế thừa PublicActivity::Activity

class Notification < PublicActivity::Activity
    scope :all_notify, ->{where activity_type: 1} # mình thêm scope này để lọc theo Notification
end

Cũng như trên, đơn giản ..... 😄

Giờ mình sẽ hưỡng dẫn realtime Trước tiên :

  • rails g channel Notify app/channels/notify_channel.rb
class NotifyChannel < ApplicationCable::Channel
  def subscribed
    stream_from "notify_chanel"
  end

  def unsubscribed
    stop_all_streams
  end
end

app/assets/javascripts/channels/notify.coffee

App.notify = App.cable.subscriptions.create "NotifyChannel",
  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) ->
  # data

Tiếp tục: rails g job NotificationBroadcastJob

class NotificationBroadcastJob < ApplicationJob
  queue_as :default

    # ở đây các bạn override nó
  def perform count, notification
    ActionCable.server.broadcast "notify_channel", counter: render_count_notiication(count)
  end

  def render_count_notiication count
    ApplicationController.renderer.render partial: "layouts/count_notification",
      locals: {count_notification: count}
  end
end
  • ở controller
class ApplicationController < ActionController::Base
  protect_from_forgery with: :exception
  before_action :load_notifications, if: :user_signed_in?

  def load_notifications
  # Bạn nhớ lọc ra nhưng record của notification thôi nhé, vì mình dùng chùng 1 table kế thừa, viết scope theo  activity_type
    @notifications = current_user.Notification.all_notify.count 
  end
end

+ở view

application.html.erb
<!DOCTYPE html>
<html>
  <head>
    <title>ElearningV1</title>
       ....
        <%= action_cable_meta_tag %>
       ...
  </head>


 <span class="label label-warning" id="notification-counter">
    <%= render partial: "layouts/count_notification",
      locals: {count_notification: @notifications} %>
  </span>

... /views/layouts/_count_notification.html.erb

 <%= count_notification %>
  • config/routes.rb
Rails.application.routes.draw do
   ...  
   mount ActionCable.server => '/cable'
   ...
end

Bây giờ muốn nó hoạt động: Bạn tạo 1 Notification

application_helper.rb

module ApplicationHelper
  ...
  def create_notification_for_member model, object, name, user_id
    Notification.create trackable_type: model, trackable_id: object.id,
       owner_type: current_user.role, owner_id: current_user.id, key: name,
       activity_type: Notification.activity_types[:notice], user_id: user_id
  end
...
end

Trước hết bạn đã tạo 1 model user nhé Mình tạo 1 user mới nhé

  • users_controller.rb
 def create
    @user = User.new user_params
    if @user.save
      create_notification_for_member User.name, @user, "create", @user.id
      flash[:success] = t "devise.registrations.signed_up"
      redirect_to admins_users_path
    else
      render :new
    end
  end
class Notification < PublicActivity::Activity
  after_create :send_notification

  def send_notification
    NotificationBroadcastJob.perform_now Notification.all_notify.count, self
  end

end

Bạn sửa lại file notify.coffee nhé

App.notify = App.cable.subscriptions.create "NotifyChannel",
  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) ->
    this.update_counter(data.counter)

  update_counter: (counter) ->
    $counter = $('#notification-counter')
    val = parseInt $counter.text()
    val++
    $counter
    .text(val)
    .css({top: '-10px'})

=> Sau khi tạo đối tượng notification xong thì nó sẽ thực hiện gọi NotificationBroadcastJob.perform rồi ra view, ở view chúng ta có file notify.coffee sẽ bắt kếnh truyền đó

++++ Bây giờ bạn muốn gửi đên từng user mà bạn muốn, MÌnh nghĩ bạn nên tạo 1 model để thấy rõ hành động này.. vì nãy giờ thực hiện tạo notification sau khi tạo 1 user. Nhưng bây giờ mình gửi notification cho user. nên bạn hạy tọ thêm 1 model. và model này chưa id người tạo. vd: rails g model Course user:references name:string Bây giờ bạn thêm trong CourseController.rb

def create
    @course = Course.new course_params
    if @course.save
      flash[:success] = t "devise.registrations.signed_up"
      create_notification_for_member Course.name, @course, "create", @course.user_id
      redirect_to courses_path
    else
      render :new
    end
  end

trước hết các bạn tạo kết nối cho nó: app/chanels/application_cable/connection.rb

module ApplicationCable
  class Connection < ActionCable::Connection::Base
    identified_by :current_user

    def connect
      self.current_user = find_verified_user
      logger.add_tags "ActionCable", current_user.email
    end

    protected

    def find_verified_user
      if current_user = User.find_by(id: cookies.signed["user.id"])
        current_user
      else
        reject_unauthorized_connection
      end
    end
  end
end

Bạn kiểm tra kết nối có thành công không. Nếu không kết nối đc chứng tỏ nó k lấy được cookies của các user đang đăng nhập, thì bạn thêm 1 file này : ../config/initializers/warden_hooks.rb

Warden::Manager.after_set_user do |user, auth, opts|
  scope = opts[:scope]
  auth.cookies.signed["#{scope}.id"] = user.id
  auth.cookies.signed["#{scope}.expires_at"] = 30.minutes.from_now
end

Warden::Manager.before_logout do |user, auth, opts|
  scope = opts[:scope]
  auth.cookies.signed["#{scope}.id"] = nil
  auth.cookies.signed["#{scope}.expires_at"] = nil
end

Bây giờ các bạn sữa lại

  • file ../noitify_chanel.rb
class NotifyChannel < ApplicationCable::Channel
  def subscribed
    stream_from "notify_channel_#{current_user.id}"
  end

  def unsubscribed
  end
end
  • file ../notification_broadcast_job.rb
class NotificationBroadcastJob < ApplicationJob
 queue_as :default
​
   # ở đây các bạn override nó
 def perform count, notification
     # ở trên khi tạo 1 notification mình có lưu id người nhận là user_id, nên kênh truyên đi cho những người đang đăng nhập đúng id đó sẽ được nhận
   ActionCable.server.broadcast "notify_channel_#{notification.user_id}", counter: render_count_notiication(count)
 end
​
 def render_count_notiication count
   ApplicationController.renderer.render partial: "layouts/count_notification",
     locals: {count_notification: count}
 end
end

Chúc các bạn thành công, Link tham khảo: activity: https://github.com/chaps-io/public_activity notification: https://gist.github.com/excid3/4ca7cbead79f06365424b98fa7f8ecf6


All Rights Reserved