Rails 5 の目玉(?)機能である ActionCable を触ってみようと、README を読んで単純なチャットアプリを作ってみました。簡単な説明とサンプルコードを載せます。
ActionCable
概要
WebSocket と REST である Rails を統合させた、Rails にリアルタイム機能を追加するためのものです。クライアントサイド (JS) と サーバーサイド (Ruby) の両方のフレームワークを提供します。
WebSocket?
WebSocket は RFC6455 で定義されている、Web でリアルタイム通信を行うための通信技術です。最初にハンドシェイクと呼ばれる HTTP 通信を行うのですが、それ以降はサーバーとクライントを接続しっぱなしにして、ステートフルに TCP 通信を行います。WebSocket の利点は主に 2 つです。
- サーバープッシュが可能(リアルタイム)
- HTTP 通信よりオーバーヘッドが小さい(軽量)
とにかく、軽量でリアルタイム性が高いのが売りです。また、以前はサーバープッシュを実現するために Comet などの技術がありましたが、それに比べて「シンプル」という利点もあります。
ただ、ステートフルなので、 REST な Rails に組み込むにはちょっと考えないといけなかったのですが、そこを今回 ActionCable がまるっとやってくれているようです。
サンプル
概要
ActionCable のサンプルとして、「ユーザー名を入れると単一のチャットルームにログインして、チャットができる」という超単純なチャットアプリを作りました。
デモ
こんな感じです!しょぼすぎてあれですが、、一人がポストすると他の Client (ActionCable では Consumer と呼びます) にも、リアルタイムにプッシュ通知されていることがわかります!
実装
ActionCable がほとんどやってくれるので、実装はとても単純です。
クライアントサイド
クライアントサイドは、ActionCable
を使う準備をして、
#= require action_cable
#= require_self
#= require_tree .
@App = {}
App.cable = ActionCable.createConsumer()
以下の4つのメソッドを定義するだけです。
App.posts = App.cable.subscriptions.create 'ChatChannel',
connected: ->
setTimeout =>
@perform 'subscribed'
, 1000
received: (data) ->
$("[data-channel='posts']").append(data.post)
rejected: ->
@perform 'unsubscribed'
disconnected: ->
@perform 'unsubscribed'
App.cable.subscriptions.create 'ChatChannel'
で ChatChannel
を subscribe する準備をし、subscribe した際の動作をそれ以降に定義しています。これらのメソッドは次のタイミングで hook されます。
メソッド | 呼ばれるタイミング |
---|---|
connected | WebSocket サーバーに接続する際 |
received | WebSocket サーバーからデータがプッシュされた際 |
rejected | WebSocket サーバーに接続を拒否された際 |
disconnected | WebSocket サーバーと接続が切れた際 |
@perform 'subscribed'
で ChatChannel#subscribed
,
@perform 'unsubscribed'
で ChatChannel#unsubscribed
が呼ばれます。では、#subscribed
, #unsubscribed
を定義しましょう。
チャンネル(サーバーサイド)
クライアントサイドで準備した ChatChannel
の動作を実装します。
class ChatChannel < ApplicationCable::Channel
def subscribed
stop_all_streams
stream_from 'sample_chat_room'
end
def unsubscribed
stop_all_streams
end
end
stop_all_streams
で全てを unsubscribe します。
そして、stream_from
で subscribe を開始します。引数には subscribe する broadcasting を指定します。今回は毎回、同じ文字列を渡していますが、チャットでいうとルーム名などを渡すイメージです。
チャンネルの実装はこれだけです。
broadcast する内容の定義
先程も紹介したように WebSocket の最大の魅力はサーバープッシュです。そのプッシュする内容を定義します。
class Post < ActiveRecord::Base
after_commit do
NewPostJob.perform_later(self)
end
...
end
class NewPostJob < ApplicationJob
def perform(post)
ActionCable.server.broadcast 'sample_chat_room',
post: PostsController.render(partial: 'posts/post', locals: {post: post})
end
end
本アプリでは、メッセージを送った際に Post が作成されるのですが、その際に NewPostJob を作成して ActionCable.server.broadcast
を呼んでいます。 (どのような方法でもとにかく ActionCable.server.broadcast ...
を呼べば OK です)
第一引数に broadcasting 名、第二引数に配信するデータを渡します。これで、sample_chat_room
を購読している全 Consumer にデータをプッシュします!
そして、データを受信した各クライアントは received
メソッドを実行します。
設定ファイル
あとは設定をちょちょっとするだけです!
production: &production
# ....
development: &development
adapter: redis
url: redis://localhost:6379
test: *development
Rails.application.configure do
...
config.action_cable.url = 'ws://localhost:28080'
end
<%= action_cable_meta_tag %>
require ::File.expand_path('../../config/environment', __FILE__)
Rails.application.eager_load!
run ActionCable.server
サーバーは以下のコマンドで起動します。
bundle exec puma -p 28080 cable/config.ru
以上です!
使ってみて
実装しなければいけない部分はほとんどなく、何も意識しないで WebSocket が使えるようになるので、とても便利だなと感じました。以前、EM-WebSocket を使って同じようにチャットアプリを作ったことがありますが、その時に比べると圧倒的に楽です。
あとはどういうケースで WebSocket を使うか、パフォーマンスはどうかが気になりますが、ActionCable
とはまた別の問題ですかねー?
また、今回は README をまねして書いたので、本当に基本的な機能しか使ってないですが、もっと色々な便利機能がありそうなので、もっと深ぼってみたいです!
それでは!