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.