Vicente Rodríguez

Nov. 3, 2016

Realtime en rails con react y redis

Ruby on rails en su version 5.0 implemento los websockets para poder trabajar en tiempo real con la información, pero si no te convence esta implementación podemos usar redis y node para remplazarla.

El código de este tutorial esta en github.

Creando la app

Empezamos con una nueva app en rails:


rails new realtime-app

En el gemfile necesitamos agregarle la gema de redis:


gem 'redis', '~>3.2'

y agregar la configuración en un initializer (config/initializers/redis.rb):


$redis = Redis.new(host: 'localhost', port: 6379)

Creamos una variable global que tenga la conexión con redis para posteriormente usarla.

Creando el modelo y el controlador

Necesitamos un modelo Post:


rails g model post title body:text

y su controlador:


rails g controller posts index new

con la siguiente configuración:


class PostsController < ApplicationController

  def index

   @posts = Post.all

   respond_to do |f|

    f.html

    f.json { render json: @posts }

   end

  end



  def new

   @post = Post.new

  end



  def create

   @post = Post.new(post_params)

   if @post.save

    redirect_to posts_path

   else

    render :new

   end

  end



  private

    def post_params

     params.require(:post).permit(:title, :body)

    end

end

El aspecto importante es que en la ruta index podamos contestar con json para poder enviar los posts a react.

Vistas

Solo tenemos dos vistas, new e index:

Index


<div id="render">



</div>

Necesitaremos un div para indicar a react donde tiene que agregar los componentes de los nuevos posts.

New


<%= form_for(@post) do |f| %>

  <%= f.label "Titulo" %>

  <%= f.text_field :title %> <br>

  <%= f.label "Body" %> 

  <%= f.text_area :body %>  <br>

  <%= f.button %>

<% end %>

Un formulario sencillo con los dos campos del post.

Creando la configuración de react

Dentro de la raíz del proyecto en rails creamos una nueva carpeta, yo la llamare node y dentro iniciaremos con un package.json:


npm init -y

instalamos los paquetes:


npm install --save-dev babel-core babel-loader babel-preset-es2015 babel-preset-react webpack

Estos paquetes los necesitamos para compilar react, parece que sea un rollo pero con la practica y tiempo te aprendes de memoria los pasos, ahora necesitamos crear el archivo webpack.config.js:


var webpack = require('webpack')

var path = require('path')

var srcPath = path.join(__dirname, "js")

var assets = path.join(__dirname, '../app/assets/javascripts')



module.exports = {

 entry: {

  index: srcPath

 },



 output: {

  filename: '[name].js',

  path: assets

 },



 module: {

  loaders: [

   {

    test: /\.js?$/,

    exclude: /node_modules/,

    loader: 'babel-loader',

    query: {

     presets: ['es2015', 'react']

    }

   }

  ]

 },

 resolve: {

  root: srcPath,

  extensions: ["", ".js"],

  moduleDirectories: ['node_modules']

 },

 debug: true

}

Es importante que el el destino del output apunte a la carpeta assets/javascripts del proyecto en rails.

Creando el index.js

Empezaremos instalando los paquetes esta vez para la aplicación:


npm install --save react react-dom redis socket.io socket.io-client superagent

Crearemos el archivo con la configuración de express para poder iniciar el servidor en node:


var redis = require('redis')

var socketio = require('socket.io')

var http = require('http')



var client = redis.createClient()

var server = http.createServer()

var port = 4000



var io = socketio(server)

client.subscribe('chanel-posts')



io.on("connection", function(socket) {

 client.on('message', function(channel, msg) {

  console.log(channel)

  socket.emit('new-post', JSON.parse(msg))

 })

})



server.listen(port, function() {

  console.log('server listening in port: ' + port)

})

Es importante que el puerto sea diferente al 3000 porque rails estará corriendo en ese puerto, lo importante de esta configuración es crear el cliente de redis, este cliente se suscribe a un canal llamado "chanel-posts" y cuando se mande información por este canal el cliente ejecutara lo que le especifiquemos:


client.on('message', function(channel, msg) {

  console.log(channel)

  socket.emit('new-post', JSON.parse(msg))

 })

en este caso le indicara a socket.io que mande la información que le llego en formato json, también es importante configurar socket.io, tenemos que pasarle el servidor de express y ponerlo a escuchar con la función io.on esta función escuchara todos los sockets y dentro de ella se podrá enviar información con emit a el front end que sera react.

Modelo post

Necesitaremos regresar a rails para agregar la configuración de redis en el modelo post:


class Post < ActiveRecord::Base

 after_create :send_data



 def send_data

      data = {

        id: self.id,

        title: self.title,

        body: self.body 

      }

   $redis.publish "chanel-posts", data.to_json

 end

end

Creamos una nueva función que se ejecutara después de que se cree un nuevo post, esta función manda un archivo json con la información del post y lo publica en el canal "chanel-posts" de redis.

Creando la app en react

Dentro de la carpeta node creamos otra llamada js, dentro de esta dos archivos:

Index.js


import React from 'react'

import ReactDOM from 'react-dom'

import request from 'superagent'

import socketio from 'socket.io-client'

import PostComponent from 'post_component'



var socket = socketio("http://localhost:4000")



class PostsComponent extends React.Component {

 constructor(props){

  super(props)

  this.state = {

   data: []

  }

 }

 loadPosts(){

   Promise.resolve(request("/posts.json"))

       .then((data) => {

        this.setState({data: data.body})

       })

 }



 componentDidMount(){

  this.loadPosts()

  socket.on('new-post', (msg) => {

   var newData = this.state.data

   newData.unshift(msg)

   this.setState({ data: newData })

  })

 }

 render() {

  return(

   <div>

    <PostComponent posts={this.state.data} />

   </div>

  )

 }

}



ReactDOM.render(

 <PostsComponent/>,

 document.querySelector("#render")

)

Este sera nuestro componente principal, aquí pedimos información existente de los posts mediante un request ajax:


 Promise.resolve(request("/posts.json"))

       .then((data) => {

        this.setState({data: data.body})

       })

también configuramos socket.io cliente escuchando el canal 'new-post', cuando se le pase información a este canal react cambiara su state provocando que se imprima el nuevo post en la pantalla.

post_component.js


import React from 'react';



export default class PostComponent extends React.Component {

 render() {

  return (

   <div>

    {this.props.posts.map((post) => {

     return (

      <div key={post.id}>

       <p>{post.title}</p>

       <span>{post.body}</span>

      </div>

     )

    })}

   </div>

  )

 }

}

Este componente e el que representa al post y al que le pasamos la información de los posts.

Ejecutando el proyecto

Para finalizar necesitamos crear el archivo de react, usamos webpack para esto:


webpack

corremos el servidor de express:


node index.js

corremos rails:


rails s

ahora podemos abrir una nueva pestaña con el index y otra con el formulario, cuando lo creemos index agregara el nuevo post sin necesidad de recargar la pagina.