Ruby
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Methods and Blocks
-
Section 4: Object-Oriented Ruby
-
Section 5: Metaprogramming
-
Section 6: Working with Data and Files
-
Section 7: Ruby Frameworks Overview
-
Section 8: Ecosystem and Testing
-
Section 9: Practical Projects
-
Section 10: Interview Practice
-
Section 11: Data Structures and Algorithms in Ruby
-
Section 12: More Practice Exercises
-
Section 13: Enumerable and Functional Style
-
Section 14: More OOP Practice
-
Section 15: More Testing
-
Section 16: Enumerable and Comparable Modules In Depth
-
Section 17: Ruby Standard Library: Core Utilities
-
Section 18: Ruby Standard Library: Data and Security
-
Section 19: Ruby Standard Library: CLI and Text
-
Section 20: Ruby Networking
-
Section 21: Ruby on Rails Deep Dive
-
Section 22: Ruby Metaprogramming Deep Dive
-
Section 23: Ruby Design Patterns
-
Section 24: Ruby Concurrency
-
Section 25: Ruby Testing Deep Dive
-
Section 26: Ruby Gems and Packaging
-
Section 27: Ruby Performance
-
Section 28: More Practice Exercises
-
Section 29: More Interview Practice
-
Section 30: Rails API Development
-
Section 31: Rails Authentication and Authorization
-
Section 32: Rails Testing Deep Dive
-
Section 33: Rails Performance
-
Section 34: Rails Deployment
-
Section 35: Sinatra and Lightweight Ruby Web Apps
-
Section 36: More Ruby Language Deep Dive
-
Section 37: Ruby 3.x Modern Features
-
Section 38: More Data Structures in Ruby
-
Section 39: More Practical Projects
-
Section 40: Ruby Ecosystem Tools
-
Section 41: More Practice and Review
-
Section 42: Final Practice and Mastery
-
Section 43: Ruby Interview Deep Dive
-
Section 44: Ruby Background Processing Deep Dive
-
Section 45: Ruby GraphQL
-
Section 46: Ruby Object Model Deep Dive
-
Section 47: Ruby Hanami Framework Overview
-
Section 48: Ruby gRPC and Protocol Buffers
-
Section 49: Ruby Data Processing
-
Section 50: Ruby Search Integration
-
Section 51: Ruby File Upload and Media
-
Section 52: Ruby Email and Notifications
-
Section 53: Ruby Admin Panels
-
Section 54: Ruby Feature Flags and Experimentation
-
Section 55: Ruby Monitoring and Observability
-
Section 56: Ruby Docker and Deployment Deep Dive
-
Section 57: Ruby Security Deep Dive
-
Section 58: More Advanced Metaprogramming
-
Section 59: More Final Projects
88: Action Cable for WebSockets
Up until now, our Rails apps have followed the classic request-response cycle: the user asks for a page, the server sends it, and the connection closes. But what happens when you need the server to push data to the user without them asking? That's where WebSockets come in, and in the Rails world, we handle that with Action Cable.
To see this in action, let's build a "Live Order Tracker" for a pizza shop. Imagine a customer is staring at their screen, waiting for their pizza to move from "Prepping" to "Out for Delivery." We don't want them hitting refresh every ten seconds; we want the status to flip the moment the chef clicks a button in the back office.
Mapping out the Order Channel
First, we need a Channel. Think of a channel as a specific "frequency" that a user can tune into. We don't want every customer seeing every pizza's status, so we'll create a channel that can be scoped to a specific order.
# app/channels/order_channel.rb
class OrderChannel < ApplicationCable::Channel
def subscribed
# We'll use the order_id passed from the frontend to create a unique stream
stream_from "order_#{params[:order_id]}"
end
def unsubscribed
# Clean up if necessary
stop_all_streams
end
end
By using stream_from, we're telling Action Cable: "Whenever a message is sent to the string order_123, send it to this specific browser connection."
Wiring up the JavaScript listener
Now we need the browser to actually listen. Action Cable provides a JavaScript consumer that handles the WebSocket handshake for us. I usually put this in a dedicated JS file or a Stimulus controller, but for the sake of clarity, here is the core logic.
// app/javascript/channels/order_channel.js
import consumer from "./consumer"
consumer.subscriptions.create({ channel: "OrderChannel", order_id: 42 }, {
connected() {
console.log("Connected to the pizza tracker!");
},
received(data) {
// This is where the magic happens.
// 'data' is the JSON payload sent from the server.
const statusElement = document.getElementById("order-status");
if (statusElement) {
statusElement.innerText = data.status;
}
}
});
Triggering the broadcast from the model
The server side is easy. Whenever an Order is updated, we just need to shout that information out to the channel. I like putting this in an after_commit hook in the model so that the update is actually saved to the database before we tell the user about it.
# app/models/order.rb
class Order < ApplicationRecord
after_commit :broadcast_status_update, on: :update
private
def broadcast_status_update
ActionCable.server.broadcast("order_#{self.id}", { status: self.status })
end
end
Fixing the "Everyone sees every pizza" bug
Now, here is where I messed up the first time I built something like this. In my initial draft, I used a generic stream name like "order_updates" for everyone. It worked great in testing with one user, but as soon as I had two customers, Customer A saw Customer B's pizza status change. It was a privacy nightmare.
I realized I was treating the stream as a category rather than a unique resource. The fix was moving to the interpolated string "order_#{self.id}" in both the channel subscription and the broadcast. Always remember: if the data is user-specific, your stream name must be unique to that resource or user. Never broadcast sensitive data to a global stream.
With this setup, the flow is seamless: Chef updates the order in the admin panel → Rails saves the record → after_commit triggers the broadcast → Action Cable pushes the JSON to the specific WebSocket → JavaScript updates the DOM instantly.
📋 Practical Task
Build a Real-time Notification Toast System
Your goal is to implement a notification system where a user receives a "toast" alert in the top-right corner of their screen whenever a new Comment is created on a post they authored.
- Create a
NotificationChannelthat streams from a unique string based on thecurrent_user.id. - Add an
after_create_commithook to theCommentmodel that broadcasts a message (containing the comment text) to the author of the post. - Write the JavaScript
received(data)function to create a new HTMLdivwith the comment text and append it to a#notifications-containeron the page. - Ensure that the notification is only sent to the author, not to every user currently online.
There are no comments for now.