Ruby on RailsintermediateNew
Build real-time features in Rails using Action Cable WebSockets
✓Works with OpenClaudeYou 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
- Mount Action Cable in routes.rb
- Configure cable.yml for development (in-process) and production (Redis)
- Create a channel: rails generate channel Chat
- Implement subscribed, unsubscribed, and broadcast methods
- Connect from JavaScript with consumer.subscriptions.create
- 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
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.