ActionCableを用いたチャットアプリでチャットルームとメッセージを紐づける方法

## 概要
　先日、[こちらの記事](https://techtechmedia.com/action-cable-rails6/)で解説されていたActionCableを用いたリアルタイムチャットアプリを動かしてみました。一通り動かせたのでこれを発展させて、複数のチャットルームを作りそれぞれに対してメッセージを投稿できるようにしてみたのでその方法をまとめました。


## 実装
※ [こちらの記事](https://techtechmedia.com/action-cable-rails6/)を前提とします。

最初に [こちらの記事](https://makolog.xyz/gem/)の通りにgonというgemをインストールします。これはコントローラからjavascriptへの変数の受け渡しが簡単に行えるようにするものです。

　![ER](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1261800/146f9360-6617-3733-9a9b-8bbebcb3581d.png)
　
```ruby:config\routes.rb
Rails.application.routes.draw do
  root to: 'rooms#show'
  resources :rooms
end
```

```slim:app\views\rooms\index.html.slim
- @rooms.each do |room|
    li
        = link_to "#{room.id}", room_path(room.id)

= link_to "room作成", new_room_path
```

```slim:app\views\rooms\show.html.slim
h1
  | Chat room

#messages data-room_id= @room.id
  = render @messages

form
  label
    | Say something:
  br
  input[type="text"]
```

```html:app\views\messages\_message.html.erb
<div class="message">
  <p><%= message.content %></p>
</div>
```

```ruby:app\controllers\rooms_controller.rb
class RoomsController < ApplicationController
  def show
    @room = Room.find(params[:id])
    gon.room = @room
    @messages = @room.messages
  end

  def index
    @rooms = Room.all
  end

  def new
    @room = Room.create
    redirect_to rooms_path
  end

  def create
  end
end
```

```ruby:app\models\room.rb
class Room < ApplicationRecord
    has_many :messages
end
```

```ruby:app\models\message.rb
class Message < ApplicationRecord
    belongs_to :room
    after_create_commit { MessageBroadcastJob.perform_later self }
end
```

```javascript:app\javascript\channels\room_channel.js
import consumer from "./consumer"

let room_id = gon.room.id;

const appRoom = consumer.subscriptions.create({channel: "RoomChannel", room_id: room_id}, {

  received(data) {
    const messages = document.getElementById('messages');
    messages.insertAdjacentHTML('beforeend', data['message']);
  },

  speak: function(message) {
    return this.perform('speak', {message: message, room_id: room_id});
  }
});

window.addEventListener("keypress", function(e) {
  if (e.keyCode === 13) {
    appRoom.speak(e.target.value);
    e.target.value = '';
    e.preventDefault();
  }
})

//ルームを離れる直前に行う処理
//明示的に購読を解除する
window.onbeforeunload = function(){
  let subscriptions = consumer.subscriptions['subscriptions'];
  subscriptions.forEach(function(subscription){
    consumer.subscriptions.remove(subscription);
  });
}
```

```ruby:app\channels\room_channel.rb
class RoomChannel < ApplicationCable::Channel
  def subscribed
    stream_from "room_channel_#{params[:room_id]}"
  end

  def unsubscribedunsubscribed
  end

  def speak(data)
    @room = Room.find_by(id: data['room_id'])
    @room.messages.create(content: data['message'])
  end
end
```

```ruby:app\jobs\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
```





## 参考資料
[【Rails6.0】ActionCableを使用したライブチャットアプリを実装する手順を解説](https://techtechmedia.com/action-cable-rails6/)
 [Rails 5 Action Cable メッセージとルームを紐付ける。](https://qiita.com/kohei1228/items/7aed5aad9c63e834c0e1)
[【JavaScript】data属性の取得・設定・更新【dataset】](https://into-the-program.com/dataset/)
[Action Cable の概要](http://railman.net/railsguides/6.1/action_cable_overview.html)
[【Rails】gonっていうRailsで定義した変数をJSで使えるgem](https://makolog.xyz/gem/)
[【ActionCable】購読中のチャンネルをjsで取得／購読解除する](https://qiita.com/NaokiIshimura/items/140d902ef01ea9602aec#%E3%81%99%E3%81%B9%E3%81%A6%E3%81%AE%E3%83%81%E3%83%A3%E3%83%B3%E3%83%8D%E3%83%AB%E3%81%AE%E8%B3%BC%E8%AA%AD%E3%82%92%E8%A7%A3%E9%99%A4%E3%81%99%E3%82%8B)