v7.0.0
Veja mais em rubyonrails.org: Mais Ruby on Rails

Action Cable Overview

Neste guia, você irá aprender como Action Cable funciona e como usar WebSockets para incorporar funcionalidades de tempo real em sua aplicação Rails.

Ao ler este guia você aprenderá:

1 O que é o Action Cable?

O Action Cable integra-se perfeitamente WebSockets com o resto da sua aplicação Rails. Permite que recursos em tempo real sejam escritos em Ruby no mesmo estilo e forma que o resto de sua aplicação Rails, ao mesmo tempo em que possui desempenho e escabilidade. Isso é uma oferta full-stack que fornece um framework Javascript do lado do cliente (client-side) e um framework Ruby do lado do servidor (server-side). Você tem acesso ao seu model de domínio completo escrito com o Active Record ou o ORM de sua escolha.

2 Terminologia

O Action Cable utiliza WebSockets ao invés do protocolo de requisição-resposta HTTP. Tanto o Action Cable quanto os WebSockets apresentam uma terminologia menos familiar:

2.1 Conexões

Conexões formam a base do relacionamento cliente-servidor. Um único servidor Action Cable pode lidar com várias instâncias de conexão. Ele possui uma instância de conexão para cada conexão via WebSocket. Um único usuário pode ter vários WebSockets abertos para sua aplicação se ele utilizar várias abas do navegador ou dispositivos.

2.2 Consumidores

O client de uma conexão WebSocket é chamado de consumidor. No Action Cable, o consumidor é criado pelo framework JavaScript do lado do cliente.

2.3 Canais

Cada consumidor pode, por sua vez, se inscrever em vários canais. Cada canal encapsula uma unidade lógica de trabalho, semelhante ao que um controller faz em uma configuração MVC regular. Por exemplo, você pode ter um ChatChannel e um AppearancesChannel, e um consumidor pode ser inscrito em um ou em ambos os canais. Um consumidor deve se inscrever em, pelo menos, um canal.

2.4 Assinantes

Quando o consumidor está inscrito em um canal, ele age como um assinante. A conexão entre o assinante e o canal é, adivinhe, chamada de assinatura. Um consumidor pode atuar como um assinante de um determinado canal qualquer número de vezes. Por exemplo, um consumidor pode se inscrever em várias salas de chat ao mesmo tempo. (E lembre-se que um usuário físico pode ter vários consumidores, um por aba/dispositivo aberto para sua conexão).

2.5 Pub/Sub

Pub/Sub ou Publish-Subscribe, refere-se a um paradigma de fila de mensageria o qual os remetentes de uma informação (publishers) enviam dados à uma classe abstrata de destinatários (subscribers) sem especificar um destinatário individual. Action Cable utiliza essa abordagem para manter a comunicação entre o servidor e diversos clientes.

2.6 Broadcastings

Uma transmissão (broadcasting) é um link pub/sub em que qualquer coisa transmitida pela emissora é enviada diretamente para os assinantes (subscribers) do canal que estão transmitindo essa transmissão. Cada canal pode transmitir zero ou mais transmissões.

3 Componentes Server-Side

3.1 Connections

Para cada WebSocket aceito pelo servidor, um objeto connection é instanciado. Esse objeto se torna o pai de todos os channel subscriptions que são criados dali pra frente. A connection em si não lida com nenhuma lógica específica da aplicação além da autenticação e autorização. O cliente de um WebSocket connection é chamado de consumer. Um usuário individual criará um par de consumer-connection para cada aba do navegador, janela ou dispositivo que ele tiver aberto.

Connections são instâncias de ApplicationCable::Connection, que estendem de ActionCable::Connection::Base. Em ApplicationCable::Connection, você autoriza a connection recebida e procede para estabelecê-la, caso o usuário possa ser identificado.

3.1.1 Configuração de uma Connection
# 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

Aqui, identified_by designa um identificador de connection que pode ser usado para encontrar uma connection específica mais tarde. Note que qualquer coisa marcada como um identificador criará automaticamente um delegate pelo mesmo nome em qualquer instância de channel criada a partir da connection.

Esse exemplo se baseia no fato de que você já lidou a autenticação do usuário em algum outro lugar na sua aplicação e essa autenticação bem sucedida definiu um cookie criptografado com o ID do usuário.

O cookie é então enviado automaticamente para a instância da connection quando há a tentativa de criar uma nova connection, e você o usa para definir o current_user. Ao identificar a connection para o mesmo usuário, você também garante que você pode retornar todas as connections em aberto para um usuário específico (e potencialmente desconectá-los, caso o usuário seja deletado ou desautorizado).

Se a sua abordagem de autenticação inclui o uso de uma sessão (session), você usa o armazenamento com cookies para a sessão, seu cookie de sessão é denominado _session e a chave de ID do usuário é user_id você pode usar esta abordagem:

verified_user = User.find_by(id: cookies.encrypted['_session']['user_id'])
3.1.2 Lidando com Exceções (Exceptions)

Por padrão, exceções não tratadas são capturadas e registradas no logger do Rails. Se você gostaria de interceptar globalmente essas exceções e relatá-las a um serviço externo de rastreamento de bugs, por exemplo, você pode fazer isso com rescue_from:

# app/channels/application_cable/connection.rb
module ApplicationCable
  class Connection < ActionCable::Connection::Base
    rescue_from StandardError, with: :report_error

    private

    def report_error(e)
      SomeExternalBugtrackingService.notify(e)
    end
  end
end

3.2 Channels

O channel encapsula uma unidade lógica de trabalho, parecido com o que um controller faz em um MVC comum. Por padrão, o Rails cria uma classe pai ApplicationCable::Channel (que estende ActionCable::Channel::Base) para encapsular a lógica compartilhada entre seus channels.

3.2.1 Configuração do Channel pai
# app/channels/application_cable/channel.rb
module ApplicationCable
  class Channel < ActionCable::Channel::Base
  end
end

Então, você criaria suas próprias classes de channel. Por exemplo, você poderia ter um ChatChannel e um AppearanceChannel:

# app/channels/chat_channel.rb
class ChatChannel < ApplicationCable::Channel
end
# app/channels/appearance_channel.rb
class AppearanceChannel < ApplicationCable::Channel
end

Um consumer poderia então ser inscrito para qualquer ou ambos os channels.

3.2.2 Subscriptions

Consumers se inscrevem a channels, agindo como subscribers. A connection deles é chamada de subscription. Mensagens produzidas são então roteadas para esses channel subscriptions baseados em um identificador enviado pelo channel consumer.

# app/channels/chat_channel.rb
class ChatChannel < ApplicationCable::Channel
  # Chamado quando o *consumer* tornou-se um *subscriber*
  # desse *channel* com sucesso.
  def subscribed
  end
end
3.2.3 Lidando com Exceções

Tal como acontece com ApplicationCable::Connection, você também pode usar rescue_from em um canal específico para lidar com exceções levantadas:

# app/channels/chat_channel.rb
class ChatChannel < ApplicationCable::Channel
  rescue_from 'MyError', with: :deliver_error_message

  private

  def deliver_error_message(e)
    broadcast_to(...)
  end
end

4 Componentes Client-Side

4.1 Conexões

Consumidores precisam de uma instância da conexão do seu lado. Esta conexão pode ser estabelecida usando o seguinte JavaScript, que é gerado por padrão pelo Rails:

4.1.1 Conectar o Consumidor
// app/javascript/channels/consumer.js
// Action Cable provides the framework to deal with WebSockets in Rails.
// You can generate new channels where WebSocket features live using the `bin/rails generate channel` command.

import { createConsumer } from "@rails/actioncable"

export default createConsumer()

Isto vai preparar um consumidor que conectará em /cable em seu servidor por padrão. A conexão não vai ser estabelecida até que você também tenha especificado ao menos uma inscrição que você tem interesse em ter.

O consumidor pode optar receber um argumento que especifica a URL para se conectar. Ela pode ser uma string ou uma função que retorna uma string que vai ser chamada quando o WebSocket é aberto.

// Especifica uma _URL_ diferente para se conectar
createConsumer('https://ws.example.com/cable')

// Utiliza uma função para gerar a _URL_ dinamicamente
createConsumer(getWebSocketURL)

function getWebSocketURL() {
  const token = localStorage.get('auth-token')
  return `https://ws.example.com/cable?token=${token}`
}
4.1.2 Assinante

Um consumidor se torna um assinante criando uma assinatura para um canal:

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

consumer.subscriptions.create({ channel: "ChatChannel", room: "Best Room" })

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

consumer.subscriptions.create({ channel: "AppearanceChannel" })

Enquanto isto cria uma assinatura, a funcionalidade necessária para responder aos dados recebidos será descrita mais tarde.

Um consumidor pode agir como um assinante para um dado canal qualquer número de vezes. Por exemplo, um consumidor pode assinar várias salas de chat ao mesmo tempo.

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

consumer.subscriptions.create({ channel: "ChatChannel", room: "1st Room" })
consumer.subscriptions.create({ channel: "ChatChannel", room: "2nd Room" })

5 Interações Cliente-Servidor

5.1 Streams

Streams fornecem o mecanismo por onde os channels direcionam o conteúdo publicado (broadcasts) para seus assinantes. Por exemplo, o código a seguir usa stream_from para se inscrever na transmissão (broadcasting) chamada chat_Best Room quando o valor do parâmetro :room é "Best Room":

# app/channels/chat_channel.rb
class ChatChannel < ApplicationCable::Channel
  def subscribed
    stream_from "chat_#{params[:room]}"
  end
end

Então, de outro lugar em sua aplicação Rails, é possível transmitir para essa room chamando broadcast:

ActionCable.server.broadcast("chat_Best Room", { body: "This Room is Best Room." })

Se você possui um stream que está relacionada a uma model, então o nome da transmissão pode ser gerada a partir do channel e model. Por exemplo, o código a seguir usa stream_for para se inscrever em uma transmissão como comments:Z2lkOi8vVGVzdEFwcC9Qb3N0LzE, onde Z2lkOi8vVGVzdEFwcC9Qb3N0LzE corresponde ao ID Global da model Post.

class CommentsChannel < ApplicationCable::Channel
  def subscribed
    post = Post.find(params[:id])
    stream_for post
  end
end

Você pode agora transmitir para esse channel chamando broadcast_to:

CommentsChannel.broadcast_to(@post, @comment)

5.2 Broadcastings

Um broadcasting é um link pub/sub em que qualquer coisa transmitida por um publisher é encaminhada diretamente para os assinantes do channel, este, que está transmitindo o broadcasting de mesmo nome. Cada channel pode estar transmitindo zero ou mais broadcastings.

Broadcastings são puramente filas de espera online e dependentes de tempo. Se um consumidor não estiver transmitindo (assinante de um determinado channel), ele não vai receber o broadcast caso se conecte mais tarde.

5.3 Subscriptions

Quando um consumidor está inscrito em um channel, ele age como assinante (subscriber). Essa conexão é chamada de assinatura (subscription). Mensagens recebidas são então direcionadas para esses inscritos do channel baseadas em um identificador enviado pelo cable consumer

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

consumer.subscriptions.create({ channel: "ChatChannel", room: "Best Room" }, {
  received(data) {
    this.appendLine(data)
  },

  appendLine(data) {
    const html = this.createLine(data)
    const element = document.querySelector("[data-chat-room='Best Room']")
    element.insertAdjacentHTML("beforeend", html)
  },

  createLine(data) {
    return `
      <article class="chat-line">
        <span class="speaker">${data["sent_by"]}</span>
        <span class="body">${data["body"]}</span>
      </article>
    `
  }
})

5.4 Passando Parâmetros para Channel

Você pode passar parâmetros do lado do cliente para o lado do servidor quando cria a subscription. Por exemplo:

# app/channels/chat_channel.rb
class ChatChannel < ApplicationCable::Channel
  def subscribed
    stream_from "chat_#{params[:room]}"
  end
end

Um objeto passado como primeiro argumento em subscriptions.create torna-se a hash params em cable channel. A keyword channel é obrigatória:

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

consumer.subscriptions.create({ channel: "ChatChannel", room: "Best Room" }, {
  received(data) {
    this.appendLine(data)
  },

  appendLine(data) {
    const html = this.createLine(data)
    const element = document.querySelector("[data-chat-room='Best Room']")
    element.insertAdjacentHTML("beforeend", html)
  },

  createLine(data) {
    return `
      <article class="chat-line">
        <span class="speaker">${data["sent_by"]}</span>
        <span class="body">${data["body"]}</span>
      </article>
    `
  }
})
# Em algum lugar do seu app isso pode ser chamado, talvez,
# por um novo NewCommentJob.
ActionCable.server.broadcast(
  "chat_#{room}",
  {
    sent_by: 'Paul',
    body: 'This is a cool chat app.'
  }
)

5.5 Retransmitindo uma Mensagem

Um caso de uso comum é ter que retransmitir uma mensagem enviada por um cliente para qualquer outro cliente conectado

# app/channels/chat_channel.rb
class ChatChannel < ApplicationCable::Channel
  def subscribed
    stream_from "chat_#{params[:room]}"
  end

  def receive(data)
    ActionCable.server.broadcast("chat_#{params[:room]}", data)
  end
end
// app/javascript/channels/chat_channel.js
import consumer from "./consumer"

const chatChannel = consumer.subscriptions.create({ channel: "ChatChannel", room: "Best Room" }, {
  received(data) {
    // data => { sent_by: "Paul", body: "This is a cool chat app." }
  }
}

chatChannel.send({ sent_by: "Paul", body: "This is a cool chat app." })

A retransmissão vai ser recebida por todos os clientes conectados, incluindo o cliente que enviou a mensagem. Note que params são os mesmos de quando você se inscreveu no channel

6 Full-Stack Exemplos

As seguintes etapas de configuração são comuns em ambos os exemplos:

  1. Configurando conexão.
  2. Configurando o canal pai.
  3. Conectando-se ao consumidor.

6.1 Exemplo 1: Aspectos do usuário

Aqui está um exemplo simples de um canal que rastreia se um usuário está online ou não e em que página ele está (isso é útil para criar recursos de presença, como mostrar um ponto verde ao lado do nome do usuário se ele estiver online).

Criando o canal de aspectos no back-end:

# app/channels/appearance_channel.rb
class AppearanceChannel < ApplicationCable::Channel
  def subscribed
    current_user.appear
  end

  def unsubscribed
    current_user.disappear
  end

  def appear(data)
    current_user.appear(on: data['appearing_on'])
  end

  def away
    current_user.away
  end
end

Quando uma inscrição é inicializada, o callback subscribed é acionado e aproveitamos a oportunidade para dizer "o usuário atual realmente apareceu". Essa API de aparecer/desaparecer pode ser apoiada via Redis, um banco de dados, ou qualquer outro.

Criando a inscrição do canal de aspectos no lado do cliente:

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

consumer.subscriptions.create("AppearanceChannel", {
  // Chamado uma vez quando a assinatura é criada.
  initialized() {
    this.update = this.update.bind(this)
  },

  // Chamado quando a assinatura está pronta para uso no servidor.
  connected() {
    this.install()
    this.update()
  },

  // Chamado quando a conexão WebSocket é fechada.
  disconnected() {
    this.uninstall()
  },

  // Chamado quando a assinatura é rejeitada pelo servidor.
  rejected() {
    this.uninstall()
  },

  update() {
    this.documentIsActive ? this.appear() : this.away()
  },

  appear() {
    // Chama `AppearanceChannel#appear(data)` no servidor.
    this.perform("appear", { appearing_on: this.appearingOn })
  },

  away() {
    // Chama `AppearanceChannel#away` no servidor.
    this.perform("away")
  },

  install() {
    window.addEventListener("focus", this.update)
    window.addEventListener("blur", this.update)
    document.addEventListener("turbolinks:load", this.update)
    document.addEventListener("visibilitychange", this.update)
  },

  uninstall() {
    window.removeEventListener("focus", this.update)
    window.removeEventListener("blur", this.update)
    document.removeEventListener("turbolinks:load", this.update)
    document.removeEventListener("visibilitychange", this.update)
  },

  get documentIsActive() {
    return document.visibilityState === "visible" && document.hasFocus()
  },

  get appearingOn() {
    const element = document.querySelector("[data-appearing-on]")
    return element ? element.getAttribute("data-appearing-on") : null
  }
})
6.1.1 Interação Cliente-Servidor
  1. Cliente conecta-se ao Servidor via App.cable = ActionCable.createConsumer("ws://cable.example.com"). (cable.js). O Servidor identifica esta conexão por current_user.

  2. Cliente se inscreve no canal de apresentação via consumer.subscriptions.create({ channel: "AppearanceChannel" }). (appearance_channel.js)

  3. Servidor reconhece que uma nova assinatura foi iniciada para o canal de aspectos e executa seu callback subscribed, chamando o método appear em current_user. (appearance_channel.rb)

  4. Cliente reconhece que uma assinatura foi estabelecida e chama connected (appearance_channel.js) que por sua vez chama install e appear. appear chama AppearanceChannel#appear(data) no servidor e fornece um hash de dados de { appearing_on: this.appearingOn }. Isso é possível porque a instância do canal do lado do servidor expõe automaticamente todos os métodos públicos declarados na classe (menos os retornos de chamada), para que possam ser alcançados como chamadas de procedimento remoto por meio do método perform de uma assinatura.

  5. Servidor recebe a solicitação da ação appear no canal de aspectos para a conexão identificada por current_user (appearance_channel.rb). Servidor recupera os dados com a chave :appearing_on do hash de dados e define como o valor da chave :on sendo passada para current_user.appear.

6.2 Exemplo 2: Recebendo novas notificações da web

O exemplo de aspectos era todo sobre expor a funcionalidade do servidor ao cliente pela conexão WebSocket. Mas o melhor do WebSockets é que ele é uma via de mão dupla. Portanto, agora vamos mostrar um exemplo em que o servidor invoca uma ação no cliente.

Este é um canal de notificação da web que permite acionar notificações da web no lado do cliente quando você transmite para os streams certos:

Criando o canal de notificações da web do lado do servidor:

# app/channels/web_notifications_channel.rb
class WebNotificationsChannel < ApplicationCable::Channel
  def subscribed
    stream_for current_user
  end
end

Criando a assinatura do canal de notificações da web no lado do cliente

// app/javascript/channels/web_notifications_channel.js
// O lado do cliente assume que você já solicitou o direito 
// de enviar notificações da web.
import consumer from "./consumer"

consumer.subscriptions.create("WebNotificationsChannel", {
  received(data) {
    new Notification(data["title"], { body: data["body"] })
  }
})

Transmite o conteúdo para uma instância do canal de notificação da web de qualquer lugar da aplicação:

# Em algum lugar da aplicação, isso é chamado, talvez de um NewCommentJob
WebNotificationsChannel.broadcast_to(
  current_user,
  title: 'New things!',
  body: 'All the news fit to print'
)

A chamada WebNotificationsChannel.broadcast_to coloca uma mensagem na fila pubsub do adaptador de assinaturas atual sob um nome de transmissão separado para cada usuário. Exemplo, para um usuário com ID 1, o nome da transmissão seria web_notifications:1.

O canal foi instruído a transmitir tudo que chegar a web_notifications:1 diretamente para o cliente, invocando o callback received. Os dados passados como argumento são o hash enviado como o segundo parâmetro para a chamada de transmissão do lado do servidor, JSON codificado, para a transição, e também descompactado, para o argumento de dados que chega como received.

6.3 Exemplos Mais Completos

Veja o repositório rails/actioncable-examples para um exemplo completo de como configurar o Action Cable em uma aplicação Rails, e adicionar canais.

7 Configuração

O Action Cable tem duas configurações necessárias: um adaptador de assinatura (subscription) e origens de requisição (request) permitidas.

7.1 Adaptador de assinatura

Por padrão, Action Cable procura um arquivo de configuração em config/cable.yml. O arquivo deve especificar um adaptador (adapter) para cada ambiente Rails. Veja o Dependências seção para obter informações adicionais sobre adaptadores.

development:
  adapter: async

test:
  adapter: test

production:
  adapter: redis
  url: redis://10.10.3.153:6381
  channel_prefix: appname_production
7.1.1 Configuração do Adaptador (Adapter)

Abaixo está uma lista dos adaptadores de assinatura disponíveis para usuários finais.

7.1.1.1 Adaptador Async

O adaptador assíncrono destina-se ao desenvolvimento / teste e não deve ser usado em produção.

7.1.1.2 Adaptador Redis

O adaptador Redis requer que os usuários forneçam uma URL apontando para o servidor Redis. Além disso, um channel_prefix pode ser fornecido para evitar colisões de nome de canal ao usar o mesmo servidor Redis para vários aplicativos. Veja a Documentação Redis PubSub para mais detalhes.

O adaptador Redis também oferece suporte a conexões SSL/TLS. Os parâmetros SSL/TLS necessários podem ser passados na chave ssl_params no arquivo yaml de configuração.

production:
  adapter: redis
  url: rediss://10.10.3.153:tls_port
  channel_prefix: appname_production
  ssl_params: {
    ca_file: "/path/to/ca.crt"
  }

As opções passadas para ssl_params são enviadas diretamente para o método OpenSSL::SSL::SSLContext#set_params e podem ser qualquer atributo válido do contexto SSL. Consulte a documentação do OpenSSL::SSL::SSLContext para outros atributos disponíveis.

Se você estiver usando certificados autoassinados para o adaptador redis atrás de um firewall e optar por ignorar a verificação de certificado, o ssl verify_mode deve ser definido como OpenSSL::SSL::VERIFY_NONE.

Não é recomendado usar VERIFY_NONE em ambiente de produção a menos que você entenda as implicações de segurança. Para definir esta opção para o adaptador Redis, a configuração deve ser ssl_params: { verify_mode: <%= OpenSSL::SSL::VERIFY_NONE %> }.

7.1.1.3 Adaptador PostgreSQL

O adaptador PostgreSQL usa o pool de conexão do Active Record e, portanto, o configuração do banco de dados config/database.yml do aplicativo, para sua conexão. Isso pode mudar no futuro. #27214

7.2 Origens de Requisição Permitidas

Action Cable só aceitará requisições de origens especificadas, que são passado para a configuração do servidor como um array. As origens podem ser instâncias de strings ou expressões regulares, contra as quais uma verificação de correspondência será realizada.

config.action_cable.allowed_request_origins = ['https://rubyonrails.com', %r{http://ruby.*}]

Para desativar e permitir requisições de qualquer origem:

config.action_cable.disable_request_forgery_protection = true

Por padrão, o Action Cable permite todas as requisições de localhost:3000 durante a execução no ambiente de desenvolvimento.

7.3 Configuração do Consumidor

Para configurar a URL, adicione uma chamada para action_cable_meta_tag em seu layout HTML HEAD. Isso usa uma URL ou caminho (path) normalmente definido via config.action_cable.url nos arquivos de configuração de ambiente.

7.4 Configuração do Worker Pool

O pool de workers é usado para executar retornos (callbacks) de conexão e ações de channel em isolamento da thread principal do servidor. Action Cable permite que a aplicação configure o número de threads processados ​​simultaneamente no worker pool.

config.action_cable.worker_pool_size = 4

Além disso, observe que seu servidor deve fornecer pelo menos o mesmo número de conexões de banco de dados que você tem de workers. O tamanho do worker pool de trabalho padrão é definido como 4, então isso significa que você deve disponibilizar pelo menos 4 conexões de banco de dados. Você pode mudar isso em config/database.yml através do atributo pool.

7.5 Log no lado do client

O log client side é desabilitado por padrão. Você pode habilitar essa configuração em ActionCable.logger.enable trocando para true.

import * as ActionCable from '@rails/actioncable'

ActionCable.logger.enabled = true

7.6 Outras Configurações

A outra opção comum de configurar são as tags de log aplicadas ao logger por conexão. Aqui está um exemplo que usa o ID da conta do usuário, se disponível, senão "sem conta" durante a marcação:

config.action_cable.log_tags = [
  -> request { request.env['user_account_id'] || "no-account" },
  :action_cable,
  -> request { request.uuid }
]

Para obter uma lista completa de todas as opções de configuração, consulte o classe ActionCable::Server::Configuration.

8 Executando Servidores Cable Autônomos

8.1 Na Aplicação

O Action Cable pode ser executado junto com sua aplicação Rails. Por exemplo, para escutar as requisições WebSocket em /websocket, especifique esse caminho para config.action_cable.mount_path:

# config/application.rb
class Application < Rails::Application
  config.action_cable.mount_path = '/websocket'
end

Você pode usar ActionCable.createConsumer() para conectar ao cable server se action_cable_meta_tag for invocado no layout. Caso contrário, um caminho é especificado como primeiro argumento para createConsumer (e.g. ActionCable.createConsumer("/websocket")).

Para cada instância de seu servidor que você cria e para cada worker que seu servidor instancia, você também terá uma nova instância do Action Cable, mas o adaptador Redis ou PostgreSQL mantém as mensagens sincronizadas entre as conexões.

8.2 Autônomo

Os cable servers a cabo podem ser separados do servidor de aplicação normal. Este ainda é uma aplicação Rack, mas é sua própria aplicação Rack. O recomendado para a configuração básica é a seguinte:

# cable/config.ru
require_relative "../config/environment"
Rails.application.eager_load!

run ActionCable.server

Então você inicia o servidor usando um binstub em bin/cable ala:

#!/bin/bash
bundle exec puma -p 28080 cable/config.ru

O código irá iniciar um cable server na porta 28080.

8.3 Notas

O servidor WebSocket não tem acesso à sessão, mas tem acesso aos cookies. Isso pode ser usado quando você precisa lidar com autenticação. Você pode ver uma maneira de fazer isso com o Devise neste artigo.

9 Dependências

O Action Cable fornece uma interface de adaptador de assinatura para processar seus pubsub internos. Por padrão, adaptadores assíncronos, inline, PostgreSQL e Redis estão incluídos. O adaptador padrão em novas aplicações Rails é o adaptador assíncrono (async).

O lado Ruby das coisas é construído em cima de websocket-driver, nio4r e concurrent-ruby.

10 Implantação

O Action Cable é alimentado por uma combinação de WebSockets e threads. Tanto o plumbing do framework e o trabalho do channel especificado pelo usuário são tratados internamente, usando suporte de thread nativo do Ruby. Isso significa que você pode usar todos os seus models dos Rails sem problemas, contanto que você não tenha cometido nenhum pecado de thread-safety.

O servidor Action Cable implementa o Rack socket hijacking API, permitindo assim o uso de um padrão multithread para o gerenciamento de conexões internamente, independentemente de o servidor de aplicativos ser multiencadeado ou não.

Assim, Action Cable funciona com servidores populares como Unicorn, Puma e Passenger.

11 Teste

Você pode encontrar instruções detalhadas de como testar a sua funcionalidade Action Cable no guia de teste.

Feedback

Você é incentivado a ajudar a melhorar a qualidade deste guia.

Por favor, contribua caso veja quaisquer erros, inclusive erros de digitação. Para começar, você pode ler nossa sessão de contribuindo com a documentação.

Você também pode encontrar conteúdo incompleto ou coisas que não estão atualizadas. Por favor, adicione qualquer documentação em falta na main do Rails. Certifique-se de checar o Edge Guides (en-US) primeiro para verificar se o problema já foi resolvido ou não no branch main. Verifique as Diretrizes do Guia Ruby on Rails para estilo e convenções.

Se, por qualquer motivo, você encontrar algo para consertar, mas não conseguir consertá-lo, por favor abra uma issue no nosso Guia.

E por último, mas não menos importante, qualquer tipo de discussão sobre a documentação do Ruby on Rails é muito bem vinda na lista de discussão rubyonrails-docs e nas issues do Guia em português.


dark theme icon