Introduction
Channels are the heart of Action Cable — they define how your server handles real-time subscriptions and messages. Creating a channel in Rails follows the same generator-driven workflow you already know from models and controllers, making it straightforward to add real-time features to any application.
Key Concepts
- Channel Generator: The
rails generate channelcommand creates the server-side channel class and client-side JavaScript subscription file. subscribedCallback: Called when a client subscribes to the channel. This is where you set up streams.unsubscribedCallback: Called when a client disconnects or explicitly unsubscribes. Use this for cleanup.stream_from: Connects the subscription to a named stream. Any data broadcast to that stream name will be delivered to this subscription.stream_for: A model-aware version ofstream_fromthat generates a stream name from an ActiveRecord object.
Real World Context
Imagine you are building a notifications feature. You need a NotificationsChannel that streams new notifications to the logged-in user. When a user opens your app, their browser subscribes to the channel. When another part of your system creates a notification (a background job, another controller action), it broadcasts to the user's stream, and the notification appears instantly — no page refresh needed.
Deep Dive
Generate a channel using the Rails generator:
bashbin/rails generate channel Notifications
This creates two files:
app/channels/notifications_channel.rb— server-side logicapp/javascript/channels/notifications_channel.js— client-side subscription
Here is the server-side channel:
ruby# app/channels/notifications_channel.rb class NotificationsChannel < ApplicationCable::Channel def subscribed # stream_for generates a unique stream name per user stream_for current_user end def unsubscribed # Clean up when the user disconnects end end
The stream_for current_user call creates a stream name like notifications:User#42. Any broadcast sent to that stream reaches only that user's subscriptions.
For scenarios where you need a custom stream name (such as a chat room), use stream_from:
rubyclass ChatChannel < ApplicationCable::Channel def subscribed room = params[:room] stream_from "chat_room_#{room}" end end
The params hash contains data sent by the client when subscribing. This lets you create dynamic streams based on client input.
On the client side, create the subscription:
javascriptimport consumer from "./consumer" consumer.subscriptions.create("NotificationsChannel", { connected() { console.log("Connected to notifications") }, disconnected() { console.log("Disconnected from notifications") }, received(data) { // Called when data is broadcast to this channel const notificationList = document.getElementById("notifications") notificationList.insertAdjacentHTML("beforeend", `<li>${data.message}</li>` ) } })
The received callback is where you handle incoming data on the client. This is the equivalent of handling a response in an HTTP request, but it fires automatically whenever the server broadcasts.
To subscribe with parameters (for example, a specific chat room):
javascriptconsumer.subscriptions.create( { channel: "ChatChannel", room: "general" }, { received(data) { // Handle incoming chat message } } )
Common Pitfalls
- Forgetting to call
stream_fromorstream_forinsubscribed— Without setting up a stream, the subscription exists but never receives any data. The channel silently does nothing. - Not validating
paramsinsubscribed— Clients can send arbitrary parameters. Always validate that the user has access to the requested resource (e.g., check that the user is a member of the chat room). - Mixing up
stream_fromandstream_for—stream_fortakes an object and generates a consistent stream name.stream_fromtakes a raw string. Using both for the same concept leads to mismatched stream names.
Best Practices
- Use
stream_forwith ActiveRecord objects — It generates consistent, collision-free stream names and works seamlessly withbroadcast_to. - Authorize in
subscribed— Check thatcurrent_userhas permission to access the requested stream. Reject unauthorized subscriptions withreject. - Keep the
receivedcallback thin — On the client side, extract complex DOM manipulation into separate functions. Thereceivedcallback should just dispatch, not contain business logic.
Summary
- Use
bin/rails generate channel ChannelNameto scaffold both server and client files. - The
subscribedcallback sets up streams;unsubscribedhandles cleanup. stream_forgenerates stream names from ActiveRecord objects;stream_fromuses raw string names.- Client subscriptions are created with
consumer.subscriptions.createand handle data in thereceivedcallback. - Always authorize access in the
subscribedcallback and validate incoming parameters.
Code Examples
# app/channels/notifications_channel.rb
class NotificationsChannel < ApplicationCable::Channel
def subscribed
stream_for current_user
end
def unsubscribed
# Cleanup when user disconnects
end
end