TransWikia.com

rails5のaction cableでメッセージが同期されるときと同期されない時がある

スタック・オーバーフロー Asked by takumi mori on November 15, 2021

Rails5のaction cableを使用して、チャット機能を作成しています。
一つのアカウントが複数のルームに出入りし、チャットしあえるようにしたいのですが、ルームに入るタイミングや、ページのリロードなどによって、メッセージが出たり出なかったりします。
その原因が何故なのかわかりません。

機能について

ログイン後、自分のアカウントに紐づいたルームの名前が表示されます。
画像の説明をここに入力

ルームを選択するとチャット画面に遷移します。
以下はそれぞれ別々のアカウントで同じルームに入った状態の画像です。
左からルームに入り、次に右が入り、左がメッセージを送ると、双方に表示されます。
画像の説明をここに入力

しかし、例えばこの状態で左だけ画面をリロードし、再度メッセージを入力すると、右に表示されません。
画像の説明をここに入力

他にも、
・どちらから先にルームに入るか
・どちらから先に送るか
・どちらがリロードするか
などによって、メッセージが送られる場合とそうでない場合があります。
それがどうしてなのかわかりません。

以下、どのようなパターンの時に表示されるか、されないかを表にまとめています。
画像の説明をここに入力

Aは左側、Bは右側の画面です
例えば上の表の3行目は、「Aからルームに入りBも入った後、Aから送った場合はBにメッセージが出たが、その後Bで画面をリロードし、Aから送った場合は出ない」という意味です
下の表の1行目は、「Aからルームに入りメッセージを送った後に、Bもルームに入り、そこでAからメッセージを送ると表示されたが、その後Aのリロードし、Aからメッセージを送ると、Bに表示されない」という意味です

ソースコード

application.js

// This is a manifest file that'll be compiled into application.js, which will include all the files
// listed below.
//
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, or any plugin's
// vendor/assets/javascripts directory can be referenced here using a relative path.
//
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
// compiled file. JavaScript code in this file should be added after the last require_* statement.
//
// Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
// about supported directives.
//
//= require rails-ujs
//= require jquery
//= require bootstrap
//= require turbolinks
//= require jquery3
//= require popper
//= require bootstrap

cable.js

// Action Cable provides the framework to deal with WebSockets in Rails.
// You can generate new channels where WebSocket features live using the `rails generate channel` command.
//
//= require action_cable
//= require_self
//= require_tree ./channels

(function() {
  this.App || (this.App = {});

  App.cable = ActionCable.createConsumer();

}).call(this);

room_channel.js

// $(function() {}; で囲むことでレンダリング後に実行される
// レンダリング前に実行されると $('#messages').data('room_id') が取得できない
// $(window).on('load', function() {  
$(function() {
    var channel = 'RoomChannel';
    var room = $('#messages').attr('data-room_id');
    chatChannel = App.cable.subscriptions.create({ channel: channel, room: room}, {
      connected() {
        // Called when the subscription is ready for use on the server
      },
  
      disconnected() {
        // Called when the subscription has been terminated by the server
      },
  
      received: function(data) {
        return $('#messages').append(data['message']);
      },
  
      speak: function(message) {
        return this.perform('speak', {
          message: message
        });
      }
    });
  
    $(document).on('keypress', '[data-behavior~=room_speaker]', function(event) {
      if (event.keyCode === 13) {
        chatChannel.speak(event.target.value);
        event.target.value = '';
        return event.preventDefault();
      }
    });
  });

assets.rb

# Be sure to restart your server when you modify this file.

# Version of your assets, change this if you want to expire all your assets.
Rails.application.config.assets.version = '1.0'

# Add additional assets to the asset load path.
# Rails.application.config.assets.paths << Emoji.images_path
# Add Yarn node_modules folder to the asset load path.
Rails.application.config.assets.paths << Rails.root.join('node_modules')

# Precompile additional assets.
# application.js, application.css, and all non-JS/CSS in the app/assets
# folder are already added.
# Rails.application.config.assets.precompile += %w( admin.js admin.css )
Rails.application.config.assets.precompile += %w( cable.js )

routes.rb

Rails.application.routes.draw do

~省略~

  # チャット用ルーティング
  mount ActionCable.server => '/cable'
  devise_for :applicants
  get '/chat', to:'rooms#index'
  # resourcesを使うとRESTfulなURLを自動生成できる
  resources :rooms, only: %i[show], param: :id_digest

end

room_channel.rb

class RoomChannel < ApplicationCable::Channel
  def subscribed
    2.times { puts '***test***' }
    stream_from "room_channel_#{params[:room]}" 
  end

  def unsubscribed
    # Any cleanup needed when channel is unsubscribed
  end

  def speak(data)
    # jsで実行されたspeakのmessageを受け取り、room_channelのreceivedにブロードキャストする
    # ActionCable.server.broadcast 'room_channel', message: data['message']
    Message.create! content: data['message'], applicant_id: current_user.id, room_id: params[:room]
  end
end

index.html.erb

<div>
  <ul>
    <% @participations.each do |p| %>
      <li><%= link_to p.room.name, room_path(p.room.id_digest), data: {"turbolinks" => false} %></li>
    <% end %>
  </ul>
</div>

show.html.erb

<%#ここの data-room_id を使ってjs側で部屋を見分ける %>
<div id='messages' data-room_id="<%= @room.id %>">
  <%= render @messages %>
</div>

<%= label_tag :content, 'Say something:' %>
<%= text_field_tag :content, nil, data: { behavior: 'room_speaker' } %>

<%= javascript_include_tag 'cable.js' %>

message.rb

class Message < ApplicationRecord
  validates :content, presence: true
  # createの後にコミットする { MessageBroadcastJobのperformを遅延実行 引数はself }
  after_create_commit { MessageBroadcastJob.perform_later self }
  belongs_to :applicant
  belongs_to :room
end

message_broadcast_job.rb

class MessageBroadcastJob < ApplicationJob
  queue_as :default

  def perform(message)
    ActionCable.server.broadcast "room_channel_#{message.room_id}", message: render_message(message)
  end

  private

    def render_message(message)
      ApplicationController.renderer.render partial: 'messages/message', locals: { message: message }
    end

end

ログ

Started GET "/cable" for 127.0.0.1 at 2020-07-18 17:06:46 +0900
Started GET "/cable/" [WebSocket] for 127.0.0.1 at 2020-07-18 17:06:46 +0900
Successfully upgraded to WebSocket (REQUEST_METHOD: GET, HTTP_CONNECTION: Upgrade, HTTP_UPGRADE: websocket)
  Applicant Load (0.2ms)  SELECT  "applicants".* FROM "applicants" WHERE "applicants"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
Registered connection (Z2lkOi8vbWF0Y2hpbmctYXBwL0FwcGxpY2FudC8x)
***test***
***test***
RoomChannel is transmitting the subscription confirmation
RoomChannel is streaming from room_channel_6
RoomChannel#speak({"message"=>"テスト"})
   (0.1ms)  begin transaction
  Applicant Load (0.5ms)  SELECT  "applicants".* FROM "applicants" WHERE "applicants"."id" = ? LIMIT ?  [["id", 2], ["LIMIT", 1]]
  Room Load (0.1ms)  SELECT  "rooms".* FROM "rooms" WHERE "rooms"."id" = ? LIMIT ?  [["id", 6], ["LIMIT", 1]]
  SQL (0.4ms)  INSERT INTO "messages" ("content", "created_at", "updated_at", "applicant_id", "room_id") VALUES (?, ?, ?, ?, ?)  [["content", "テスト"], ["created_at", "2020-07-18 08:06:52.345133"], ["updated_at", "2020-07-18 08:06:52.345133"], ["applicant_id", 2], ["room_id", 6]]
   (2.5ms)  commit transaction
[ActiveJob] Enqueued MessageBroadcastJob (Job ID: 813258ad-3d30-4c46-90ec-d24f4a83e2ff) to Async(default) with arguments: #<GlobalID:0x00007f9d4b4cee98 @uri=#<URI::GID gid://matching-app/Message/345>>
  Message Load (0.3ms)  SELECT  "messages".* FROM "messages" WHERE "messages"."id" = ? LIMIT ?  [["id", 345], ["LIMIT", 1]]
[ActiveJob] [MessageBroadcastJob] [813258ad-3d30-4c46-90ec-d24f4a83e2ff] Performing MessageBroadcastJob (Job ID: 813258ad-3d30-4c46-90ec-d24f4a83e2ff) from Async(default) with arguments: #<GlobalID:0x00007f9d4fac3048 @uri=#<URI::GID gid://matching-app/Message/345>>
[ActiveJob] [MessageBroadcastJob] [813258ad-3d30-4c46-90ec-d24f4a83e2ff]   Applicant Load (0.1ms)  SELECT  "applicants".* FROM "applicants" WHERE "applicants"."id" = ? LIMIT ?  [["id", 2], ["LIMIT", 1]]
[ActiveJob] [MessageBroadcastJob] [813258ad-3d30-4c46-90ec-d24f4a83e2ff]   Rendered messages/_message.html.erb (1.5ms)
[ActiveJob] [MessageBroadcastJob] [813258ad-3d30-4c46-90ec-d24f4a83e2ff] [ActionCable] Broadcasting to room_channel_6: {:message=>"<div class='message'>n  <p>テスト2: テスト</p>n  <p>2020-07-18 08:06:52 UTC</p>n</div>"}
[ActiveJob] [MessageBroadcastJob] [813258ad-3d30-4c46-90ec-d24f4a83e2ff] Performed MessageBroadcastJob (Job ID: 813258ad-3d30-4c46-90ec-d24f4a83e2ff) from Async(default) in 6.85ms
RoomChannel transmitting {"message"=>"<div class='message'>n  <p>テスト2: テスト</p>n  <p>2020-07-18 08:06:52 UTC</p>n</div>"} (via streamed from room_channel_6)
RoomChannel transmitting {"message"=>"<div class='message'>n  <p>テスト2: テスト</p>n  <p>2020-07-18 08:06:52 UTC</p>n</div>"} (via streamed from room_channel_6)
RoomChannel#speak({"message"=>"テストです"})
   (0.1ms)  begin transaction
  Applicant Load (0.2ms)  SELECT  "applicants".* FROM "applicants" WHERE "applicants"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
  Room Load (0.1ms)  SELECT  "rooms".* FROM "rooms" WHERE "rooms"."id" = ? LIMIT ?  [["id", 6], ["LIMIT", 1]]
  SQL (1.0ms)  INSERT INTO "messages" ("content", "created_at", "updated_at", "applicant_id", "room_id") VALUES (?, ?, ?, ?, ?)  [["content", "テストです"], ["created_at", "2020-07-18 08:07:38.239354"], ["updated_at", "2020-07-18 08:07:38.239354"], ["applicant_id", 1], ["room_id", 6]]
   (3.0ms)  commit transaction
[ActiveJob] Enqueued MessageBroadcastJob (Job ID: e6707164-e702-4d10-87ff-720ce3b2b22e) to Async(default) with arguments: #<GlobalID:0x00007f9d4cc4bfa8 @uri=#<URI::GID gid://matching-app/Message/346>>
  Message Load (0.3ms)  SELECT  "messages".* FROM "messages" WHERE "messages"."id" = ? LIMIT ?  [["id", 346], ["LIMIT", 1]]
[ActiveJob] [MessageBroadcastJob] [e6707164-e702-4d10-87ff-720ce3b2b22e] Performing MessageBroadcastJob (Job ID: e6707164-e702-4d10-87ff-720ce3b2b22e) from Async(default) with arguments: #<GlobalID:0x00007f9d4f87a6d8 @uri=#<URI::GID gid://matching-app/Message/346>>
[ActiveJob] [MessageBroadcastJob] [e6707164-e702-4d10-87ff-720ce3b2b22e]   Applicant Load (0.2ms)  SELECT  "applicants".* FROM "applicants" WHERE "applicants"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
[ActiveJob] [MessageBroadcastJob] [e6707164-e702-4d10-87ff-720ce3b2b22e]   Rendered messages/_message.html.erb (3.2ms)
[ActiveJob] [MessageBroadcastJob] [e6707164-e702-4d10-87ff-720ce3b2b22e] [ActionCable] Broadcasting to room_channel_6: {:message=>"<div class='message'>n  <p>テスト1: テストです</p>n  <p>2020-07-18 08:07:38 UTC</p>n</div>"}
[ActiveJob] [MessageBroadcastJob] [e6707164-e702-4d10-87ff-720ce3b2b22e] Performed MessageBroadcastJob (Job ID: e6707164-e702-4d10-87ff-720ce3b2b22e) from Async(default) in 11.23ms
RoomChannel transmitting {"message"=>"<div class='message'>n  <p>テスト1: テストです</p>n  <p>2020-07-18 08:07:38 UTC</p>n</div>"} (via streamed from room_channel_6)
RoomChannel transmitting {"message"=>"<div class='message'>n  <p>テスト1: テストです</p>n  <p>2020-07-18 08:07:38 UTC</p>n</div>"} (via streamed from room_channel_6)
Started GET "/rooms/e7f6c011776e8db7cd330b54174fd76f7d0216b612387a5ffcfb81e6f0919683" for 127.0.0.1 at 2020-07-18 17:07:40 +0900
Processing by RoomsController#show as HTML
  Parameters: {"id_digest"=>"e7f6c011776e8db7cd330b54174fd76f7d0216b612387a5ffcfb81e6f0919683"}
  Applicant Load (0.4ms)  SELECT  "applicants".* FROM "applicants" WHERE "applicants"."id" = ? ORDER BY "applicants"."id" ASC LIMIT ?  [["id", 1], ["LIMIT", 1]]
  Room Load (0.4ms)  SELECT  "rooms".* FROM "rooms" WHERE "rooms"."id_digest" = ? LIMIT ?  [["id_digest", "e7f6c011776e8db7cd330b54174fd76f7d0216b612387a5ffcfb81e6f0919683"], ["LIMIT", 1]]
  Rendering rooms/show.html.erb within layouts/application
  Message Load (5.3ms)  SELECT "messages".* FROM "messages" WHERE "messages"."room_id" = ?  [["room_id", 6]]
  Applicant Load (0.2ms)  SELECT  "applicants".* FROM "applicants" WHERE "applicants"."id" = ? LIMIT ?  [["id", 2], ["LIMIT", 1]]
  CACHE Applicant Load (0.0ms)  SELECT  "applicants".* FROM "applicants" WHERE "applicants"."id" = ? LIMIT ?  [["id", 2], ["LIMIT", 1]]
  CACHE Applicant Load (0.0ms)  SELECT  "applicants".* FROM "applicants" WHERE "applicants"."id" = ? LIMIT ?  [["id", 2], ["LIMIT", 1]]
  Applicant Load (0.4ms)  SELECT  "applicants".* FROM "applicants" WHERE "applicants"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
  CACHE Applicant Load (0.0ms)  SELECT  "applicants".* FROM "applicants" WHERE "applicants"."id" = ? LIMIT ?  [["id", 2], ["LIMIT", 1]]
  CACHE Applicant Load (0.0ms)  SELECT  "applicants".* FROM "applicants" WHERE "applicants"."id" = ? LIMIT ?  [["id", 2], ["LIMIT", 1]]
~省略~
  CACHE Applicant Load (0.0ms)  SELECT  "applicants".* FROM "applicants" WHERE "applicants"."id" = ? LIMIT ?  [["id", 2], ["LIMIT", 1]]
  CACHE Applicant Load (0.0ms)  SELECT  "applicants".* FROM "applicants" WHERE "applicants"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]


  Rendered collection of messages/_message.html.erb [88 times] (242.0ms)
  Rendered rooms/show.html.erb within layouts/application (291.9ms)
  Rendered layouts/_rails_default.erb (44.2ms)
  Rendered layouts/_shim.html.erb (0.7ms)
  Rendered layouts/_header.html.erb (1.6ms)
  Rendered layouts/_footer.html.erb (0.7ms)
Completed 200 OK in 359ms (Views: 334.2ms | ActiveRecord: 19.2ms)


Finished "/cable/" [WebSocket] for 127.0.0.1 at 2020-07-18 17:07:41 +0900
RoomChannel stopped streaming from room_channel_6
Started GET "/cable" for 127.0.0.1 at 2020-07-18 17:07:41 +0900
Started GET "/cable/" [WebSocket] for 127.0.0.1 at 2020-07-18 17:07:41 +0900
Successfully upgraded to WebSocket (REQUEST_METHOD: GET, HTTP_CONNECTION: Upgrade, HTTP_UPGRADE: websocket)
  Applicant Load (0.2ms)  SELECT  "applicants".* FROM "applicants" WHERE "applicants"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
Registered connection (Z2lkOi8vbWF0Y2hpbmctYXBwL0FwcGxpY2FudC8x)
***test***
***test***
RoomChannel is transmitting the subscription confirmation
RoomChannel is streaming from room_channel_6
RoomChannel#speak({"message"=>"あああ"})
   (0.1ms)  begin transaction
  Applicant Load (0.3ms)  SELECT  "applicants".* FROM "applicants" WHERE "applicants"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
  Room Load (0.1ms)  SELECT  "rooms".* FROM "rooms" WHERE "rooms"."id" = ? LIMIT ?  [["id", 6], ["LIMIT", 1]]
  SQL (0.7ms)  INSERT INTO "messages" ("content", "created_at", "updated_at", "applicant_id", "room_id") VALUES (?, ?, ?, ?, ?)  [["content", "あああ"], ["created_at", "2020-07-18 08:07:46.724639"], ["updated_at", "2020-07-18 08:07:46.724639"], ["applicant_id", 1], ["room_id", 6]]
   (2.6ms)  commit transaction
[ActiveJob] Enqueued MessageBroadcastJob (Job ID: 669bd380-4fef-47c4-a968-00543c534abc) to Async(default) with arguments: #<GlobalID:0x00007f9d5095ea30 @uri=#<URI::GID gid://matching-app/Message/347>>
  Message Load (0.6ms)  SELECT  "messages".* FROM "messages" WHERE "messages"."id" = ? LIMIT ?  [["id", 347], ["LIMIT", 1]]
[ActiveJob] [MessageBroadcastJob] [669bd380-4fef-47c4-a968-00543c534abc] Performing MessageBroadcastJob (Job ID: 669bd380-4fef-47c4-a968-00543c534abc) from Async(default) with arguments: #<GlobalID:0x00007f9d4fb298e8 @uri=#<URI::GID gid://matching-app/Message/347>>
[ActiveJob] [MessageBroadcastJob] [669bd380-4fef-47c4-a968-00543c534abc]   Applicant Load (0.2ms)  SELECT  "applicants".* FROM "applicants" WHERE "applicants"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
[ActiveJob] [MessageBroadcastJob] [669bd380-4fef-47c4-a968-00543c534abc]   Rendered messages/_message.html.erb (7.1ms)
[ActiveJob] [MessageBroadcastJob] [669bd380-4fef-47c4-a968-00543c534abc] [ActionCable] Broadcasting to room_channel_6: {:message=>"<div class='message'>n  <p>テスト1: あああ</p>n  <p>2020-07-18 08:07:46 UTC</p>n</div>"}
[ActiveJob] [MessageBroadcastJob] [669bd380-4fef-47c4-a968-00543c534abc] Performed MessageBroadcastJob (Job ID: 669bd380-4fef-47c4-a968-00543c534abc) from Async(default) in 23.72ms
RoomChannel transmitting {"message"=>"<div class='message'>n  <p>テスト1: あああ</p>n  <p>2020-07-18 08:07:46 UTC</p>n</div>"} (via streamed from room_channel_6)
Finished "/cable/" [WebSocket] for 127.0.0.1 at 2020-07-18 17:12:11 +0900
RoomChannel stopped streaming from room_channel_6

どうしてメッセージが出る時と出ない時があるのか知りたいです。
同じ「room_channel_6」に接続しているように見えるのですが・・・

Add your own answers!

Ask a Question

Get help from others!

© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP