mcpField
A
action-cable

action-cable

github.com/thinkoodle開発7190 回インストール8 週間前に更新
リポジトリ

action-cable について

Rails 開発者向けの Action Cable スキルで、WebSocket 接続、チャンネル、サブスクリプション、ブロードキャスト、認証、アダプター、テストを扱います。Turbo Streams で足りる場合と Action Cable が必要な場合を区別します。

主な機能

  1. 01WebSocket 接続とチャンネルの設定
  2. 02サブスクリプションとブロードキャストの実装
  3. 03認証と切断時のクリーンアップ
  4. 04Solid Cable・Redis などの設定
  5. 05チャンネルとブロードキャストのテスト

活用シーン

  1. 01Rails アプリへのリアルタイムチャット追加
  2. 02プレゼンスや入力中表示の実装
  3. 03ジョブの進捗やライブデータの配信
  4. 04Action Cable チャンネルのテスト作成

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/thinkoodle/rails-skills --skill action-cable

インストール後、Claude Code で/action-cableを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

references/
adapters-config.md
broadcasting.md
channels.md
client-side.md
connection-auth.md
patterns.md
testing.md

詳細

Rails Action Cable エキスパート

Action Cable を使って Rails にリアルタイムの WebSocket 機能を構築します。チャンネル、サブスクリプション、ブロードキャスト、Solid Cable に対応します。

重要な判断:Action Cable と Turbo Streams のどちらを使うか

Action Cable のコードを書く前に、必ず「Turbo Streams で解決できるか?」と確認してください。

次の場合は Turbo Streams のブロードキャストを使用してください(生の Action Cable は使用しないでください):
  • モデルの変更(作成/更新/削除)をブロードキャストして、ページのフラグメントを更新する場合
  • シンプルな「何かが変わったので、ページのこの部分を更新する」シナリオ
  • CRUD を起点とするリアルタイム更新(チャットメッセージ、コメント、通知リスト)
  • JavaScript を書かずに DOM を自動更新したい場合
# This is Turbo Streams — NOT raw Action Cable. Prefer this.
class Message < ApplicationRecord
  broadcasts_to :room  # That's it. No channel file needed.
end
次の場合は生の Action Cable を使用してください:
  • カスタムのクライアント側動作 — DOM の置き換えを超える received() コールバックのロジックが必要な場合
  • 双方向通信 — クライアントから perform()/send() を使ってサーバーへデータを送信する場合
  • プレゼンス/タイピングインジケーター — オンライン中のユーザーや入力中のユーザーを追跡する場合
  • HTML 以外のペイロード — JSON データ、座標、ゲーム状態を送信する場合
  • 進行状況の追跡 — ファイルのアップロード状況やジョブのステータスを更新する場合
  • 外部連携 — 外部の WebSocket ソースからデータを取り込む場合
  • 細かなストリーム制御 — ユーザー操作に応じて動的にサブスクライブ/サブスクライブ解除する場合

目安: モデルのコールバック後に HTML をブロードキャストして DOM 要素を置き換えたり追加したりするだけなら、Turbo Streams を使用してください。カスタムの JavaScript 処理や双方向通信が必要なら、Action Cable を使用してください。

このスキルを使用する場面

  • WebSocket 接続とチャンネルの設定
  • リアルタイム機能(チャット、通知、プレゼンス、ライブダッシュボード)の実装
  • Solid Cable、Redis、または PostgreSQL アダプターの設定
  • WebSocket 接続の認証
  • クライアント側のチャンネルサブスクリプションの記述
  • Action Cable チャンネルのテスト
  • 本番環境への Action Cable のデプロイ

手順

Step 1: 既存の設定を確認する
# Check if Action Cable is already configured
cat config/cable.yml
ls app/channels/application_cable/

# Check for existing channels
ls app/channels/
ls app/javascript/channels/ 2>/dev/null

# Check if Turbo is present (prefer Turbo Streams if so)
grep -r "turbo-rails" Gemfile
grep -r "broadcasts" app/models/

既存プロジェクトの規約に合わせてください。 アプリですでに Turbo Streams のブロードキャストを使っている場合、Turbo Streams で対応できる機能に生の Action Cable を導入しないでください。

Step 2: 接続認証を設定する

必ず接続を認証してください。省略してはいけません。

# 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
        # Cookie-based auth (most common with Devise/standard Rails auth)
        if verified_user = User.find_by(id: cookies.encrypted[:user_id])
          verified_user
        else
          reject_unauthorized_connection
        end
      end
  end
end

設定別の認証パターン:

認証方式実装
Cookie/セッション(デフォルト)cookies.encrypted[:user_id] または cookies.encrypted["_session"]["user_id"]
Deviseenv["warden"].user または cookies.signed["user.id"]
トークンベース(API)クエリパラメーターでトークンを渡します:createConsumer(\/cable?token=${token}`)`

よくある間違い: connect を空のままにしたり、reject_unauthorized_connection を呼び出さなかったりすることです。これにより、認証されていないユーザーが任意のチャンネルをサブスクライブできてしまいます。

Step 3: チャンネルを作成する

ジェネレーターを使用します:

bin/rails generate channel Chat

次のファイルが作成されます:

  • app/channels/chat_channel.rb — サーバー側のチャンネル
  • app/javascript/channels/chat_channel.js — クライアント側のサブスクリプション(importmap/jsbundling を使用する場合)
  • テストファイル

ストリームを使うチャンネル:

# app/channels/chat_channel.rb
class ChatChannel < ApplicationCable::Channel
  def subscribed
    # stream_from — string-based stream name (manual)
    stream_from "chat_#{params[:room_id]}"

    # OR stream_for — model-based stream name (preferred for model association)
    # room = Room.find(params[:room_id])
    # stream_for room
  end

  def unsubscribed
    # Cleanup when client disconnects
  end

  # Custom actions callable from client via perform()
  def speak(data)
    Message.create!(
      room_id: params[:room_id],
      user: current_user,
      body: data["body"]
    )
  end
end
Step 4: ストリームの種類を選択する

stream_from と stream_for の違い:

メソッド使用する場面ストリーム名
stream_from "chat_#{id}"文字列を手動で制御する場合、モデル以外のストリーム自分で定義します
stream_for roomモデルベースのストリームを使い、broadcast_to と組み合わせる場合GlobalID から自動生成されます

ストリームがモデルに対応する場合は stream_for を優先してください。 GlobalID を使用するため、推測されやすい ID を使う方法より安全で、broadcast_to とも自然に組み合わせられます。

# stream_for + broadcast_to (preferred for model streams)
class NotificationsChannel < ApplicationCable::Channel
  def subscribed
    stream_for current_user
  end
end

# Broadcasting from anywhere:
NotificationsChannel.broadcast_to(user, { title: "New message", body: "..." })

# stream_from + broadcast (for non-model or shared streams)
class DashboardChannel < ApplicationCable::Channel
  def subscribed
    stream_from "dashboard_#{params[:team_id]}"
  end
end

# Broadcasting from anywhere:
ActionCable.server.broadcast("dashboard_#{team.id}", { metric: "users", value: 42 })
Step 5: クライアント側のサブスクリプション
// app/javascript/channels/consumer.js
import { createConsumer } from "@rails/actioncable"
export default createConsumer()
// app/javascript/channels/chat_channel.js
import consumer from "./consumer"

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

    // Called when WebSocket closes
    disconnected() {
      console.log("Disconnected from chat")
    },

    // Called when server broadcasts data
    received(data) {
      const messagesEl = document.getElementById("messages")
      messagesEl.insertAdjacentHTML("beforeend", data.html)
    },

    // Custom method — calls server-side ChatChannel#speak
    speak(body) {
      this.perform("speak", { body: body })
    }
  }
)

主なクライアントコールバック:

  • connected() — サブスクリプションが確定し、WebSocket が開いたとき
  • disconnected() — WebSocket が閉じたとき(ネットワークの問題、サーバーの再起動)
  • received(data) — ブロードキャストを通じてサーバーがデータを送信したとき
  • rejected() — サーバーがサブスクリプションを拒否したとき
  • this.perform("action", data) — サーバー側のチャンネルメソッドを呼び出します
  • this.send(data) — 生のデータを送信します(サーバー側に receive メソッドが必要です)
ステップ 6:ブロードキャスト

モデル/コントローラー/ジョブから:

# Using broadcast (string stream name)
ActionCable.server.broadcast("chat_#{room.id}", {
  html: render_to_string(partial: "messages/message", locals: { message: message }),
  sent_by: message.user.name
})

# Using broadcast_to (model stream — pairs with stream_for)
ChatChannel.broadcast_to(room, {
  html: render_to_string(partial: "messages/message", locals: { message: message }),
  sent_by: message.user.name
})

**よくある間違い:**データをブロードキャストしすぎることです。クライアントが必要とするものだけを送信してください。関連付けを含むモデル属性全体ではなく、レンダリング済みの HTML パーシャルまたは最小限の JSON を使ってください。

重いレンダリングを行う場合は、バックグラウンドジョブからブロードキャストします。

class MessageBroadcastJob < ApplicationJob
  def perform(message)
    html = ApplicationController.renderer.render(
      partial: "messages/message",
      locals: { message: message }
    )
    ActionCable.server.broadcast("chat_#{message.room_id}", { html: html })
  end
end
ステップ 7:アダプターを設定する

Rails 8 のデフォルト:Solid Cable(データベースベース、Redis 不要)

bin/rails solid_cable:install

これにより config/cable.yml が設定され、db/cable_schema.rb が作成されます。config/database.yml を更新して、ケーブル用データベースを追加してください。

config/cable.yml — クイックセットアップ:

development:
  adapter: async

test:
  adapter: test

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

**アダプターの選択肢:**Solid Cable(デフォルト、データベースベース)、Redis(最高のスループット)、PostgreSQL(ペイロードの上限は 8KB)。アダプターの詳しい比較と設定例については、references/adapters-config.md を参照してください。

ステップ 8:本番環境の設定
# config/environments/production.rb
Rails.application.configure do
  # Mount path (default: /cable)
  config.action_cable.mount_path = "/cable"

  # Allowed origins (REQUIRED in production)
  config.action_cable.allowed_request_origins = [
    "https://yourdomain.com",
    %r{https://.*\.yourdomain\.com}
  ]

  # Worker pool (match to available DB connections)
  config.action_cable.worker_pool_size = 4

  # URL for standalone cable server (if separated)
  # config.action_cable.url = "wss://cable.yourdomain.com"
end

レイアウトにメタタグを追加します:


<head>
  <%= action_cable_meta_tag %>
</head>
ステップ 9:切断に対処する

サーバー側のクリーンアップ:

class PresenceChannel < ApplicationCable::Channel
  def subscribed
    stream_from "presence"
    current_user.update!(online: true)
  end

  def unsubscribed
    current_user.update!(online: false)
  end
end

**クライアント側の再接続は自動で行われます。**Action Cable は指数バックオフで再接続します。ただし、UI の状態には対処してください。

connected() {
  document.getElementById("status").textContent = "Connected"
},

disconnected() {
  document.getElementById("status").textContent = "Reconnecting..."
}

**よくある間違い:**プレゼンスの追跡やリソースの保持を行っているのに、unsubscribed を実装しないことです。必ずクリーンアップしてください。

ステップ 10:テスト

チャンネルのテスト(単体テスト):

class ChatChannelTest < ActionCable::Channel::TestCase
  test "subscribes to room stream" do
    subscribe room_id: rooms(:general).id
    assert subscription.confirmed?
    assert_has_stream "chat_#{rooms(:general).id}"
  end

  test "rejects without room_id" do
    subscribe room_id: nil
    assert subscription.rejected?
  end
end

接続テスト、stream_for のアサーション、ブロードキャストのアサーションパターンについては、references/testing.md を参照してください。

クイックリファレンス

ジェネレーター
bin/rails generate channel ChannelName [action1 action2]
# Example:
bin/rails generate channel Chat speak
サーバー側 API
# Stream methods (in subscribed)
stream_from "stream_name"                    # String-based stream
stream_for model_instance                    # Model-based stream (GlobalID)

# Broadcasting (from anywhere)
ActionCable.server.broadcast("stream_name", data)   # To string stream
MyChannel.broadcast_to(model_instance, data)         # To model stream

# Connection identifiers
identified_by :current_user                  # In Connection class
current_user                                 # Available in channels

# Reject
reject                                       # Reject subscription (in subscribed)
reject_unauthorized_connection               # Reject connection (in connect)

# Disconnect a user (from anywhere)
ActionCable.server.remote_connections.where(current_user: user).disconnect
クライアント側 API
// Create subscription
const sub = consumer.subscriptions.create(
  { channel: "ChatChannel", room_id: 1 },
  { connected() {}, disconnected() {}, received(data) {} }
)

// Call server action
sub.perform("speak", { body: "Hello" })

// Send raw data (needs server `receive` method)
sub.send({ body: "Hello" })

// Unsubscribe
sub.unsubscribe()

// Enable logging
ActionCable.logger.enabled = true
Stimulus + Action Cable パターン

コントローラーの例を含む、Stimulus + Action Cable の完全な統合パターンについては、references/client-side.md を参照してください。

避けるべきアンチパターン

  1. Turbo Streams で十分なときに Action Cable を使う — DOM の更新を伴うモデル変更のブロードキャストだけが必要なら、モデルで broadcasts_to を使ってください
  2. 認証されていない接続 — 必ず connect を実装し、reject_unauthorized_connection を使ってください
  3. モデル全体をブロードキャストする — model.as_json ではなく、最小限のデータまたは事前にレンダリングした HTML を送信してください
  4. unsubscribed でのクリーンアップをしない — subscribed で状態を追跡する場合は、unsubscribed でクリーンアップしてください
  5. チャンネル内で重い処理を同期実行する — コストの高いブロードキャストにはバックグラウンドジョブを使ってください
  6. 本番環境で async アダプターを使う — 単一プロセス専用です。Solid Cable または Redis を使ってください
  7. allowed_request_origins を設定しない — クロスオリジンハイジャックを防ぐため、本番環境では必須です
  8. ストリーム名にユーザー入力を文字列補間する — stream_from "chat_#{params[:room]}" を使う前に、パラメーターを検証/サニタイズしてください
  9. クライアント側で disconnected() に対処しない — 接続が切れたときに、ユーザーが視覚的に分かるようにする必要があります
  10. ジェネレーターを使わずにチャンネルを作成する — bin/rails generate channel を使うと、サーバーとクライアントの両方のファイルが正しく設定されます

デバッグのヒント

# Server-side: check active connections
ActionCable.server.connections.length

# Server-side: log tags for debugging
# config/environments/development.rb
config.action_cable.log_tags = [
  -> request { request.env["user_account_id"] || "no-account" },
  :action_cable,
  -> request { request.uuid }
]
// Client-side: enable logging
import * as ActionCable from "@rails/actioncable"
ActionCable.logger.enabled = true

// Check connection state
consumer.connection.isOpen()
consumer.connection.isActive()
# Check cable config
cat config/cable.yml

# Verify WebSocket endpoint
curl -i -N -H "Connection: Upgrade" -H "Upgrade: websocket" \
  -H "Sec-WebSocket-Version: 13" -H "Sec-WebSocket-Key: test" \
  http://localhost:3000/cable

詳しいパターン、エッジケース、高度な設定については、references/ ディレクトリを参照してください。

  • references/connection-auth.md — 認証パターン(Devise、トークン、JWT、複数の識別子)
  • references/channels.md — チャンネルのパターン、コールバック、動的ストリーム
  • references/broadcasting.md — モデルやコントローラーからのブロードキャスト、対象指定/条件付きブロードキャスト
  • references/client-side.md — Stimulus との統合、再接続、Turbo Streams との比較
  • references/adapters-config.md — Solid Cable、Redis、PostgreSQL の設定、デプロイ
  • references/testing.md — チャンネル、接続、ブロードキャスト、システムテストのパターン
  • references/patterns.md — プレゼンス、入力インジケーター、進行状況の追跡、エラー処理、セキュリティ

コメント