Introduction
Action Cable integrates WebSockets seamlessly into Rails by mapping familiar Ruby concepts — connections, channels, and subscriptions — onto the WebSocket protocol. Understanding this architecture is essential before you write a single line of channel code.
Key Concepts
- Action Cable Server: A Rack-compatible server that runs alongside your Rails app, managing all WebSocket connections and routing messages to the correct channels.
- Connection: Represents a single WebSocket link between a client and the server. Each browser tab that opens a WebSocket creates one connection. Connections handle authentication.
- Channel: The logical unit of work in Action Cable, similar to a controller in MVC. Each channel encapsulates a specific feature (e.g., ChatChannel, NotificationsChannel).
- Subscription: When a client subscribes to a channel, a subscription object is created on the server side. One connection can have multiple subscriptions.
- Consumer: The client-side JavaScript object that establishes and manages the WebSocket connection.
- Stream: A named broadcasting pipeline within a channel. Streams connect the server-side broadcasting mechanism to individual subscriptions.
Real World Context
When you build a chat application, the Connection authenticates the user, the ChatChannel defines the behavior, and each chat room is a separate stream. A user viewing three chat rooms has one connection but three subscriptions, each streaming from a different room. This architecture keeps your real-time code organized the same way controllers organize your HTTP endpoints.
Deep Dive
Action Cable's architecture follows a layered model. At the outermost layer, the Cable Server handles raw WebSocket connections. When a client connects, it creates a Connection object:
The Connection class is where you authenticate the WebSocket user:
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 if verified_user = User.find_by(id: cookies.encrypted[:user_id]) verified_user else reject_unauthorized_connection end end end end
The identified_by declaration establishes a connection identifier that lets you find specific connections later — for example, to disconnect a user who logs out.
Channels inherit from ApplicationCable::Channel, much like controllers inherit from ApplicationController:
ruby# app/channels/application_cable/channel.rb module ApplicationCable class Channel < ActionCable::Channel::Base # Shared channel behavior goes here end end
On the client side, the consumer connects to the server and manages subscriptions:
javascript// app/javascript/channels/consumer.js import { createConsumer } from "@rails/actioncable" export default createConsumer()
The createConsumer() call establishes a WebSocket connection to the /cable endpoint. Action Cable automatically handles reconnection if the connection drops.
The data flow follows this path:
- Client consumer connects to Cable Server via WebSocket.
- Connection authenticates and identifies the user.
- Client subscribes to a channel, creating a Subscription.
- The channel sets up streams for the subscription.
- Server broadcasts data to a stream name.
- All subscriptions listening on that stream receive the data.
Common Pitfalls
- Skipping connection authentication — An unauthenticated WebSocket connection allows anyone to subscribe to channels. Always verify the user in
Connection#connectand callreject_unauthorized_connectionfor invalid users. - Confusing channels with streams — A channel is a class that defines behavior. A stream is a named pipeline within a channel. One channel can manage many streams (e.g.,
ChatChannelstreams fromchat_room_1,chat_room_2, etc.).
Best Practices
- Keep channels focused — Each channel should handle one feature. Do not combine chat, notifications, and presence into a single channel.
- Use
ApplicationCable::Channelfor shared logic — Put common authorization checks or logging in the base channel class so all channels inherit them. - Identify connections by user — The
identified_bymechanism lets you disconnect specific users, which is critical for logout and account suspension flows.
Summary
- Action Cable maps WebSocket concepts to familiar Rails patterns: Connection (authentication), Channel (behavior), and Subscription (client link).
- The Connection class authenticates users via cookies or tokens and rejects unauthorized access.
- Channels are the equivalent of controllers for real-time features, each encapsulating one domain.
- Streams are named broadcasting pipelines that route messages from the server to the correct subscriptions.
- The client-side consumer manages the WebSocket connection and handles automatic reconnection.
Code Examples
# 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
verified_user = User.find_by(id: cookies.encrypted[:user_id])
verified_user || reject_unauthorized_connection
end
end
end