Что такое ActionCable в Rails и для чего он используется?
ActionCable — встроенный WebSocket-фреймворк Rails, работающий поверх Rack hijack. Позволяет создавать «каналы» для двусторонней real-time коммуникации с браузером через pub/sub на Redis.
Что такое ActionCable
ActionCable интегрирует WebSocket в Rails-приложение как первоклассный гражданин. Он использует Rack hijack API для удержания TCP-соединения открытым и обёртывает его абстракцией «канал» (Channel). Внутри используется EventMachine или async-адаптер для обработки тысяч одновременных соединений в рамках одного процесса.
Основные компоненты
- Connection — объект соединения, аутентифицирует пользователя при handshake.
- Channel — серверный класс, наследует
ApplicationCable::Channel, определяет методыsubscribed,unsubscribedи action-методы. - Subscription — клиентская сторона, JavaScript-объект, подписывающийся на канал.
- Adapter — pub/sub бэкенд: Redis (production), Async (development), PostgreSQL (альтернатива).
Пример: чат-канал
# app/channels/chat_channel.rb
class ChatChannel < ApplicationCable::Channel
def subscribed
room = Room.find(params[:room_id])
reject unless current_user.can_access?(room)
stream_from "chat_room_#{room.id}"
end
def speak(data)
message = Message.create!(
room_id: params[:room_id],
user: current_user,
body: data["body"]
)
ActionCable.server.broadcast(
"chat_room_#{message.room_id}",
{ user: current_user.name, body: message.body }
)
end
end
# 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 (token = request.params[:token])
User.find_by!(ws_token: token)
else
reject_unauthorized_connection
end
end
end
end
// JavaScript-клиент (Stimulus / Vanilla)
import consumer from "./consumer";
const subscription = consumer.subscriptions.create(
{ channel: "ChatChannel", room_id: 42 },
{
received(data) {
console.log(data.user, data.body);
},
speak(body) {
this.perform("speak", { body });
},
}
);
Конфигурация Redis-адаптера
# config/cable.yml
production:
adapter: redis
url: <%= ENV["REDIS_URL"] %>
channel_prefix: myapp_production
Broadcast из фоновой задачи
Из любого места приложения (модель, Job, сервис) можно вызвать:
ActionCable.server.broadcast(
"notifications_#{user.id}",
{ type: "order_shipped", order_id: order.id }
)
Масштабирование
ActionCable работает в async-режиме внутри Puma. При нескольких процессах/серверах все они подписываются на один Redis-канал, поэтому broadcast доходит до нужного клиента независимо от того, к какому поду он подключён. Для Kubernetes используйте channel_prefix чтобы изолировать окружения.
Подводные камни
- Каждое открытое WebSocket-соединение удерживает поток или файловый дескриптор — при 10 000 соединений нужен тюнинг
ulimitиconfig.action_cable.worker_pool_size. - ActionCable не работает нормально за nginx без директив
proxy_http_version 1.1иproxy_set_header Upgrade $http_upgrade. - Вызов тяжёлых операций напрямую внутри action-метода канала блокирует весь пул потоков — выносите в ActiveJob.
- Redis-адаптер сохраняет подписки только до перезапуска сервера: reconnect клиента нужно обрабатывать явно через
consumer.subscriptions.reload(). - CSRF-защита на WS не работает стандартным способом — аутентифицируйте через
request.sessionили подписанный токен, не через куки напрямую. - При деплое без sticky sessions часть клиентов будет переподключаться к другому поду и снова проходить handshake — добавьте exponential backoff на клиенте.
- Тестирование через
ActionCable::Channel::TestCaseтребует явного вызоваsubscribeи проверкиtransmissions— легко пропустить при написании specs.
Common mistakes
- Сводить action cable к названию метода без lifecycle и failure path.
- Игнорировать модель runtime: Rails 8.1 строит приложение вокруг Rack, routing, controllers, Active Record, views и conventions over configuration.
- Не отделять validation, authorization, transaction boundary и business logic.
- Не обсуждать idempotency, retries, shutdown и observability.
What the interviewer is testing
- Объясняет action cable через конкретную точку lifecycle в Ruby on Rails.
- Приводит корректный минимальный пример без вымышленных методов или callbacks.
- Называет edge cases: пустые значения, ошибки, транзакции, безопасность или concurrency.
- Связывает решение с метриками, backpressure, retry policy и graceful shutdown.