Ruby on Rails 5 でWebSockets通信を試してみる

木内智史之介(シャッチョー)
ミンカさんけっこんしてくださいおねがいします(ズザー
SEGAさん、DIVAの筐体ください(ズザー

Action Cable とは?

Ruby on Rails 5 で追加された機能の一つに「Action Cable」というものがあります。
これは、「WebSockets」に関するフレームワークになります。

WebSockets とは?

たとえば、ごく普通のHTTP通信は「クライアント → サーバー」への一方通行な通信になります。
リクエストに対してのレスポンスはありますが、「リクエストの発起人」になるのは常にクライアントです。

AJAXの登場で、ユーザー体験上ではサーバーとクライアントでリアルタイムな通信が行われているように錯覚できるようになりましたが、 それはJavascript側でバックグラウンドで通信を行い、「リアルタイム通信っぽいこと」を実現しているに過ぎません。

WebSocketsとは、「クライアント → サーバー」「サーバー → クライアント」という「双方向での通信」をサポートするための規格になります。

お断り

本記事は、「Rails 5 + ActionCableで作る!シンプルなチャットアプリ」を参考にしています。
より詳しい記載は元記事を参考にしていただいた方がよいかもしれません。

実行環境

  • Ruby 2.3.1
  • Rails 5.0.1

簡易チャットアプリの実装

簡易チャットアプリの作成を通して、WebSocketsに関する実装の感覚を掴んでみたいと思います。

RoRプロジェクトの作成

1
2
$ rails new ror-web-sockets
$ cd ror-web-sockets

chatコントローラーの作成

まずは、chat関係のコントローラーやモデルの作成を行います。

1
2
3
4
5
6
# コントローラーやビューの作成
$ rails g controller chat show

# メッセージモデルの作成
# 本記事では、メッセージの保存は行わないのでmigrateは実行しない
$ rails g model message body:text

ここまでは、ごく普通のRoR開発の手順と同様ですね。

チャット用チャンネルを作成

続いて、チャット関係の通信を行うためのチャンネルを作成します。

1
2
# チャット通信用のチャンネルを作成
$ rails g channel chat speak

生成されたファイルはこんな感じです。

  • app/channels/chat_channel.rb: WebSockets通信におけるサーバー側の処理を記述
  • app/assets/javascripts/channels/chat.coffee: WebSockets通信におけるクライアント側の処理を記述

実はここまでで、WebSocketsを使用する準備がすでに整っています。
試しにブラウザから http://localhost:3000/chat/show にアクセスしてみましょう。

1
$ rails server

一見、なんの変哲もない、ごく普通のrailsアプリケーションですが、ソースを見てみると、

1
2
3
4
5
<script src="/assets/action_cable.self-17ebe4af84895fa064a951f57476799066237d7bb5dc4dc351a8b01cca19cce9.js?body=1" data-turbolinks-track="reload"></script>
<script src="/assets/cable.self-6e0514260c1aa76eaf252412ce74e63f68819fd19bf740595f592c5ba4c36537.js?body=1" data-turbolinks-track="reload"></script>
<script src="/assets/channels/chat.self-660d1dfffd7c22551eb29709ea5f789dbd62d8a2e602aa8e9126bd90ab1de200.js?body=1" data-turbolinks-track="reload"></script>
<script src="/assets/chat.self-877aef30ae1b040ab8a3aba4e3e309a11d7f2612f44dde450b5c157aa5f95c05.js?body=1" data-turbolinks-track="reload"></script>
<script src="/assets/application.self-b89234cf2659d7fedea75bca0b8d231ad7dfc2f3f57fcbaf5f44ed9dc384137b.js?body=1" data-turbolinks-track="reload"></script>

Action Cableっぽい何かを読み込んでいる!

Consoleからアプリケーションをコールしてみると…

おお、なんか動いてるっぽい。
Puma側のログにもコールされた記録がのこるので、通信は間違いなく成功しているようです。

メッセージのブロードキャスト

WebSockets通信の疎通が問題なさそうなので、メッセージのブロードキャストを試してみることにしましょう。

app/assets/javascripts/channels/chat.coffee

1
2
3
# speakメソッドでメッセージを要求
speak: (message) ->
  @perform 'speak', message: message

app/channels/chat_channel.rb

1
2
3
4
5
6
7
8
9
10
# chatチャンネル接続時にコールされる
def subscribed
  stream_from "chat_channel"
  ActionCable.server.broadcast 'chat_channel', message: 'connected.'
end

# chatチャンネルのspeakメソッドは、受け取ったメッセージを全クライアントにブロードキャストする
def speak(data)
  ActionCable.server.broadcast 'chat_channel', message: data['message']
end

さて、ブラウザをリロードして、再度speakメソッドを呼び出してみましょう。

素晴らしいですね…
こんなに簡単でいいのでしょうか?

チャットらしくメッセージの入力フォームを設置してみる

メッセージを発信するための入力欄を設置してみる事にしましょう。

app/views/chat/show.html.erb

1
2
3
4
5
6
<ul id="messages">
</ul>

<form>
    <label>say something: <input type="text" data-behavior="chat_input"></label>
</form>

app/assets/javascripts/channels/chat.coffee

1
2
3
4
5
$(document).on 'keypress', '[data-behavior~=chat_input]', (event) ->
  if event.keyCode is 13 # return = send
    App.chat.speak event.target.value
    event.target.value = ''
    event.preventDefault()

入力欄に入力した文字列が、Enterキーの押下時にブロードキャストされました。

受け取ったメッセージをメッセージ欄に表示してみる

console.log上に記録されるだけでは、一般の方には認知不能なチャットアプリケーションになってしまうので、 メッセージ表示欄に描画するように調整してみる事にします。

app/assets/javascripts/channels/chat.coffee

1
2
3
App.chat = App.cable.subscriptions.create "ChatChannel",
  received: (data) ->
    $('#messages').append '<li>' + data['message'] + '</li>'

たったこれだけで、チャットとしての最低限の機能が実装されました!
恐ろしいの一言に尽きます。

リアルタイム通信を用いた開発は、ゲーム系ではPhotonをもっぱら用いていましたが、RoRのAction Cableを用いた開発も視野に入れていっていい気がします! 開発環境の構築も驚くほど楽ですし。