Action CableでWebSocketを実現するときにconfig.action_cable.urlは必ずしも設定しなくて良い

## Rails で Action Cable の `config.action_cable.url` を設定しなくても動いたのだけども…

Rails8 から Solid Cable が標準搭載されるようになりましたね。

それによって、Redisを使用しなくてもリアルタイム機能の実装がグッと楽になりました！ これによって WebSocket を使ったリアルタイム性のあるユーザー体験を少し楽に提供できます。

ところで、Action Cable の設定を見ていると `config/environments/*.rb` ファイルに `config.action_cable.url` という項目があります。これは WebSocket サーバーの URL を設定するもののようですが...

「あれ？これ設定しなくても Action Cable 動いてるぞ？」
「仮で入れていたURLなのに Action Cable 動いてるぞ？」

なんて思ったことはありませんか？（あまりないかもしれません）

実は、クライアント側（Vue, React など）で WebSocket の接続先 URL をちゃんと指定していれば、`config.action_cable.url` は多くの場合、設定不要です。

この記事では、その理由を Rails の情報も交えながら、少し掘り下げて解説していきます。

## そもそも `config.action_cable.url` さんのお仕事は？

まず、この設定項目が本来何のために用意されているのかを見てみましょう。Railsガイドによると、モノリシックな実装を行う際にフロントエンドへaction_cableの値を伝えるための`action_cable_meta_tag` で使われることが説明されています。

https://railsguides.jp/configuring.html#config-action-cable-url

`action_cable_meta_tag` は、クライアントサイド JavaScript が接続先を知るための `<meta>` タグを HTML に出力するビューヘルパーです。

```html
<meta name="action-cable-url" content="ws://localhost:3000/cable">
```

では、このヘルパーは `config.action_cable.url` をどのように使うのでしょうか？

実際の Rails のソースコード (`ActionCable::Helpers::ActionCableHelper`) を確認すると、このヘルパーは以下のように動作します。

https://api.rubyonrails.org/v8.0/classes/ActionCable/Helpers/ActionCableHelper.html

1.  まずアプリケーション設定の `config.action_cable.url` を参照します
2.  もし `config.action_cable.url` が設定されていなければ、`config.action_cable.mount_path` (デフォルトは `/cable`) に基づいて URL を自動生成します

https://github.com/rails/rails/blob/3235827585d87661942c91bc81f64f56d710f0b2/actioncable/lib/action_cable/helpers/action_cable_helper.rb#L36

```rb:rails/actioncable/lib/action_cable/helpers/action_cable_helper.rb
def action_cable_meta_tag
  tag "meta", name: "action-cable-url", content: (
    ActionCable.server.config.url ||
    ActionCable.server.config.mount_path ||
    raise("No Action Cable URL configured -- please configure this at config.action_cable.url")
   )
end
```

このように、`config.action_cable.url` は、主に `action_cable_meta_tag` ヘルパーが URL を決定する際に（優先的に）参照する設定、という位置づけになります。

Frontend も Rails で完結させるアプリケーション構成（いわゆるモノリス構成）では、ページに埋め込まれた JavaScript がこのメタタグを読み取って、「あ、WebSocket サーバーはここね！」と接続先を知る、という使い方をしていました。このメタタグの `content` に `config.action_cable.url` の値が使われるわけです（設定されていなければ、デフォルトの `/cable` パスが使われます）。

この時点で既にあれ？API modeの時はじゃあ使わなくていいのでは？となると思いますが、その通りです。

## なぜ設定なしでも動くのか？ API mode × モダンフロントエンドのような場合

では、なぜ `config.action_cable.url` を設定しなくても、Vue や React を使ったアプリケーションで Action Cable が動くのでしょうか？ 答えはクライアント側の実装にあります。

1. クライアントライブラリ `@rails/actioncable`

Vue や React などのフロントエンドフレームワークから Action Cable を使う場合、通常 `npm` や `yarn` で `@rails/actioncable` という JavaScript ライブラリをインストールします。

```bash
npm install @rails/actioncable
# or
yarn add @rails/actioncable
# etc...
```

2. `createConsumer()` で接続先を直接指定

このライブラリの中心的な機能の一つが `createConsumer()` 関数です。これを使って WebSocket サーバーへの接続（Consumer）を作成するのですが、この関数には接続先の WebSocket URL を直接引数として渡せます

Rails ガイドにもしっかり書かれています。

https://guides.rubyonrails.org/action_cable_overview.html#client-side-components

> ## 4.1.1. Connect Consumer
>``` js
>// app/javascript/channels/consumer.js
>// Action Cable provides the framework to deal with WebSockets in Rails.
>// You can generate new channels where WebSocket features live using the `bin/rails >generate channel` command.
>
>import { createConsumer } from "@rails/actioncable"
>
>export default createConsumer()
>```
>
>This will ready a consumer that'll connect against /cable on your server by default. >The connection won't be established until you've also specified at least one >subscription you're interested in having.
>
>The consumer can optionally take an argument that specifies the URL to connect to. >This can be a string or a function that returns a string that will be called when the >WebSocket is opened.
>
>```js
>// Specify a different URL to connect to
>createConsumer('wss://example.com/cable')
>// Or when using websockets over HTTP
>createConsumer('https://ws.example.com/cable')
>
>// Use a function to dynamically generate the URL
>createConsumer(getWebSocketURL)
>
>function getWebSocketURL() {
>  const token = localStorage.get('auth-token')
>  return `wss://example.com/cable?token=${token}`
>}

つまり、Vue や React のコードの中で、

```ts
import { createConsumer } from "@rails/actioncable"

// .env ファイルなどから URL を取得するのがベスト
const wsUrl = 'ws://localhost:3000/cable'
const consumer = createConsumer(wsUrl)

// あとは consumer を使って Channel をsubscribe...
```

のように、接続先の URL を JavaScript 側で明示的に指定してしまえば、Rails 側の `config.action_cable.url` 設定や `action_cable_meta_tag` ヘルパーは全く必要なくなるわけです。クライアント自身が接続先を知っているので、サーバーに教えてもらう必要がないんですね。

## サーバー内部のブロードキャストは URL を見ていない

「でも、サーバー側で `ActionCable.server.broadcast` するときに URL が必要じゃないの？」という疑問も湧くかもしれません。

### 答えは「通常は不要」です。

　
Rails アプリケーション内部（モデルのコールバック、コントローラーのアクション、ジョブなど）からブロードキャストを行う場合、Action Cable は `config/cable.yml` で設定された Pub/Sub アダプタ (`async`, `redis`, `postgresql`, `solid_cable` など) を介してメッセージを配信します。

今回は私がSolid Cableを使用しているので、`solid_cable`です

```yaml
# config/cable.yml (例)
development:
  adapter:  solid_cable

production:
  adapter: solid_cable
  polling_interval: 0.1.seconds
  message_retention: 1.day
```

`async` や `solid_cable` なら同じプロセスやDB内で、`redis` や `postgresql` ならRedisサーバーや、`LISTEN / NOTIFY` という非同期通知メカニズムを利用して、「このチャンネルを購読している人たちにこのデータを送って！」という指示が伝わります。この仕組みにおいて、外部からアクセス可能な WebSocket の URL (`config.action_cable.url` で設定するようなもの) は直接的には使われません。

まぁ、railsの中のことなので絶対パスがなくても大丈夫みたいな感じでしょうか

## じゃあ、`config.action_cable.url` はいつ使うの？ (補足)

この設定が完全に無意味かというと、そうでもありません。以下のような少し特殊なケースでは設定が必要になることがあります。

* Rails のビューで `action_cable_meta_tag` を使って URL をクライアントに伝えたい場合（最もベーシックな使い方）
* Rails アプリケーション外部のプロセス（例: 別のマイクロサービス、独立した Rake タスクなど）から Action Cable サーバーにブロードキャストしたい場合に、その接続先としてこの URL が必要になることがあります（ただし、このケースはあまり想像がつかないので無いかもしれません）
* Action Cable のテストで URL を明示的に設定する必要がある場合（テストは大事ですが、何かしらのAPIを叩いたら発火や、DBの登録で発火などの実装が多いと思いますので、そのAPIを叩いてテストすればいいと思います）

## まとめ

以下の条件が揃っていれば、`config.action_cable.url` を設定しなくても Action Cable は問題なく動作します。

* クライアント側 (Vue, React など) で `@rails/actioncable` を使い、`createConsumer()` で WebSocket の URL を直接指定している
* サーバーからのブロードキャストは Rails アプリケーション内部から行われている
* Rails のビューヘルパー `action_cable_meta_tag` を使っていない

調べてみると、これはどうやらバグや偶然ではなく、Rails の設計通りの挙動だったようです。

モダンな Rails API + SPA (Single Page Application) 構成では、この設定は省略できることが多いので、`config/environments/*.rb` が少しスッキリするかもしれません。

ただし、あったらエラーになるというわけでもないので、将来的に上記の「必要になるケース」が発生する可能性も考慮して、明示的に設定しておく、という判断ももちろんアリだと思います！

---

**参考:**

https://github.com/rails/rails/tree/3235827585d87661942c91bc81f64f56d710f0b2

https://guides.rubyonrails.org/action_cable_overview.html

https://www.npmjs.com/package/@rails/actioncable

---