#概要
この記事はAction Cableでリアルタイムチャットアプリの作成方法 (Rails 5.1.4にて)(その1)herokuで動かす!!のその2である。
その1で作成したチャットアプリにグループチャット機能を追加していく。
機能の概要としては、今まですべてのユーザーにブロードキャストしていたものグループ別に分け、そのグループをsubscribeしているユーザーだけに送信(append)するというもの。
前提として、ログイン機能が実装されている投稿アプリがあり、session[:user_id]でユーザーのログインを管理している。
つまずいた部分としては、channelやjobからはsessionが使えないためログインしているユーザーのidとそのユーザーがsubscribeしているグループ(チャンネル)の取得する方法にかなりてこずった。最終的には各々の投稿に投稿者がsubscribeしているgroupの情報を登録することで解決した(かなり無理やり)。
#環境
Ruby2.2.4
Rails 5.1.4
#参考にしたサイト
Rails5のActionCableで簡易チャットの作成 ~モデルに応じたチャンネルを聴講する方法~
このサイトにはDMを実装する方法が書かれているが、作成したい機能が若干異なる(今回はグループ別にチャンネルを作りたい)のと、ブロードキャストするアクションの記載場所が異なるため、この記事を参考にしながら独自に書き替えた。
#手順
##1. 下準備(groupカラムの追加)
・usersモデルにgroupカラムを追加
・postsモデルにgroupカラムの追加
##2. viewにgroupを選択するselectボックスの設置
今回はこんな感じ
<div class="new-post">
<input name="content" rows="2" cols="65" data-behavior="room_speaker">
<select id="group" name="group">
<option value="1">Group1</option>
<option value="2">Group2</option>
<option value="3">Group3</option>
</select>
</div>
(省略)
##3.room.coffeeの書き替え
エンター入力時に手順2で作成したselectボックスの値を取得してspeakメゾットに送信し、それを引数に加えて、room_channel.rbのspeakアクションを呼ぶ。
speak: (message, group)->
@perform 'speak',message: message, group: group
$(document).on 'keypress', '[data-behavior~=room_speaker]', (event) ->
if event.keyCode is 13 # return = send
App.room.speak event.target.value, $('#group').val()
event.target.value = ''
event.preventDefault()
##4. room_channel.rbの編集
まず、post.createにgroupを追加し、送られてきたdata['group']を登録postに登録する。
また、投稿者のidもcurrent_userから取得しuser_idに登録する。
さらに、userにもgroup情報を登録する
そして、subscribeの購読先にstream_for current_user.groupを追加して、selectボックスで指定したチャンネルを購読できるようにする。
channelの中でcurrent_userはこのままでは使えず、次の手順が必要である。
class RoomChannel < ApplicationCable::Channel
def subscribed
stream_from "room_channel"
stream_for current_user.group
end
def unsubscribed
# Any cleanup needed when channel is unsubscribed
end
def speak(data)
Post.create!(content: data['message'], user_id: current_user.id, group: data['group'])
user = User.find_by(id: current_user.id)
user.group = data['group']
user.save
end
Memo:heroku上でテストをしたら上記のコードではうまく動かなかった。
原因は、処理の順番。
postモデル(post.rb)でjobを呼び出して、投稿をブロードキャストしているが、サーバーの処理速度の関係でこの時点ではuserテーブルにgroup情報が登録できておらず、stream_for current_user.groupで購読先を検索できずにストリーミングを受信できないようであった。
そのため、speakメゾットをまず、userのgroup登録を行い、その後に、postを登録、postがトリガーとなりブロードキャストという順番に以下のように書き替えた。
def speak(data)
user = User.find_by(id: current_user.id)
user.group = data['group']
user.save
Post.create!(content: data['message'], user_id: current_user.id, group: data['group'])
end
すると、うまく動作するようになった。
サーバーでの実行速度などを考慮して処理の順番を考えたコーディングをすることの重要性を学んだ。
##5.channelでcurrent_user(session情報)を使えるようにする。
これは、上記の参考サイトを参考にした。
非常に助かりました。
module ApplicationCable
class Connection < ActionCable::Connection::Base
identified_by :current_user
def connect
self.current_user = find_verified_user
end
protected
def find_verified_user
if verified_user = User.find_by(id: session['user_id'])
verified_user
else
reject_unauthorized_connection
end
end
def session
cookies.encrypted[Rails.application.config.session_options[:key]]
end
end
end
##6. message_broadcast_job.rbを編集
ログインしているユーザーの購読しているチャンネルにのみブロードキャストするするように変える。
手順4のstream_forで指定したchannelにブロードキャストするにはRoomChannel.broadcast_toを使う。
(全員にはActionCable.server.broadcast)
post.rbから送られてきたmessageはモデルそのものなので、message.groupとすれば、その投稿のgroupカラムに登録されたデータが取れる。
(省略)
def perform(message)
# Do something later
group = message.group
RoomChannel.broadcast_to(group, message: render_message(message))
end
以上
#感想
userモデルにgroupを追加しても、channel内やjob内で取得できないので、postモデルに追加することで解決したが、すこし変な実装になってしまった。
job内でcurrent_userを使うやり方を調べたがわからなかったため、このような形になってしまった。
もし、job内でcurrent_userを使う方法がわかれば、もう少しきれいに書けるず。
分かる方、コメントで教えてください。
お願いします。