Help us understand the problem. What is going on with this article?

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

More than 3 years have 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で受け取ってゴニョゴニョするしかないんですかね、、
とりあえず私が調べた限りはこれくらいしかなさそうでしたが、、

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした