Skip to Content
Course content

163: Building a Real-Time Chat App with Action Cable

Click on the "Edit" button in the top corner of the screen to edit your slide content.

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 ChatChannel called typing that broadcasts a "typing" event to the chat_room stream, including the current user's name.
  • Add a JavaScript event listener to your message input field that calls this typing method on the Action Cable subscription whenever a user presses a key.
  • Update your received(data) function in chat_channel.js to distinguish between a new message and a "typing" notification, updating a specific <div id="typing-indicator"> on the page accordingly.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.