Edited at

Rails 5 Action Cable メッセージとルームを紐付ける。

More than 1 year has passed since last update.


 概要

Rails5 Action Cableを使ってリアルタイムなチャットアプリを作るという記事が多いが、常にと言っていいほどメッセージモデルとルームモデルを紐付ける方法が質問されているので、その方法を書く。


Modelの構成

User, Message, Roomモデルがあり、MessageModelはUserModelとRoomModelに紐づいている。


app/models/user.rb

class User < ActiveRecord::Base

has_many :messages
end


app/models/room.rb

class Room < ActiveRecord::Base

has_many :messages
end


app/models/message.rb

class Message < ActiveRecord::Base

# メッセージの保存に成功したらBroadCastJobに投げる
after_create_commit { BroadCastMessageJob.perform_later self }
# こうしとくと誰が送ったか識別しやすい
belongs_to :sent_user, class_name: "User", foreign_key: 'user_id'
belongs_to :room
end


Controller


app/controllers/rooms_controller.rb

class RoomsController < ApplicationController

def show
@room = Room.find(params[:id])
@messages = @room.messages
end

end



view


app/views/rooms/show.html.erb

<div id="messages", data-room_id="<%= @room.id %>">

<%= render @messages %>
</div>
<div class=
"input">
<form>
<label>post</label>
<input type="text" data-behavior="room_speaker">
</
form>
</div>


app/views/messages/_message.html.erb

<div class="message">

<p><%= "#{message.sent_user.name}: #{message.content}" %></p>
</div>


Room_idを受け取る

要するにあらかじめrooms_controllerからshow.html.erbにroomの情報(room_id)を渡しておいて、それをjs(room.coffee)で受け取り、その情報に基づいてチャンネル(room_channel.rb)の中でメッセージを作る。


app/assets/javascripts/channels/room.coffee

jQuery(document).on 'turbolinks:load', ->

# viewの<div id="messages">から受け取る
messages = $('#messages')
if $('#messages').length > 0

# ここやで
App.room = App.cable.subscriptions.create { channel: "RoomChannel", room_id: messages.data('room_id') },
received: (data) ->
$('#messages').append data['message']

speak: (message) ->
@perform 'speak', message:
message

$(document).on 'keypress', '[data-behavior~=room_speaker]', (event) ->
if event.keyCode is 13
App.room.speak event.target.value
event.target.value = ''
event.preventDefault()



app/channels/room_channel.rb

class RoomChannel < ApplicationCable::Channel

def subscribed
# このparams["room_id"]は上のroom.coffeeでsubscriptionを作る際に渡しているのがポイント。
stream_from "room_channel_#{params['room_id']}"
end

def speak(data)
# ここでRoomとMessageが紐付けられる。
Message.create(content: data['message'], sent_user: current_user, room: Room.find(params['room_id']))
end
end



ジョブに投げる

MessageModelの中のafter_create_commitで、メッセージの保存に成功したらjobに飛ばすように指定してる。


app/jobs/broadcast_message_job.rb

class BroadCastMessageJob < ApplicationJob

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


これで完成。

投稿した内容が保存され、かつリアルタイムで画面に表示されれば完成。


結論

本当にビューの中にroom_id渡してJSで受け取ってゴニョゴニョするしかないんですかね、、

とりあえず私が調べた限りはこれくらいしかなさそうでしたが、、