0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

第31章|今さら学ぶ「Action Cable(WebSocket)」

0
Last updated at Posted at 2026-03-03

第31章|今さら学ぶ「Action Cable(WebSocket)」

📚 シリーズ目次はこちら → 「今さら学ぶ」シリーズ — はじめに
🗺️ KnowledgeNoteの設計を確認 → 設計マップ

この章でわかること

  • WebSocketとは — 電話のようにつなぎっぱなしの通信
  • HTTPとの違い — 毎回かけ直す電話 vs つなぎっぱなしの電話
  • Action Cableの仕組み — RailsでWebSocketを使うためのフレームワーク
  • チャンネル・サブスクリプション・ブロードキャストの役割
  • Solid Cable(Rails 8.0 標準)— Redis不要のWebSocket
  • KnowledgeNoteでの例:リアルタイム通知

🏠 たとえ話で掴む「WebSocket」

通常のHTTP通信は 毎回かけ直す電話 です。「新しい通知ある?」→「ないよ」→ 切る。5秒後にまた「新しい通知ある?」→「ないよ」→ 切る。何もなくても電話をかけ続けるので非効率になります。

WebSocketつなぎっぱなしの電話 です。一度つないだら切らずに、サーバ側から「新しい通知が来たよ!」と即座に伝えられます。

HTTP WebSocket
毎回電話をかけ直す つなぎっぱなし
クライアントから聞きに行く サーバから教えてくれる
リクエスト→レスポンスの一方通行 双方向通信
ページ表示・フォーム送信に適する チャット・通知・リアルタイム更新に適する

WebSocketとは何か — 技術的な定義

HTTPは ステートレス なプロトコルです(→ 第6章)。クライアントがリクエストを送り、サーバがレスポンスを返したら、その接続は終わります。サーバから能動的にデータを送る手段がありません。

これでは「新しいコメントがついた瞬間にページを更新する」「チャットメッセージをリアルタイムで表示する」といったことができません。クライアントが定期的にサーバに問い合わせる ポーリング という方法もありますが、変化がないときも問い合わせ続けるので無駄が多くなります。

WebSocket は、この問題を解決するための通信プロトコルです。最初のHTTPリクエスト( ハンドシェイク)で「これからWebSocketで話そう」と合意した後、HTTPの接続を WebSocket接続にアップグレード します。以降は接続が維持され、サーバとクライアントの 双方向通信 が可能になります。

【HTTPのポーリング(従来のやり方)】
クライアント → サーバ  「新着ある?」   → 「ない」
クライアント → サーバ  「新着ある?」   → 「ない」
クライアント → サーバ  「新着ある?」   → 「1件あるよ」
(変化がなくても毎回リクエストが飛ぶ)

【WebSocket】
クライアント → サーバ  「WebSocketで接続したい」(ハンドシェイク)
クライアント ⇄ サーバ  (接続が維持される)
           サーバ → クライアント  「新着が来たよ!」(必要なときだけ送信)

URLスキームの違い

WebSocketの接続先URLは、HTTPとは異なるスキームを使います。

プロトコル スキーム 用途
HTTP http:// / https:// 通常のWebページ
WebSocket ws:// / wss:// WebSocket通信(sはSSL暗号化)

いつWebSocketを使うか

すべての通信をWebSocketにする必要はありません。使い分けの判断基準は「サーバからのプッシュが必要かどうか」です。

方式 仕組み 適する場面
通常のHTTP クライアントがリクエスト→レスポンス ページ表示、フォーム送信、API呼び出し
ポーリング 一定間隔でHTTPリクエストを繰り返す 更新頻度が低い場合(数分〜数十分に1回)
WebSocket 接続を維持して双方向通信 チャット、通知、リアルタイム共同編集

KnowledgeNoteのようなアプリでは、記事の閲覧やフォーム送信は通常のHTTPで十分です。リアルタイム通知の部分だけWebSocketを使います。


📡 Action Cable — RailsのWebSocketフレームワーク

Action Cable は、RailsでWebSocketを扱うための標準フレームワークです。WebSocketの接続管理・認証・メッセージの振り分けを、Railsの作法(チャンネルという概念)に沿って実装できます。

基本概念

サーバ(放送局)                 クライアント(視聴者)
┌──────────────┐              ┌──────────────────┐
│  Connection  │              │                  │
│ (認証・接続管理) │              │                  │
│              │              │                  │
│  Channel     │   WebSocket  │  subscription    │
│ (通知チャンネル) │ ◄──────────► │ (チャンネル登録)    │
│              │   双方向通信   │                  │
└──────────────┘              └──────────────────┘
用語 意味 たとえ
Connection WebSocket接続の入り口。認証を担当 放送局の受付(会員証を確認)
Channel サーバ側の通信窓口。用途ごとに分ける テレビのチャンネル(NHK、日テレ等)
Subscription クライアントがチャンネルに接続すること チャンネル登録
Broadcast サーバから登録者全員にデータを送ること 放送
Stream 誰にどのデータを送るかの振り分け 「user_1 宛の通知」のような個別配信

Connection — 接続時の認証

WebSocket接続が確立されるとき、最初にApplicationCable::Connectionが呼ばれます。ここで「誰が接続してきたか」を特定します。

# app/channels/application_cable/connection.rb
module ApplicationCable
  class Connection < ActionCable::Connection::Base
    identified_by :current_user

    def connect
      self.current_user = find_verified_user
    end

    private

    # Devise の Warden からログイン中のユーザーを取得する
    def find_verified_user
      env["warden"]&.user || reject_unauthorized_connection
      # → Warden(Devise)が認証情報を保持しているため、セッション済みのユーザーをそのまま取得できる
      # → 認証できなければ接続を拒否
    end
  end
end

identified_by :current_user を宣言すると、以降のチャンネル内で current_user が使えるようになります。HTTPのコントローラでいう current_user と同じ役割です。

💡 認証方式による違い

  • Devise を使っている場合 (KnowledgeNote): env["warden"]&.user でログイン済みユーザーを取得できます。
  • Rails 8 組み込み認証を使っている場合: Session.find_by(id: cookies.signed[:session_token])&.user のように、セッションテーブル経由で取得します(rails generate authentication で生成される Session モデルを使用)。
  • cookies.encrypted[:user_id] はどちらの方式にも標準では対応していないため、Auth方式に合わせた方法を選んでください。

🛠️ KnowledgeNoteでの具体例:リアルタイム通知

「田中さんが記事を投稿したら、フォロワーの鈴木さんの画面に即座に通知バッジが表示される」——この流れをAction Cableで実装します。

1. チャンネルの作成

# app/channels/notification_channel.rb
class NotificationChannel < ApplicationCable::Channel
  def subscribed
    # ログインユーザー専用のストリームに接続
    # → current_user は Connection で設定したもの
    stream_for current_user
  end

  def unsubscribed
    # 接続が切れたときのクリーンアップ処理(必要に応じて)
  end
end

stream_for current_user は「このユーザー専用の配信チャンネル」を作ります。田中さん宛の通知は田中さんにだけ、鈴木さん宛は鈴木さんにだけ届きます。

2. サーバからブロードキャスト

# app/jobs/notification_job.rb
# 記事の公開時にフォロワーへ通知を送るジョブ(→ [第29章](https://qiita.com/harapeco-mgn/items/420a7d80595504b0444d))
class NotificationJob < ApplicationJob
  queue_as :default

  def perform(article)
    article.user.followers.find_each do |follower|
      # 通知レコードを作成
      notification = Notification.create!(
        user: follower,
        notifiable: article,
        action_type: "published"
      )

      # WebSocket経由でリアルタイム送信
      NotificationChannel.broadcast_to(
        follower,  # 送信先ユーザー(stream_for の対象)
        {
          count: follower.notifications.unread.count,
          # ↑ unread スコープは[第16章](https://qiita.com/harapeco-mgn/items/49f2667692e4e109a0c5)で定義済み
          message: "#{article.user.name}さんが新しい記事を投稿しました"
        }
      )
    end
  end
end
# コントローラからジョブを呼び出す
# app/controllers/articles_controller.rb
def publish
  @article.published!
  NotificationJob.perform_later(@article)
  # → ジョブはバックグラウンドで実行される(→ [第29章](https://qiita.com/harapeco-mgn/items/420a7d80595504b0444d))
  redirect_to @article, notice: t("articles.publish.success")
end

3. クライアント側(JavaScript)

// app/javascript/channels/notification_channel.js
import consumer from "channels/consumer"

// NotificationChannel に接続(サブスクリプション)
consumer.subscriptions.create("NotificationChannel", {
  // サーバからデータが届いたときのコールバック
  received(data) {
    const badge = document.querySelector("[data-notification-badge]")
    if (badge) {
      badge.textContent = data.count
      badge.classList.remove("hidden")
    }
  }
})
<%# app/views/layouts/_header.html.erb %>
<%# 通知バッジ(未読数を表示) %>
<span
  data-notification-badge
  class="<%= 'hidden' if current_user.notifications.unread.count.zero? %>"
>
  <%= current_user.notifications.unread.count %>
</span>

4. 動作の全体フロー

① 田中さんが記事を公開する
    ↓
② コントローラが NotificationJob.perform_later を呼ぶ
    ↓
③ ジョブがバックグラウンドで実行(Solid Queue → [第29章](https://qiita.com/harapeco-mgn/items/420a7d80595504b0444d))
    ↓
④ フォロワー(鈴木さん)に Notification レコードを作成
    ↓
⑤ NotificationChannel.broadcast_to で鈴木さん宛にJSONを送信
    ↓
⑥ 鈴木さんのブラウザが WebSocket 経由で受信
    ↓
⑦ received(data) が実行され、通知バッジの数字がリアルタイムで更新(🔔 3)

🔌 Solid Cable(Rails 8.0 標準)

Action Cableは、WebSocketのメッセージを中継するために Pub/Sub(パブリッシュ/サブスクライブ)バックエンド を必要とします。

従来はこのバックエンドに Redis を使うのが一般的でした。Redisはインメモリのデータストアで高速ですが、別途サーバの用意と管理が必要でした。

Rails 8.0 では Solid Cable がデフォルトのバックエンドです。Pub/SubをDBで管理するため、Redisサーバが不要になります。

# config/cable.yml

# 開発環境
development:
  adapter: async
  # → 開発中はサーバプロセス内で完結する簡易アダプタ

# 本番環境
production:
  adapter: solid_cable
  # → Redis不要。DBテーブルでPub/Subを管理
  silence_polling: true
  # → ポーリングのログを抑制
  polling_interval: 0.1.seconds
  # → メッセージの確認間隔

従来のRedis方式との比較

従来(Redis) Solid Cable(Rails 8.0)
追加ミドルウェア Redis サーバが必要 不要(DBだけで動く)
設定 redis gem + Redis接続設定 デフォルトで有効
パフォーマンス 非常に高速(インメモリ) DBアクセスが入るぶん若干遅い
適するアプリ 大規模チャットアプリ(数千同時接続) 通知・軽いリアルタイム更新
インフラコスト Redis サーバの費用が追加 DB のみで済む

KnowledgeNoteのような「通知バッジのリアルタイム更新」程度であれば、Solid Cableで十分です。秒間数千メッセージを処理するチャットアプリのような場合は、Redis方式を検討します。

💡 Rails 7 との違い
Rails 7 まではAction CableのデフォルトバックエンドがRedisでした。
Rails 8.0 では rails new した時点で Solid Cable が設定されており、
Redisのインストールなしに WebSocket が動作します。


💼 面接で聞かれたら?

Q:WebSocketとHTTPの違いを説明してください。

「HTTPはクライアントがリクエストを送って初めてサーバが応答する一方通行の通信です。WebSocketは最初のHTTPハンドシェイクの後に接続をアップグレードして維持し、サーバからクライアントに任意のタイミングでデータを送れる双方向通信です。リアルタイム通知やチャット機能に適しています。RailsではAction Cableで実装し、Rails 8.0ではSolid CableによりRedis不要で利用できます。」

深掘りされたら:

  • 「Action Cableの構成要素は?」→ Connection(認証)、Channel(通信窓口)、Subscription(クライアントの接続)の3層構造。Connectionで current_user を特定し、Channelで stream_for を使って誰にどのデータを送るかを振り分ける。
  • 「ポーリングとの違いは?」→ ポーリングはクライアントが一定間隔でHTTPリクエストを繰り返す方式。変化がなくてもリクエストが飛ぶので無駄が多い。WebSocketは接続を維持して必要なときだけデータを送るので効率的。
  • 「Solid Cableとは?」→ Rails 8.0標準のAction Cableバックエンド。従来はRedisが必要だったPub/Sub機能をDBで実現し、追加のミドルウェアなしでWebSocketが動く。通知レベルの用途には十分な性能。

🔗 もっと深く知りたい人へ(1次情報リンク)


まとめ

  • ✅ WebSocketは「つなぎっぱなしの電話」。HTTPのハンドシェイク後に接続をアップグレードし、双方向通信を実現する
  • ✅ Action CableはRails標準のWebSocketフレームワーク。Connection(認証)→ Channel(通信窓口)→ Stream(振り分け)の3層構造
  • stream_for current_user で特定ユーザー宛の配信チャンネルを作り、broadcast_to でデータを送信
  • ✅ クライアント側は consumer.subscriptions.create でチャンネルに接続し、received(data) でデータを受け取る
  • ✅ Rails 8.0のSolid CableでRedis不要。通知レベルの用途にはDBベースのPub/Subで十分

📚 シリーズ目次:「今さら学ぶ」シリーズ — はじめに

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?