Implementing Real-Time Updates with Rails Action Cable
Learn how to implement real-time updates in Rails using Action Cable and Redis to synchronize data across multiple server processes.
15 Aug 2026, 03:00 UTC

Adding real-time features like notification badges or live dashboards often leads developers to look for external push services. However, Rails provides Action Cable, a built-in framework that integrates WebSockets—a protocol allowing full-duplex communication—directly into the Rails ecosystem. This removes the need for clients to constantly poll the server for updates.
The primary technical hurdle with WebSockets in a production environment is state. Because Rails apps typically run across multiple server processes or containers, a client connected to Server A cannot receive a message triggered by a process on Server B. To solve this, Action Cable uses a Pub/Sub (Publisher/Subscriber) adapter to synchronize messages across all active server instances.
The Architecture of a WebSocket Connection
Action Cable operates via two main components: the client-side consumer and the server-side channel. The consumer establishes the persistent connection, while the channel encapsulates the business logic for that connection.
Channels handle the connection lifecycle through specific hooks. The connect method is used for initial authentication, while subscribed defines which "streams" the client should listen to. By isolating users into unique streams, you prevent data leakage and ensure users only receive updates relevant to their session.
Configuring Redis for Multi-Server Scaling
By default, Rails uses an async adapter, which only works within a single process. For any application running on more than one worker or server, you must use a shared backend like Redis. This ensures that when a broadcast is triggered, Redis distributes it to every Rails process currently hosting connected clients.
First, add the redis gem to your Gemfile:
gem 'redis'
Configure config/cable.yml to point to your Redis instance. Use environment variables for production to avoid hardcoding credentials:
development:
adapter: redis
url: redis://localhost:6379/1
production:
adapter: redis
url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %>
Worked Example: User-Specific Notifications
To implement a system where users receive private notifications, generate a channel:
rails generate channel Notification
In app/channels/notification_channel.rb, restrict the stream to the authenticated user:
class NotificationChannel < ActionCable::Channel
def subscribed
# stream_from creates a unique pipe for this specific user
stream_from "notifications_#{current_user.id}"
end
def unsubscribed
# Stop streaming upon disconnection
stop_all_streams
end
end
To trigger the update from anywhere in your application—such as a model callback or a background job—use the ActionCable.server.broadcast method:
# Example: Triggering a broadcast after a record is created
class Notification < ApplicationRecord
after_create_commit :send_realtime_update
private
def send_realtime_update
ActionCable.server.broadcast(
"notifications_#{user_id}",
{ message: "New alert received", timestamp: Time.current }
)
end
end
Verification and Diagnostics
To verify the Pub/Sub flow is working across processes, follow this sequence:
- Start your Redis server:
redis-server. - Start the Rails server:
rails s. - Open two separate browser windows to the application to establish two distinct WebSocket connections.
- Open the Rails console:
rails c. - Run a manual broadcast:
ActionCable.server.broadcast("notifications_1", {test: "success"}).
If configured correctly, the client with user ID 1 will receive the payload instantly. You can further verify Redis traffic by running redis-cli MONITOR in your terminal to see the Pub/Sub commands passing through the broker in real-time.
Limitations and Trade-offs
Action Cable is highly effective for moderate real-time needs, but it is not a replacement for high-throughput messaging systems like Apache Kafka. Because every connection consumes server memory and Redis handles the message distribution, an extreme volume of concurrent connections can exhaust server resources.
Additionally, ensure your load balancer (e.g., Nginx or AWS ALB) is configured to support WebSockets. Without specific headers like Upgrade and Connection, the WebSocket handshake will fail, and the client will fall back to long-polling, which significantly increases server overhead.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.