Publicidad:
La Coctelera

Riding to NoWhere

Categoría: rails

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.

22 Marzo 2009

Siguiendo con mi adaptación completa a Ubuntu y con la búsqueda de las herramientas útiles para el desarrollo en Rails, me he encontrado con los dos siguientes post:

En el primero de ellos se habla de la instalación de Mumbles, herramienta de envio de notificaciones al escritorio similar a Growl y que se puede descargar aquí. Orientado al uso del sistema de notificaciones con Autotest. Esto permite que mientras desarrollamos, los test se ejecuten de forma automática y así se nos notificará si hemos roto algo o todo los tests siguen pasando.

En el segundo post se comenta el sistema de notificaciones para utilizarlo en Ubuntu para tareas comunes.

PD: Comenzando a utilizar Vim más seriamente

18 Febrero 2009

Nueva release de esta librería para la creación de Factorías en Rails. Voy a tener que poner en práctica los añadidos que ha incorporado como: en la creación de secuencias, la herencia de factorías, creación de stubs. Todo esto ayudará a tener un código de test más DRY.

Por otro lado me parece muy curioso que hallan incluido sabores en la sintaxis a la hora de utilizarlo, así se acercan a un mayor número de personas acostumbras a dichas sintaxis.

Para más informacion:

12 Febrero 2009

Hace un par de días estuve viendo el nuevo screencast de Ryan Bates sobre la nueva característica de templates incoporada en la versión 2.3 de Rails. Y la verdad que esta característica facilita mucho la creación y configuración por defecto de aplicaciones Rails. Crear la aplicación, crear un repositorio Git, instalar las gemas y plugins que utilizamos normalmente, pero no solo esto. También es posible invocar los generadores de rails, solicitar al usuario parámetros de configuración, etcétera, etcétera.

Hace un tiempo encontre un post de Pratik, donde utilizaba las templates de rails que ya estaban incorporadas en el edge, y donde se comentan todas sus características. Y lo consulte como punto de partida para hacer una aplicación base de Tog, pero mi falta de experiencia me impidió llegar a buen puerto, posteriormente apareció una aplicación base de Tog_demo que fue la que finalmente utilice para un proyectillo que tengo en marcha.

Para terminar, Ryan Bates recomienda consultar las templates creadas por Jeremy McAnally en http://github.com/jeremymcanally/rails-templates/tree/master. Han creado templates que pueden ser muy útiles, me apunto para estudiar la template de facebook y echar un vistazo calmado al resto de templates disponibles.

Tengo pendiente también echarle un profundo vistazo a clearance, de los creadores de shoulda y factory_girl que ya estoy utilizando en mis proyectos.

Para resumir, en Rails de momento no paran de aparecer características y desarrollos muy interesentes y productivos; que hace que no pare de aprender y de sorprenderme.

19 Diciembre 2008

De entre los repositorios de GitHub que sigo se encuentra SearchLogic. Una gema que facilita la búsqueda, paginación, y ordenación and more! en objetos ActiveRecord.

En una aplicación que estoy trabajando me ha surgido la necesidad de crear un buscador (formulario) en el que se pueden incluir ciertos criterio de búsqueda, la lista de los mismos esta por ampliar.

Dentro de los criterios actuales se encuentra la posiblidad de introducir keywords y dentro de un select determinar un rango numérico de distancia a utilizar en la búsqueda.

Como este formulario crecerá con el tiempo he querido buscar una solución que me permite introducir nuevos criterios de forma poco invasiva y sin reinventar la rueda.

Entremos en detalle:

Con SearchLogic es posible establecer condiciones de búsqueda de keywords del siguiente modo:

:keywords :kwords, :kw Splits into each word and omits meaningless words, a true keyword search

 search.conditions( :keywords => params[:search][:keywords])
 

Sin embargo en mi caso quiero buscar keywords en dos campos por lo que tengo que crear un grupo con condicion OR ya que no es necesario que el keyword este presente en ambos campos. Hago lo siguiente

 search.conditions.group do |group|   
  group.nombre_kw = params[:search][:keyword]
  group.or_descripcion_kw = params[:search][:keyword]
 end
 

# SELECT rutas.* FROM rutas WHERE ( rutas.nombre LIKE "%keyword1%" OR rutas.descripcion LIKE "%keyword1%" AND rutas.nombre LIKE "%keywordi%" OR rutas.descripcion LIKE "%keywordi%")

o bien

 search(:conditions => {:group => {:name_kw => keyword, :or_descripcion_kw => keyword }} )
 

Por lo tanto me creo el siguiente método

 def condicion_keywords(keywords)
 {:group => {:name_kw => keyword, :or_descripcion_kw => keyword }} 
 end
 

Para los rangos numéricos de distancia los establezco por correspondencia con un código del siguiente modo:

 def condicion_distancia(code)
   code ||= "0"
   case code
     when "0"; {}
     when "1"; {:distancia_lt => 10}
     when "2"; {:distancia_gte => 10, :distancia lt => 25}
     when "3"; {:distancia_gte => 25, :distancia lt => 50}
     when "4"; {:distancia_gte => 50}
   end
 end
 
 search(:conditions => condificion_distancia(params[:search][:distance]))
 

Para agrupar todas las condiciones de cada uno de los criterios de búsqueda que vaya a establecer me he creado el siguiente método

 def compute_searchlogic_conditions(search)
   conditions = {}
   conditions.merge!(condicion_distancia(search[:distancia])) unless search[:distancia].blank?
   conditions.merge!(condicion_keywords(search[:keywords])) unless search[:keywords].blank?
   # Sucesivas condiciones
 end
 

Ahora solo queda utilizarlo en el controlador

 def index  
   search = Ruta.new_search(:conditions => compute_searchlogic_conditions(params[:search]))
   rutas = search.all.paginate(:page => @page, :per_page => 10, :order => @order)
 end
 

Con SearchLogic es posible incluir la paginación y la ordenación a la hora crear el objeto search pero incluye su propio sistema de paginación y no es compatible directamente con mislav-will_paginate. Si se quiere usar la paginación de SearchLogic lo hariamos de la siguiente forma:

3 Octubre 2008

Al estar desarrollando una aplicación en Rails para mi proyecto fin de carrera veía necesario aprender a usar Git y el repositorio GitHub ya que para mi aplicación he utilizado múltiples gemas y plugins alojadas allí. (Ya queda poco para terminar el proyecto, al menos la memoria)

Bueno ciertamente la información que aparece en las guías de GitHub es más que suficiente para comenzar a usar Git, también el documento Git Internals de Peepcode si se quiere entrar en mayor nivel de detalle.

El otro día navegando descubrí un nuevo api de LastFM la versión 2.0. Así que busque en GitHub por alguna librería estilo Scrobbler que encapsulará su uso. Y encontre el plugin lastfm.

Pero no estaba muy completo así que lo he completado un poco, aunque quedan cosas por hacer (aqui).

También intente utilizar ActiveResource para utilizar el nuevo api de LastFm pero obtenía errores no muy normales con cosas sencillitas. Así que, si alguien comienza a utilizar ActiveResource para utilizar el nuevo api de Lastfm que me avise.

7 Agosto 2008

Bueno ante la necesidad de incluir un apartado sobre pruebas (testing) y validación para mi proyecto fin de carrera he comenzado a utilizar Shoulda para probar mi aplicación.

Según se puede ver en una presentación de Sergio Gil, su título "Más allá del testing", la única solución para probar mi aplicación después de su implementación es el DDT "Development Driven Testing" que siempre será mejor que no testear.

Al final me decante por utilizar Shoulda también después de leer la presentación "BDD with Should" de la gente que ha desarrollado este framework de testing. Principalmente porque se pueden enlazar contextos (cierto parecido a rspec), los nombres de los tests están bien, compatible con Test::Unit, están cubiertos la mayoría de los macros de ActiveRecord y de ActionController y también tiene cierta magia con los diseños REST.

Por otro lado, no he dejado de utilizar las fixtures, que son odiadas por gran parte de la comunidad porque de momento me sobra con aprender Shoulda. Para evitar el uso de las fixtures me ha parecido interesante el uso de factorias. La gente de thoughtbot utilizan FactoryGirl.

A continuación algunos ejemplos de el uso que le he dado a shoulda para probar modelos y controladores.

Testeando modelo Artist

   should_require_attributes :name
   should_belong_to :catalog
   should_have_many :albums
   should_have_many :tracks
   should_have_named_scope :recent, :include => :catalog, :order => 'artists.created_at desc',
     :limit => 9, :group => 'catalog_id'
   should_have_named_scope 'most_played(10)', :joins => "INNER JOIN tracks on tracks.artist_id = artists.id",
       :select => "SUM(play_count) as sum_play_count, artists.*",
       :group => "tracks.artist_id", :order => "sum_play_count DESC", :limit => 10
 

Testeando controller Artist

 class ArtistsControllerTest < Test::Unit::TestCase
   fixtures :users, :catalogs, :artists, :albums, :tracks, :playlists
   def setup
     @controller = ArtistsController.new
     @request    = ActionController::TestRequest.new
     @response   = ActionController::TestResponse.new
   end
   logged_in_as(:admin) do   
     context "on GET to :index" do
       setup do
         get :index, :catalog_id => catalogs(:catalog_itunes_pacoguzman).id
       end
       should_assign_to :catalog
       should_assign_to :artists
       #FIXME También se asigna @recommended
       should_assign_to :recommended
       should_respond_with :success
       should_not_set_the_flash
       should_render_a_form # search form 
       should "has one link to new artist page" do
         assert_select "a[href=?]", new_catalog_artist_path(catalogs(:catalog_itunes_pacoguzman)), 
           :count => 1, :text => "New artist"
       end    
       should "has one link to back catalog page" do
         assert_select "a[href=?]", catalog_path(catalogs(:catalog_itunes_pacoguzman).id), 
           :count => 1, :text => catalogs(:catalog_itunes_pacoguzman).type_catalog
       end
     end
   end
 end
 

Para definir el contexto anterior he utilizado el macro logged_in_as del siguiente modo (test_helper.rb)

   def self.logged_in_as(user = :admin)
     context "When logged in as #{user}" do
       setup { @logged_in_user = login_as user }
       yield
     end
   end
 

Hasta aquí he resumido el uso que he dado a shoulda en mi aplicación, y para enlazar aquí dejo unos cuantos links:

9 Julio 2008

Hoy me voy a dedicar a simplificar un poco mi aplicación Rails que estoy desarrollando para mi proyecto fin de carrera en la Universidad Carlos III de Madrid.

De momento esta en fase pre-alfa se podría decir, pero pronto estará accesible a todos para que al menos la podáis echar un vistazo.

Para la simplificación de vistas me he basado en un capítulo de la web railscasts el episodio denominado simplify views with rails 2.

Partiendo de catalogs/show.html.erb

 <h1>Catalog <%=h @catalog.id %></h1>
 <div class="details">
   <p>
     <b>Type </b>
     <%=h @catalog.type_catalog %>
   </p>
   <p>
     <b>User:</b>
     <%=h @catalog.user.login %>
   </p>
   <p>
     <b>Created at:</b>
     <%=h @catalog.created_at %>
   </p>
   <p>
     <b>Updated at:</b>
     <%=h @catalog.updated_at %>
   </p>
 </div>
 

Podemos sustituirlo por el código del partial catalogs/_catalog.html.erb

 <%= render :partial => 'catalog', :object => @catalog %>
 

Pero lo que podemos hacer para reducir aún más el código es pasar directamente el objeto al partial, rails se encarga de ver que clase de objeto y buscar el partial asociado.

 <%= render :partial => @catalog %>
 

Finalmente modificamos el partial ligeramente:

 <% div_for catalog do %>
 <h1>Catalog <%=h catalog.id %></h1>
 <div class="details">
   <p>
     <b>Type </b>
     <%=h catalog.type_catalog %>
   </p>
   <p>
     <b>User:</b>
     <%=h catalog.user.login %>
   </p>
   <p>
     <b>Created at:</b>
     <%=h catalog.created_at %>
   </p>
   <p>
     <b>Updated at:</b>
     <%=h catalog.updated_at %>
   </p>
 </div>
 <% end %>
 

La etiqueta div_for incluye una etiqueta div con class="catalog" e id="catalog_#{catalog.id}"

Un saludo

Sobre Riding to NoWhere

www.flickr.com
Éste es un módulo Flickr que muestra fotos o videos públicos de Paco Guzman - Tikhon. Crea tu propio módulo aquí.

Categorías