複数のプライベートルームとグループチャットオプションでRails


このポストは、チュートリアルではないことに注意してくださいRails 5 ActionCable and ReactJS / Javascript カスタムライブラリビル.

(この短いポストはこのフロントエンドコンポーネントをビルドする方法を示しません)
つの素晴らしい機能が付属してRails 5 is ActionCable . With ActionCable , あなたはWebSocket経由で考えることができるすべてのリアルタイム機能を構築することができます.一方、チャットシステムを構築するために苦労して、私はRails 5 ActionCable しかし、彼らはまた、任意の実際の生活のチャットアプリケーションのコンセプトを適用するために簡単です.私は、このようなチャットシステムを構築する方法を示しています.
  • Rails 5 APIバックエンドとreactjsフロントエンド
  • 複数の個室
  • 部屋のユーザーの任意の正の数
  • チャットシステムマイフレンドTim Chang 私は構築しました.
  • 複数のプライベートチャットルーム
  • 複数のチャットルーム
  • 各ユーザーのオンライン/オフラインのステータス
  • リアルタイム“タイピング…”ステータス
  • リアルタイム読み取り領収書
  • この短いポストでは、1 . 1と1 . 2の基本を示します.あなたが私にあなたがサイ3 , 5 , 4と5 , 5を造る方法を示すことを望むならば、下記のコメントを残してください.私はRails 5をバックエンドAPIとreactjsライブラリをフロントエンドで使用しています.
    バックエンド
    作成時には、柵はすべてのリアルタイムマジックが発生するチャネルフォルダとファイルを生成します.
    app/channels/application_cable/channel.rb
    app/channels/application_cable/connection.rb
    
    認証
    まず、接続中のRailsサーバへのWebSocket接続要求を認証しましょう.rb
    module ApplicationCable
      class Connection < ActionCable::Connection::Base
        identified_by :current_user
    
        def connect
          self.current_user = find_verified_user
        end
    
        private
          def find_verified_user
            # or however you want to verify the user on your system
            access_token = request.params[:'access-token']
            client_id = request.params[:client]
            verified_user = User.find_by(email: client_id)
            if verified_user && verified_user.valid_token?(access_token, client_id)
              verified_user
            else
              reject_unauthorized_connection
            end
          end
      end
    end
    
    プロジェクトで使用する認証gemまたはサービスによって異なります.find_verified_user メソッドを必要に応じて変更する必要があります.という方法があるvalid_token? 確認するaccess-token and client_id WebSocketリクエストで渡します.要求が認証されないならば、それは拒絶されます.
    データ構造
    アイデアは非常に基本的です:複数のメッセージを持っているチャットルームは、各メッセージは、コンテンツと送信者があります.メッセージに「受信機」がないことに注意してください.これは、部屋のどのように多くの参加者に関係なく、送信者からのすべてのメッセージが部屋に表示されなくなりますので、メッセージの受信機を気にする必要はありませんので、部屋の任意の数のユーザーを持つことができます.したがって、これは私が使用するデータ構造です:
  • 多くのメッセージ、ユーザー、IDを持つ
  • メッセージ:所有者は会話に、送付者を持って、テキスト内容を持っています
  • 送信者:ユーザ
  • その結果、3つのモデルを作成しました.
    # message.rb
    class Message < ApplicationRecord
      belongs_to :conversation
      belongs_to :sender, class_name: :User, foreign_key: 'sender_id'
    
      validates_presence_of :content
    
      after_create_commit { MessageBroadcastJob.perform_later(self) }
    end
    
    # conversation.rb
    class Conversation < ApplicationRecord
      has_many :messages, dependent: :destroy
      has_and_belongs_to_many :users
    end
    
    # user.rb
    class User < ApplicationRecord
      has_and_belongs_to_many :conversations, dependent: :destroy
    end
    
    アクショントリガー
    クライアントが接続(購読)またはメッセージ(放送)を放送すると、バックエンドはアクションと反応します.インサイドフォルダapp/channels , ファイルを作成しますroom_channel.rb .
    # room_channel.rb
    class RoomChannel < ApplicationCable::Channel
      # calls when a client connects to the server
      def subscribed
        if params[:room_id].present?
          # creates a private chat room with a unique name
          stream_from("ChatRoom-#{(params[:room_id])}")
        end
      end
    
      # calls when a client broadcasts data
      def speak(data)
        sender    = get_sender(data)
        room_id   = data['room_id']
        message   = data['message']
    
        raise 'No room_id!' if room_id.blank?
        convo = get_convo(room_id) # A conversation is a room
        raise 'No conversation found!' if convo.blank?
        raise 'No message!' if message.blank?
    
        # adds the message sender to the conversation if not already included
        convo.users << sender unless convo.users.include?(sender)
        # saves the message and its data to the DB
        # Note: this does not broadcast to the clients yet!
        Message.create!(
          conversation: convo,
          sender: sender,
          content: message
        )
      end
    
      # Helpers
    
      def get_convo(room_code)
        Conversation.find_by(room_code: room_code)
      end
    
      def get_sender
        User.find_by(guid: id)
      end
    end
    
    コメントの中で、クライアントが「話す」の後、放送はまだ起きていません新しいメッセージだけがその内容とデータで作成されます.アクションの連鎖は、メッセージがDBに保存された後に発生します.メッセージモデルを見てみましょう.
    after_create_commit { MessageBroadcastJob.perform_later(self) }
    
    スケーラビリティ
    このコールバックは、メッセージが生成され、DBにコミットされた後にのみ呼び出されます.私はスケールするためにこのアクションを処理するためにバックグラウンドジョブを使用しています.あなたがメッセージを送信する何千ものクライアントがあると想像してください.バックグラウンドジョブの使用はここでの要件です.
    # message_broadcast_job.rb
    class MessageBroadcastJob < ApplicationJob
      queue_as :default
    
      def perform(message)
        payload = {
          room_id: message.conversation.id,
          content: message.content,
          sender: message.sender,
          participants: message.conversation.users.collect(&:id)
        }
        ActionCable.server.broadcast(build_room_id(message.conversation.id), payload)
      end
    
      def build_room_id(id)
        "ChatRoom-#{id}"
      end
    end
    
    放送が起こるときは、ここにあります.ActionCable は、ペイロードを指定された部屋に提供されたペイロードで放送します.
    ActionCable.server.broadcast(room_name, payload)
    
    ケーブルルート
    あなたは/WebSocketルートを追加する必要がありますroutes.rb あなたのクライアントがこの終点を放送して、メッセージを受け取ることができるように.
    mount ActionCable.server => '/cable'
    
    そして、それはバックエンド側のためです!reactjsフロントエンドライブラリを見てみましょう.
    クライアントライブラリ
    あなたのプロジェクトの詳細に応じて、このライブラリのこのコードの概念を理解し、必要に応じて変更する必要がありますのでご注意ください.
    まず、インストールActionCableJS 経由npm .
    クリエイトアChatConnection.js あなたのサービスの1つとしてファイルReactJs アプリ.
    // ChatConnection.js
    
    import ActionCable from 'actioncable'
    
    import {
      V2_API_BASE_URL,
      ACCESS_TOKEN_NAME,
      CLIENT_NAME,
      UID_NAME
    } from '../../globals.js'
    
    function ChatConnection(senderId, callback) {
      let access_token = localStorage.getItem(ACCESS_TOKEN_NAME)
      let client = localStorage.getItem(CLIENT_NAME)
    
      var wsUrl = 'ws://' + V2_API_BASE_URL + '/cable'
      wsUrl += '?access-token=' + access_token + '&client=' + client
    
      this.senderId = senderId
      this.callback = callback
    
      this.connection = ActionCable.createConsumer(wsUrl)
      this.roomConnections = []
    }
    
    ChatConnection.prototype.talk = function(message, roomId) {
      let roomConnObj = this.roomConnections.find(conn => conn.roomId == roomId)
      if (roomConnObj) {
        roomConnObj.conn.speak(message)
      } else {
        console.log('Error: Cannot find room connection')
      }
    }
    
    ChatConnection.prototype.openNewRoom = function(roomId) {
      if (roomId !== undefined) {
        this.roomConnections.push({roomId: roomId, conn: this.createRoomConnection(roomId)})
      }
    }
    
    ChatConnection.prototype.disconnect = function() {
      this.roomConnections.forEach(c => c.conn.consumer.connection.close())
    }
    
    ChatConnection.prototype.createRoomConnection = function(room_code) {
      var scope = this
      return this.connection.subscriptions.create({channel: 'RoomChannel', room_id: room_code, sender: scope.senderId}, {
        connected: function() {
          console.log('connected to RoomChannel. Room code: ' + room_code + '.')
        },
        disconnected: function() {},
        received: function(data) {
          if (data.participants.indexOf(scope.senderId) != -1) {
            return scope.callback(data)
          }
        },
        speak: function(message) {
          return this.perform('speak', {
            room_id: room_code,
            message: message,
            sender:  scope.senderId
          })
        }
      })
    }
    
    export default ChatConnection
    
    だからここにフックがありますcreateRoomConnection , クライアントは(接続する)と接続しようとしますRoomChannel 我々はバックエンドでは、一度接続されて(予約)、それは部屋の名前からストリームされます作成されたChatRoom-id 見るroom_channel.rb 上に再び.)一度接続されている、それは頻繁に呼ばれる2つの方法があり、どの1つを推測することができますか?
    彼らは:受信と話す!
    サーバからクライアントにメッセージ放送があるとき、受け取られたメソッドは呼ばれます、反対側では、クライアントがメッセージをサーバーに放送するとき、話すことは呼ばれます.
    Voila!それです.繰り返しますが、これは各プロジェクトが異なっているので、チュートリアルのボックスの種類から実行する準備ができてされていませんが、それはあなたがどのように複数のプライベートチャットルームと部屋ごとに複数のユーザーとチャットシステムを構築するためのアイデアを与えることを願っています.質問があればコメント欄でお知らせください.
    そして、これはあなたのプロジェクトに役立つ見つける場合は、愛のボタンを押すことを忘れないでください!