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.