<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	xmlns:georss="http://www.georss.org/georss" xmlns:geo="http://www.w3.org/2003/01/geo/wgs84_pos#" xmlns:media="http://search.yahoo.com/mrss/"
	>

<channel>
	<title>Vía Rails &#187; 2010 &#187; enero &#187; 05</title>
	<atom:link href="http://viarails.wordpress.com/2010/01/05/feed/" rel="self" type="application/rss+xml" />
	<link>http://viarails.wordpress.com</link>
	<description>Ruby on Rails a Máxima Velocidad - en Español</description>
	<lastBuildDate>Mon, 27 Jun 2011 07:27:46 +0000</lastBuildDate>
	<language>es</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.com/</generator>
<cloud domain='viarails.wordpress.com' port='80' path='/?rsscloud=notify' registerProcedure='' protocol='http-post' />
<image>
		<url>http://s2.wp.com/i/buttonw-com.png</url>
		<title>Vía Rails &#187; 2010 &#187; enero &#187; 05</title>
		<link>http://viarails.wordpress.com</link>
	</image>
	<atom:link rel="search" type="application/opensearchdescription+xml" href="http://viarails.wordpress.com/osd.xml" title="Vía Rails" />
	<atom:link rel='hub' href='http://viarails.wordpress.com/?pushpress=hub'/>
		<item>
		<title>Buscando con Searchlogic</title>
		<link>http://viarails.wordpress.com/2010/01/05/buscando-con-searchlogic/</link>
		<comments>http://viarails.wordpress.com/2010/01/05/buscando-con-searchlogic/#comments</comments>
		<pubDate>Tue, 05 Jan 2010 18:26:39 +0000</pubDate>
		<dc:creator>Gabriel Somoza</dc:creator>
				<category><![CDATA[Plugins]]></category>
		<category><![CDATA[Refactorización]]></category>
		<category><![CDATA[búsqueda]]></category>
		<category><![CDATA[named_scopes]]></category>
		<category><![CDATA[permisos]]></category>
		<category><![CDATA[plugin]]></category>
		<category><![CDATA[rails]]></category>
		<category><![CDATA[ruby on rails]]></category>
		<category><![CDATA[searchlogic]]></category>
		<category><![CDATA[Usuarios]]></category>

		<guid isPermaLink="false">http://viarails.wordpress.com/?p=168</guid>
		<description><![CDATA[Searchlogic es un plugin muy útil que nos permite buscar en modelos de forma mucho más ágil, por medio de named scopes (introducidos en Rails 2.x). Los named scopes (la mejor traducción no-literal que se me ocurre es: &#8220;filtros de alcance&#8220;) son básicamente filtros SQL con nombre, pero esto es más fácil de mostrar que [...]<img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=viarails.wordpress.com&#038;blog=8688884&#038;post=168&#038;subd=viarails&#038;ref=&#038;feed=1" width="1" height="1" />]]></description>
			<content:encoded><![CDATA[<p><a title="Searchlogic - Repositorio Git" href="http://github.com/binarylogic/searchlogic" target="_blank">Searchlogic</a> es un plugin muy útil que nos permite buscar en modelos de forma mucho más ágil, por medio de <em>named scopes</em> (introducidos en Rails 2.x). Los <em>named scopes</em> (la mejor traducción no-literal que se me ocurre es: <em>&#8220;filtros de alcance</em>&#8220;) son básicamente filtros SQL <em>con nombre</em>, pero esto es más fácil de mostrar que de explicar. La principal ventaja de estos filtros es que nos permiten hacer nuestro código más DRY (<em>Don&#8217;t Repeat Yourself</em>, es decir: menos repetitivo), lo que a su vez acelera muchísimo el tiempo de desarrollo. <span id="more-168"></span></p>
<h3>1- Instalación de Searchlogic</h3>
<p>Por supuesto, la instalación de Searchlogic es muy simple:</p>
<p><pre class="brush: plain; light: true;">
sudo gem install searchlogic
</pre></p>
<p>Una vez instalado el gem agregamos la siguiente línea a nuestro archivo de configuración de entorno:</p>
<p><pre class="brush: ruby;">
#config/environment.rb
config.gem &quot;searchlogic&quot;
</pre></p>
<p>Aunque es más recomendable el método anterior, también se puede instalar como plugin:</p>
<p><pre class="brush: plain; light: true;">
script/plugin install git://github.com/binarylogic/searchlogic.git
</pre></p>
<h3>2- Conceptos Básicos</h3>
<p>Searchlogic es un plugin muy completo y flexible: nos brinda filtros y una clase para encapsularlos, métodos de ordenamiento, helpers para nuestras vistas, integración con otros plugins (como WillPaginate), etc. No vamos a cubrir todos sus aspectos, por lo menos no en esta ocasión; pero sí conoceremos algunos conceptos básicos que nos darán el puntapié necesario para animarnos a usarlo en nuestras aplicaciones. Comenzaremos viendo su concepto central: filtros.</p>
<h4>Filtros (Named Scopes)</h4>
<p>Una vez instalado, Searchlogic nos permite utilizar cierto número de filtros predeterminados, que en realidad son los famosos <em>named_scopes</em> de Rails. En la <a title="Documentación de Searchlogic" href="http://rdoc.info/projects/binarylogic/searchlogic" target="_blank">documentación</a> hay una <a title="Lista de Filtros" href="http://rdoc.info/rdoc/binarylogic/searchlogic/blob/dbec136d866c2aa5b9dd58bd33e32c57ce8997f8/Searchlogic/NamedScopes/Conditions.html" target="_blank">lista completa</a> de todas las opciones que podemos usar; aquí conoceremos sólo algunas.</p>
<p>Searchlogic no necesita nada de configuración: podemos utilizar sus filtros directamente. Supongamos que tenemos un modelo llamado <em>User</em> con algunos de los campos más comunes: <em>username, email, password </em>y<em> age</em> (nombre de usuario, email, contraseña y edad, respectivamente). También ya tenemos varios usuarios registrados, y lo que queremos es ver cuáles de los usuarios registrados menores a 20 años tienen una cuenta de email dentro del Reino Unido (termina en &#8220;<em>.co.uk</em>&#8220;). Para ello podemos levantar nuestra consola rails y simplemente ingresar lo siguiente:</p>
<p><pre class="brush: ruby;">
User.age_lt(20).email_ends_with(&quot;.co.uk&quot;)
</pre></p>
<p>Searchlogic nos devuelve una lista con los usuarios que cumplen con dichos criterios. Podemos concatenar toda la cantidad de filtros que sea necesario, uno tras otro. El método <em>age_lt</em> significa <em>age_less_than</em> (edad menor a); y como ésta, existen también otras formas de comparación similares, junto con sus abreviaturas. En la documentación podemos encontrar todas las posibilidades.</p>
<p>Es importante notar que podemos usar estos filtros con cualquier campo. Si por ejemplo tuviéramos un campo que sea <em>login_count</em> para almacenar la cantidad de veces que el usuario inició sesión en nuestro sitio, podemos buscar todos los usuarios del Reino Unido que hayan entrado más de 100 veces <em>o más</em> de la siguiente forma:</p>
<p><pre class="brush: ruby;">
User.email_ends_with(&quot;.co.uk&quot;).login_count_gte(100)
# gte es la abreviatura de &quot;greater than or equal&quot;
# y significa &quot;mayor o igual a&quot;.
</pre></p>
<h4>Negando Filtros</h4>
<p>De la misma forma que podemos buscar los usuarios cuyos emails terminen con &#8220;.co.uk&#8221;, también podemos buscar aquellos cuyos emails <em>no</em> terminen con dicha cadena:</p>
<p><pre class="brush: ruby;">
User.email_does_not_end_with(&quot;.co.uk&quot;)

#Otro ejemplo:
User.username_not_like(&quot;juan&quot;)
#devuelve todos aquellos cuyo nombre de usuario no contiene la cadena &quot;juan&quot;
</pre></p>
<p>Podemos hacer lo mismo con cualquier otro filtro.</p>
<h4>Combinando Filtros</h4>
<p>Puede ser el caso que querramos buscar a todos los usuarios cuyo nombre (name) o nombre de usuario (username) contengan la cadena &#8220;juan&#8221;. Para ello podemos combinar los filtros en un mismo llamado por medio de la palabra &#8220;or&#8221;, de la siguiente forma:</p>
<p><pre class="brush: ruby;">
User.name_or_username_like(&quot;juan&quot;)
</pre></p>
<h4>Todos o Cualquiera</h4>
<p>Los filtros aceptan también más de un elemento, pero para ello debemos usar alguno de los modificadores especiales para múltiples parámetros: <em>all</em> (todos) o <em>any</em> (cualquiera). Veamos algunos ejemplos:</p>
<p><pre class="brush: ruby;">
#Busca los usuarios con cuenta de email del Reino Unido o de Argentina:
User.email_ends_with_any(&quot;.co.uk&quot;, &quot;.ar&quot;)

#Busca los usuarios que tengan &quot;Juan&quot; y &quot;Perez&quot; dentro de su nombre:
User.name_like_all(&quot;Juan&quot;, &quot;Perez&quot;)
#Algunos resultados pueden ser:
#  Juan Perez Aguilar
#  Juan Marcos Perez

#También podemos pasar un Array:
User.name_like_all([&quot;Juan&quot;, &quot;Perez&quot;])
</pre></p>
<h4>Ordenamiento</h4>
<p>En SQL es posible ordenar los resultados que obtenemos, y Searchlogic no se ha olvidado de ello. Para ordenar los resultados anteriores por nombre de usuario podemos hacer lo siguiente:</p>
<p><pre class="brush: ruby;">
#Ordena por nombre de usuario desde la A a la Z
User.email_ends_with(&quot;.co.uk&quot;).login_count_gte(100).ascend_by_username
#Ordena por nombre de usuario desde la Z a la A
User.email_ends_with(&quot;.co.uk&quot;).login_count_gte(100).descend_by_username
</pre></p>
<p>De forma similar a los demás filtros, <em>&#8220;ascend_by</em>&#8221; y &#8220;<em>descend_by</em>&#8221; son filtros dinámicos, y en su caso particular deben ser precedidos por el nombre del campo a ordenar (<em>username</em>).</p>
<h4>Rendimiento</h4>
<p>De aquí nos puede surgir la duda de si Searchlogic implementa todos los named scopes posibles para cada modelo de forma predeterminada. Por suerte esto no es así, sino que los crea a medida que los vamos utilizando. Además, una vez creado el named scope, Searchlogic <em>no lo vuelve a crear</em> si lo necesitamos de nuevo. Por lo tanto, no hay que temer por nuestros recursos: el rendimiento con Searchlogic es muy similar al rendimiento que obtendríamos si usáramos nuestros propios <em>named scopes</em>.</p>
<p>Algo más que vale destacar en cuanto a rendimiento: Searchlogic no ejecuta ningún query en la base de datos sino hasta que llamemos al último filtro en la cadena. En el caso anterior, por ejemplo, la siguiente consulta SQL se ejecuta sólo <em>luego</em> de aplicarse el filtro &#8220;<em>login_count_gte</em>&#8220;:</p>
<p><pre class="brush: sql;">
SELECT * FROM `users` WHERE ((users.login_count &gt;= 100) AND (users.email LIKE '%.co.uk'))
</pre></p>
<h3>3- La clase Search</h3>
<p>Searchlogic también nos permite buscar utilizando una clase llamada <em>Search</em>. Esta clase nos sirve para añadir o quitar filtros a una búsqueda, cuyas condiciones quedan almacenadas en la instancia. Igual que en el caso anterior, Searchlogic ejecuta un sólo query a la hora de buscar. En este caso, sin embargo, lo ejecuta luego de que llamemos a algunos de los métodos de búsqueda. Veamos un ejemplo:</p>
<p><pre class="brush: ruby;">
@search = User.search #Crea el objeto Search para el modelo User
@search.age_lt(20)
@search.email_ends_with(&quot;.co.uk&quot;)
# Hasta aquí todavía no se ha hecho ningún query a la base de datos

@search.all #ejecuta el query y devuelve todos los registros
@search.first #ejecuta el query y devuelve sólo el primer registro
@search.count #devuelve el número de registros encontrados
#etc.
</pre></p>
<p>Otra posibilidad menos atractiva pero potencialmente más flexible es acceder directamente al Hash de filtros, de la siguiente manera:</p>
<p><pre class="brush: ruby;">
@search = User.search #Crea el objeto Search
@search.conditions[:age_lt] = 20 # notar la diferencia de sintaxis
@search.conditions[:name_begins_with] = &quot;a&quot;
# Otra forma que puede ser útil en algunos casos:
@search.conditions.merge!({:email_ends_with =&gt; &quot;.co.uk&quot;})
#Ahora @search contiene el siguiente hash:
# {:age_lt=&gt;20, :email_ends_with=&gt;&quot;.co.uk&quot;, :name_begins_with=&gt;&quot;a&quot;}
@search.all #devuelve todos los resultados filtrados
</pre></p>
<p>Para quitar un filtro del objeto Search, basta con llamar su método <em>delete</em> y pasarle la condición que queremos borrar:</p>
<p><pre class="brush: ruby;">
@search.delete(:name_begins_with)
#=&gt; {:age_lt=&gt;20, :email_ends_with=&gt;&quot;.co.uk&quot;}
</pre></p>
<h3>4- Búsquedas Avanzadas</h3>
<p>Hay varias formas de búsqueda que requieren utilizar conceptos un poco más avanzados, como los JOINS o INCLUDES de SQL. Sin embargo, Searchlogic nuevamente nos facilita la tarea con sus potentes herramientas. Para esta sección vamos a suponer que los usuarios de la tabla <em>Users</em> &#8220;tienen muchos&#8221; <em>Permissions</em> (permisos) a través de una relación <em>habtm</em> (explicar los conceptos básicos de este tipo de relación escapa a nuestro propósito en este momento).</p>
<h4>Buscando en clases Asociadas</h4>
<p>De acuerdo con la relación que tenemos entre los usuarios y sus permisos, supongamos que queremos buscar a todos los usuarios que tengan permiso para <em>comentar</em>:</p>
<p><pre class="brush: ruby;">
@search = User.search
@search.permissions_name_is(&quot;comment&quot;)
@search.all # ejecuta la consulta
</pre></p>
<p>La consula SQL generada es:</p>
<p><pre class="brush: sql;">
SELECT `users`.* FROM `users` INNER JOIN `permissions_users` ON `permissions_users`.user_id = `users`.id INNER JOIN `permissions` ON `permissions`.id = `permissions_users`.permission_id WHERE (permissions.name = 'comment')
</pre></p>
<p>En otras palabras, podemos acceder a la descendencia de clases simplemente concatenándola al llamar al filtro. Otro ejemplo, con más niveles de descendencia, en donde los usuarios tienen un rol que a su vez tiene muchos permisos, y en donde queremos encontrar todos los usuarios que puedan comentar:</p>
<p><pre class="brush: ruby;">
@search = User.search
@search.role_permissions_name_is(&quot;comment&quot;)
@search.all # ejecuta la consulta
</pre></p>
<p>Si queremos resolver un problema de consultas N+1, también podemos especificar un INCLUDE en alguno de los métodos de búsqueda:</p>
<p><pre class="brush: ruby;">
@results = @search.all(:include =&gt; :permissions) # ejecuta dos consultas SQL
@results.first.permissions # no ejecuta ninguna consulta SQL
</pre></p>
<p>Las consultas SQL ejecutadas por la primera línea:</p>
<p><pre class="brush: sql;">
SELECT `users`.* FROM `users` INNER JOIN `permissions_users` ON `permissions_users`.user_id = `users`.id INNER JOIN `permissions` ON `permissions`.id = `permissions_users`.permission_id WHERE (permissions.name = 'comment')
SELECT `permissions`.*, t0.user_id as the_parent_record_id FROM `permissions` INNER JOIN `permissions_users` t0 ON `permissions`.id = t0.permission_id WHERE (t0.user_id IN (1,200))
</pre></p>
<h4>Procedimientos de Filtro (Scoped Procedures)</h4>
<p>En nuestra aplicación queremos filtrar a los usuarios que <em>no</em> sean del Reino Unido (UK) y cuya edad se encuentre entre los 12 y 20 años inclusive (es decir, que sean adolescentes). Supongamos que queremos hacer esto en muchos lugares de nuestra aplicación, pero volver a llamar a ambos filtros uno por uno, definitivamente, no es muy DRY.</p>
<p>En casos como este, podemos recurrir a los <em>Scope Procedures</em>, que básicamente significa que podemos darle un nombre a esa combinación de filtros y añadirla como un procedimiento de nuestro modelo User. Para el ejemplo, vamos a nombrar esa combinación de filtros <em>uk_teens</em> (&#8220;adolescentes del Reino Unido&#8221; en inglés):</p>
<p><pre class="brush: ruby;">
#Para crear el Scope Procedure
@search = User.search
@search.email_does_not_end_with(&quot;.co.uk&quot;)
@search.age_lt(20).age_gt(12)
User.scope_procedure :uk_teens, lambda{ @search }

#Otra forma de hacerlo, pero no tan limpia:
User.scope_procedure :uk_teens, lambda { User.email_does_not_end_with(&quot;.co.uk&quot;).age_lt(20).age_gt(12) }

# Finalmente, para utilizar el Scope Procedure
User.uk_teens.all # devuelve todos los resultados
User.search(:uk_teens =&gt; true) # lo mismo que lo anterior
</pre></p>
<p>Es importante notar el uso del método <em>lambda</em>, que lo que hace es recibir un bloque que va a ser ejecutado <em>cada vez</em> que llamamos al Scoped Procedure. Explicar esto a fondo escapa del ámbito de este tutorial. Otro elemento importante es el parámetro que le pasamos al método <em>search</em>, que le dice a Searchlogic que quiere utilizar el método <em>uk_teens</em> como si fuera un filtro más. Podemos utilizar la misma convención para llamar a cualquier <em>named_scope </em>que hayamos declarado manualmente.</p>
<p>También es importante advertir que el ejemplo en la documentación oficial <em>no funciona</em> (tal como está a la fecha de publicación de este artículo). El ejemplo en este artículo demuestra el uso correcto, a mi entender, de los Scoped Procedures.</p>
<h3>Conclusión</h3>
<p>En este tutorial, basado en la documentación oficial de Searchlogic, cubrimos los aspectos básicos de Searchlogic y algunos de los avanzados. Como fue mencionado antes, el plugin es muy flexible, y los usos que le podemos dar son tan variados como nuestra imaginación. Decidí no cubrir la parte de búsqueda con formularios, explicada en la documentación y en el <a title="Railscasts - Searchlogic" href="http://railscasts.com/episodes/176-searchlogic" target="_blank">Railscast de Searchlogic</a>, porque es útil sólo para casos muy específicos (vistas de administrador) y se va del eje central de este artículo.</p>
<p>Por favor, no dudes en dejar un rápido comentario ante cualquier duda, corrección o sugerencia! También podés <a href="../feed/" target="_blank">subscribirte al feed</a> o por email (en la barra de navegación) para recibir notificaciones de los últimos artículos.</p>
<br />  <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gocomments/viarails.wordpress.com/168/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/comments/viarails.wordpress.com/168/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godelicious/viarails.wordpress.com/168/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/delicious/viarails.wordpress.com/168/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gofacebook/viarails.wordpress.com/168/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/facebook/viarails.wordpress.com/168/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gotwitter/viarails.wordpress.com/168/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/twitter/viarails.wordpress.com/168/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gostumble/viarails.wordpress.com/168/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/stumble/viarails.wordpress.com/168/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godigg/viarails.wordpress.com/168/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/digg/viarails.wordpress.com/168/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/goreddit/viarails.wordpress.com/168/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/reddit/viarails.wordpress.com/168/" /></a> <img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=viarails.wordpress.com&#038;blog=8688884&#038;post=168&#038;subd=viarails&#038;ref=&#038;feed=1" width="1" height="1" />]]></content:encoded>
			<wfw:commentRss>http://viarails.wordpress.com/2010/01/05/buscando-con-searchlogic/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
	
		<media:content url="http://0.gravatar.com/avatar/4a3fbfe8b5b31e7003508bc5ee947290?s=96&#38;d=identicon&#38;r=G" medium="image">
			<media:title type="html">Gabriel Somoza</media:title>
		</media:content>
	</item>
	</channel>
</rss>
