【ActionCable】購読中のチャンネルをjsで取得／購読解除する

# はじめに

ActionCableについて調べると、チャンネルを購読（サブスクリプション）する方法は多く見つかるのですが、「購読中のチャンネル情報を取得する方法」「購読中のチャンネルを購読解除する方法」については情報があまり見当たらなかったため、試行錯誤した結果について投稿させていただきます。

投稿時のバージョンは**Rails 5.2.0**です。

ActionCableの動作確認用に作成したアプリのソースコードはこちらに配置してあります。
[NaokiIshimura/action\-cable\-example](https://github.com/NaokiIshimura/action-cable-example)

# 前提条件

## 想定サービス
- 複数のチャットルームが作成できるチャットサービス
- チャットルームにアクセスするとチャットページが開く
- チャットページを開いている間はリアルタイムにチャットの内容が更新される

## チャンネルの購読
- クライアント(JavaScript)側では以下のコードでチャンネルを購読する

```js:app/assets/javascripts/channels/chat.js
var channel = "ChatChannel"; // チャンネル名
var room    = "1";           // チャットルームID

App.cable.subscriptions.create({channel: channel, room: room}, {
    received: function (data) {
      //...
    },
    //...
});
```

# デバッグ用

デバッグ用途などで購読中のチャンネル情報をログ出力したいときがあるので作成しました。

## 購読中のチャンネル情報をログ出力する

```js:app/assets/javascripts/channels/chat.js
/**
 * 【デバッグ用】購読中のチャンネル情報をログ出力する
 */
function logOutputChannel() {
    console.log('++++++++++debug++++++++++');
    console.log('Subscribed channel');

    // 購読中のチャンネル数
    var count = App.cable.subscriptions['subscriptions'].length;
    console.log('> count:' + count);

    // 購読中のチャンネル情報
    var subscriptions = App.cable.subscriptions['subscriptions'];
    subscriptions.forEach(function (subscription) {
        var identifier = subscription.identifier;

        obj = JSON.parse(identifier);
        //=> {channel: "MessagesChannel", room: "1"}

        console.log('> channnel:' + obj.channel + ',room:' + obj.room);

    });
    console.log('+++++++++++++++++++++++++');
}
```

# 購読数を取得

## すべてのチャンネルの購読数を取得する

（特に用途はなかったけど、）購読中のチャンネル数を取得する時に利用します。

```js:app/assets/javascripts/channels/chat.js
/**
 * すべてのチャンネルの購読数を取得する
 * @returns {number} 購読数
 */
function countAllChannel() {
    console.log('countAllChannel');

    var count = App.cable.subscriptions['subscriptions'].length;
    console.log('> count:' + count);

    return count;

}
```

## 指定したチャンネルの購読数を取得する

同じチャンネルを２重に購読しないようにする時などに利用します。

```js:app/assets/javascripts/channels/chat.js
/**
 * 指定したチャンネルの購読数を取得する
 * @param channel    チャンネル名
 * @param room       チャットルームID
 * @returns {number} 購読数
 */
function countSpecifieChannel(channel, room) {
    console.log('countSpecifieChannel channel:' + channel + ',room:' + room);

    var i = 0;

    var subscriptions = App.cable.subscriptions['subscriptions'];
    subscriptions.forEach(function (subscription) {
        var identifier = subscription.identifier;

        obj = JSON.parse(identifier);

        if (channel == obj.channel && room == obj.room) {
            i += 1;
        }

    });

    console.log('> count:' + i)
    return i;

}
```

# 購読解除

クライアント(JavaScript)側では購読中のチャンネル情報は`App.cable.subscriptions['subscriptions']`に配列で格納されるため、この配列内の情報(identifier)を参考に購読解除(remove)を実施するようにしました。

## すべてのチャンネルの購読を解除する

ログアウトしたときなど、すべてのチャンネルの購読を解除したい時に利用します.


```js:app/assets/javascripts/channels/chat.js
/**
 * すべてのチャンネルの購読を解除する
 */
function removeAllChannel() {
    console.log('removeAllChannel');

    var subscriptions = App.cable.subscriptions['subscriptions'];

    subscriptions.forEach(function (subscription) {
        App.cable.subscriptions.remove(subscription);
    });

}
```

## 指定したチャンネルの購読を解除する

チャットルームを削除した時など、不要になったチャンネルを購読解除したい時に利用します。

```js:app/assets/javascripts/channels/chat.js
/**
 * 指定したチャンネルの購読を解除する
 * @param {string} channel チャンネル名
 * @param {string} room    チャットルームID
 */
function removeSpecifiedChannel(channel, room) {
    console.log('removeSpecifiedChannel channel:' + channel + ',room:' + room);

    var subscriptions = App.cable.subscriptions['subscriptions'];

    subscriptions.forEach(function (subscription) {
        var identifier = subscription.identifier;

        obj = JSON.parse(identifier);

        if (channel == obj.channel && room == obj.room) {
            App.cable.subscriptions.remove(subscription);
        }

    });
}
```

## 指定したチャンネル以外の購読を解除する

WebViewなど、常に１つのウィンドウでしかチャットアプリを閲覧しない場合に、閲覧してないチャットのチャンネルを購読解除するために利用します。

```js:app/assets/javascripts/channels/chat.js
/**
 * 指定したチャンネル以外の購読を解除する
 * @param {string} channel チャンネル名
 * @param {string} room    チャットルームID
 */
function removeUnspecifiedChannel(channel, room) {
    console.log('removeUnspecifiedChannel channel:' + channel + ',room:' + room);

    var subscriptions = App.cable.subscriptions['subscriptions'];
    subscriptions.forEach(function (subscription) {
        var identifier = subscription.identifier;

        obj = JSON.parse(identifier);

        if (channel != obj.channel || room != obj.room) {
            App.cable.subscriptions.remove(subscription);
        }

    });
}
```

# 補足

複数のサイトで購読解除について以下のようなコードが見当たるのですが、購読解除(remove)しても`App.room`はnullになるわけではないため利用は避けました。

```js
// 購読
App.room = App.cable.subscriptions.create(...)
// 購読解除
if (App.room) App.cable.subscriptions.remove(App.room);
```

# 参考

- [Action Cable の概要 \| Rails ガイド](https://railsguides.jp/action_cable_overview.html)
- [javascript \- How do you remove an actioncable channel subscription in Rails 5 with App\.cable\.subscriptions\.remove? \- Stack Overflow](https://stackoverflow.com/questions/36964152/how-do-you-remove-an-actioncable-channel-subscription-in-rails-5-with-app-cable?rq=1)