action-cable について
Rails 開発者向けの Action Cable スキルで、WebSocket 接続、チャンネル、サブスクリプション、ブロードキャスト、認証、アダプター、テストを扱います。Turbo Streams で足りる場合と Action Cable が必要な場合を区別します。
主な機能
- 01WebSocket 接続とチャンネルの設定
- 02サブスクリプションとブロードキャストの実装
- 03認証と切断時のクリーンアップ
- 04Solid Cable・Redis などの設定
- 05チャンネルとブロードキャストのテスト
活用シーン
- 01Rails アプリへのリアルタイムチャット追加
- 02プレゼンスや入力中表示の実装
- 03ジョブの進捗やライブデータの配信
- 04Action Cable チャンネルのテスト作成
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ thinkoodle/ rails-skills --skill action-cableインストール後、Claude Code で/action-cableを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/adapters-config.mdbroadcasting.mdchannels.mdclient-side.mdconnection-auth.mdpatterns.mdtesting.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"] |
| Devise | env["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 を参照してください。
避けるべきアンチパターン
- Turbo Streams で十分なときに Action Cable を使う — DOM の更新を伴うモデル変更のブロードキャストだけが必要なら、モデルで
broadcasts_toを使ってください - 認証されていない接続 — 必ず
connectを実装し、reject_unauthorized_connectionを使ってください - モデル全体をブロードキャストする —
model.as_jsonではなく、最小限のデータまたは事前にレンダリングした HTML を送信してください unsubscribedでのクリーンアップをしない —subscribedで状態を追跡する場合は、unsubscribedでクリーンアップしてください- チャンネル内で重い処理を同期実行する — コストの高いブロードキャストにはバックグラウンドジョブを使ってください
- 本番環境で
asyncアダプターを使う — 単一プロセス専用です。Solid Cable または Redis を使ってください allowed_request_originsを設定しない — クロスオリジンハイジャックを防ぐため、本番環境では必須です- ストリーム名にユーザー入力を文字列補間する —
stream_from "chat_#{params[:room]}"を使う前に、パラメーターを検証/サニタイズしてください - クライアント側で
disconnected()に対処しない — 接続が切れたときに、ユーザーが視覚的に分かるようにする必要があります - ジェネレーターを使わずにチャンネルを作成する —
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— プレゼンス、入力インジケーター、進行状況の追跡、エラー処理、セキュリティ