Free 40-page Claude guide — setup, 120 prompt codes, MCP servers, AI agents. Download free →
CLSkills
Ruby on RailsintermediateNew

Rails Action Cable WebSockets

Share

Build real-time features in Rails using Action Cable WebSockets

Works with OpenClaude

You are the #1 Rails real-time expert from Silicon Valley — the engineer that companies hire when their chat feature is using polling and falling over at 1000 users. The user wants to add real-time features (chat, notifications, live updates) to a Rails app using Action Cable.

What to check first

  • Rails 6+ for ActionCable improvements
  • Redis available for the Action Cable adapter (production)
  • Decide on authentication method

Steps

  1. Mount Action Cable in routes.rb
  2. Configure cable.yml for development (in-process) and production (Redis)
  3. Create a channel: rails generate channel Chat
  4. Implement subscribed, unsubscribed, and broadcast methods
  5. Connect from JavaScript with consumer.subscriptions.create
  6. Authenticate connections in ApplicationCable::Connection

Code

# Gemfile
gem 'redis'

# config/cable.yml
development:
  adapter: async
production:
  adapter: redis
  url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %>
  channel_prefix: myapp_production

# config/routes.rb
Rails.application.routes.draw do
  mount ActionCable.server => '/cable'
end

# 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
      # Use cookies, JWT, or session
      if (user_id = cookies.encrypted[:user_id]) && (user = User.find_by(id: user_id))
        user
      else
        reject_unauthorized_connection
      end
    end
  end
end

# app/channels/chat_channel.rb
class ChatChannel < ApplicationCable::Channel
  def subscribed
    @room = Room.find(params[:room_id])
    return reject unless current_user.can_access?(@room)

    stream_for @room
  end

  def unsubscribed
    # Cleanup if needed
  end

  def speak(data)
    message = @room.messages.create!(
      user: current_user,
      content: data['message']
    )

    # Broadcast to all subscribers of this room
    ChatChannel.broadcast_to(@room, {
      id: message.id,
      user: message.user.name,
      content: message.content,
      created_at: message.created_at.iso8601,
    })
  end
end

// app/javascript/channels/chat_channel.js
import consumer from "./consumer"

const subscription = consumer.subscriptions.create(
  { channel: "ChatChannel", room_id: 123 },
  {
    connected() {
      console.log("Connected to chat");
    },

    disconnected() {
      console.log("Disconnected");
    },

    received(data) {
      // New message — append to UI
      const messagesEl = document.getElementById('messages');
      const messageEl = document.createElement('div');
      messageEl.innerHTML = `
        <strong>${data.user}</strong>: ${data.content}
      `;
      messagesEl.appendChild(messageEl);
    },

    speak(message) {
      this.perform('speak', { message: message });
    },
  }
);

// Send a message
document.getElementById('send').addEventListener('click', () => {
  const input = document.getElementById('message-input');
  subscription.speak(input.value);
  input.value = '';
});

# Broadcasting from a controller (notifications, not just chat)
class NotificationsController < ApplicationController
  def create
    notification = Notification.create!(notification_params)

    # Push to specific user's channel
    NotificationsChannel.broadcast_to(notification.user, {
      title: notification.title,
      body: notification.body,
    })
  end
end

# Subscribing to notifications for current user
class NotificationsChannel < ApplicationCable::Channel
  def subscribed
    stream_for current_user
  end
end

Common Pitfalls

  • Using the async adapter in production — only works for single-process
  • Not authenticating connections — anyone can subscribe to private channels
  • Broadcasting to too many subscribers — Redis becomes the bottleneck
  • Long-running operations in the channel — blocks the worker thread

When NOT to Use This Skill

  • For one-way notifications — Server-Sent Events (SSE) is simpler
  • When polling at 5+ second intervals is acceptable

How to Verify It Worked

  • Open two browser tabs and confirm messages appear in both
  • Test reconnection after network interruption
  • Test authorization — try subscribing as wrong user

Production Considerations

  • Set up Redis as the cable adapter — async breaks at scale
  • Monitor cable connection count via JMX or custom metrics
  • Use a dedicated Action Cable server (puma or anycable) for high volume

Quick Info

Difficultyintermediate
Version1.0.0
AuthorClaude Skills Hub
railsaction-cablewebsockets

Install command:

Want a Ruby on Rails skill personalized to YOUR project?

This is a generic skill that works for everyone. Our AI can generate one tailored to your exact tech stack, naming conventions, folder structure, and coding patterns — with 3x more detail.