Введение

Если вы когда-либо хотели, чтобы ваше приложение на Rails выглядело живым, вам стоит обратить внимание на Action Cable. Это встроенный способ Rails работы с WebSockets, что в основном означает, что ваш сервер и браузер могут оставаться подключенными и общаться в реальном времени. Больше никаких обновлений страниц, никакого громоздкого опроса — обновления появляются мгновенно.

В этом руководстве мы создадим забавную небольшую демонстрацию: счетчик трафика в реальном времени. Когда кто-то заходит на ваш сайт, мы будем регистрировать этот визит в базе данных, увеличивать некоторые счетчики в памяти и транслировать обновленные числа всем онлайн. Закрыли вкладку? Количество онлайн-пользователей сразу же уменьшается. Это просто, но показывает магию реального времени, которую Action Cable приносит в Rails 7.

Шаг 1: Модель визита

Сначала давайте создадим модель для регистрации визитов.

bin/rails g model Visit session_id:string ip:string user_agent:string
bin/rails db:migrate
# app/models/visit.rb
class Visit < ApplicationRecord
end

Шаг 2: Создание пользовательского подключения

Rails уже предоставляет вам два базовых класса в app/channels/application_cable/:
- channel.rb → базовый класс для всех ваших каналов.
- connection.rb → базовый класс для всех ваших WebSocket подключений.

Мы обычно не редактируем их. Вместо этого мы расширяем их, создавая свой собственный класс подключения внутри app/channels/. Это позволяет сохранить стандартные настройки чистыми и разместить нашу логику в понятном месте.

# app/channels/traffic_connection.rb
module ApplicationCable
  class TrafficConnection < Connection
    identified_by :session_id, :ip, :user_agent

    def connect
      self.session_id = request.session.id
      self.ip         = request.ip
      self.user_agent = request.user_agent
    end
  end
end

Шаг 3: Счетчики в памяти

Мы будем использовать атомарные счетчики, чтобы сделать все быстро. При запуске мы получаем общее количество визитов из БД и начинаем с нуля для онлайн-пользователей.

# config/initializers/traffic_counter.rb
$online_users = Concurrent::AtomicFixnum.new(0)
$total_visits = Concurrent::AtomicFixnum.new(Visit.count)

Шаг 4: Канал

Вот где начинается веселье — регистрируем визит, обновляем счетчики и транслируем.

# app/channels/traffic_channel.rb
class TrafficChannel < ApplicationCable::Channel
  def subscribed
    # регистрируем визит
    Visit.create!(session_id: connection.session_id,
                  ip: connection.ip,
                  user_agent: connection.user_agent)

    # обновляем счетчики
    $online_users.increment
    $total_visits.increment

    stream_from "traffic_channel"
    broadcast_counts
  end

  def unsubscribed
    $online_users.decrement
    broadcast_counts
  end

  private

  def broadcast_counts
    ActionCable.server.broadcast("traffic_channel", {
      online: $online_users.value,
      total:  $total_visits.value
    })
  end
end

Шаг 5: Сторона JavaScript

Когда вы запускаете bin/rails generate channel Traffic, Rails создает два файла: Ruby-канал и файл подписки JavaScript. По умолчанию JS-файл импортирует consumer.js, который Rails также настроил для вас при создании приложения.

// app/javascript/channels/traffic_channel.js
import consumer from "./consumer"

consumer.subscriptions.create("TrafficChannel", {
  received(data) {
    document.querySelector("#online-count").textContent = data.online
    document.querySelector("#total-count").textContent = data.total
  }
})

Файл consumer.js создается Rails, когда вы создаете новое приложение. Он настраивает глобальное WebSocket подключение к /cable и выглядит так:

// app/javascript/channels/consumer.js
import { createConsumer } from "@rails/actioncable"

export default createConsumer()

Шаг 6: Вид

<h1>🚦 Счетчик трафика в реальном времени</h1>
<p>В настоящее время онлайн: <span id="online-count">0</span></p>
<p>Всего визитов: <span id="total-count"><%= Visit.count %></span></p>

Шаг 7: Маршруты

# config/routes.rb
Rails.application.routes.draw do
  root "home#index"
  mount ActionCable.server => "/cable"
end

Шаг 8: Попробуйте

Запустите ваше приложение с помощью bin/dev. Откройте ваш сайт в нескольких вкладках. Счетчик онлайн-пользователей увеличивается с каждой вкладкой и уменьшается, когда вы их закрываете. Общее количество визитов просто продолжает расти. Супер приятно наблюдать!

Заметки для продакшена

- Несколько рабочих процессов Puma? Используйте Redis для счетчиков — память не будет синхронизироваться между процессами.
- Хотите отслеживать фактических пользователей вместо сессий? Добавьте current_user в подключение.
- Хранение визитов в БД означает, что вы можете проводить аналитику позже, например, ежедневный трафик или разбивку по браузерам.

И это всё — счетчик трафика в реальном времени в Rails 7 с Action Cable. Менее чем на 100 строках кода вы получили мгновенные обновления и представление о том, что могут сделать WebSockets. Эта же настройка работает для уведомлений, панелей управления и всех видов функций в реальном времени. Как только вы попробуете это, вам захочется использовать это повсюду.