ActionCable and WebSockets in Rails

· 5 min read · Updated March 17, 2026 · intermediate
rails actioncable websockets real-time ruby javascript

Real-time features have become essential for modern web applications. Users expect instant updates, live notifications, and seamless collaborative experiences. ActionCable is Rails’ native solution for adding WebSocket functionality to your applications, providing a unified API that integrates smoothly with the Rails ecosystem.

What Is ActionCable?

ActionCable is a framework for handling WebSocket connections in Rails applications. It combines WebSockets with the rest of your Rails app, allowing you to stream data to connected clients in real-time while leveraging your existing authentication, authorization, and business logic.

WebSockets provide a persistent connection between the client and server, unlike traditional HTTP requests where the client must constantly poll the server for updates. This bidirectional communication channel enables:

  • Real-time updates without page refreshes
  • Live notifications
  • Collaborative editing features
  • Live chat applications
  • Real-time dashboards and monitoring

Setting Up ActionCable

ActionCable comes bundled with Rails 5 and later. To enable it, you need to configure both the server-side and client-side components.

Server-Side Configuration

First, ensure your Rails application has the ActionCable engine mounted. In config/routes.rb:

Rails.application.routes.draw do
  mount ActionCable.server => '/cable'
  # Your other routes...
end

Then configure the cable settings in config/cable.yml:

development:
  adapter: async

test:
  adapter: async

production:
  adapter: redis
  url: redis://localhost:6379/1
  channel_prefix: rails_actioncable_production

The Redis adapter is recommended for production as it supports multiple server instances and handles connections across a cluster.

Client-Side Setup

In your JavaScript, initialize the ActionCable consumer:

import { createConsumer } from "@rails/actioncable"

const cable = createConsumer("/cable")

This creates a WebSocket connection to the /cable endpoint.

Channels: The Building Blocks

Channels are the core abstraction in ActionCable. They define how messages are routed between the server and connected clients.

Creating a Channel

Generate a new channel using Rails generators:

rails generate channel chat

This creates two files:

  • app/channels/chat_channel.rb (server-side)
  • app/javascript/channels/chat_channel.js (client-side)

Server-Side Channel

Define your channel class:

class ChatChannel < ApplicationCable::Channel
  def subscribed
    stream_from "chat_#{params[:room_id]}"
  end

  def unsubscribed
    # Clean up any resources
  end

  def send_message(data)
    Message.create!(
      content: data['message'],
      room_id: params[:room_id],
      user_id: current_user.id
    )
  end
end

The subscribed method is called when a client subscribes to the channel. Use stream_from for broadcast messaging or stream_for for user-specific streams.

Client-Side Channel

Handle the connection on the client:

import consumer from "./consumer"

consumer.subscriptions.create("ChatChannel", {
  connected() {
    console.log("Connected to chat")
  },

  disconnected() {
    console.log("Disconnected from chat")
  },

  received(data) {
    // Handle incoming messages
    this.appendMessage(data)
  },

  send_message(message) {
    return this.perform("send_message", { message })
  },

  appendMessage(data) {
    const messagesContainer = document.getElementById("messages")
    messagesContainer.insertAdjacentHTML("beforeend", `
      <div class="message">
        <strong>${data.user}:</strong> ${data.content}
      </div>
    `)
  }
})

Broadcasting Messages

From anywhere in your Rails application, you can broadcast messages to connected clients:

ActionCable.server.broadcast("chat_#{room.id}", {
  user: message.user.name,
  content: message.content,
  timestamp: message.created_at.iso8601
})

This sends the data to all clients subscribed to the “chat_#{room.id}” stream.

Broadcasting from Models

A common pattern is to broadcast from model callbacks:

class Message < ApplicationRecord
  after_create_commit do
    broadcast_append_to(
      "chat_#{room_id}",
      partial: "messages/message",
      locals: { message: self }
    )
  end
end

The broadcast_append_to method automatically streams the rendered partial to subscribers.

Authentication and Authorization

ActionCable integrates with your existing authentication system.

Using Devise

In your connection authorization:

class ApplicationCable::Connection < ActionCable::Connection::Base
  identified_by :current_user

  def connect
    self.current_user = find_verified_user
  end

  private

  def find_verified_user
    if current_user = env['warden'].user
      current_user
    else
      reject_unauthorized_connection
    end
  end
end

Channel Authorization

Restrict channel subscriptions to authorized users:

class ChatChannel < ApplicationCable::Channel
  def subscribed
    if current_user.can_access_room?(params[:room_id])
      stream_from "chat_#{params[:room_id]}"
    else
      reject
    end
  end
end

Performance Considerations

ActionCable handles many concurrent connections, but you should follow best practices:

1. Use Redis in Production

The async adapter works for development but doesn’t persist connections. Use Redis for production:

# Gemfile
gem "redis", "~> 4.0.1"

2. Limit Connection Pool Size

Configure your Redis connection pool:

# config/cable.yml
production:
  adapter: redis
  url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %>
  channel_prefix: myapp_production
  timeout: 1
  max_pool_size: 10

3. Monitor Connections

Use ActionCable’s built-in statistics:

ActionCable.server.statistics
# => { connections: 150, channels: 230 }

4. Graceful Degradation

Always have a fallback for clients that don’t support WebSockets:

if (window.ActionCable) {
  // Use WebSocket
} else {
  // Poll via AJAX
  setInterval(fetchMessages, 5000)
}

Advanced Patterns

Private Channels

For user-specific streams:

class NotificationsChannel < ApplicationCable::Channel
  def subscribed
    stream_for current_user
  end
end

# Broadcast to a specific user
NotificationsChannel.broadcast_to(
  current_user,
  { type: "notification", message: "New activity!" }
)

Presence

Track who’s online:

class ChatChannel < ApplicationCable::Channel
  def subscribed
    @room = Room.find(params[:room_id])
    
    presence = {
      current_user: {
        id: current_user.id,
        name: current_user.name,
        avatar: current_user.avatar_url
      }
    }
    
    stream_for @room
    appear(presence)
  end
end

Testing ActionCable

ActionCable includes test helpers for both unit and integration tests.

Channel Tests

require "test_helper"

class ChatChannelTest < ActionCable::Channel::TestCase
  test "subscribes to room stream" do
    stub_connection params: { room_id: 1 }
    
    subscribe
    
    assert subscription.confirmed?
    assert_has_stream "chat_1"
  end
end

Integration Tests

require "test_helper"

class ChatIntegrationTest < ActionDispatch::IntegrationTest
  test "messages are broadcasted" do
    # Set up a WebSocket connection
    cable = ActionCable.create_connection
    
    # Subscribe to channel
    cable.subscriptions.create "ChatChannel", room_id: 1
    
    # Perform action that creates a message
    post "/messages", params: { message: "Hello", room_id: 1 }
    
    # Verify broadcast was sent
    assert_broadcast "chat_1", { content: "Hello" }
  end
end

Common Issues and Solutions

Connection Drops

If connections drop frequently, check:

  • Network stability
  • Redis connectivity
  • Server resource usage (memory, CPU)

Memory Leaks

Prevent memory leaks by:

  • Cleaning up subscriptions in unsubscribed
  • Closing connections properly
  • Monitoring Redis memory usage

Cross-Origin Connections

For cross-domain WebSocket connections, configure allowed origins:

# config/environments/production.rb
config.action_cable.allowed_request_origins = [
  "https://yourdomain.com",
  /https:\/\/.*\.yourdomain\.com/
]

ActionCable makes adding real-time features to Rails applications straightforward. By leveraging the power of WebSockets and integrating seamlessly with Rails’ existing architecture, you can build interactive features that keep users engaged without the complexity of managing separate real-time services.

See Also