Action Cableの習作: シンプルなチャット

外観

前回のアプリはメッセージを送受信するだけでDBにメッセージを保存していませんでした。今回はDBにメッセージを保存するチャットアプリを作成したいと思います。

画像1

リポジトリ

環境
macOS 10.15.4
Ruby 2.7.1
Rails 6.0.3
Yarn 1.22.4
Node 13.12.0

参照
Rails 5: Action Cable demo - YouTube
Rails 5 + ActionCableで作る!シンプルなチャットアプリ(DHH氏のデモ動画より) - Qiita

Rooms#showでのMessageの表示

Rails new
rails new -MT --skip-active-storage try_ac_simple_chat
cd try_ac_simple_chat

Generate rooms controller
bin/rails g controller rooms show

Root to Rooms#show
config/routes.rb

Rails.application.routes.draw do
  root 'rooms#show'
end

Generate Message model
bin/rails g model Message content:text
bin/rails db:migrate

Add Message seeds
db/seeds.rb

Message.create! content: 'Hello world!'

bin/rails db:seed:replant

Show Message in Rooms#show
app/controllers/rooms_controller.rb

class RoomsController < ApplicationController
  def show
    @messages = Message.all
  end
end

mkdir app/views/messages

app/views/messages/_message.html.erb

<div class="message">
  <p><%= message.content %></p>
</div>

app/views/rooms/show.html.erb

<h1>Chat room</h1>

<div id="messages">
  <%= render @messages %>
</div>

動作確認
bin/rails s

画像3

Roomチャンネルの作成

Generate RoomChannel
bin/rails g channel room speak

Add speak IF to RoomChannel
app/channels/room_channel.rb

class RoomChannel < ApplicationCable::Channel
  def subscribed
    # stream_from 'room_channel'
    stream_from 'room:message'
  end

  # ...

  def speak(data)
    # ActionCable.server.broadcast 'room_channel', message: data['message']
    RoomChannel.broadcast_to('message', data)
  end
end

Add speak and receive IF to consumer
app/javascript/channels/room_channel.js

// consumer.subscriptions.create("RoomChannel", {
window.room = consumer.subscriptions.create("RoomChannel", {

  // ...

  received(data) {
    alert(data['message'])
  },

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

動作確認
ウェブブラウザをリロードする。
room.speak('Hello world')
アラート表示を確認する。

画像4

Add form to Rooms#show
app/views/rooms/show.html.erb

<form>
  <label>Say something:</label><br>
  <input type="text" data-behavior="room_speaker">
</form>

動作確認
ウェブブラウザをリロードする。

画像5

Send message from form
app/javascript/channels/room_channel.js

const roomChannel = consumer.subscriptions.create("RoomChannel", {

  // ...

})

document.addEventListener('turbolinks:load', () => {
  const els = document.querySelectorAll('[data-behavior~=room_speaker]')
  els.forEach(el => {
    el.addEventListener('keypress', event => {
      if (event.keyCode === 13) {
        roomChannel.speak(event.target.value)
        event.target.value = ''
        event.preventDefault()
      }
    })
  })
})

動作確認
ウェブブラウザをリロードする。
メッセージを入力しアラート表示を確認する。

画像6

メッセージの保存とブロードキャストの非同期化

Save Message on RoomChannel#speak
app/channels/room_channel.rb

  # ...

  def speak(data)
    # ActionCable.server.broadcast 'room_channel', message: data['message']
    # RoomChannel.broadcast_to('message', data)
    Message.create! content: data['message']
  end
end

app/models/message.rb

class Message < ApplicationRecord
  after_create_commit { MessageBroadcastJob.perform_later self }
end

Generate MessageBroadcastJob
bin/rails g job MessageBroadcast

Add render_message on MessageBroadcastJob
app/jobs/message_broadcast_job.rb

class MessageBroadcastJob < ApplicationJob
  queue_as :default

  def perform(message)
    # ActionCable.server.broadcast 'room_channel', message: render_message(message)
    RoomChannel.broadcast_to('message', render_message(message))
  end

  private

  def render_message(message)
    # ApplicationController.renderer.render(partial: 'messages/message', locals: { message: message })
    ApplicationController.render(partial: 'messages/message', locals: { message: message })
  end
end

app/javascript/channels/room_channel.js

  received(data) {
    // alert(data['message'])
    alert(data)
  },

動作確認
ウェブブラウザをリロードする。
メッセージを入力しアラート表示を確認する。

画像2

メッセージの画面表示

Append message in Rooms#show
app/javascript/channels/room_channel.js

  // ...

  received(data) {
    const el = document.getElementById('messages')
    el.insertAdjacentHTML('beforeend', data)
  },

動作確認
ウェブブラウザをリロードする。
メッセージを入力する。メッセージが画面に追加されることを確認する。
他のウィンドウから入力したメッセージが表示されることを確認する。

以上です。

この記事が気に入ったらサポートをしてみませんか?