Publicidad:
La Coctelera

Riding to NoWhere

Categoría: ruby

12 Diciembre 2009

Si nos vamos a echar un ojo al código fuente de Rails que se encarga de la precarga de asociaciones preload_associations, el código que se ejecuta al utilizar un :include => :patatas nos encontramos con lo siguiente:

Application developers should not use this module directly.

Pero yo no estoy totalmente de acuerdo con esto y suelo utilizar la funcionalidad que ofrece este módulo a mi antojo. Por un lado no me gusta meter includes al definir las asociaciones en los modelos, sino hacerlo al recuperar los registros en los controladores y añadir los includes que necesite para las vistas que vaya a mostrar.

También suele ocurrir que el número de includes a utilizar es grande por lo que una vez recuperados los registros en el controlador (paginados o no) realizo la carga de las asociaciones a posteriori. (¿Por qué haberlo hecho en el modelo?)

Vale hasta aquí tal vez no haya sido nada convincente y seguro que mis razones sean puramente estéticas sin entrar en detalles de rendimiento. Aún así me he encontrado con un caso, tal vez un "patrón", en el que la precarga de asociaciones puede reducir el número de query's a realizar en la base de datos. Siempre hablando del eager loading que realiza Rails desde la versión 2.1 y cuando no se introducen condiciones en los registros asociados.

El caso consta de los modelos comment, user y profile descritos aquí

Básicamente un perfil de usuario puede ser comentado a través de un comentario root "root_graffity". Este comentario root puede recibir comentarios "sons" y cada uno de esos comentarios acepta respuestas "replys". En el caso de querer recuperar lo que he denominado "graffities" junto con todos sus datos debería hacer lo que muestro aquí Como vemos se realizan 7 consultas  a la base de datos, y el detalle está en que a la tabla de users y profiles se accede en dos ocasiones. Esto último lo podemos evitar si realizar un precarga de asociaciones tal y como muestro aquí

Si estamos cargando asociaciones auto-referenciadas (o algo así), es decir, aquellas que cargan modelos de la misma clase, si esta clase necesita asociaciones de otras clases. Veo que puede ser de utilidad agrupar los modelos de la primera clase y para ese conjunto cargar sus asociaciones. Ya que lo que hace Rails en el módulo en cuestión es recuperar los registros asociados a partir de las claves que contiene la colección de registros cuyas asociaciones se quieren precargar.

Alé que es sábado y habrá que beberse unas cervezas! A vuestra salud!

2 Noviembre 2009

Durante las últimas semanas o mejor dicho, el último par de meses he estado trabajando con proyectos que presentan funcionalidades de búsqueda de texto en aplicaciones Rails, utilizando el plugin ultrasphinx.

En esta entrada no voy a contar que es ultrasphinx ni como utilizarlo en detalle, pero si intentaré explicar como solucione un problema que tuve al introducir una nueva asociación sobre la que la aplicación debía realizar búsquedas.

Un punto que conviene no olvidar cuando se utiliza la instrucción:

 class model < ActiveRecord::Base
   is_indexed
 end
 

Es que con esta instrucción y tras ejecutar las tareas que incorpora el plugin de ultrasphinx se generan los índices sobre los que se realizarán las consultas. Estos índices quedan definidos por una o varias queries que podemos consultar en los ficheros *.conf y que también generan las tareas de ultrasphinx. Llevarnos estas queries a nuestro query browser favorito y jugar con ellas nos permite comprobar porque los resultados que devuelve nuestra aplicación no son los que esperábamos, al menos en cierta medida.

Y ya vamos con el problema que me encontre. El problema radica en como generar el índice que permite realizar búsquedas a través de asociaciones has_many. Teniendo los siguientes modelos:

 class User < ActiveRecord::Base
   has_many :recipes
   has_many :ingredients, :through => :recipes
 end
 class Recipe < ActiveRecord::Base
   has_many :ingredients
 end
 

La búsqueda a implementar son búsquedas de usuarios por ingredientes, vale pues a configurar el índice con un poco de DSL:

 class User < ActiveRecord::Base
   ...
   is_indexed :fields => [{:field => 'email', :as => 'user_email'}],
              :include => [{
                 :association_name => "ingredient", 
                 :field => 'name',  
                 :association_sql => "LEFT OUTER JOIN recipies on recipes.user_id = users.id LEFT OUTER JOIN ingredients on ingredients.recipe_id = recipes.id"}]
   ...
 end
 

Pero esto no funciona, ya que el índice solo lo forman el primer ingrediente de cada receta. Hecho que puede llevar perfectamente a que la jodas. La pista me la dio la query que se genera en los ficheros .conf. Cogí esa query la coloque en el query browser y solo aparecieron los nombres de los primeros ingredientes, el resto de ingredientes no existían.

En la query se realiza una agrupación por users.id lo que lleva a que se pierdan el resto de ingredientes. La solución era sencilla crear un nuevo campo en la query en la que se guardarán en forma de lista los ingredientes asociados. ¿Pero eso como lo llevamos al DSL de ultrasphinx?

La solución la encontré en este hilo "How to set up a has_many :through association with Ultrasphinx"

Tenemos que indicarle a ultrasphinx que queremos concatenar los ingredientes, para que aparezcan todos y cada uno de los ingredientes asociados a la receta. Así que nos quedamos con esto:

 class User < ActiveRecord::Base
   ...
   is_indexed :fields => [{:field => 'email', :as => 'user_email'}],
              :concatenate => [{ 
                   :class_name => 'Ingredient', 
                   :field => 'name',
                   :association_sql => "LEFT OUTER JOIN recipies on recipes.user_id = users.id LEFT OUTER JOIN ingredients on ingredients.recipe_id = recipes.id", 
                   :as => "ingredient_list"
               }]
   ...
 end
 

Respondiendo al autor del hilo, gracias! me ha ahorrado bastante tiempo.

15 Enero 2009

Llevo ya más de 1 año desde que descubrí Rails, pero nunca me había detenido a estudiar Ruby con suficiente detenimiento. Ya lo sé, no es una buena práctica y que no recomiendo a nadie; pero las prisas por comenzar el proyecto fin de carrera y por acabarlo me llevaron por otros derroteros.

Pero ha llegado el momento de querer hacer proyectos más elaborados, más eficientes. También querer colaborar en proyectos open source, fundamentalmente los alojados en GitHub, y noto que debo fortalecer la base.

Durante mi aprendizaje de Rails claro que revise Ruby pero no al detalle que debería haberlo hecho. Bueno nunca es tarde.

Llevo unos días estudiando The Ruby Programming Language de O'Reilly y he descubierto muchos temas muy interesante y que desconocía o algunos otros que sabía que existían pero que de lo que no tenía ni la más remota idea de como se llevaban acabo.

La verdad que es un libro que recomiendo a todo el que se inicie en el aprendizaje en Ruby.

La próxima lectura cuando termine con este libro, sera leerme la biblia de Ruby me refiero a Programming Ruby de Pragmatic o si esta listo Programming Ruby 1.9 también de Pragmatic.

Sobre Riding to NoWhere

Categorías