

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

# 機能について

ログイン後、自分のアカウントに紐づいたルームの名前が表示されます。  
[![画像の説明をここに入力](https://i.stack.imgur.com/W8HRG.png)](https://i.stack.imgur.com/W8HRG.png)

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

しかし、例えばこの状態で左だけ画面をリロードし、再度メッセージを入力すると、右に表示されません。  
[![画像の説明をここに入力](https://i.stack.imgur.com/Zi9lT.png)](https://i.stack.imgur.com/Zi9lT.png)

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

以下、どのようなパターンの時に表示されるか、されないかを表にまとめています。  
[![画像の説明をここに入力](https://i.stack.imgur.com/GFHnE.png)](https://i.stack.imgur.com/GFHnE.png)

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

# ソースコード

application.js

[code]

    // 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
    
[/code]

cable.js

[code]

    // 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);
    
    
[/code]

room_channel.js

[code]

    // $(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();
          }
        });
      });
    
[/code]

assets.rb

[code]

    # 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 )
    
[/code]

routes.rb

[code]

    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
    
    
[/code]

room_channel.rb

[code]

    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
    
[/code]

index.html.erb

[code]

    <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>
    
[/code]

show.html.erb

[code]

    <%#ここの 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' %>
    
[/code]

message.rb

[code]

    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
    
[/code]

message_broadcast_job.rb

[code]

    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
    
[/code]

# ログ

[code]

    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
    
[/code]

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


CC BY-SA 4.0
2020-07-18T09:12:37.500
68697
2020-07-18T09:26:12.687
2020-07-18T09:26:12.687
41136
41136
question
ruby-on-rails websocket
rails5のaction cableでメッセージが同期されるときと同期されない時がある