#概略
Action Cableを使ったリアルタイムチャットアプリの作り方を解説したサイトは複数あるが、どれもbeta版やrc版を使って解説したものである。
そこで、Rails5.1.4使用してAction Cableを使用するためにいろいろ調べたものをまとめてメモとしてこの記事を書いている。
beta版と正式版ではいろいろ仕様が異なる。(redisが不要になったなど)
調べながら作業をすると、頭が混乱するので、記事に調べたものをまとめ、この記事を見ながら作業するのが目的。
まず、Action Cableを使った単純なチャットアプリを作り、その後、特定のユーザーへのダイレクトメッセージ機能を作る(予定)
その1では、Action Cableのサンプルを作る。(おまけでherokuに公開)
その2では、ダイレクトメッセージを実装(予定)
#環境
Ruby2.2.4
Rails 5.1.4
#参考にしたサイト
TICLECODE Ruby on Rails5.0.0.rc1のAction Cableを使ってチャットアプリを作ろう!#Rails #coedorb
Qiita Rails 5 + ActionCableで作る!シンプルなチャットアプリ(DHH氏のデモ動画より)
CodeZine Rails 5の目玉機能「Action Cable」で双方向通信を実装
以上の3つ
#手順
##1. アプリの作成
Action Cableのテスト用アプリなのでac_testとします。
$rails new ac_test
##2. コントローラー作成
roomsコントローラーを作成し、showアクションとviewも同時に作成する。
$rails g controller rooms show
サーバーを起動し、http://localhost:3000/rooms/show
にアクセスするとページが表示されるのを確認。
$rails s
##3. messageモデルを作成する。
messageモデルを作成し、同時にtextデータ型のcontentカラムも作成する。
その後、マイグレーションする。
$rails g model message content:text
$rails db:migrate
##4. controller・viewの作成・編集
roomsのコントローラーのshowアクションを上記のように編集(追記)し、@messages変数に全ての投稿を取得、代入する。
def show
@messages = Message.all
end
viewファイルを以下のように編集し、controllerで代入した@messagesを表示する。
<h1>Chat room</h1>
<div id="messages">
<%= render @messages %>
</div>
_message.html.erbというファイルを以下にように作成・編集する。
このファイルは投稿を一件だけ取得するためのテンプレート
<div class="message">
<p><%= message.content %></p>
</div>
Memo:ファイル名の先頭にアンダーバーをつけるのは、パーシャルという機能を使用するため?
##5. 投稿データを登録してテスト
コンソールを起動
$rails c
MessageテーブルのcontentカラムにHelloを登録。
手順2と同じ手順でサーバー起動してアクセスすると、投稿したデータが表示されている(はず)。
Message.create! content: "Hello"
##6. Roomチャンネルの作成(未知の世界へ)
speakというアクションを持つRoomチャンネルを作成。
すると、
app/channels/room_channel.rb
(サーバーサイド用)
app/assets/javascripts/channels/room.coffee
(クライアントサイド用)
という2つのファイルが生成される。
$ rails g channel room speak
サーバーを起動して、ブラウザーのコンソールから
App.room.speak()
を入力してtrue
返ってくれば成功。
Memo:beta版ではこの後、routes.rbのファイルの編集が必要であったが、正式版では不要になった。また、cable.coffeeはcable.jsに変更され、編集が不要になった。(戸惑った...)
##7. speakアクションの定義
クライアントサイドのspeakアクションを定義して、そのアクションでサーバーサイドのspeakアクションを呼び、messageをmessageというパラメーターとして渡す。
(省略)
speak: (message) ->
@perform 'speak', message: message
subscribedアクションの中身をコメントを外し、some_channelをroom_channelに書き換える。
また、サーバーサイドのspeakアクションを定義して、クライアントサイドのspeakアクションから渡されたパラメーターをdataとして受け取り、room_channelにmessageをブロードキャストする。
class RoomChannel < ApplicationCable::Channel
def subscribed
stream_from "room_channel"
end
def unsubscribed
# Any cleanup needed when channel is unsubscribed
end
def speak(data)
ActionCable.server.broadcast 'room_channel', message: data['message']
end
end
データを受け取った時の仮のアクション(アラート表示)を以下のように定義して動作確認を行う。
received: (data) ->
alert data['message']
サーバーを起動してコンソールにApp.room.speak("test")
を入力してアラートが表示されれば成功。
Memo:channelsというディレクトリがApp内やJavascript内など複数の場所に同名で存在したり、rooms.coffeeやroom.coffeeなどややこしい名前のファイルが多く、混乱しやすいので注意!!!
##8.フォームを使ったデータ送信
showのviewに入力フォームを追加する。
<form>
<label>Say something:</label><br>
<input type="text" data-behavior="room_speaker">
</form>
次に、入力後にエンターキーを押したときの動作を定義
$(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()
サーバーを起動し、ブラウザーにアクセスして、テキストボックスに文字を入力して、エンターキーをおして、アラートが出れば、成功。
Memo:エンターキーの処理をするのにjQueryを使用するので、jQueryの読み込みが必要。
##9. データの保存とブロードキャストの改善
このままだと、入力とブロードキャストされたものはアラートされるだけで、保存がされない。
まず、speakアクションを書き換える。(ブロードキャスト処理は非同期通信のためにjobに移動する)
def speak(data)
Message.create! content: data['message']
end
次に、MessageBroadcastのjobを作成する。
$rails g job MessageBroadcast
次に作成されたjobファイルを編集し、ブロードキャスト処理を追加する。
最初に作成したテンプレートのmessages/_message.html.erbにmessageを渡して、代入したものを再度messageに代入する。
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
Memo:Active jobは処理をキューに登録することでバックグラウンドで順番に処理を行うためのフレームワーク?(いまいち理解できていない...)
非同期通信をするのに使っている?
次に、messageモデルを編集して、speakのアクションによってmessageテーブルにデータがcreateされた後に、MessageBroadcastのperformアクションを実行するように追記する。そして、speakアクションから送信されたデータをselfを使ってmessageモデルとして渡す。
class Message < ApplicationRecord
after_create_commit { MessageBroadcastJob.perform_later self }
end
この状態で、ブラウザーにアクセスし、文字をエンターすると、テンプレートに代入されたmessageがアラートされる。
##10.非同期で画面に入力した文字を表示させる。
最後に、ブロードキャストされたmessageをクライアントサイドのrecieveの処理を変更し、viewにappendする。
received: (data) ->
$('#messages').append data['message']
これで、入力したものが更新なしで画面に反映されるようになった。
また、同時にデータベースに登録される。
その1おわり
##おまけ herokuで公開するための設定(難関)
herokuに公開するのに非常に苦労しました。
beta版まではAction Cableの利用にredisが必要で、正式版ではredisが不要になった。
herokuでredisを使用するためには有料のプランのアドオンを使用しなければならず、今回Action Cableの使用に挑戦したのはredisが不要になったという理由があります。
しかし、情報がない。
localでは動くのに、herokuにあげると動かない。
ログを確認すると、こんなエラーが...
Gem::LoadError
(Specified 'redis' for Action Cable pubsub adapter, but the gem is not loaded. Add `gem 'redis'` to your Gemfile
あら、redisは不要じゃなかったの!!?
さんざん悩んだ結果以下の2点を変更することで解決。
###1. production.rbを編集(これは情報アリ)
config/environment/production.rbに以下を追記(コメントを解除し編集)
config.action_cable.allowed_request_origins = [ /http:\/\/.*/ ]
###2. config/cable.ymlを編集(これに悩んだ)
production:に書かれているものをコメントアウトして、adapter: asyncを追加。
production:
# adapter: redis
# url: redis://localhost:6379/1
# channel_prefix: ac_test_production
adapter: async
これで、無事heroku上で動作しました。
以上
その2では、ダイレクトメッセージの実装をする(予定)
追記
Action Cableでリアルタイムチャットアプリの作成方法 (Rails 5.1.4にて)(その2)グループ別チャンネルの作成!!を公開しました~。