27/09/2020
Utiliser plusieurs canaux Action Cable
Action Cable != Broadcast
Depuis Rails 5 les développeurs Rails peuvent utiliser un mécanisme de Pub/Sub pour mettre à jour des pages dynamiquement. Le scénario classique est l'affichage d'un tableau de bord avec moultes calculs et graphiques qui montrent le résultat des ventes d'un produit. Imaginer que pendant que vous regarder ce magnifique tableau, un commercial enregistre une grosse commande. Il faudrait rafraichir le tableau pour la voir apparaitre puisqu'elle a eu lieu après le calcul et l'affichage des résultats des ventes...
Mais ça c'était avant. En effet Action Cable permet d'abonner le tableau de résultats à tout changement ayant lieu dans les ventes. Quand ce changement a lieu, il suffit de diffuser le nouveau contenu pour que les pages abonnées soient averties de la mise à jour et remplace leur contenu par le nouveau contenu reçu.
Pour que ce soit plus clair, rien ne vaut un exemple concret et son code.
Nous allons créer une liste de produits (nom, catégorie, prix) et une page qui devra afficher la somme des prix des produits appartenants à une des catégorie.
Si tout fonctionne bien, la page 'Dash' affichant la somme des produits 'A' sera actualisée si vous modifiez, dans une autre page, le prix d'un des produits de la même catégorie.
Allez, au boulot !
$ rails new testActionCableApp
$ cd testActionCableApp
$ rails g scaffold Product name category price:integer
$ rails db:migrate
Ajouter maintenant des produits sans oublié la catégorie ! Une fois le catalogue produits bien rempli, nous allons créer le "Dashboard" et brancher les câbles.
$ rails g controller Dash index
$ rails g channel products
Modifiez comme ci-dessous les fichiers suivants
<%= form_tag "index", method: 'get' do %>
<%= label_tag(:category, "Category:") %>
<%= text_field_tag :category, @category %>
<%= submit_tag("Search") %>
<% end %>
app/views/dash/index.html.erb
Dash
<%= "Total catégorie '#{ @category }' = #{ @sum }" %>
Cette petite page est constituée d'un formulaire dans lequel l'utilisateur viendra saisir la catégorie pour laquelle il veut afficher le total des prix.
app/controllers/dash_controller.rb
class DashController < ApplicationController
def index
unless params[:category].blank?
@category = params[:category]
@sum = Product
.where(category: @category)
.sum(:price)
end
end
end
Ici on calcule la somme des prix pour la catégorie choisie par l'utilisateur
app/channels/products_channel.rb
class ProductsChannel < ApplicationCable::Channel
def subscribed
stream_from "category_#{ params[:category] }"
end
def unsubscribed
# Any cleanup needed when channel is unsubscribed
end
end
On souscrit au canal Produits. Les flux sont nommés par le nom de la catégorie (ex: 'categoy_A').
app/javascript/channels/products_channel.js
import { logger } from "@rails/actioncable";
import consumer from "./consumer"
$(document).on('turbolinks:load', function () {
consumer.subscriptions.create(
{
channel: "ProductsChannel",
category: $('#sum-panel').attr('data-category')
}
, {
connected() {
// Called when the subscription is ready for use on the server
},
disconnected() {
// Called when the subscription has been terminated by the server
},
received(data) {
// Called when there's incoming data on the websocket for this channel
const dashElement = document.querySelector("main.dash")
if (dashElement) {
dashElement.innerHTML = data.html
}
}
});
})
C'est ici que ça devient intéressant car on soucrit à un flux nommé. Sinon, toutes la pages abonnées recevraient le même contenu, quelque soit la catégorie choisie par l'utilisateur.
Cette information est obtenue en allant lire dans la page les données sum-panel.data-category
app/controllers/products_controller.rb
# PATCH/PUT /products/1
# PATCH/PUT /products/1.json
def update
respond_to do |format|
if @product.update(product_params)
format.html { redirect_to @product, notice: 'Product was successfully updated.' }
format.json { render :show, status: :ok, location: @product }
@category = @product.category
@sum = Product
.where(category: @category)
.sum(:price)
ActionCable.server.broadcast "category_#{ @category }",
html: render_to_string('dash/index', layout: false)
else
format.html { render :edit }
format.json { render json: @product.errors, status: :unprocessable_entity }
end
end
end
Il ne reste plus qu'à envoyer dans le tuyaux le nouveau contenu qui devra apparaître dans toutes les pages abonnées.
Effet 'waouh!' garanti ;-)