##環境
ruby '2.6.3'
'rails', '~> 5.2.3'
##参考
Rails 5 + ActionCableで作る!シンプルなチャットアプリ(DHH氏のデモ動画より)
Rails 5+ ActionCableで作る! シンプルなチャットアプリ ハマった所
Action Cableでリアルタイムチャットアプリの作成方法 (Rails 5.1.4にて)(その1) herokuで動かす!
#アプリケーション作成手順
##1.アプリ作成
chat_roomという名前のアプリを作成。
$ rails new chat_room
$ cd chat_room
Gemfile に追加。
gem 'jquery-rails'
gem 'jquery-turbolinks'
$ bundle install
application.jsファイルに追加
//= require jquery
//= require jquery_ujs
この順になれば良い
//
//= require rails-ujs
//= require activestorage
//= require jquery
//= require jquery_ujs
//= require turbolinks
//= require_tree .
##2.roomsコントローラー作成
showアクションをもつroomsコントローラーを作成。
$ rails g controller rooms show
get 'rooms/show'
をroot to: 'rooms#show'
に変更します。
Rails.application.routes.draw do
root to: 'rooms#show'
# For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
# Serve websocket cable requests in-process
# mount ActionCable.server => '/cable'
end
サーバーを起動
$ rails s
localhost:3000でアクセスできるようになった。
##3.Messageモデル作成
contentという属性を持つ、Messageクラスを作成。
$ rails g model message content:text
$ rails db:migrate
##4.controllerとview作成・編集
roomsのコントローラーのshowアクションを編集し、@messages変数に全ての投稿を代入する。
@messages = Message.all
を追加。
class RoomsController < ApplicationController
def show
@messages = Message.all
end
end
app/views/messages
viewsにmessagesというフォルダーを作成。
app/views/messages/_message.html.erb
そのmessagesに_message.html.erbというファイルを作成。
controllerで代入した@messagesを表示。全て書き換える。
<h1>Chat room</h1>
<div id="messages">
<%= render @messages %>
</div>
投稿を1件だけ出力
<div class="message">
<p><%= message.content %></p>
</div>
Action Cableを有効にするため、mount ActionCable.server => '/cable'
を追加。
Rails.application.routes.draw do
root to: 'rooms#show'
mount ActionCable.server => '/cable'
end
##5.Roomチャンネル作成
speakアクションを持つRoomチャンネルを作成。
app/channels/room_channel.rb
(サーバーサイド用)
app/assets/javascripts/channels/room.coffee
(クライアントサイド用)
という2つのファイルが作られる。
$ rails g channel room speak
サーバーを起動
$ rails s
ブラウザーのコンソール(デベロッパーツールからconsoleを開く)にApp.room.speak()
を入力し、trueが返ってくれば成功。
##6.speakアクション
クライアントサイドのspeakアクションを定義する。サーバーサイドのspeakアクションを呼びだし、messageをパラメータとして渡す。
speak: -> @perform 'speak'
を
speak: (message) -> @perform 'speak', message: message
と書き換える。
App.room = App.cable.subscriptions.create "RoomChannel",
connected: ->
disconnected: ->
received: (data) ->
speak: (message) ->
@perform 'speak', message: message
サーバーサイドのspeakアクションを定義します。
def subscribed # stream_from "some_channel" end
から#
を削除し、some_channel
をroom_channel
に書き換える。
def speak end
を
def speak(data) ActionCable.server.broadcast 'room_channel', message: data['message'] end
に書き換える。
class RoomChannel < ApplicationCable::Channel
def subscribed
stream_from "room_channel"
end
def unsubscribed
end
def speak(data)
ActionCable.server.broadcast 'room_channel', message: data['message']
end
end
サーバーからデータを受け取ったときの動きを定義します。
receivedのところにalert data['message']
を追加
App.room = App.cable.subscriptions.create "RoomChannel",
connected: ->
disconnected: ->
received: (data) ->
alert data['message']
speak: (message) ->
@perform 'speak', message: message
サーバーを再起動します。
$ rails s
ブラウザのコンソールから App.room.speak('Hello world')
を入力し、trueが返ってくれば成功。
ブラウザ内のフォームからデータを送信できるようにする。
フォーム`
Say something:
`を追加。
<h1>Chat room</h1>
<div id="messages">
<%= render @messages %>
</div>
<form>
<label>Say something:</label><br>
<input type="text" data-behavior="room_speaker">
</form>
テキストボックスのkeypressイベントを定義します。
リターンキーが押されたときに、Roomチャンネルのspeakアクションを実行。
App.room = App.cable.subscriptions.create "RoomChannel",
# (省略)
$(document).on 'keypress', '[data-behavior~=room_speaker]', (event) ->
if event.keyCode is 13 # return = send
App.room.speak event.target.value
event.target.value = ''
event.preventDefault()
##データの保存とブロードキャスト処理の改善
このままではmessageが保存されないので、データベースに保存。
ActionCable.server.broadcast 'room_channel', message: data['message']
をMessage.create! content: data['message']
に書き換える。
class RoomChannel < ApplicationCable::Channel
def subscribed
stream_from "some_channel"
end
def unsubscribed
end
def speak(data)
Message.create! content: data['message']
end
end
Messageモデルのコールバックを定義し、データが作成されたら非同期でブロードキャスト処理を実行する。
トランザクションをコミットしたあとでブロードキャストしないと、他のクライアントからデータが見えない恐れがある。
class Message < ApplicationRecord
after_create_commit { MessageBroadcastJob.perform_later self }
end
非同期でブロードキャストするためのMessageBroadcastジョブを作成
$ rails g job MessageBroadcast
def perform(*args) # Do something later end
を
def perform(message) ActionCable.server.broadcast 'room_channel', message: render_message(message) end private def render_message(message) ApplicationController.renderer.render(partial: 'messages/message', locals: { message: message }) end
に書き換える。
class MessageBroadcastJob < ApplicationJob
queue_as :default
def perform(message)
ActionCable.server.broadcast 'room_channel', message: render_message(message)
end
private
def render_message(message)
ApplicationController.renderer.render(partial: 'messages/message', locals: { message: message })
end
end
##サーバーからデータを受け取ったらブラウザ内の表示を書き換える
サーバーからデータを受け取ったら、ブラウザ内の表示を更新する。
入力したものが更新なしで画面に反映される。同時にデータベースに登録される。
received: (data) -> alert data['message']
を
received: (data) -> $('#messages').append data['message']
に書き換える。
App.room = App.cable.subscriptions.create "RoomChannel",
connected: ->
disconnected: ->
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 # return = send
App.room.speak event.target.value
event.target.value = ''
event.preventDefault()
ブラウザをリロードし、文字列を入力。
リターンキーを押すと、ブラウザの表示が更新。
これでチャットアプリの完成。