Riding to NoWhere http://pacoguzman.lacoctelera.net 2011-07-04T10:20:24Z Francisco Javier Guzmán Rivas ElasticSearch y Couchdb river http://pacoguzman.lacoctelera.net/post/2011/07/04/elasticsearch-y-couchdb-river 2011-07-04T10:20:24Z Riding to NoWhere En este post vamos a ver como incorporar un motor de búsqueda en una aplicación (Rails) para indexar documentos almacenados... <p>En este post vamos a ver como incorporar un motor de búsqueda en una aplicación (Rails) para indexar documentos almacenados en Couchdb.</p> <p><a href="http://www.elasticsearch.org/">ElasticSearch</a> es un motor de búsqueda construido sobre <a href="http://lucene.apache.org/">Lucene</a>, es Open Source (Apache 2), es distruido y presenta un interfaz Restful. ElasticSearch cuenta con una funcionalidad denominada <a href="http://www.elasticsearch.org/guide/reference/river/">River</a>. River es un sistema de plugins que una vez instalado en ElasticSearch permite extraer datos (o enviarle datos) que será indexados e incoporados al cluster de ElasticSearch, para su posterior consulta. ElasticSearch cuenta con 4 diferentes rivers listos para ser instalados: <a href="http://www.elasticsearch.org/guide/reference/river/couchdb.html">Couchdb</a>, <a href="http://www.elasticsearch.org/guide/reference/river/rabbitmq.html">RabbitMQ</a>, <a href="http://www.elasticsearch.org/guide/reference/river/twitter.html">Twitter</a> y <a href="http://www.elasticsearch.org/guide/reference/river/wikipedia.html">Wikipedia</a>. Aquí nos centraremos en el uso del river para Couchdb.</p> <p>El river para Couchdb se basa en una funcionalidad de Couchdb denominada <a href="http://guide.couchdb.org/draft/notifications.html#continuous">"Continuous Changes API"</a>. Una vez te conectas a este API y manteniendo la conexión abierta Couchdb te enviará las modificaciones y seguirá manteniendo la conexión abierta para enviarte las siguientes modificaciones hasta que decidas cerrar la conexión. Siempre tendremos una conexión abierta (por river definido) pero Couchdb es capaz de manejar un gran número de conexiones sin problemas.</p> <p>Como instalamos ElasticSearch y el river de Couchdb:</p> <p> <script src="https://gist.github.com/1062993.js?file=INSTALL_AND_SETUP"></script> </p> <p>El propio river de Couchdb con la configuración que le hemos dado se enganchará a la siguiente url, para ir recibiendo las modificaciones que hagamos en la base de datos de couchdb e ir indexando documentos.</p> <blockquote> <p>http://localhost:5984/couchdb_myapp_development/_changes?feed=continuous&amp;include_docs=true&amp;heartbeat=10000</p> </p></blockquote> <p>Sin embargo con esta configuración incluimos en el mismo índice todos los documentos de Couchdb, si queremos establecer un indice para dos tipos de documentos como por ejemplo Product y Person, debemos establecer un filtro a la hora de crear el river. Este filtro debe hacer referencia al nombre de un filtro especificado a la hora de definir un <a href="http://guide.couchdb.org/draft/notifications.html#filters">design document</a></p> <p> <script src="https://gist.github.com/1062993.js?file=DESIGN_DOCS"></script> <script src="https://gist.github.com/1062993.js?file=FILTERING"></script> </p> <p>Debemos tener en cuenta que nuestros documentos de couchdb tienen especificado un atributo 'type' que permite realizar el filtrado.</p> <p>Por último crearemos un documento de tipo 'Product' y realizaremos una búsqueda</p> <p> <script src="https://gist.github.com/1062993.js?file=EXAMPLE"></script> </p> bit.ly y prototype acortando urls http://pacoguzman.lacoctelera.net/post/2011/04/08/bit-ly-y-prototype-acortando-urls 2011-04-08T11:55:06Z Riding to NoWhere Esto va rápido, que luego soy un pesado. Queremos utilizar un servicio para acortar las urls de nuestra aplicación a la ho... <p>Esto va rápido, que luego soy un pesado.</p> <p>Queremos utilizar un servicio para acortar las urls de nuestra aplicación a la hora de compartir dichas urls en redes sociales.</p> <p>Vamos a hacerlo directamente con JavaScript (lo podríamos hacer en servidor a la hora de definir la url del recurso, pero no es el caso). Tenemos soluciones con <a href="http://www.justinswan.com/jquery-url-shortener.html">jQuery</a> y <a href="http://jsfiddle.net/dimitar/xVtZy/">MootTools</a>. Pero y con Prototype ("ese gran abandonado").</p> <p>Prototype no tiene soporte para hacer peticiones ajax a otros dominios así que utilizaremos <a href="https://github.com/dandean/Ajax.JSONRequest">Ajax.JSONRequest</a> para poder hacer este tipo de peticiones (con razón se abandona :P).</p> <p>Así que simplemente nos montamos una petición con los parámetros oportunos al api de <a href="http://code.google.com/p/bitly-api/wiki/ApiDocumentation#/v3/shorten">bit.ly</a> y a aquí va el <a href="http://jsfiddle.net/NWFhC/11/">ejemplo</a></p> <p>Ahh se me olvidaba es viernes :P y <a href="http://jsfiddle.net">jsfiddle.net</a> lo peta!</p> Sencillo renderer de CSV en Rails 3 http://pacoguzman.lacoctelera.net/post/2010/12/08/sencillo-renderer-csv-rails-3 2010-12-08T22:24:07Z Riding to NoWhere En el artículo anterior vimos como generar texto en formato CSV en ruby 1.9.2 y como utilizar una sencilla clase dentro de u... <p>En el artículo <a href="http://pacoguzman.lacoctelera.net/post/2010/12/04/csv-y-ruby-1-9-y-encoding-yo-escribo-enconding">anterior</a> vimos como generar texto en formato CSV en ruby 1.9.2 y como utilizar una sencilla clase dentro de una aplicación rails 3 que generaba texto en dicho formato. En este artículo vamos a ver como incorporar lo aprendido para que nuestra aplicación responda a peticiones de formato CSV de una manera muy sencilla.</p> <p>Partimos del siguiente controlador que simplemente crea un objecto ActiveRecord::Relation (nuestra aplicación no tiene muchos usuarios no nos preocupamos por recuperar todos los registros)</p> <pre class="brush:ruby"> class Admin::UsersController < Admin::AdminController def index @users = User.scoped end end </pre> <p>Ahora simplemente debemos declarar que nuestro controlador y/o acción responde a peticiones csv</p> <pre class="brush:ruby"> class Admin::UsersController < Admin:AdminController respond_to :html, :csv def index @users = User.scoped respond_with(@users) end end </pre> <p>Sin embago si abrimos nuestro navegador y vamos a "/admin/users.csv" nos encontramos con:</p> <blockquote> <p>Template is missing</p> <p>Missing template admin/users/index with {:handlers=&gt;[:erb, :rjs, :builder, :rhtml, :rxml], :formats=&gt;[:csv], :locale=&gt;[:es, :es]}</p> </p></blockquote> <p>Al utilizar el respond_with y gracias al respond_to :csv hemos indicado a la aplicación que responde a peticiones de formato :csv, pero rails primero comprobará si hemos añadido un bloque para dicho formato o si tenemos una plantilla para hacer el render (index.csv.erb), sino ejecutará un <a href="https://github.com/rails/rails/blob/master/actionpack/lib/action_controller/metal/responder.rb">responder por defecto</a>. Y este responder por defecto producirá el error mostrado anteriormente ya que intenta ejecutar igualmente render(:csv =&gt; @users) pero no tenemos ninguna plantilla definida.</p> <p>Pero en rails 3 podemos definir que nuestra aplicación responda a xml y json y funcionariá sin definir ninguna plantilla. Y esto es porque rails 3 define "renderers" tanto para xml y json</p> <pre class="brush:ruby"> class Admin::UsersController < Admin:AdminController respond_to :html, :csv, :json, :xml def index @users = User.scoped respond_with(@users) end end module ActionController module Renderers ... add :json do |json, options| json = json.to_json(options) unless json.respond_to?(:to_str) json = "#{options[:callback]}(#{json})" unless options[:callback].blank? self.content_type ||= Mime::JSON self.response_body = json end add :xml do |xml, options| self.content_type ||= Mime::XML self.response_body = xml.respond_to?(:to_xml) ? xml.to_xml(options) : xml end ... end end </pre> <p>Entonces simplemente lo que necesitamos es añadir un nuevo "renderer" para el formato csv. Nos creamos el initializer csv_renderer.rb</p> <pre class="brush:ruby"> ActiveSupport.on_load :action_controller do ActionController.add_renderer :csv do |relation, options| csv_instance = CsvDelegator.new(relation) options = {:type => "text/csv; charset=utf8", :filename => csv_instance.filename}.merge(options) send_data csv_instance.to_csv, options.slice(:type, :filename) end end </pre> <p>Y no necesitamos añadir nada más, bueno si definir nuestra clase CsvDelegator, pero eso no tiene mucha historia y os lo dejo a vosotros</p> <p>Y bueno unos links que pueden resultar de interes:</p> <ul> <li> <a href="http://bigbangtechnology.com/post/rails3_upgrade">Rails 3 upgrade</a> </li> <li> <a href="http://www.engineyard.com/blog/2010/render-options-in-rails-3/">Render Options in Rails 3</a> </li> <li> <a href="http://media.pragprog.com/titles/jvrails/create.pdf">Creating your own renderer</a> </li> <li> <a href="http://weblog.rubyonrails.org/2009/8/31/three-reasons-love-responder">Three reasons love responder</a> </li> </ul> <p>NOTA: ¿Por qué en los diferentes ejemplos utilizan ActionController::Renderers.add en lugar de ActionController.add_renderer? ¡Qué alguien me cuente!</p> CSV y ruby 1.9 y el 'encoding' (yo lo escribo 'enconding') http://pacoguzman.lacoctelera.net/post/2010/12/04/csv-y-ruby-1-9-y-encoding-yo-escribo-enconding 2010-12-04T15:59:22Z Riding to NoWhere En algunos de los proyectos rails en los que he trabajado hemos tenido la necesidad de exportar datos en formato CSV y siempr... <p>En algunos de los proyectos rails en los que he trabajado hemos tenido la necesidad de exportar datos en formato <a href="http://en.wikipedia.org/wiki/Comma-separated_values">CSV</a> y siempre la primera opción era utilizar <a href="http://fastercsv.rubyforge.org/">FasterCSV</a>, pero en mi actual proyecto en rails estamos desarrollando en ruby 1.9.2 y me he encontrado con algunas sorpresas para incorpar la exportación en formato CSV en la aplicación.</p> <p>En primer lugar después de incluir fastercsv en mi <a href="http://gembundler.com/">Gemfile</a> y tratar de arrancar la aplicación me encuentro con el siguiente mensaje:</p> <blockquote> <p>Please switch to Ruby 1.9's standard CSV library. It's FasterCSV plus<br />support for Ruby 1.9's m17n encoding engine.</p> </p></blockquote> <p>Bueno parece que no necesito ningúna libreria extra solo utilizar la librería standard de <a href="http://ruby-doc.org/ruby-1.9/classes/CSV.html">CSV de Ruby 1.9</a>. Pues nada me creo un initializer dependencies.rb e incluyo un require 'csv'. Ahora manos a la obra con el CSV en ruby 1.9, empezamos probando cosas, en un irb:</p> <p>CSV.generate{|csv| csv &lt;&lt; ["nombre", "apellidos"]} =&gt; "nombre,apellidos\n"</p> <p>Bien pinta bien, ahora unos carácteres especiales (unas tildes por ejemplo)</p> <p>CSV.generate{|csv| csv &lt;&lt; ["dirección", "población"]} =&gt; "direcci\xC3\xB3n,poblaci\xC3\xB3n\n"</p> <p>Vale ya aparecen los primeros problemas con la codificación. Yo siempre he trabajo en "UTF-8" tanto a nivel de aplicación como en base de datos, así que supongo que deberé especificar la codificación en algún sitio. Esto en ruby 1.9 es fácil, e igual de fácil pasarselo a la libreria de CSV.</p> <p>CSV.generate(:encoding =&gt; "".encoding){|csv| csv &lt;&lt; ["dirección", "población"]} =&gt; "dirección,población\n"</p> <p>La cosa mejora y me creo un fichero dentro del directorio lib de mi aplicación rails 3</p> <pre class="brush:ruby"> class CsvBlog def self.to_csv CSV.generate(:encoding =&gt; "".encoding) { |csv| csv << ["dirección", "población"] } end end </pre> <p>Ahora en lugar de arrancar el irb arranco la consola de rails:</p> <p>CsvBlog.to_csv<br />/lib/csv_blog.rb:5: invalid multibyte char (US-ASCII)<br />/lib/csv_blog.rb:5: syntax error, unexpected $end, expecting ']'<br /> csv &lt;&lt; ["dirección", "población"]</p> <p>Bueno parece que rails 3 no establece la codificación para los ficheros dentro del directorio lib de la aplicación, así que toca especificar la codificación en este nuevo fichero y ya no tenemos mayor problema. Pero queda feo tener que especificar la codificación al generar el csv, pero miremos lo que ocurre:</p> <p>&gt;&gt; CSV.generate{|csv| puts "".encoding;csv &lt;&lt; ["dirección"] }<br />UTF-8<br />=&gt; "direcci\xC3\xB3n\n"<br />&gt;&gt; CSV.generate{|csv| puts csv.encoding;csv &lt;&lt; ["dirección"] }<br />US-ASCII<br />=&gt; "direcci\xC3\xB3n\n"<br />&gt;&gt; CSV.generate(""){|csv| puts csv.encoding;csv &lt;&lt; ["dirección"] }<br />UTF-8<br />=&gt; "dirección\n"</p> <p>La codificación de nuestros datos es UTF-8 pero si no se especifica a la librería mediante la opción :encoding o mediate un string (extrae la codificación de el), el objeto csv nos hace un estropicio.</p> <p>Hasta aquí todo en la próxima entrega comentaré como integrar esto con los Responders o Renderers incluidos en rails 3, el tema nos va a quedar muy sencillo.</p> Mis últimos meses con RoR http://pacoguzman.lacoctelera.net/post/2010/05/20/mis-ultimos-meses-con-ror 2010-05-20T00:08:43Z Riding to NoWhere Después de unos cuantos meses de abandono de mi blog, me he propuesto con este post resumir lo que he hecho durante este tie... <p>Después de unos cuantos meses de abandono de mi blog, me he propuesto con este post resumir lo que he hecho durante este tiempo, esperando no tardar tanto tiempo en volver a escribir alguna tontuna.</p> <p>En <a href="http://aspgems.com">ASPgems</a> hemos estado organizando charlas de formación interna una vez al mes, abiertas a cualquiera que se quisiera apuntar, para contar/exponer materias de interes para nosotros. Y con estas charlas llego mi estreno, con una charla sobre <a href="http://github.com/pacoguzman/ruby_testing">testing de aplicaciones ruby/rails</a> junto a mi compañero <a href="http://twitter.com/jorgegorka">@jorgegorka</a>, incluso hay un <a href="http://www.vimeo.com/10556566">vídeo</a>, en el canal de <a href="http://www.vimeo.com/aspgems">aspgems en vimeo</a>. No fue nada espectular, no se podía esperar otra cosa si yo andaba ahí metido, después sobre todo de querer mostrar código en directo y abandonarlo a los 10 minutos :)  También hemos realizado alguna charla más informal al estilo "lightning talks" muy insperadoras de lo que esta por llegar...en la web</p> <p>También durante este tiempo he estado revisando código de aquí y de allá, he liberado pequeños pedacitos de código en forma de plugins para aplicaciones tog: <a href="http://github.com/pacoguzman/tog_activity">tog_activity</a> y <a href="http://github.com/pacoguzman/tog_wall">tog_wall</a> para compensar todo el curro que se ha dado la gente de <a href="http://www.toghq.com/">tog</a></p> <p>He seguido apoyando <a href="http://github.com/nando/mundo-pepino">mundo-pepino</a> sobre todo después de una charla del madrid-rb sobre capybara de <a href="http://twitter.com/porras">@porras</a> que me empujo a actualizar mundo-pepino para que se pudieran utilizar las últimas versiones de cucumber y para que se pudiera utilizar capybara o webrat, y de aquí surgió la <a href="http://github.com/nando/mundo-pepino/commit/9afc417e26b305c4f56b7c065b692db84f9052f3">Paco's release</a> gracias <a href="http://nando.lacoctelera.net/">Nando</a>! ;)</p> <p>También participe en el Desafio Abredatos junto con: <a href="http://twitter.com/elafo">@eLafo</a>, <a href="http://twitter.com/jorgegorka">@jorgegorka</a> y <a href="http://twitter.com/leptom">@leptom</a> y bueno la verdad que lo pasamos genial creando <a href="http://desenchufatucasa.es/">DesenchufaTuCasa</a>, viendo la formula 1 el domingo, zampandonos unas pizzas del dominos, vamos zampando toda la comida rápida que pudimos.</p> <p>Y bueno también he tenido tiempo de participar en el pasado <a href="http://www.bugmash.com/">BugMash</a>, he  entrado en la <a href="http://contributors.rubyonrails.org/contributors/paco-guzman/commits">Rails Contributors</a>, he estado en Amsterdam una semanita de vacaciones.</p> <p>Y lo que viene seguro que es mejor, empezando la semana que viene con el viaje a la <a href="http://euruko2010.org/">Euruko 2010</a></p> human_name and error_message_for controversy http://pacoguzman.lacoctelera.net/post/2010/01/22/human_name-and-error_message_for-controversy 2010-01-22T15:55:33Z Riding to NoWhere Parace que nuestros amigos ActiveRecord::Base.human_name y ActionView::Base::Helpers.error_messages_for parece que no están ... <p>Parace que nuestros amigos <em>ActiveRecord::Base.human_name</em> y <em>ActionView::Base::Helpers.error_messages_for</em> parece que no están en la misma onda o ola o como queráis. El método human_name intenta proporcionar un nombre más "humano" a nuestros modelos de active record y <em>error_messages_for</em> intenta proporcionarnos unos bonitos mensajes de error al intentar crear/editar nuestro modelo de active record.</p> <p>Además <em>error_messages_for</em> tiene multitud de opciones que nos permiten definir enteramente el contenido de los mensajes, su estructura html y otras cosillas. Pero el problema viene cuando tratamos con los valores por defecto. Este método captura la variable de instancia a partir de su primer parámetro y debe obtener la variable <em>options[object_name]</em> de dicha variable si la opción <em>object_name</em> no es pasada como parámetro. Y aquí llegamos a la controversia, ¿Que clave de nuestros locale recuperamos para generar el <em>object_name</em> en caso de que no lo proporcione el programador?</p> <p>Pues como suponéis <em>error_messages_for</em> no recupera la misma clave que <em>human_name</em> con lo que nos surge un problema.</p> <pre class="brush:ruby;"> def error_messages_for(*params) options[:object_name] ||= params.first ... I18n.with_options :locale => options[:locale], :scope => [:activerecord, :errors, :template] do |locale| ... object_name = options[:object_name].to_s.gsub('_', ' ') object_name = I18n.t(object_name, :default => object_name, :scope => [:activerecord, :models], :count => 1) ... end ... end def human_name(options = {}) defaults = self_and_descendants_from_active_record.map do |klass| :"#{klass.name.underscore}" end defaults << self.name.humanize I18n.translate(defaults.shift, {:scope => [:activerecord, :models], :count => 1, :default => defaults}.merge(options)) end </pre> <p>Pero ante la llegada inminente de Rails 3 o eso nos comentaba Yehuda en el grupo del core team se han puesto de acuerdo estos muchachos con la ayuda de ActiveModel y tenemos esto. </p> <pre class="brush:ruby"> module ActiveModel::Naming # Transform the model name into a more humane format, using I18n. By default, # it will underscore then humanize the class name (BlogPost.model_name.human #=> "Blog post"). # Specify +options+ with additional translating options. def human(options={}) # No nos interesa que clave recupera pero vemos que será la misma ... end end module ActionView module Helpers module ActiveModel def error_messages_for(*params) ... if object.class.respond_to?(:model_name) options[:object_name] ||= object.class.model_name.human.downcase end ... end end end end </pre> <p>Así por ahora solo nos queda esperar o pasarle a error_messages_for el parámetro object_name con le valor que necesitemos. Ciau</p> </<> Precarga de Asociaciones - Usandolo a mi antojo http://pacoguzman.lacoctelera.net/post/2009/12/12/precarga-asociaciones-usandolo-mi-antojo 2009-12-12T19:45:16Z Riding to NoWhere Si nos vamos a echar un ojo al código fuente de Rails que se encarga de la precarga de asociaciones preload_associations, el... <p>Si nos vamos a echar un ojo al código fuente de Rails que se encarga de la precarga de asociaciones <a href="http://github.com/rails/rails/blob/v2.3.5/activerecord/lib/active_record/association_preload.rb">preload_associations</a>, el código que se ejecuta al utilizar un :include =&gt; :patatas nos encontramos con lo siguiente:</p> <blockquote> <p>Application developers should not use this module directly.</p> </p></blockquote> <p>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.</p> <p>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?)</p> <p>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.</p> <p>El caso consta de los modelos comment, user y profile descritos <a class="gist" href="http://gist.github.com/254989">aquí </a></p> <p>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 <a href="http://gist.github.com/254993">aquí</a> 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 <a href="http://gist.github.com/255002">aquí</a></p> <p>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.</p> <p>Alé que es sábado y habrá que beberse unas cervezas! A vuestra salud!</p> Una de asociaciones has_many :through en ultrasphinx http://pacoguzman.lacoctelera.net/post/2009/11/02/una-asociaciones-has_many-through-ultrasphinx 2009-11-02T13:30:50Z Riding to NoWhere Durante las últimas semanas o mejor dicho, el último par de meses he estado trabajando con proyectos que presentan funciona... <p>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 <a href="http://github.com/fauna/ultrasphinx/">ultrasphinx</a>.</p> <p>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.</p> <p>Un punto que conviene no olvidar cuando se utiliza la instrucción:</p> <pre class="brush: ruby;"> class model < ActiveRecord::Base is_indexed end </pre> <p>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.</p> <p>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:</p> <pre class="brush: ruby;"> class User < ActiveRecord::Base has_many :recipes has_many :ingredients, :through => :recipes end class Recipe < ActiveRecord::Base has_many :ingredients end </pre> <p>La búsqueda a implementar son búsquedas de usuarios por ingredientes, vale pues a configurar el índice con un poco de DSL:</p> <pre class="brush:ruby;"> 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 </pre> <p>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.</p> <p>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?</p> <p>La solución la encontré en este hilo<a href="http://www.ruby-forum.com/topic/179278"> "How to set up a has_many :through association with Ultrasphinx"</a></p> <p>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:</p> <pre class="brush:ruby;"> 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 </pre> <p>Respondiendo al autor del hilo, gracias! me ha ahorrado bastante tiempo.</p> jQuery in Action terminado, ¿Ahora qué? http://pacoguzman.lacoctelera.net/post/2009/10/02/jquery-in-action-terminado-ahora-que 2009-10-02T18:13:31Z Riding to NoWhere Durante las últimas tres semanas estuve leyendo el libro jQuery In Action de la editorial Manning durante esos maravillosos ... <p>Durante las últimas tres semanas estuve leyendo el libro <a href="http://www.manning.com/bibeault/">jQuery In Action</a> de la editorial Manning durante esos maravillosos remansos de paz que son los trenes de cercanias. Aunque este libro esta basado en la versión 1.2 y actualmente jQuery se encuentra en su version <a href="http://jquery.com/">1.3.2</a> considero que es un muy buen punto de partida para empezar a trabajar con jQuery, ya que durante el libro se exponen multitud de ejemplos e incluso una introducción a JavaScript que también me ha enseñado multitud de cosas.</p> <p>¿Por qué jQuery? Vale no es que yo tenga una gran experiencia con otros frameworks como pudiera ser Prototype, ni tampoco con el mismo lenguaje JavaScript; así esta atracción por jQuery se resume por lo que vi en los screencasts sobre paginación de <a href="http://railscasts.com/tags/11">Ryan Bates</a> y también por el hecho de que un tal <a href="http://yehudakatz.com/">Yehuda Katz</a> halla escrito este libro.</p> <p>A partir de ahora siempre que me sea posible utilizare jQuery en las aplicaciones Rails que desarrolle, y sino pues a tirar de la siguiente estructura que evita que jQuery entre en conflicto con otras librerias que estemos usando:</p> <pre class="brush: javascript;"> (function($){ cualquier cotenido javascript cuyo scope queremos protejer })(jQuery); </pre> <p>La ventaja de lo anterior es que dentro de dicha declaración podemos seguir utilizando el caracter $, caracter que identifica al framework jQuery en este caso. Y ahora un primer ejemplito que nos sirve para hacer submit por ajax de los campos del formulario que no esten en blanco:</p> <pre class="brush: javascript;"> (function($){ $("form").submit(function(event){ var FormValues = {}; $("form input[value]").each(function(){ FormValues[$(this).attr("name")] = $(this).val(); }); $.post($("form").attr("action"), FormValues); }); })(jQuery); </pre> <p>Y con esto hasta mañana a las ocho</p> Círculos con número de orden en la web http://pacoguzman.lacoctelera.net/post/2009/08/17/circulos-con-numero-orden-la-web 2009-08-17T15:34:41Z Riding to NoWhere Bueno me ha tocado crear unos bullets circulares con número de orden para incrustar en una página web y he comprado lo real... <p>Bueno me ha tocado crear unos bullets circulares con número de orden para incrustar en una página web y he comprado lo realmente sencillo que es llevarlo acabo con css3 a través del siguiente artículo <a href="http://blog.ardes.com/2009/3/4/css-circles">"How to Make Circles in CSS"</a>.</p> <p>Mi impletación final fue con el siguiente código css:</p> <pre class="brush: css;"> div.search-circle { float:left; height: 2em; width: 2em; -webkit-border-radius: 1em; -moz-border-radius: 1em; background-color: #FF7900; margin: auto; margin-right:1em; } div.search-circle p{ color:#FFF; text-align: center; margin-bottom: .1em; font-weight:bold; font-size:1.5em; } </pre> <p>Y el siguiente fragmento de HTML:</p> <pre class="brush: html;">&lt;div class="search-circle"&gt;&lt;p&gt;1&lt;/p&gt;&lt;/div&gt; </pre> <p>Moraleja al querer contentar a los usuarios de IE nos toca crear una imagen con GIMP (por ejemplo) del tamaño y color que necesitemos y utilizar el siguiente código css:</p> <pre class="brush: css;"> div.search-ballon { float:left; background:url(/images/layout/ballon.gif) no-repeat; width:26px; height:26px; margin-right:1em; } div.search-ballon p{ color:#FFF; text-align: center; margin-bottom: .1em; font-weight:bold; font-size:1.5em; } </pre> <p>Y el código html en este caso sería el siguiente:</p> <p></p> <p>Al final nos vemos obligados a aprender a utilizar un editor de imágenes, que seguro que mal no nos viene.</p> <p> </p>