ActionCable の README を読んでチャットアプリを作ってみた

Rails 5 の目玉(？)機能である [ActionCable](https://github.com/rails/rails/tree/master/actioncable) を触ってみようと、README を読んで単純なチャットアプリを作ってみました。簡単な説明とサンプルコードを載せます。

# ActionCable

## 概要
WebSocket と REST である Rails を統合させた、Rails にリアルタイム機能を追加するためのものです。クライアントサイド (JS) と サーバーサイド (Ruby) の両方のフレームワークを提供します。

## WebSocket?

WebSocket は [RFC6455](https://tools.ietf.org/html/rfc6455) で定義されている、Web でリアルタイム通信を行うための通信技術です。最初にハンドシェイクと呼ばれる HTTP 通信を行うのですが、それ以降はサーバーとクライントを接続しっぱなしにして、ステートフルに TCP 通信を行います。WebSocket の利点は主に 2 つです。

1. サーバープッシュが可能（リアルタイム）
2. HTTP 通信よりオーバーヘッドが小さい（軽量）

とにかく、軽量でリアルタイム性が高いのが売りです。また、以前はサーバープッシュを実現するために Comet などの技術がありましたが、それに比べて「シンプル」という利点もあります。
ただ、ステートフルなので、 REST な Rails に組み込むにはちょっと考えないといけなかったのですが、そこを今回 ActionCable がまるっとやってくれているようです。

# サンプル

## 概要

ActionCable のサンプルとして、「ユーザー名を入れると単一のチャットルームにログインして、チャットができる」という超単純なチャットアプリを作りました。

## デモ

こんな感じです！しょぼすぎてあれですが、、一人がポストすると他の Client (ActionCable では Consumer と呼びます) にも、リアルタイムにプッシュ通知されていることがわかります！

![demo4.gif](https://qiita-image-store.s3.amazonaws.com/0/41416/ee15a395-b282-c432-856a-ac7cc1509cc8.gif)

## 実装

ActionCable がほとんどやってくれるので、実装はとても単純です。

### クライアントサイド

クライアントサイドは、`ActionCable` を使う準備をして、

```coffeescript:app/assets/javascripts/channels/cable.coffee
#= require action_cable
#= require_self
#= require_tree .

@App = {}
App.cable = ActionCable.createConsumer()
```

以下の4つのメソッドを定義するだけです。

```coffeescript:app/assets/javascripts/channels/chat.coffee
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` の動作を実装します。

```rb:app/channels/chat_channel.rb
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 の最大の魅力はサーバープッシュです。そのプッシュする内容を定義します。

```rb:app/models/post.rb
class Post < ActiveRecord::Base
  after_commit do
    NewPostJob.perform_later(self)
  end

  ...
end
```

```rb:app/jobs/new_post_job.rb
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` メソッドを実行します。

### 設定ファイル

あとは設定をちょちょっとするだけです！

```yml:config/cable.yml
production: &production
  # ....
development: &development
  adapter: redis
  url: redis://localhost:6379
test: *development
```

```rb:config/environments/development.rb
Rails.application.configure do
  ...
  config.action_cable.url = 'ws://localhost:28080'
end
```

```html+erb:app/views/layouts/application.html.erb
<%= action_cable_meta_tag %>
```

```rust:cable/config.ru
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](https://github.com/igrigorik/em-websocket) を使って同じようにチャットアプリを作ったことがありますが、その時に比べると圧倒的に楽です。

あとはどういうケースで WebSocket を使うか、パフォーマンスはどうかが気になりますが、`ActionCable` とはまた別の問題ですかねー？

また、今回は README をまねして書いたので、本当に基本的な機能しか使ってないですが、もっと色々な便利機能がありそうなので、もっと深ぼってみたいです！

それでは！