Action Cableでリアルタイムチャットアプリの作成方法 （Rails 5.1.4にて）（その2）グループ別チャンネルの作成！！

#概要
この記事は[Action Cableでリアルタイムチャットアプリの作成方法 （Rails 5.1.4にて）（その１）herokuで動かす！！](https://qiita.com/Hijiri-K/items/c3774c72a2cb68e1a720)のその２である。
その１で作成したチャットアプリにグループチャット機能を追加していく。
機能の概要としては、今まですべてのユーザーにブロードキャストしていたものグループ別に分け、そのグループをsubscribeしているユーザーだけに送信（append）するというもの。
前提として、ログイン機能が実装されている投稿アプリがあり、session[:user_id]でユーザーのログインを管理している。

つまずいた部分としては、channelやjobからはsessionが使えないためログインしているユーザーのidとそのユーザーがsubscribeしているグループ（チャンネル）の取得する方法にかなりてこずった。最終的には各々の投稿に投稿者がsubscribeしているgroupの情報を登録することで解決した（かなり無理やり）。

#環境
Ruby2.2.4
Rails 5.1.4

#参考にしたサイト
[Rails5のActionCableで簡易チャットの作成 ~モデルに応じたチャンネルを聴講する方法~](http://blog.aqutras.com/entry/2016/04/27/210000)
このサイトにはDMを実装する方法が書かれているが、作成したい機能が若干異なる（今回はグループ別にチャンネルを作りたい）のと、ブロードキャストするアクションの記載場所が異なるため、この記事を参考にしながら独自に書き替えた。

#手順
##1. 下準備（groupカラムの追加）
・usersモデルにgroupカラムを追加
・postsモデルにgroupカラムの追加

##2. viewにgroupを選択するselectボックスの設置
今回はこんな感じ

```erb:app/assets/view/posts/index.html.erb
  <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の書き替え
エンター入力時に手順２で作成したselectボックスの値を取得してspeakメゾットに送信し、それを引数に加えて、room_channel.rbのspeakアクションを呼ぶ。

```coffee:app/assets/javascripts/channels/room.coffee
  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はこのままでは使えず、次の手順が必要である。

```rb:app/channels/room_channel.rb
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情報)を使えるようにする。
これは、上記の参考サイトを参考にした。
非常に助かりました。

```rb:app/channels/application_cable/connection.rb
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カラムに登録されたデータが取れる。

```rb:app/jobs/message_broadcast_job.rb
(省略)
  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を使う方法がわかれば、もう少しきれいに書けるず。

分かる方、コメントで教えてください。
お願いします。