Action Cable & WebSocket Patterns

Real-time WebSocket features with Action Cable in Rails. Use when: (1) Building real-time chat, (2) Live notifications/presence, (3) Broadcasting model updates, (4) WebSocket authorization. Trigger keywords: Action Cable, WebSocket, real-time, channels, broadcasting, stream, subscriptions, presence, cable

181 stars

Best use case

Action Cable & WebSocket Patterns is best used when you need a repeatable AI agent workflow instead of a one-off prompt.

Real-time WebSocket features with Action Cable in Rails. Use when: (1) Building real-time chat, (2) Live notifications/presence, (3) Broadcasting model updates, (4) WebSocket authorization. Trigger keywords: Action Cable, WebSocket, real-time, channels, broadcasting, stream, subscriptions, presence, cable

Teams using Action Cable & WebSocket Patterns should expect a more consistent output, faster repeated execution, less prompt rewriting.

When to use this skill

  • You want a reusable workflow that can be run more than once with consistent structure.

When not to use this skill

  • You only need a quick one-off answer and do not need a reusable workflow.
  • You cannot install or maintain the underlying files, dependencies, or repository context.

Installation

Claude Code / Cursor / Codex

$curl -o ~/.claude/skills/action-cable-patterns/SKILL.md --create-dirs "https://raw.githubusercontent.com/majiayu000/claude-skill-registry/main/skills/data/action-cable-patterns/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/action-cable-patterns/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How Action Cable & WebSocket Patterns Compares

Feature / AgentAction Cable & WebSocket PatternsStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

Real-time WebSocket features with Action Cable in Rails. Use when: (1) Building real-time chat, (2) Live notifications/presence, (3) Broadcasting model updates, (4) WebSocket authorization. Trigger keywords: Action Cable, WebSocket, real-time, channels, broadcasting, stream, subscriptions, presence, cable

Where can I find the source code?

You can find the source code on GitHub using the link provided at the top of the page.

SKILL.md Source

# Action Cable Patterns

Real-time WebSocket features for Rails applications.

## Real-Time Feature Decision Tree

```
What real-time feature?
│
├─ User notifications
│   └─ Personal stream: stream_from "notifications_#{current_user.id}"
│
├─ Chat room messages
│   └─ Group stream: stream_from "chat_room_#{room.id}"
│
├─ Model updates (live editing)
│   └─ Model stream: stream_for @post (with broadcast_to)
│
├─ Presence tracking (who's online)
│   └─ Presence stream + Redis: stream_from "presence_room_#{room.id}"
│
└─ Dashboard/analytics
    └─ Scoped stream: stream_from "dashboard_#{account.id}"
```

---

## Core Principles (CRITICAL)

### 1. Authorization First

```ruby
# WRONG - Security vulnerability!
def subscribed
  stream_from "private_data"  # Anyone can subscribe!
end

# RIGHT - Explicit authorization
def subscribed
  reject unless current_user
  reject unless current_user.can_access?(params[:resource_id])
  stream_from "private_#{params[:resource_id]}"
end
```

### 2. Persist First, Broadcast Second

```ruby
# WRONG - Data lost if client offline
def speak(data)
  ActionCable.server.broadcast("chat", message: data['text'])
end

# RIGHT - Persist then broadcast
def speak(data)
  message = Message.create!(user: current_user, text: data['text'])
  ActionCable.server.broadcast("chat", message: message)
end
```

### 3. Use stream_for for Models

```ruby
# WRONG - Manual naming (error-prone)
stream_from "posts:#{params[:id]}"
ActionCable.server.broadcast("posts:#{@post.id}", data)

# RIGHT - Type-safe model broadcasting
stream_for @post
PostChannel.broadcast_to(@post, data)
```

---

## NEVER Do This

**NEVER** skip authorization:
```ruby
# Every channel MUST have: reject unless current_user
# Plus resource-specific authorization
```

**NEVER** broadcast before commit:
```ruby
# WRONG
post.save
ActionCable.server.broadcast(...)  # Transaction may rollback!

# RIGHT - Use after_commit callback
after_create_commit { broadcast_creation }
```

**NEVER** broadcast full objects:
```ruby
# WRONG - Leaks data, slow
ActionCable.server.broadcast("posts", post: @post)

# RIGHT - Only needed fields
ActionCable.server.broadcast("posts", post: @post.as_json(only: [:id, :title]))
```

**NEVER** create subscriptions without cleanup (JavaScript):
```javascript
// WRONG - Memory leak
consumer.subscriptions.create("ChatChannel", { ... })

// RIGHT - Cleanup on unmount
useEffect(() => {
  const sub = consumer.subscriptions.create(...)
  return () => sub.unsubscribe()
}, [])
```

---

## Channel Template

```ruby
class NotificationsChannel < ApplicationCable::Channel
  def subscribed
    # 1. Authorization (REQUIRED)
    reject unless current_user

    # 2. Subscribe to stream
    stream_from "notifications_#{current_user.id}"
  end

  def unsubscribed
    # Cleanup (optional)
  end

  # Client action: channel.perform('mark_as_read', {id: 123})
  def mark_as_read(data)
    notification = current_user.notifications.find(data['id'])
    notification.mark_as_read!

    ActionCable.server.broadcast(
      "notifications_#{current_user.id}",
      action: 'count_updated',
      unread_count: current_user.notifications.unread.count
    )
  end
end
```

---

## Stream Patterns Quick Reference

| Pattern | Use Case | Code |
|---------|----------|------|
| Personal | Notifications | `stream_from "user_#{current_user.id}"` |
| Model | Live updates | `stream_for @post` → `PostChannel.broadcast_to(@post, data)` |
| Group | Chat rooms | `stream_from "room_#{room.id}"` |
| Presence | Who's online | `stream_from "presence_#{room.id}"` + Redis |

---

## Broadcasting Patterns

### From Model (Recommended)
```ruby
class Post < ApplicationRecord
  after_create_commit { broadcast_creation }
  after_update_commit { broadcast_update }

  private

  def broadcast_creation
    PostChannel.broadcast_to(self, action: 'created', post: as_json(only: [:id, :title]))
  end
end
```

### From Controller
```ruby
def create
  @comment = @post.comments.create!(comment_params)
  CommentsChannel.broadcast_to(@post, action: 'created', comment: @comment.as_json)
end
```

### From Background Job
```ruby
class BroadcastJob < ApplicationJob
  def perform(channel_name, data)
    ActionCable.server.broadcast(channel_name, data)
  end
end
```

---

## Connection Authentication

```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
      # Cookie auth (default Rails)
      if user = User.find_by(id: cookies.encrypted[:user_id])
        user
      # Token auth (API clients)
      elsif user = find_user_from_token
        user
      else
        reject_unauthorized_connection
      end
    end

    def find_user_from_token
      token = request.params[:token]
      return nil unless token
      payload = JWT.decode(token, Rails.application.secret_key_base).first
      User.find_by(id: payload['user_id'])
    rescue JWT::DecodeError
      nil
    end
  end
end
```

---

## Testing Quick Reference

```ruby
# spec/channels/notifications_channel_spec.rb
RSpec.describe NotificationsChannel, type: :channel do
  let(:user) { create(:user) }

  before { stub_connection(current_user: user) }

  it 'subscribes to user stream' do
    subscribe
    expect(subscription).to be_confirmed
    expect(subscription).to have_stream_from("notifications_#{user.id}")
  end

  it 'rejects unauthenticated users' do
    stub_connection(current_user: nil)
    subscribe
    expect(subscription).to be_rejected
  end

  it 'broadcasts on action' do
    subscribe
    expect {
      perform :mark_as_read, id: notification.id
    }.to have_broadcasted_to("notifications_#{user.id}")
  end
end
```

---

## Production Config

```yaml
# config/cable.yml
production:
  adapter: redis
  url: <%= ENV['REDIS_URL'] %>
  channel_prefix: myapp_production
```

```ruby
# config/environments/production.rb
config.action_cable.url = ENV['ACTION_CABLE_URL']
config.action_cable.allowed_request_origins = ['https://example.com']
```

---

## References

Detailed examples in `references/`:
- `javascript-consumers.md` - Client-side subscription patterns
- `presence-tracking.md` - Complete presence implementation with Redis
- `deployment.md` - Nginx, scaling, production configuration

Related Skills

advanced-patterns

181
from majiayu000/claude-skill-registry

Advanced T-SQL patterns and techniques for SQL Server. Use this skill when: (1) User needs help with CTEs or recursive queries, (2) User asks about APPLY operator, (3) User wants MERGE or OUTPUT clause help, (4) User works with temporal tables, (5) User needs In-Memory OLTP guidance, (6) User asks about advanced grouping (ROLLUP, CUBE, GROUPING SETS).

advanced-js-mocking-patterns

181
from majiayu000/claude-skill-registry

Advanced mocking patterns for Jest and Vitest including module mocking, spies, and fake timers. PROACTIVELY activate for: (1) Module mocking, (2) Partial mocking with spies, (3) Mock lifecycle management, (4) Fake timers for time-dependent code, (5) Complex mock implementations. Triggers: "jest.mock", "vi.mock", "spyOn", "fakeTimers", "mockImplementation", "mockReturnValue", "mock lifecycle"

Advanced GetX Patterns

181
from majiayu000/claude-skill-registry

Advanced GetX features including Workers, GetxService, SmartManagement, GetConnect, GetSocket, bindings composition, and testing patterns

adr-decision-extraction

181
from majiayu000/claude-skill-registry

Extract architectural decisions from conversations. Identifies problem-solution pairs, trade-off discussions, and explicit choices. Use when analyzing session transcripts for ADR generation.

add-ws-action

181
from majiayu000/claude-skill-registry

Add a new outgoing WebSocket action with typed payload and API exposure

add-reaction

181
from majiayu000/claude-skill-registry

Slack メッセージにリアクションを追加する。「リアクション追加」「リアクションつけて」「👍つけて」「絵文字で反応」「リアクションで返信」「いいねして」「リアクション送って」などで起動。User Token があればユーザーとしてリアクション、なければ Bot としてリアクション。

patterns/adapter

181
from majiayu000/claude-skill-registry

Adapter (Wrapper) Pattern pattern for C development

ActiveRecord Query Patterns

181
from majiayu000/claude-skill-registry

Complete guide to ActiveRecord query optimization, associations, scopes, and PostgreSQL-specific patterns. Use this skill when writing database queries, designing model associations, creating migrations, optimizing query performance, or debugging N+1 queries and grouping errors.

github-actions

181
from majiayu000/claude-skill-registry

Create and configure GitHub Actions. Use when building custom actions, setting up runners, implementing security practices, or publishing to the marketplace.

actions-pattern

181
from majiayu000/claude-skill-registry

Garante que novas Actions sigam o padrão de classes actions reutilizáveis do Easy Budget.

actions-debugger

181
from majiayu000/claude-skill-registry

GitHub Actions のワークフロー実行エラーを調査し、原因を特定して解決策を提案する。「Actions エラー」「ワークフロー失敗」「CI が落ちた」「ビルド失敗」「テスト失敗」「Actions を調べて」「CI のエラーを見て」などで起動。失敗したジョブのログを分析し、具体的な修正方法を提示。

actions-cicd-practices

181
from majiayu000/claude-skill-registry

GitHub Actions and CI/CD best practices for automated testing, building, and deployment.