質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

88.91%

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

受付中

回答 0

投稿

  • 評価
  • クリップ 0
  • VIEW 180

takumimori

score 10

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

どうしてメッセージが出る時と出ない時があるのか知りたいです。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正の依頼

  • pecchan

    2020/07/28 16:11

    私も同様の現象出てるかもです。
    エラーログ確認されましたか?&ログ追記されたら回答しやすいかもです。

    キャンセル

まだ回答がついていません

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 88.91%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る