WebSockets y Rails 5

  • por Franco Restaino

WebSockets es una tecnología que permite una comunicación bidireccional entre cliente y servidor. Este mecanismo permite una mejora en el rendimiento de nuestras aplicaciones, en especial de funcionalidades que requieren estar consultando continuamente al servidor si han habido cambios (Polling). 

Rails 5 incopora esta tecnología a través de Action Cable, lo cual permite tener funcionalidades "en tiempo real" con la estructura tradicional de Rails, con lo que se tiene acceso a todo el modelo de datos y funcionalidades desarrolladas.

Para probar esta nueva característica se desarrolló una pequeña aplicación con un chat, al cual todas las personas pueden escribir y ver lo que dice el resto. Para esto tenemos un modelo Message que solo tiene contenido y un controller Room. 

rails g controller rooms show
 
rails g model message content:text

Luego, nuestras vistas deberían verse de la siguiente manera.

# app/views/rooms/show.html.erb

<h1>Sala Chat</h1>
  
<div id="messages">
  <%= render @messages %>
</div>
  
<form>
  <label>Escribe:</label><br>
  <input type="text" data-behavior="room_speaker">
</form>

# app/controllers/rooms_controller.rb

class RoomsController < ApplicationController
  def show
    @messages = Message.all
  end
end

# app/view/messages/_message.html.erb

<div class=“message”>
  <p><%= message.content %></p>
</div>

Ahora vamos a crear el canal.

rails g channel room speak

# app/channels/room_channel.rb

class RoomChannel < ApplicationCable::Channel
  def subscribed
    stream_from "room_channel"
  end

  def unsubscribed
  end

  def speak(data)
    message = Message.create! content: data['message']
    ActionCable.server.broadcast 'room_channel', message: render_message(message)
  end

  private
    def render_message(message)
      ApplicationController.renderer.render(partial: 'messages/message', locals: { message: message })
    end
end

A grandes rasgos, el método speak se ejecuta cada vez que el cliente llama al servidor. Lo que hace es crear un nuevo mensaje, y una vez que esté listo emite un mensaje a todos los clientes suscritos con el contenido del nuevo mensaje. Esto luego es procesado por cada cliente como veremos a continuación.

# app/assets/javascripts/cable.coffee

// Action Cable provides the framework to deal with WebSockets in Rails.
// You can generate new channels where WebSocket features live using the rails generate channel command.
//
//= require action_cable
//= require_self
//= require_tree ./channels

(function() {
  this.App || (this.App = {});

  App.cable = ActionCable.createConsumer();

}).call(this);

# app/assets/javascripts/channels/room.coffee

App.room = App.cable.subscriptions.create "RoomChannel",
  connected: ->

  disconnected: ->

  received: (data) ->
    $('#messages').append data['message']

  speak: (message) ->
    @perform 'speak', message: message

$(document).on 'keypress', '[data-behavior~=room_speaker]', (event) ->
  if event.keyCode is 13 # return = send
    App.room.speak event.target.value
    event.target.value = ""
    event.preventDefault()

Acá se muestra cómo desde el cliente se emite un mensaje, como también que agrega uno nuevo al stack cada vez que recibe información desde el servidor.

El archivo routes.rb debería verse de la siguiente manera.

# config/routes.rb

Rails.application.routes.draw do
  get 'rooms/show'
  root to: 'rooms#show'

  mount ActionCable.server => '/cable'
end

Con el código recién mostrado tenemos una pequeña demostración de lo que se puede llegar a lograr con los nuevos componentes incorporados en Rails 5. Cabe mencionar que esto se combina normalmente con Active Job, pero eso es tema para otro post.

Código

https://github.com/nnodes/chat-sample

Referencias

http://edgeguides.rubyonrails.org/action_cable_overview.html

https://hectorperezarenas.com/2015/12/26/rails-5-tutorial-how-to-create-a-chat-with-action-cable/