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:

install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
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"
manifest: skills/data/action-cable-realtime/SKILL.md
source content

Action 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:

  • references/action-cable-patterns.md
    : Chat, notifications, presence patterns
  • references/solid-cable.md
    : Database-backed pub/sub in Rails 8

For code examples (in

examples/
):

  • chat-channel.rb
    : Real-time chat with typing indicators
  • notifications-channel.rb
    : User-specific push notifications
  • presence-channel.rb
    : Online status tracking
  • dashboard-channel.rb
    : Admin dashboard with live stats
  • multi-room-chat.rb
    : Multiple rooms with private messages
  • collaborative-editing.rb
    : Document editing with cursors
  • live-feed.rb
    : Real-time feed updates

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.