martes, 1 de abril de 2014

Búsquedas en Elasticsearch

En este post voy a hacer una recopilación de algunas querys más o menos complejas sobre un índice de Elasticsearch.
En primer lugar, el mapping usado (ficticio) será el siguiente, con un tipo película donde tenemos varios campos de distintos tipos: cadenas de caracteres, números, un array de enteros y otro de objetos. La generación del mapping, modificada, se realizó a partir del índice de un modelo en Rails, como explico en el post sobre integrar Elasticsearch en Ruby.

{
  "pelicula":{
    "index_analyzer":"default_index",
    "dynamic_templates":[
      {
        "string_template":{
          "mapping":{
            "type":"multi_field",
            "fields":{
              "analyzed":{
                "index":"analyzed",
                "type":"string"
              },
              "{name}":{
                "index":"not_analyzed",
                "type":"string"
              }
            }
          },
          "match":"*",
          "match_mapping_type":"string"
        }
      }
    ],
    "properties":{
      "presupuesto":{
        "type":"float"
      },
      "imdb":{
        "type":"string",
        "fields":{
          "imdb_sort":{
            "type":"string",
            "index":"not_analyzed"
          }
        }
      },
      "categoria":{
        "type":"string",
        "analyzer":"keyword"
      },
      "duracion":{
        "type":"long"
      },
      "bn":{
        "type":"boolean"
      },
      "actores":{
        "type":"long",
        "index_name":"actor"
      },
      "titulo":{
        "type":"string",
        "fields":{
          "titulo_sort":{
            "type":"string",
            "index":"not_analyzed"
          }
        }
      },
      "premios":{
        "type":"nested",
        "properties":{
          "fecha":{
            "type":"date",
            "format":"dateOptionalTime"
          },
          "actor":{
            "type":"long"
          },
          "nombre":{
            "type":"string",
            "analyzer":"keyword"
          }
        }
      }
    }
  }
}

Un par de comentarios sobre el mapping.
En los campos tipo String sobre los que vamos a tener que realizar operaciones tanto de filtrado como de ordenación, tendremos que convertir el campo en un multifield, de modo que se duplique. Así, la ordenación se realizará sobre el campo not analyzed, y el filtrado sobre el analyzed.
También otra característica de los campos String es que elasticsearch realizará un análisis stemming dependiendo del idioma. En algunos campos querremos evitarlo, como en identificadores tipo cadena. Para ello usaremos el analyzer tipo keyword.

lunes, 31 de marzo de 2014

Comunicación Rails - Node JS con Sidekiq

En Rails, para ejecutar tareas en segundo plano, disponemos de varias gemas. Las más usadas son Resque y Sidekiq. Ambas son bastante similares, incluso he realizado pruebas donde encolaba trabajos usando Resque y los ejecutaba mediante Sidekiq, y no tenía ningún problema. La mayor diferencia es la concurrencia. Mientras que Resque levanta un proceso,
Sidekiq puede ejecutar hasta 50 hilos distintos.

Llevando este concepto a una aplicación sobre Node JS, he hecho prebas para comprobar qué difícil sería enviar trabajos desde esta aplicación web a un backend Rails. Node necesitaría conectarse a Redis e instanciar un cliente que encolara los trabajos. Para ambas cosas hay librerías disponibles.

viernes, 21 de marzo de 2014

AngularJS, ui-autocomplete con Node JS

En este post mostraré el uso de la directiva ui-autocomplete con AngularJS, obteniendo los datos de modo remoto contra Node js. En el ejemplo de la documentación de esta directiva, vemos que con dats estáticos es bastante sencillo. En mi caso quería realizar el filtrado directamente en Node, ya que es más eficiente en el caso de que tengamos una gran cantidad de registros.

Mostraré primero la parte relevante de Node js y luego la de AngularJS:

Node js

Por un lado tendremos el modelo, usando mongoose para el mapeo con MongoDB. Nada del otro mundo.

En el controlador definiremos una acción, que será el servicio REST encargado de buscar los datos que concuerden con el filtro y devolverlos en formato JSON.
exports.txt_search = function (req, res, next) {
  var util = require('util');

  var searchTerm = req.query.term;
  Objeto.find({name: new RegExp('^' + searchTerm, "i")}, function (err, list_objs) {
    if (list_objs && list_objs.length) {
      res.send(list_objs);
    } else {
      res.send([]);
    }
  });

};

Definimos la ruta necesaria para que las llamadas ejecuten esta función:
app.get('/api/objects', league.txt_search);




jueves, 20 de marzo de 2014

#canonAEDE

DECLARACIÓN CONJUNTA DE LA RED Y LOS AUTORES CULTURALES SOBRE EL PROYECTO DE REFORMA DE LA LEY DE PROPIEDAD INTELECTUAL


El pasado 14 de febrero de 2014 el Consejo de Ministros aprobó el proyecto de reforma de la Ley de Propiedad Intelectual (texto publicado en el B.O.C.G. el 21 de febrero).

Este proyecto de Ley, arcaico en su concepción, recorta numerosos derechos en España, afecta muy negativamente a amplios sectores de la sociedad, pone en peligro la cultura libre y cuestiona el funcionamiento de internet, limitando la cita y el enlace a una actividad meramente mercantil.

MEAN Stack (MongoDB, Express, AngularJS, Node) y Yeoman

Hace un tiempo escribí un post sobre la integración de AngularJS en Rails. Una manera sencilla de aprovechar una aplicación para meter un Framework MVC JavaScript. Aún que la integración fue más o menos sencilla, le veo algunos problemas debido a la naturaleza de Rails.

Rails pierde su función original de framework MVC, ya que se limita a servir datos en formato JSON y del resto se debe encargar JS. Además de esto, está orientado a servir distintas páginas, una por cada recurso y action. Esto choca con la idea de AngularJS de poder realizar aplicaciones en una sóla página, modificando sólo el contenido necesario.
Para solucionar esto, es necesario modificar el backend, que es el que se encargará de la gestión de los datos y servirlos en formato JSON. Dentro de las distintas opciones, actualmente la tendencia es el MEAN stack (MongoDB, Express, AngularJS, Node)



martes, 18 de marzo de 2014

GIT: workflow, merge/rebase de ramas remotas.

Hay una gran variedad de workflows distintos para el trabajo con GIT. Dependiendo del tipo de equipo conviene adaptarse a uno u a otro. El más usado posiblemente sea git-flow, donde tenemos una rama master donde se integran los cambios, para pasar una vez validados a la rama stable.

La bonita utopía 

En este workflow, cada desarrollador tiene su copia local de master, desde donde creará ramas locales para implementar las funcionalidades del proyecto

git checkout -b mitarea

Cada vez que finalice la funcionalidad se integrará con la rama master y los cambios que haya podido sufrir durante el desarrollo. Lo ideal sería algo así:
git checkout master
git pull origin master
git checkout mitarea
git rebase master
git rebase master -i
git checkout master
git merge mitarea
git push origin master

Con esto nos aseguramos de que nuestro master este actualizado; hacemos rebase contra nuestra rama local, de modo que añadiremos los commit que hemos realizado. Luego haremos el rebase interactivo (opcional) para que todos nuestros commit se integren en uno, y la rama estará lista para que sea subida a master.

jueves, 6 de marzo de 2014

Despliegue de aplicaciones Rails con Capistrano v3

Como decía en el anterior post sobre la configuración de una aplicación rails con Nginx y Unicorn, ahora comentaré cómo he realizado el despliegue usando Capistrano 3. Realmente es una herramiente muy sencilla, que permite incluir muchas opciones de configuración, aunque lo básico ya viene prácticamente hecho de serie.

Para empezar, incluimos en el Gemfile las gemas que vamos a usar:


# Deploy with Capistrano
gem 'capistrano'
gem 'capistrano-rails'
gem 'capistrano-rvm'
# integrate bundler with capistrano
gem 'capistrano-bundler'

Ejecutamos bundle install y una vez que hemos instalado las gemas, creamos la estructura de ficheros que necesita Capistrano. Para ello simplemente habrá que ejecutar: cap install.
A partir de aquí, simplemente tendremos que configurar los ficheros generados de acuerdo a las necesidades de nuestro servidor.



miércoles, 5 de marzo de 2014

Nginx + Unicorn + Capistrano 3 + Rails 3

Como ya expliqué en el último post, conseguí configurar una instancia de EC2 con Ubuntu 13.10 para tener una aplicación de Ruby on Rails, con Ruby en su versiíon 2.0.0 y rails en la 3.2 (en breve haré el upgrade).

https://github.com/blog/517-unicorn
Esquema de la arquitectura
Ahora explicaré la mejor forma, al menos para mi, de montar todo el tinglado para hacer despliegues más o menos cómodos y rápidos. Comenzaré desde fuera para dentro, donde fuera es el servidor web de aplicaciones, para el que elegí Nginx, que es más ligero (una microinstancia de EC2 con 512MB no está para derrochar!). Tras Nginx tenemos Unicorn, el servidor HTTP, que será el que se comunique directamente con nuestra App Rails.

La comunicación entre Unicorn y Nginx se realizará por medio de un socket compartido.



lunes, 3 de marzo de 2014

Alternativas a Heroku para aplicaciones Rails

Hasta ahora, prácticamente todas las aplicaciones web las tenía alojadas en Heroku. Al ser aplicaciones pequeñas, podía acogerme al plan gratuito sin problemas, y usar los Add-ons que ofrecen una mini versión sin coste.
El mayor problema llega a la hora, por ejemplo, de incluir en una aplicación un proceso en Backend como Resque o Sidekiq. En este caso es necesario tener un dyno para la parte web, y otro para el proceso.

Buscando otras opciones, me han llamado especialmente la atención DigialOcean, que últimamente está haciendo una gran campaña publicitaria; OpenShift y Amazon AWS.
Las tres variantes ofrecen Iaas (Infraestructure as a Service) es decir, un servidor virtual (VPS) donde es necesario realizar la configuración de la máquina y la instalación de todos los componentes.
En el caso de Heroku, se trata de un PaaS (Platform as a Service), lo que quiere decir que nos ofrece todas las facilidades para desplegar las aplicaciones, ocupándonos solamente del código en si. En este post de Stackoverflow hay una explicación más completa.

miércoles, 19 de febrero de 2014

Integrar elasticsearch en una aplicación Rails con Mongoid

Realizar una búsqueda sobre campos de texto siempre es costosa. En el caso de que esta búsqueda se haga sobre una gran cantidad de registros, la cosa se empeora.

Si en nuestra aplicación Rails estamos usando una base de datos MongoDB con Mongoid, la primera opción y más sencilla es usar la gema 'mongoid_search' o similar. Esta gema crea un campo _keywords para cada documento donde mete un array de palabras clave, que es sobre el que va a realizar la búsqueda.




Cuando el número de documentos sobre el que trabajamos aumenta y empezamos a hablar de millones, conviene pasar a un motor de búsqueda como tal. En mi caso he barajado tres posibilidades:
Sphinx, Solr y elasticsearch. A priori no tenía ninguna preferencia, ya que hasta ahora sólo había trabajado con tecnologías de este tipo a modo de consumidor.
Tras buscar en distintos foros, llegué a la conclusión de que, a pesar de ser la más reciente (menos madura), el motor de elasticsearch parece que ofrece lo mismo que los otros dos y además cubre otros aspectos pendientes.

Lo que me interesaba principalmente era una sencilla integración con Rails y MongoDB, ya que no me era necesario realizar búsquedas trabajosas, ni tengo estructuras demasiado complejas.
Para realizar esta integración, lo primero es tener instalado elasticsearch. Para OSX:

$ brew install elasticsearch

Una vez que lo tenemos instalado, pasaremos a integrar elasticsearch en la aplicación. Esto lo he realizado con la gema searchkick. La incluimos en el Gemfile:

gem "searchkick"

Añadimos la línea searchkick dentro del modelo (Product) y desde la consola de rails ejecutamos: <<Product.reindex>> o bien el comando << rake searchkick:reindex CLASS=Products>> desde la Termina.

Ahora añadimos un formulario de búsqueda en nuestra aplicación, e implementamos la acción search para realizar la búsqueda:
results = Product.search "garbanzo", fields: [:name, :brand], limit: 10, offset: 50
Al hacer map sobre results, obtendremos los datos buscados:

results.map(&:name)

Y de esta forma tenemos implementado un buscador simple sobre un conjunto grande de documentos de MongoDB. La documentación, tanto de ES como de la gema, muestra ejemplos y documentación de muchas más funciones y posibilidades (términos sugeridos, autocompletar, ...), por lo que recomiendo que le echéis un vistazo.

lunes, 3 de febrero de 2014

Angular JS con Rails

Probando nuevos frameworks y arquitecturas para conseguir una mejor experiencia de usuario en aplicaciones web, ,he estado trabajando con Angular JS. Es un framework Javascript bastante potente, que permite tener aplicaciones en una sola página, aunque se puedan visualizar las 'rutas' dentro del navegador.

Mi primera idea fue usar rails junto con Angular JS, de modo que rails sirviera los ficheros estáticos, haciendo de backend para proveer de los datos necesarios a la aplicación web. Para esto, los controller de rails van a devolver todas (o casi todas) las peticiones en formato JSON.

Lo primero será incluir las librerías necesarias para angular. Esto se puede hacer mediante gemas, usando 'angularjs-rails', junto con underscore.

gem 'angularjs-rails'
gem 'underscore-rails'

Mediante estas gemas tendremos dentro del assets pipeline de la aplicación los ficheros necesarios para lanzar Angular con el frontend. En el fichero application.js habrá que incluir los ficheros de angular, tal y como indica la página de la gema.

También debemos preparar nuestro layout application.html.haml para que ejecute Angular JS. Para esto, dentro de la etiqueta HTML incluiremos la directiva de angular:

%html{'ng-app'=>"miaplicacion"}

Para empezar la aplicación, crearemos simplemente un controlador (home) con un index, de modo que sea el root_path, y será la única vista de la aplicación rails. En la vista incluiremos la directiva ng-view de angular JS:

.container{'ng-view'=>true}

De este modo, angular ejecutará la vista correspondiente, que deberemos definir en sus rutas. Esto lo indicaremos en el fichero principal de JS (o coffeeScript) de la aplicación: home.js.coffee en mi caso, que se incluirá desde application.js, eliminando el require self . Este fichero tendrá lo siguiente;

#= require_self
#= require_tree ./angular/controllers

@miaplicacion = angular.module('miaplicacion', ['ngRoute'])

@miaplicacion.config(["$httpProvider", (provider) ->
  provider.defaults.headers.common['X-CSRF-Token'] = $('meta[name=csrf-token]').attr('content')
])

@miaplicacion.config(['$routeProvider', ($routeProvider) ->
  $routeProvider.when('/jugador/new', { templateUrl: '../assets/jugadorCreate.html', controller: 'CreateJugadorCtrl' })
  $routeProvider.when('/jugadores', { templateUrl: '../assets/jugadores.html', controller: 'JugadorIndexCtrl' })
  $routeProvider.when('/jugador/:jugadorId', { templateUrl: '../assets/jugador.html', controller: 'JugadorCtrl' })

  # Default
  $routeProvider.otherwise({ templateUrl: '../assets/mainIndex.html', controller: 'HomeCtrl' })

])

Nuestra aplicación simplemente tendrá un modelo Jugador, con las operaciones básicas para listar, crear y mostrar. Incluimos el token para el CSRF, ya que deberá estar incluido en las llamadas a rails.
Toda la lógica de angular JS la tendremos en la carpeta angular dentro de nuestros javascripts.
Por otro lado, los templates de angular JS los hemos metido en una carpeta dentro de los assets de la aplicación.

Aunque no he incluido el código íntegro, llegué a finalizar los servicios básicos y las acciones sobre el modelo Jugador (guardando la entidad en una base de datos MongoDB), integrando la aplicación con Twitter Bootstrap. Tras este breve resumen, saco las conclusiones sobre estas pruebas con Angular JS:

En mi opinión, usar rails junto con Angular JS pierde efectividad por varios motivos. En primer lugar, rails es un framework MVC en sí. Sustituyendo la vista y gran parte de las funciones del controlador por Angular JS, supone perder gran parte de la potencia y utilidad del framework.
Además, se pierde totalmente el sentido de la arquitectura y la organización por carpetas, teniendo que meter todos los ficheros de angular dentro del asset pipeline de rails. Esto además supone una pérdida de tiempo al configurar la aplicación para su puesta en producción, y dificulta la separación de los assets estáticos a la lógica en sí de Angular.

Por tanto, para usar la potencia que nos da Angular JS, es conveniente usar como backend un framework que se adapte mejor, para aumentar la eficiencia y la facilidad de programación.