At-mention notifications with Rails Actioncable

Giới thiệu

Trong ứng dụng chat cũng như mạng xã hội thường có chức năng mention (@) để notify hoặc thông báo đến người đó là bạn muốn mention đến. Ví dụ khi chat, bạn muốn mention đến userA sẽ là "@userA Good morning", .... Hôm này mình sẽ làm demo về chức năng mention trong Rails sử dụng actioncable.

Implement

Có rất nhiều bài viết về actioncable trong chat, ... Bây giờ mình sẽ không giới thiệu chi tiết về actioncable nữa. Mình sẽ hướng dẫn luôn về cách làm chức năng mention luôn.

Subscribe channel

Đầu tiên, phải subscribe vào channel như sau. Khi có broadcast đến channel đó, bạn sẽ nhận được thông tin gửi đến.

app/channels/room_channel.rb
 class RoomChannel < ApplicationCable::Channel
  def subscribed
    stream_from "room_channel_user_#{current_user.id}"
  end

  def unsubscribed
    # Any cleanup needed when channel is unsubscribed
  end
end

Làm mention như thế nào?

Ý tưởng là bạn cần dùng regex để lấy được các từ mà người ta mention, tức là từ ngay sau @. Vào rails console để thử nhé:

$ rails console
>>  NAME_REGEX = /@(\w+)/
>> content = "This mentions @alice and @bob, and also @nonexistent"

>> content = content.scan(NAME_REGEX)
=> [["alice"], ["bob"], ["nonexistent"]]

>> mention_names = content.flatten
=> ["alice", "bob", "nonexistent"]

// tìm user có tên trong content
>> mention_names.map do |username|
?>   User.find_by(username: username)
>> end.compact
=> [#<User id: 1, username: "alice"...>, #<User id: 2, username: "bob"]

Đến đây là mình có thể tạo hàm mention được rồi.

app/models/message.rb
class Message < ApplicationRecord
NAME_REGEX = /@(\w+)/

  belongs_to :user
  validates :content, presence: true
    
  // trả về danh sách users đã mention trong message content
  def mentions
    content.scan(NAME_REGEX).flatten.map do |username|
      User.find_by(username: username)
    end.compact
  end
end

Thực hiện broadcast và nhận kết quả

Sau khi tạo message thành công, mình sẽ broadcast tới channel của các users trong danh sách đã mention.

app/controllers/messages_controller.rb
 class MessagesController < ApplicationController
  .
  .
  .
  def create
    message = current_user.messages.build(message_params)
    if message.save
      message.mentions.each do |user|
        ActionCable.server.broadcast "room_channel_user_#{user.id}", content: "#{message.user.name} đã mention bạn."
      end
    end
  end
  .
  .
  .
end

Khi broadcast được thực hiện, user trong danh sách được mention đó sẽ nhận được data gửi về channel RoomChannel của họ.

app/assets/javascripts/channels/room.coffee
 App.room = App.cable.subscriptions.create "RoomChannel",
    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) ->
      alert(data.content);

Khi có người mention bạn, bạn sẽ nhận được alert lên là "User đã mention bạn." Đến đây, bạn sẽ hoàn thành chức năng mention rồi.

References:

https://www.learnenough.com/action-cable-tutorial

https://viblo.asia/p/using-reactjs-with-rails-action-cable-MJykjWZYvPB

https://viblo.asia/search?q=actioncable