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
どうしてメッセージが出る時と出ない時があるのか知りたいです。
あなたの回答
tips
プレビュー