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
163: Building a Real-Time Chat App with Action Cable
You've probably got your chat messages saving to the database just fine, but you've noticed something frustrating: the messages only appear when you hit refresh. You've set up Action Cable, you've written your channel, and you're calling broadcast in your controller, yet the UI is dead silent. This is where most people get stuck with Action Cable because it fails silently.
# app/channels/chat_channel.rb
class ChatChannel < ApplicationCable::Channel
def subscribed
stream_from "chat_room"
end
end
# app/controllers/messages_controller.rb
def create
@message = Message.create(message_params)
if @message.save
ActionCable.server.broadcast "messages", @message # The bug is here
redirect_to chat_path
end
end
The Case of the Vanishing Broadcast
If you look at your Rails server logs, you'll see that the broadcast method is being called. The server thinks it's doing its job. But on the frontend, your JavaScript consumer is listening to "chat_room", while your controller is screaming into a void called "messages". Action Cable doesn't throw an error when you broadcast to a channel that has no subscribers; it just sends the data into the ether.
I've spent way too many hours debugging this by staring at the code. The trick is to use the browser's Network tab. If you filter by "WS" (WebSockets), you can see the frames being sent. If you see the subscription frame for chat_room but no incoming data frames when you send a message, you know there's a mismatch between your stream name and your broadcast target.
Aligning the Stream and the Broadcast
To fix this, the string passed to stream_from in your channel must exactly match the string passed to ActionCable.server.broadcast. I usually recommend defining a constant or a helper method if you're using dynamic room names, but for a global chat, just keep them consistent.
# app/controllers/messages_controller.rb
def create
@message = Message.create(message_params)
if @message.save
# Match this exactly to the stream_from in ChatChannel
ActionCable.server.broadcast "chat_room", @message
redirect_to chat_path
end
end
Handling the Data in JavaScript
Once the server is actually hitting the right target, you need to make sure your JavaScript is prepared to handle the payload. Remember that Action Cable sends data as JSON. If you pass a Ruby object (like an ActiveRecord model) to broadcast, Rails will call to_json on it automatically.
// app/javascript/channels/chat_channel.js
import consumer from "./consumer"
consumer.subscriptions.create("ChatChannel", {
received(data) {
const messageContainer = document.getElementById("messages");
const messageElement = document.createElement("div");
// 'data' is the JSON representation of the @message object
messageElement.innerHTML = `${data.username}: ${data.content}`;
messageContainer.appendChild(messageElement);
}
});
Scaling to Private Rooms
A global "chat_room" is great for a demo, but you'll quickly want private rooms. Instead of a hardcoded string, you can pass parameters from the frontend to the subscribed method. You can use these parameters to build a unique stream name, like "chat_room_#{params[:room_id]}". Just be careful: since this happens over a WebSocket, you should always verify that the current user actually has permission to join that specific room ID before calling stream_from, otherwise, anyone could sniff out a room ID and listen in on private conversations.
📋 Practical Task
Building a "User is Typing" Indicator
Modify your existing chat application to show a real-time "User is typing..." notification. You will need to:
- Create a new method in your
ChatChannelcalledtypingthat broadcasts a "typing" event to thechat_roomstream, including the current user's name. - Add a JavaScript event listener to your message input field that calls this
typingmethod on the Action Cable subscription whenever a user presses a key. - Update your
received(data)function inchat_channel.jsto distinguish between a new message and a "typing" notification, updating a specific<div id="typing-indicator">on the page accordingly.
There are no comments for now.