描いて理解する Action Cable

# はじめに

コロナ騒ぎで時間があるので Rails の Action Cable を触ってみました。

基本的な使い方は Rails ガイドで調べましたが、いまいちピンとこなかったので理解するために図を描きました。特に[用語についての章](https://railsguides.jp/action_cable_overview.html#%E7%94%A8%E8%AA%9E%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6)を読んでもそれぞれの用語の関係性がよく分からなかったので、自分なりに整理しています。

# 用語について

## Connection, Consumer

![ActionCable-Connection.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/86330/c8428b5c-f333-10ee-69d7-306802220197.png)

### WebSocket の基礎

* Web 上でクライアント、サーバ間の双方向通信を実現するための通信規格
* `Upgrade: websocket` ヘッダをつけた HTTP リクエストで開始
* レスポンスとして HTTP ステータス 101(Switching Protocols) が返ってきたら TCP コネクションを使いまわして WebSocket として通信ができる
* 送信できるデータはテキストまたはバイナリ
* HTTP とは別のプロトコルなので Cookie などは送られない
* 通信が確立したあとはクライアント、サーバどちらからでもメッセージを送ることが可能

### Connection

* WebSocket としてのコネクションを表すクラス
* （おそらく）WebSocket として通信が確立した時点でインスタンスが生成される
* 開始時のリクエストはふつうの HTTP なので Cookie も送られる
* なので `connection.rb` では `cookies` が使える
* 認証処理はこのクラス内に記述

### Consumer

* WebSocket コネクションのクライアント（ブラウザなど）
* HTTP とは異なり、同じブラウザの異なるタブは完全に別のクライアントとして扱われる

### Consumer を区別する方法

HTTP とは異なり毎回認証情報が送られたりはしないので、コネクション確立時に「このコネクションは誰のものか」をサーバー側で管理する必要がある。

そのためのしくみとして Action Cable では Connection クラスの `identified_by` を使う。

```ruby
# app/channels/application_cable/connection.rb
module ApplicationCable
  class Connection < ActionCable::Connection::Base
    identified_by :current_user
 
    def connect
      self.current_user = User.find_by(id: cookies.encrypted[:user_id])
    end
 
  end
end
```

## Channel, Subscription

![ActionCable-Subscription.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/86330/231ccc0f-3e7f-0daa-609e-fe1953bd0481.png)

### Channel

* 文字通りのチャンネル
* Controller のようなもの

### Subscription

* チャネルの購読
* ひとつのコネクション内でいくつでも作成可能
* あるチャネルの購読をしている、という意味ではコンシューマをサブスクライバとも呼ぶ

以下のように `consumer.subscriptions.create` で新たな `Subscription` オブジェクトが作成され、`consumer.subscriptions` に追加される。

```javascript:chat_channel.js
import consumer from "./consumer"
consumer.subscriptions.create("ChatChannel", {
  // 略
});
```

## Stream

![ActionCable-Stream.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/86330/f79b113f-e8da-42b1-c537-cba7186e1398.png)

* ブロードキャストを行う場合に使う
* チャネルがサブスクリプションとストリームの紐付けを行う
* あるストリームにブロードキャストした場合、そのストリームに紐づくサブスクリプションにメッセージが送られる

### 例
たとえば以下のようなチャネルクラスを定義しておくと `ChatChannel` をサブスクライブしたときに `chat_001` というストリームに紐付けられる。

```rb:chat_channel.rb
class ChatChannel < ApplicationCable::Channel
  def subscribed
    stream_from "chat_001"
  end
end
```

この状態で以下のコードが実行されると、サブスクライバに `{"text": "Hello, World!"}` というJSON文字列が送られる。

```rb:broadcast
data = {text: "Hello, World!"}
ActionCable.server.broadcast("chat_001", data)
```

ストリームに対するブロードキャスト自体は、Rails アプリケーションのどこからでも実行可能。

### 複数のStream

![ActionCable-Stream-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/86330/5339b237-f34e-e08b-b5b9-92f88efff33f.png)

前節の例では固定文字列だったので、どのサブスクリプションも同じストリームに紐付いていました。しかし、チャネルをサブスクライブするときサーバー側にパラメータを渡すことができるので、ユーザーごと、もしくは開いているページごとに異なるストリームに紐付けることが可能です。


# まとめ

![ActionCable-まとめ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/86330/b8b9005c-5bbe-e05d-d4de-2933ad8af0b4.png)

ストリームとサブスクリプションの関係がよく分からなかったのでそこを中心に図示してみました。この図だとストリームが必須みたいに見えてしまうので、action パラメータによってサーバー側のメソッドを実行する、という観点でも整理して別途記事にしてみようと思います。