Action Cable et WebSockets dans Rails : Guide Complet pour les Entretiens Techniques

Action Cable integre les WebSockets directement dans Rails. Ce guide approfondi couvre l'architecture des connexions, les channels, Solid Cable, Turbo Streams, le scaling avec Redis et les patterns de test pour les entretiens techniques.

Diagramme illustrant la communication bidirectionnelle WebSocket avec Action Cable dans une application Rails

Action Cable représente l'intégration native des WebSockets dans Ruby on Rails, permettant d'établir des connexions bidirectionnelles persistantes entre le serveur et les clients. Cette technologie constitue un pilier fondamental pour le développement d'applications temps réel modernes. Les recruteurs évaluent fréquemment la compréhension des candidats sur ce sujet, car la maîtrise d'Action Cable témoigne d'une expertise approfondie de l'écosystème Rails et des architectures événementielles.

Les questions d'entretien sur Action Cable visent généralement trois axes : l'architecture des connexions et channels, les stratégies de diffusion (broadcasting), et la configuration en production. Une préparation solide sur ces trois piliers permet de démontrer une compréhension complète du framework.

Architecture et Authentification des Connexions

Le point d'entrée d'Action Cable réside dans la classe Connection, responsable de l'authentification et de l'identification de chaque connexion WebSocket. Contrairement aux requêtes HTTP classiques, une connexion WebSocket persiste dans le temps, rendant l'authentification initiale cruciale pour la sécurité de l'application.

ruby
# 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 verified_user = User.find_by(id: cookies.encrypted[:user_id])
        verified_user
      else
        reject_unauthorized_connection
      end
    end
  end
end

La directive identified_by établit un identifiant unique pour chaque connexion, permettant de cibler des utilisateurs spécifiques lors des diffusions. La méthode find_verified_user illustre l'approche recommandée : utiliser des cookies chiffrés plutôt que des paramètres d'URL, évitant ainsi l'exposition des tokens d'authentification dans les logs serveur.

L'appel à reject_unauthorized_connection ferme immédiatement la connexion WebSocket et déclenche une erreur côté client. Cette approche défensive garantit qu'aucune connexion non authentifiée ne puisse s'établir, même partiellement.

Conception des Channels et Gestion des Abonnements

Les channels constituent l'abstraction principale pour organiser la logique temps réel. Chaque channel encapsule un domaine fonctionnel spécifique et gère le cycle de vie des abonnements ainsi que la réception des messages.

ruby
# app/channels/chat_channel.rb
class ChatChannel < ApplicationCable::Channel
  def subscribed
    room = ChatRoom.find(params[:room_id])
    if room.members.include?(current_user)
      stream_for room
    else
      reject
    end
  end

  def receive(data)
    message = ChatMessage.create!(
      room_id: params[:room_id],
      user: current_user,
      body: data["body"]
    )
    ChatChannel.broadcast_to(
      message.room,
      { id: message.id, body: message.body, user: current_user.name }
    )
  end

  def unsubscribed
    AppearanceTracker.mark_offline(current_user)
  end
end

La méthode subscribed effectue une vérification d'autorisation avant d'établir le stream. L'utilisation de stream_for avec un objet Active Record génère automatiquement un nom de stream unique basé sur le modèle, simplifiant la gestion des identifiants.

La méthode receive traite les messages entrants des clients. Elle persiste le message en base de données puis le diffuse à tous les abonnés du salon. Cette séparation entre persistance et diffusion permet de garantir la cohérence des données même en cas de déconnexion temporaire.

Le callback unsubscribed gère le nettoyage lors de la fermeture de connexion, permettant par exemple de mettre à jour le statut de présence des utilisateurs.

Intégration JavaScript Côté Client

L'établissement de la connexion WebSocket côté client s'effectue via le consumer JavaScript fourni par Rails. La configuration des callbacks permet de réagir aux différents événements du cycle de vie de la connexion.

app/javascript/channels/chat_channel.jsjavascript
import consumer from "./consumer"

const chatChannel = consumer.subscriptions.create(
  { channel: "ChatChannel", room_id: roomId },
  {
    connected() {
      console.log("Connected to chat room", roomId)
    },

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

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

    sendMessage(body) {
      this.perform("receive", { body: body })
    }
  }
)

Le premier argument de subscriptions.create spécifie le channel et les paramètres transmis à la méthode subscribed côté serveur. Les callbacks connected et disconnected permettent d'adapter l'interface utilisateur selon l'état de la connexion.

La méthode received traite les messages diffusés par le serveur, tandis que sendMessage illustre l'envoi de données vers le serveur via this.perform. Cette méthode invoque la méthode correspondante dans le channel Ruby.

Solid Cable : L'Adaptateur Natif de Rails 8

Rails 8 introduit Solid Cable comme adaptateur par défaut, éliminant la dépendance à Redis pour de nombreux cas d'usage. Solid Cable utilise la base de données existante pour la messagerie, simplifiant considérablement l'infrastructure de déploiement.

yaml
# config/cable.yml (Rails 8 default)
development:
  adapter: solid_cable

production:
  adapter: solid_cable
  connects_to:
    database:
      writing: cable
  polling_interval: 0.1.seconds
  message_retention: 1.day

La configuration multi-base de données permet d'isoler les opérations Action Cable sur une base dédiée, évitant toute contention avec les requêtes applicatives principales.

ruby
# config/database.yml (Rails 8 multi-database)
production:
  primary:
    <<: *default
    database: myapp_production
  cable:
    <<: *default
    database: myapp_cable_production
    migrations_paths: db/cable_migrate

Cette séparation offre plusieurs avantages : les opérations de polling fréquentes n'impactent pas les performances de la base principale, et la rétention des messages peut être configurée indépendamment. Le paramètre message_retention permet de purger automatiquement les anciens messages, maintenant une empreinte mémoire maîtrisée.

Stratégies de Diffusion et Patterns Courants

La diffusion de messages peut s'effectuer depuis différents contextes applicatifs. Les callbacks de modèle permettent de déclencher des notifications automatiques lors des modifications de données.

ruby
# Broadcasting from a model callback
class Notification < ApplicationRecord
  belongs_to :user
  after_create_commit :broadcast_to_user

  private

  def broadcast_to_user
    ActionCable.server.broadcast(
      "notifications_#{user_id}",
      { id: id, title: title, read: false }
    )
  end
end

# Broadcasting from a background job
class DashboardUpdateJob < ApplicationJob
  queue_as :default

  def perform(dashboard_id)
    dashboard = Dashboard.find(dashboard_id)
    stats = dashboard.compute_stats
    ActionCable.server.broadcast(
      "dashboard_#{dashboard_id}",
      { stats: stats, updated_at: Time.current.iso8601 }
    )
  end
end

L'utilisation de after_create_commit garantit que la diffusion n'intervient qu'après la validation de la transaction, évitant de notifier les clients pour des données qui pourraient être annulées par un rollback.

Les jobs en arrière-plan conviennent particulièrement aux calculs coûteux dont les résultats doivent être diffusés. Cette approche découple le calcul de la diffusion et permet un meilleur contrôle de la charge serveur.

Intégration avec Turbo Streams

Hotwire Turbo simplifie drastiquement l'implémentation du temps réel en automatisant la diffusion et le rendu des mises à jour.

ruby
# app/models/message.rb
class Message < ApplicationRecord
  belongs_to :chat_room
  broadcasts_to :chat_room
end

# app/views/chat_rooms/show.html.erb
<%= turbo_stream_from @chat_room %>
<div id="messages">
  <%= render @chat_room.messages %>
</div>

La directive broadcasts_to configure automatiquement les callbacks de diffusion pour les opérations CRUD. Côté vue, turbo_stream_from établit l'abonnement WebSocket et applique automatiquement les mises à jour DOM reçues.

Cette intégration réduit considérablement le code boilerplate nécessaire pour les fonctionnalités temps réel standard, tout en restant extensible pour les cas d'usage personnalisés.

Configuration Redis pour la Haute Disponibilité

Pour les déploiements à grande échelle ou multi-serveurs, Redis demeure la solution de référence en raison de ses performances et de sa capacité à gérer des clusters de serveurs applicatifs.

ruby
# config/cable.yml — Redis adapter for high-scale production
production:
  adapter: redis
  url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %>
  channel_prefix: myapp_production

Le paramètre channel_prefix isole les messages de l'application, permettant de partager une instance Redis entre plusieurs applications ou environnements. Cette configuration s'avère indispensable lors de l'utilisation de services Redis managés partagés.

Redis offre également des fonctionnalités avancées comme le pub/sub natif et la persistance optionnelle, garantissant une fiabilité accrue pour les applications critiques.

Tests des Channels Action Cable

Rails fournit une suite complète d'outils de test pour valider le comportement des channels, incluant les helpers pour simuler les connexions et vérifier les diffusions.

ruby
# test/channels/chat_channel_test.rb
require "test_helper"

class ChatChannelTest < ActionCable::Channel::TestCase
  test "subscribes to a valid room" do
    room = chat_rooms(:general)
    user = users(:alice)
    stub_connection current_user: user

    subscribe room_id: room.id

    assert subscription.confirmed?
    assert_has_stream_for room
  end

  test "rejects subscription for unauthorized user" do
    room = chat_rooms(:private)
    user = users(:outsider)
    stub_connection current_user: user

    subscribe room_id: room.id

    assert subscription.rejected?
  end

  test "broadcasts messages to room subscribers" do
    room = chat_rooms(:general)
    stub_connection current_user: users(:alice)
    subscribe room_id: room.id

    assert_broadcast_on(room, hash_including(body: "Hello")) do
      perform :receive, body: "Hello"
    end
  end
end

La méthode stub_connection permet de simuler une connexion authentifiée sans établir de véritable WebSocket. Les assertions assert_has_stream_for et assert_broadcast_on vérifient respectivement l'établissement des streams et le contenu des messages diffusés.

Ces tests unitaires complètent les tests d'intégration système, offrant une couverture rapide et ciblée des règles métier implémentées dans les channels.

Passez à la pratique !

Conclusion

Action Cable représente une solution mature et intégrée pour les fonctionnalités temps réel dans Rails. La compréhension approfondie de son architecture, depuis l'authentification des connexions jusqu'aux stratégies de diffusion, constitue un atout majeur lors des entretiens techniques. Les évolutions récentes comme Solid Cable et l'intégration Turbo Streams témoignent de l'engagement continu de l'équipe Rails à simplifier le développement d'applications réactives.

La maîtrise des patterns de test spécifiques à Action Cable démontre également une approche professionnelle du développement, garantissant la fiabilité des fonctionnalités temps réel en production.

Pratiquer les questions d'entretien ActionCable et WebSockets

Passe à la pratique !

Teste tes connaissances avec nos simulateurs d'entretien et tests techniques.

Tags

#ruby on rails
#action cable
#websockets
#temps reel
#solid cable
#rails 8

Partager

Articles similaires