Action CableとWebSocket完全ガイド:2026年Ruby on Rails技術面接対策

Ruby on RailsのAction CableとWebSocketの深掘り解説。コネクション、チャンネル、ブロードキャスト、Rails 8のSolid Cable、Redisによるスケーリング、テスト手法まで面接で問われるポイントをコード例とともに網羅します。

Action CableとRuby on RailsのWebSocket深掘り解説イラスト

Action Cableは、WebSocketサポートをRailsフレームワークに直接統合する仕組みであり、チャット、通知、ライブダッシュボードなどのリアルタイム機能を外部依存なしに実現できます。2026年の技術面接では、Action Cableの内部構造――コネクションのライフサイクルから本番環境でのスケーリングまで――を深く理解しているかどうかが、候補者の実力を見極める重要な判断基準となっています。

面接での重要ポイント

Action Cableは、コントローラやモデルと同じRailsの規約に従ってWebSocketを統合します。Rails 8で導入されたSolid Cableは、データベースベースのアダプターにより、pub/subメッセージングにおけるRedis依存を排除します。

WebSocketプロトコルの基礎とRailsにおける位置づけ

WebSocketプロトコル(RFC 6455)は、単一のTCP接続上で永続的な全二重通信チャンネルを確立します。HTTPのリクエスト・レスポンスサイクルとは異なり、WebSocketは接続を維持し続け、クライアントとサーバーの双方がいつでもメッセージを送信できます。

Action Cableは、このプロトコルをRailsに馴染みやすい抽象化で包み込みます。サーバーは/cableでWebSocketのアップグレードを処理し、コネクション認証を管理し、チャンネルを通じてメッセージをルーティングします。クライアントサイドのJavaScriptライブラリが、サブスクリプションの作成と管理を自動的に行います。

一般的なHTTPリクエストはミリ秒単位で完了して閉じられますが、WebSocket接続は数分、数時間、あるいはセッション全体にわたって維持されます。この根本的な違いが、Action Cableにおけるすべてのアーキテクチャ上の判断を左右します。

Action Cableアーキテクチャ:コネクション、チャンネル、サブスクリプション

Action Cableは、3つのコア抽象化によるレイヤードアーキテクチャに従います。コネクションは認証を処理し、チャンネルはビジネスロジックをカプセル化し、サブスクリプションはコンシューマーと特定のチャンネルを結びつけます。

ruby
# 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

    def find_verified_user
      # Cookies are available in WebSocket handshake
      if verified_user = User.find_by(id: cookies.encrypted[:user_id])
        verified_user
      else
        reject_unauthorized_connection
      end
    end
  end
end

コネクションクラスは、WebSocketハンドシェイクごとに1回実行されます。認証はここで行われ、個々のチャンネルでは行いません。identified_by宣言はユーザーIDを登録し、その接続上のすべてのチャンネルサブスクリプションで利用可能にします。

ruby
# app/channels/chat_channel.rb
class ChatChannel < ApplicationCable::Channel
  def subscribed
    room = ChatRoom.find(params[:room_id])
    # Authorization: verify user belongs to this room
    if room.members.include?(current_user)
      stream_for room
    else
      reject
    end
  end

  def receive(data)
    message = ChatMessage.create!(
      room_id: params[:room_id],
      user: current_user,
      body: data["body"]
    )
    ChatChannel.broadcast_to(
      message.room,
      { id: message.id, body: message.body, user: current_user.name }
    )
  end

  def unsubscribed
    # Cleanup: mark user as offline
    AppearanceTracker.mark_offline(current_user)
  end
end

チャンネルは3つのライフサイクルコールバックを定義します:subscribedreceiveunsubscribedstream_forメソッドは、サブスクリプションを特定のモデルインスタンスにバインドし、名前空間付きのストリームを作成します。そのストリームへのブロードキャストは、接続しているすべてのサブスクライバーにメッセージを配信します。

クライアントサイドのJavaScriptサブスクリプション

クライアントサイドのコンシューマーは、Action Cableサーバーに接続し、サブスクリプションを管理します。各サブスクリプションは、サーバーサイドのチャンネルに対応します。

app/javascript/channels/chat_channel.jsjavascript
import consumer from "./consumer"

const chatChannel = consumer.subscriptions.create(
  { channel: "ChatChannel", room_id: roomId },
  {
    connected() {
      // Called when the subscription is ready
      console.log("Connected to chat room", roomId)
    },

    disconnected() {
      // Called when the subscription is closed
      console.log("Disconnected from chat room")
    },

    received(data) {
      // Called when data is broadcast to the channel
      const messageList = document.getElementById("messages")
      messageList.insertAdjacentHTML("beforeend",
        `<div class="message"><strong>${data.user}</strong>: ${data.body}</div>`
      )
    },

    sendMessage(body) {
      // Calls ChatChannel#receive on the server
      this.perform("receive", { body: body })
    }
  }
)

receivedコールバックは、サーバーがサブスクライブ中のストリームにブロードキャストするたびに発火します。performメソッドは、クライアントからサーバーへデータを送信し、対応するチャンネルメソッドを呼び出します。

Ruby on Railsの面接対策はできていますか?

インタラクティブなシミュレーター、flashcards、技術テストで練習しましょう。

Rails 8のSolid Cable:データベースベースのPub/Sub

Rails 8では、Solid QueueやSolid Cacheと並ぶ「Solid三部作」の一つとして、Solid Cableが導入されました。Solid Cableは、メッセージをデータベーステーブルに保存することで、pub/subバックエンドとしてのRedisを置き換えます。

yaml
# config/cable.yml (Rails 8 default)
development:
  adapter: solid_cable

production:
  adapter: solid_cable
  connects_to:
    database:
      writing: cable
  polling_interval: 0.1.seconds
  message_retention: 1.day

Solid Cableは、各ブロードキャストメッセージをデータベーステーブルに書き込み、設定可能な間隔で新しいメッセージをポーリングする仕組みで動作します。デフォルトのポーリング間隔100msは、ほとんどのアプリケーションでほぼリアルタイムの配信を実現します。

トレードオフは明確です。Solid Cableはインフラストラクチャ依存(Redis)を排除する代わりに、若干高いレイテンシーとデータベース負荷が発生します。すでにPostgreSQLやMySQLを運用しているアプリケーションでは、このトレードオフが適切な選択となることが多いです。1秒あたり数千メッセージの高頻度ブロードキャストでは、依然としてRedisの方が適しています。

ruby
# config/database.yml (Rails 8 multi-database)
production:
  primary:
    <<: *default
    database: myapp_production
  cable:
    <<: *default
    database: myapp_cable_production
    migrations_paths: db/cable_migrate

Solid Cableは専用データベースを使用して、プライマリデータベースとの競合を回避します。この分離により、メッセージポーリングがアプリケーションクエリに干渉することを防ぎます。

ブロードキャストパターンとサーバーサイドトリガー

モデル、ジョブ、コントローラからのブロードキャストは、本番Railsアプリケーションで最も一般的な3つのパターンをカバーします。

ruby
# Broadcasting from a model callback
class Notification < ApplicationRecord
  belongs_to :user
  after_create_commit :broadcast_to_user

  private

  def broadcast_to_user
    ActionCable.server.broadcast(
      "notifications_#{user_id}",
      { id: id, title: title, read: false }
    )
  end
end

# Broadcasting from a background job
class DashboardUpdateJob < ApplicationJob
  queue_as :default

  def perform(dashboard_id)
    dashboard = Dashboard.find(dashboard_id)
    stats = dashboard.compute_stats
    ActionCable.server.broadcast(
      "dashboard_#{dashboard_id}",
      { stats: stats, updated_at: Time.current.iso8601 }
    )
  end
end

モデルコールバックは、レコード変更によってトリガーされるシンプルな通知に適しています。バックグラウンドジョブは、ダッシュボード統計の計算やデータ集約など、より重い処理をリクエストサイクルをブロックせずに処理します。ブロードキャスト自体は常に軽量で、ペイロードをシリアライズしてアダプターにパブリッシュするだけです。

Turbo StreamsとAction Cableの統合

Rails 8のHotwireは、Action CableをTurbo Streamsのトランスポートレイヤーとして使用し、カスタムJavaScriptを書くことなくリアルタイムのDOM更新を実現します。

ruby
# app/models/message.rb
class Message < ApplicationRecord
  belongs_to :chat_room
  # Automatically broadcasts append to subscribers
  broadcasts_to :chat_room
end

# app/views/chat_rooms/show.html.erb
<%= turbo_stream_from @chat_room %>
<div id="messages">
  <%= render @chat_room.messages %>
</div>

broadcasts_toマクロは、after_create、after_update、after_destroyのコールバックを生成し、Turbo StreamフラグメントをAction Cable経由でブロードキャストします。クライアント側のturbo_stream_fromヘルパーは、対応するストリームをサブスクライブします。新しいメッセージを作成すると、レンダリングされたパーシャルが接続中のすべてのクライアントのDOMに自動的に追加されます。

このパターンにより、リアルタイム機能はモデル宣言とビューヘルパーだけに集約されます。カスタムチャンネルもJavaScriptハンドラーも手動のDOM操作も不要です。

本番環境でのAction Cableスケーリング

本番デプロイメントでは、アダプターの選択、接続数の上限、水平スケーリングについて慎重に検討する必要があります。

ruby
# config/cable.yml — Redis adapter for high-scale production
production:
  adapter: redis
  url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %>
  channel_prefix: myapp_production

Redisはpub/subの基盤として機能し、あるRailsプロセスでパブリッシュされたメッセージが、他の任意のプロセスに接続しているサブスクライバーに確実に届くようにします。Redis(またはSolid Cable)がない場合、asyncアダプターはブロードキャストを単一プロセスに限定するため、マルチプロセスやマルチサーバーのデプロイメントでは使用できません。

接続上限はサーバーに依存します。PumaとAction Cableは、HTTPリクエストと同じプロセスでWebSocket接続を処理します。各WebSocketは永続的な接続を保持し、スレッド(またはRuby 3.xのファイバーベーススケジューラーではファイバー)を消費します。5スレッド・4ワーカーの一般的なPuma構成では、飽和状態になるまでに約200の同時WebSocket接続を処理できます。

数千の同時接続を必要とするアプリケーションでは、AnyCableがRubyのWebSocketサーバーをGoベースのサーバーに置き換え、プロトコルレベルで接続を処理しながら、gRPCを通じてチャンネルロジックをRailsにルーティングします。このアーキテクチャは、ノードあたり10,000以上の同時接続をサポートします。

面接での洞察

面接官はAction Cableのスケーリング限界について頻繁に質問します。重要な回答のポイント:Action Cable自体がボトルネックではなく、WebSocket接続を処理するRubyプロセスがボトルネックになります。AnyCableは、接続管理をGoにオフロードしながらチャンネルロジックをRubyに維持することで、この問題を解決します。

Action Cableチャンネルのテスト

Railsは、チャンネルのユニットテスト用にActionCable::Channel::TestCaseを、コネクション認証のテスト用にActionCable::Connection::TestCaseを提供しています。

ruby
# test/channels/chat_channel_test.rb
require "test_helper"

class ChatChannelTest < ActionCable::Channel::TestCase
  test "subscribes to a valid room" do
    room = chat_rooms(:general)
    user = users(:alice)
    stub_connection current_user: user

    subscribe room_id: room.id

    assert subscription.confirmed?
    assert_has_stream_for room
  end

  test "rejects subscription for unauthorized user" do
    room = chat_rooms(:private)
    user = users(:outsider)
    stub_connection current_user: user

    subscribe room_id: room.id

    assert subscription.rejected?
  end

  test "broadcasts messages to room subscribers" do
    room = chat_rooms(:general)
    stub_connection current_user: users(:alice)
    subscribe room_id: room.id

    assert_broadcast_on(room, hash_including(body: "Hello")) do
      perform :receive, body: "Hello"
    end
  end
end

stub_connectionメソッドは、実際のWebSocketを使わずにコネクションのコンテキストを設定します。assert_has_stream_forはストリームバインディングを検証します。assert_broadcast_onはブロック内のブロードキャストをキャプチャし、正しいペイロードが正しいストリームに到達することを確認します。

Action Cableに関する頻出面接質問

Action Cableに関する技術面接では、プロトコルの理解、アーキテクチャの判断、本番環境での懸念事項が典型的に問われます。

Action Cableはどのようにコネクションを認証するか? 認証は、WebSocketハンドシェイク中にApplicationCable::Connection#connectで行われます。HTTPセッションのCookieはこの時点で利用可能です。トークンベースの認証では、WebSocket URLのクエリパラメータとしてトークンを渡し、connectで検証します。

WebSocket接続が切断された場合はどうなるか? クライアントライブラリは、指数バックオフ付きの自動再接続を実装しています。サーバー側では、その接続がサブスクライブしていた各チャンネルに対してunsubscribedが発火します。ステートフルなクリーンアップ(ユーザーのオフラインマーク、ロックの解放)はunsubscribedで行います。

Solid CableとRedisのどちらを使うべきか? Solid Cableは、中程度のリアルタイムニーズ(毎秒100メッセージ以下)を持ち、Redisインフラを避けたいアプリケーションに適しています。高スループットシナリオや10ms未満の配信レイテンシーが求められる場合には、依然としてRedisが必要です。

よくある間違い

個々のチャンネルではなく、コネクションクラスに認可ロジックを配置してしまうケースがあります。コネクションは認証(このユーザーは誰か?)を担当し、チャンネルは認可(このユーザーはこのルームにアクセスできるか?)を担当します。これらの責務を混同すると、セキュリティ上の隙が生まれます。

まとめ

  • Action Cableは、コネクション・チャンネル・サブスクリプションをコア抽象化として、WebSocketプロトコルをRailsの規約に統合する
  • 認証はApplicationCable::Connectionで行い、認可は個々のチャンネルのsubscribedコールバックで行う
  • Rails 8のSolid Cableは、データベースをpub/subに使用してRedis依存を排除する。中程度のスループットのアプリケーションに適している
  • Turbo Streamsは、Action Cableを活用して最小限のコードで自動的なリアルタイムDOM更新を実現する
  • 本番環境のスケーリングには、マルチプロセスデプロイメントにおいてRedisまたはSolid Cableが必要。AnyCableは、WebSocket管理をGoにオフロードすることで10,000以上の接続を処理する
  • チャンネルのテストにはstub_connectionassert_has_stream_forassert_broadcast_onを使用する。実際のWebSocket接続は不要
  • これらの概念を定着させるために、Action Cable & WebSocketの面接問題で練習することが効果的です

今すぐ練習を始めましょう!

面接シミュレーターと技術テストで知識をテストしましょう。

タグ

#ruby on rails
#action cable
#websockets
#real-time
#solid cable
#rails 8

共有

関連記事