Claude-skill-registry action-cable-realtime
This skill should be used when the user asks about Action Cable, WebSockets, real-time features, channels, broadcasting, subscriptions, chat applications, live notifications, presence indicators, collaborative editing, server push, pub/sub patterns, Solid Cable, or streaming updates. Also use when discussing real-time architecture, WebSocket deployment, or alternatives like polling and Server-Sent Events. Examples:
git clone https://github.com/majiayu000/claude-skill-registry
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/action-cable-realtime" ~/.claude/skills/majiayu000-claude-skill-registry-action-cable-realtime && rm -rf "$T"
skills/data/action-cable-realtime/SKILL.mdAction Cable & Real-Time: WebSockets in Rails
Overview
Action Cable integrates WebSockets with Rails, enabling real-time features like chat, notifications, and live updates. It provides both server-side Ruby and client-side JavaScript frameworks that work together seamlessly.
Action Cable enables:
- Real-time chat and messaging
- Live notifications
- Presence indicators (who's online)
- Collaborative editing
- Live dashboard updates
- Real-time feeds
Rails 8 introduces Solid Cable, which replaces Redis with database-backed pub/sub, simplifying deployment.
Core Concepts
WebSockets vs HTTP
HTTP (Request-Response):
Client → Request → Server Client ← Response ← Server [Connection closes]
WebSocket (Persistent Connection):
Client ↔ Persistent Connection ↔ Server [Messages flow both directions] [Connection stays open]
Benefits:
- Bi-directional communication
- Low latency (no connection overhead)
- Server can push to clients
- Efficient for real-time features
Action Cable Architecture
Browser (Consumer) → WebSocket → Connection → Channels → Broadcasters
Connection: WebSocket connection (one per browser tab) Channel: Logical grouping (like a controller) Subscription: Consumer subscribed to a channel Broadcasting: Message sent to all channel subscribers
Channels
Channels are like controllers for WebSockets.
Creating a Channel
rails generate channel Chat
Generates:
# app/channels/chat_channel.rb class ChatChannel < ApplicationCable::Channel def subscribed # Called when consumer subscribes stream_from "chat_#{params[:room_id]}" end def unsubscribed # Called when consumer unsubscribes (cleanup) end def speak(data) # Called when consumer sends message Message.create!( content: data['message'], user: current_user, room_id: params[:room_id] ) end end
// app/javascript/channels/chat_channel.js import consumer from "./consumer" consumer.subscriptions.create( { channel: "ChatChannel", room_id: 123 }, { connected() { console.log("Connected to chat") }, disconnected() { console.log("Disconnected from chat") }, received(data) { // Handle broadcasted message const messagesContainer = document.getElementById("messages") messagesContainer.insertAdjacentHTML("beforeend", data.html) }, speak(message) { this.perform("speak", { message: message }) } } )
Streaming
Subscribe to broadcasts:
class ChatChannel < ApplicationCable::Channel def subscribed # Stream from named channel stream_from "chat_room_#{params[:room_id]}" # Stream for current user only stream_for current_user # Stop streaming stop_all_streams end end
Channel Callbacks
Channels support lifecycle callbacks and exception handling:
class ChatChannel < ApplicationCable::Channel before_subscribe :verify_access after_subscribe :log_subscription rescue_from UnauthorizedError, with: :handle_unauthorized def subscribed stream_from "chat_#{params[:room_id]}" end private def verify_access reject unless current_user.can_access?(params[:room_id]) end def log_subscription Rails.logger.info "User #{current_user.id} subscribed to chat" end def handle_unauthorized(exception) # Handle error, optionally broadcast error message transmit(error: "Unauthorized access") end end
Available callbacks:
before_subscribe, after_subscribe, before_unsubscribe, after_unsubscribe.
Broadcasting
Send messages to channel subscribers:
From Models
class Message < ApplicationRecord belongs_to :room belongs_to :user after_create_commit :broadcast_message private def broadcast_message ActionCable.server.broadcast( "chat_room_#{room_id}", { html: ApplicationController.render( partial: 'messages/message', locals: { message: self } ), user: user.name } ) end end
From Controllers
class MessagesController < ApplicationController def create @message = Message.new(message_params) if @message.save # Broadcast happens in model callback head :ok else render json: { errors: @message.errors }, status: :unprocessable_entity end end end
From Jobs
class NotificationBroadcastJob < ApplicationJob queue_as :default def perform(notification) ActionCable.server.broadcast( "notifications_#{notification.user_id}", { html: render_notification(notification) } ) end private def render_notification(notification) ApplicationController.render( partial: 'notifications/notification', locals: { notification: notification } ) end end
Authentication
Authenticate WebSocket connections:
# 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 if verified_user = User.find_by(id: cookies.encrypted[:user_id]) verified_user else reject_unauthorized_connection end end end end
Now
current_user is available in all channels.
Rails 8: Solid Cable
Solid Cable replaces Redis with database-backed pub/sub.
Configuration
# config/cable.yml production: adapter: solid_cable polling_interval: 0.1 # 100ms message_retention: 1.day
# No Redis needed! # Solid Cable stores messages in database # Polls for new messages every 100ms
Migration
rails solid_cable:install rails db:migrate
Creates
solid_cable_messages table.
Trade-offs
Solid Cable:
- Simpler deployment (no Redis)
- One database to manage
- ~100-150ms latency
- Sufficient for chat, notifications, updates
Redis:
- Lower latency (<50ms)
- Higher throughput
- Better for millions of connections
For most apps, Solid Cable is simpler and sufficient.
See
references/solid-cable.md for details.
Common Patterns
Chat Application
# Channel class ChatChannel < ApplicationCable::Channel def subscribed stream_from "chat_#{params[:room_id]}" end def speak(data) Message.create!( room_id: params[:room_id], user: current_user, content: data['message'] ) end end # Model class Message < ApplicationRecord after_create_commit -> { broadcast_append_to "chat_#{room_id}", target: "messages", partial: "messages/message", locals: { message: self } } end
Live Notifications
class NotificationChannel < ApplicationCable::Channel def subscribed stream_for current_user end end # Broadcast to specific user NotificationChannel.broadcast_to(user, { html: render_notification(notification) })
Presence (Who's Online)
class AppearanceChannel < ApplicationCable::Channel def subscribed stream_from "appearances" broadcast_appearance("online") end def unsubscribed broadcast_appearance("offline") end def appear broadcast_appearance("online") end def away broadcast_appearance("away") end private def broadcast_appearance(status) ActionCable.server.broadcast("appearances", { user_id: current_user.id, username: current_user.name, status: status }) end end
See
references/action-cable-patterns.md for more examples.
Testing
Channel Tests
require "test_helper" class ChatChannelTest < ActionCable::Channel::TestCase test "subscribes to stream" do subscribe room_id: 42 assert subscription.confirmed? assert_has_stream "chat_42" end test "receives broadcasts" do subscribe room_id: 42 perform :speak, message: "Hello!" assert_broadcast_on("chat_42", message: "Hello!") end end
Integration Tests
test "broadcasts message to chat" do room = rooms(:general) assert_broadcasts("chat_#{room.id}", 1) do Message.create!(room: room, user: users(:alice), content: "Hello!") end end
Deployment Considerations
Standalone Server
For high-traffic apps, run Action Cable on separate servers:
# config/cable.yml production: adapter: solid_cable url: wss://cable.example.com
Scaling
Action Cable scales horizontally:
- Multiple app servers
- Shared pub/sub (Solid Cable database or Redis)
- Load balancer with WebSocket support
Monitoring
Track connection count, message throughput, latency, and errors.
Alternatives to Action Cable
Server-Sent Events (SSE)
One-way server → client:
def stream response.headers['Content-Type'] = 'text/event-stream' response.headers['Cache-Control'] = 'no-cache' sse = SSE.new(response.stream) sse.write({ message: "Hello" }) ensure sse.close end
Use when: Only server → client needed (notifications, live feeds)
Polling
Regular HTTP requests:
setInterval(() => { fetch('/api/notifications/latest') .then(r => r.json()) .then(data => updateUI(data)) }, 5000)
Use when: Simple updates, low frequency, broad browser support needed
Further Reading
For deeper exploration:
: Chat, notifications, presence patternsreferences/action-cable-patterns.md
: Database-backed pub/sub in Rails 8references/solid-cable.md
For code examples (in
examples/):
: Real-time chat with typing indicatorschat-channel.rb
: User-specific push notificationsnotifications-channel.rb
: Online status trackingpresence-channel.rb
: Admin dashboard with live statsdashboard-channel.rb
: Multiple rooms with private messagesmulti-room-chat.rb
: Document editing with cursorscollaborative-editing.rb
: Real-time feed updateslive-feed.rb
Summary
Action Cable provides:
- WebSocket integration with Rails
- Channels for logical grouping
- Broadcasting to connected clients
- Authentication via connection identification
- Solid Cable for database-backed pub/sub (Rails 8)
- Turbo Streams integration for HTML updates
Master Action Cable and you'll build real-time features that feel magical.