Action Cable

Ruby on Rails Intermediate/Advanced — Action Cable
Ruby on Rails Intermediate/Advanced
Course 2 · Chapter 6 · Action Cable

📡 Action Cable

Chapter 5's JSON API is still request/response: the client asks, the server answers, the connection closes. Some features genuinely need the server to speak first — a new comment appearing on everyone else's screen without a refresh, a live notification badge. express2-6 covered Socket.IO for exactly this in Express; Action Cable is Rails' own built-in answer to the same problem.

🔗 Connections and Channels

# 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 (user = User.find_by(id: cookies.encrypted[:user_id])) user else reject_unauthorized_connection end end end end

A Connection is established once per browser tab, the moment the WebSocket handshake happens — it's where authentication lives, reusing the same encrypted session cookie rails2-1 already set during a normal HTTP login. identified_by :current_user declares that every message flowing through this connection knows which user it belongs to, and reject_unauthorized_connection refuses the handshake entirely for anyone who fails the check — the WebSocket equivalent of rails2-1's require_login before_action.

A single Connection can host many Channels — one per feature that needs real-time updates:

# app/channels/comments_channel.rb class CommentsChannel < ApplicationCable::Channel def subscribed post = Post.find(params[:post_id]) stream_for post end end

stream_for post subscribes this client to a stream scoped to one specific post — Action Cable derives a unique stream identifier from the object automatically, so every browser tab currently viewing that post's comments joins the same stream, and tabs viewing a different post don't.

📢 Broadcasting

# app/models/comment.rb class Comment < ApplicationRecord belongs_to :post after_create_commit :broadcast_comment private def broadcast_comment CommentsChannel.broadcast_to(post, CommentSerializer.new(self).as_json) end end

after_create_commit is a callback (from Course 1's validations/callbacks chapter, rails1-7) that fires only after the database transaction actually commits — broadcasting before that point risks telling clients about a comment that a later rollback then undoes. broadcast_to(post, ...) pushes the payload to every client streaming that specific post, reusing the exact same CommentSerializer Chapter 5 wrote for the JSON API — one source of truth for "what a comment looks like," whether it arrives via a REST request or a live broadcast.

💻 The Client Side

// app/javascript/channels/comments_channel.js import consumer from "./consumer" consumer.subscriptions.create( { channel: "CommentsChannel", post_id: postId }, { received(data) { appendCommentToPage(data) } } )

The client subscribes with the same post_id param the server's subscribed method reads, and received fires every time a broadcast arrives on that stream — no polling, no manually re-fetching the comments list.

📜 Action Cable vs Socket.IO

Rails Action Cable vs Express + Socket.IO (express2-6)

ConcernExpress + Socket.IORails Action Cable
Connection authio.use() middlewareConnection#connect, identified_by
Grouping clientsRooms (socket.join(room))Streams (stream_for / stream_from)
Sending a messageio.to(room).emit(event, data)Channel.broadcast_to(object, data)
Feature organizationEvent names on one connectionSeparate channel class per feature
Triggering a broadcastCalled explicitly wherever neededOften a model callback (after_create_commit)

The concepts map closely — a Socket.IO "room" and an Action Cable "stream" solve the same "only these clients get this message" problem. The organizational difference is Rails' usual one: a dedicated channel class per feature (CommentsChannel, NotificationsChannel) instead of a flat set of event name strings on one shared connection.

💻 Coding Challenges

Challenge 1: A Notifications Channel

Write a NotificationsChannel where subscribed streams for the current connection's current_user (not a param — the user identified on the connection itself).

Goal: Practice streaming scoped to the authenticated user rather than a URL parameter.

→ Solution

Challenge 2: Broadcasting on Create

Add an after_create_commit callback to a Notification model that broadcasts the new notification (serialized) to the NotificationsChannel stream for its owning user.

Goal: Practice triggering a broadcast from a model callback, tied to the right stream.

→ Solution

Challenge 3: Client Subscription

Write the JavaScript client code subscribing to NotificationsChannel and appending each received notification to a #notifications element on the page.

Goal: Practice wiring the client side of a channel subscription to a broadcast.

→ Solution

💎 Not Every Feature Needs a Socket

Action Cable is the right tool when data genuinely needs to appear without the user doing anything — a live chat, a notification badge. If a page only needs "show me the latest data when I next visit or refresh," a plain request (Chapter 5's JSON API, or a normal page load) is simpler, has one less moving part (no persistent connection to manage), and is usually the better default.

🎯 What's Next

Next chapter: Hotwire — Turbo & Stimulus — how modern Rails apps add interactivity to server-rendered HTML without reaching for a full frontend framework.