<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>ASCIIcasts - Full Episode Feed</title>
    <description>The latest episodes from ASCIIcasts</description>
    <link>http://asciicasts.com/</link>
    <pubDate>Thu, 29 Jul 2010 08:41:19 +0000</pubDate>
    <ttl>1440</ttl>
    <item>
      <title>Gr&#225;ficas</title>
      <description>&lt;p&gt;Una buena forma de mostrar res&amp;uacute;menes o estad&amp;iacute;sticas, si tenemos una aplicaci&amp;oacute;n que contiene muchos datos, es mostrar una gr&amp;aacute;fica con dichos datos.  A continuaci&amp;oacute;n se muestra una captura de pantalla de la interfaz administrativa de una aplicaci&amp;oacute;n de comercio electr&amp;oacute;nico.  Cada pedido tiene un n&amp;uacute;mero de pedido, una fecha de compra, un campo que indica si el &amp;iacute;tem ha sido enviado y su coste total.  Este sistema contiene cientos de pedidos a lo largo de decenas de p&amp;aacute;ginas, lo que hace que sea tedioso calcular a mano cualquier dato estad&amp;iacute;stico acerca de dichos datos.&lt;/p&gt;

&lt;div class="imageWrapper"&gt;
  &lt;img src="/system/photos/423/original/E223I01.png" width="816" height="381" alt="La p&#225;gina de pedidos."/&gt;
&lt;/div&gt;

&lt;p&gt;Si tuvi&amp;eacute;semos una gr&amp;aacute;fica en la parte de superior de la p&amp;aacute;gina que mostrase un resumen de los pedidos a lo largo del tiempo nos ser&amp;iacute;a mucho m&amp;aacute;s f&amp;aacute;cil detectar tendencias en los datos de ventas.  Eso es precisamente lo que haremos en este episodio.&lt;/p&gt;

&lt;h3&gt;Highcharts&lt;/h3&gt;

&lt;p&gt;Hay muchas librer&amp;iacute;as de gr&amp;aacute;ficas disponibles pero la que vamos a usar es &lt;a href="http://www.highcharts.com"&gt;Highcharts&lt;/a&gt;. Se trata de una soluci&amp;oacute;n de cliente y utiliza JavaScript y SVG o VML, por lo que no hay dependencias de &lt;em&gt;plugins&lt;/em&gt; como Flash o generadores de im&amp;aacute;genes en el lado del servidor como ImageMagick.  Como veremos, Highcharts puede generar gr&amp;aacute;ficas atractivas con s&amp;oacute;lo unas l&amp;iacute;neas de JavaScript.&lt;/p&gt;

&lt;p&gt;Hay que tener en cuenta, antes de considerar el uso de Highcharts para nuestros proyectos,  que es gratuita s&amp;oacute;lo para proyectos no comerciales.   Si este no es nuestro caso, veremos algunas alternativas m&amp;aacute;s adelante.&lt;/p&gt;

&lt;p&gt;Una vez que hayamos 
 &lt;a href="http://www.highcharts.com/download"&gt;descargado Highcharts&lt;/a&gt; tendremos que extraer el fichero &lt;code&gt;highcharts.js&lt;/code&gt; en el directorio &lt;code&gt;/public/javascripts&lt;/code&gt; de nuestra aplicaci&amp;oacute;n.  Tambi&amp;eacute;n tendremos que a&amp;ntilde;adir la &amp;uacute;ltima versi&amp;oacute;n de la librer&amp;iacute;a jQuery y &lt;a href="http://github.com/rails/jquery-ujs"&gt;la versi&amp;oacute;n compatible con jQuery de &lt;code&gt;rails.js&lt;/code&gt; &lt;/a&gt;, que es necesaria porque se trata de una aplicaci&amp;oacute;n Rails 3.  Hay m&amp;aacute;s detalles de todo esto en el Episodio 205  [ &lt;a href="http://railscasts.com/episodes/205-unobtrusive-javascript"&gt;verlo&lt;/a&gt;, &lt;a href="http://es.asciicasts.com/episodes/205-javascript-no-intrusivo"&gt;leerlo&lt;/a&gt;].&lt;/p&gt;

&lt;p&gt;Con estos archivos en su sitio ya podemos a&amp;ntilde;adir la siguiente l&amp;iacute;nea a la secci&amp;oacute;n
&lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; del fichero de &lt;em&gt;layout&lt;/em&gt; de nuestra aplicaci&amp;oacute;n, para que sean correctamente referenciados.&lt;/p&gt;

&lt;p class="codeFilePath"&gt;/app/views/layouts/applications.html.erb&lt;/p&gt;
&lt;pre class="ruby"&gt;
&amp;lt;%= javascript_include_tag &amp;quot;jquery-1.4.2.min&amp;quot;, &amp;quot;rails&amp;quot;, &amp;quot;highcharts&amp;quot; %&amp;gt;
&lt;/pre&gt;

&lt;p&gt;Highcharts depende de jQuery o MooTools, as&amp;iacute; que tendremos que incluir una de estas dos librer&amp;iacute;as antes de cargar el fichero &lt;code&gt;highcharts.js&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;Creaci&amp;oacute;n de una gr&amp;aacute;fica&lt;/h3&gt;

&lt;p&gt;Una vez que hemos instalado Highcharts podemos empezar a&amp;ntilde;adiendo una gr&amp;aacute;fica a nuestra p&amp;aacute;gina de pedidos.  Lo que queremos es a&amp;ntilde;adir una gr&amp;aacute;fica de l&amp;iacute;nea sencilla que muestre los ingresos obtenidos por d&amp;iacute;a.  Lo primero que tenemos que hacer es a&amp;ntilde;adir un elemento vac&amp;iacute;o en la p&amp;aacute;gina de pedidos donde queremos que aparezca la gr&amp;aacute;fica.&lt;/p&gt;


&lt;p class="codeFilePath"&gt;/app/views/orders/index.html.erb&lt;/p&gt;
&lt;pre class="ruby"&gt;
&amp;lt;%= will_paginate(@orders) %&amp;gt;

&amp;lt;div id=&amp;quot;orders_chart&amp;quot; style=&amp;quot;width: 560px; height: 300px;&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;

&amp;lt;table class=&amp;quot;pretty&amp;quot;&amp;gt;
  &amp;lt;!-- Orders table code here --&amp;gt;
&amp;lt;/table&amp;gt;
&lt;/pre&gt;

&lt;p&gt;Tenemos que dar un &lt;code&gt;id&lt;/code&gt; para poder identificarlo y para poder especificar un ancho y un alto.  Por lo general definir&amp;iacute;amos el estilo de un elemento en un fichero CSS separado, pero por comodidad lo hemos incluido aqu&amp;iacute; en el marcado HTML.&lt;/p&gt;

&lt;p&gt;A continuaci&amp;oacute;n tenemos que a&amp;ntilde;adir el JavaScript para generar la gr&amp;aacute;fica.  Una vez m&amp;aacute;s, en una aplicaci&amp;oacute;n de producci&amp;oacute;n tendr&amp;iacute;amos que crear un fichero separado en lugar de embeberlo pero aqu&amp;iacute; seguiremos el enfoque m&amp;aacute;s f&amp;aacute;cil.  N&amp;oacute;tese que si tuvi&amp;eacute;semos que extraer el JavaScript a su propio archivo esto tendr&amp;iacute;a el efecto (no deseado) de que el c&amp;oacute;digo para generar la gr&amp;aacute;fica ser&amp;iacute;a m&amp;aacute;s complejo porque vamos a generar JavaScript din&amp;aacute;micamente con ERb.  En una aplicaci&amp;oacute;n de producci&amp;oacute;n har&amp;iacute;amos esto realizando peticiones AJAX seg&amp;uacute;n cargue la p&amp;aacute;gina, pero con el c&amp;oacute;digo en l&amp;iacute;nea que vamos a usar en nuestro ejemplo esto no ser&amp;aacute; un problema.&lt;/p&gt;

&lt;p&gt;Queremos que la gr&amp;aacute;fica sea s&amp;oacute;lo generada cuando el DOM de la p&amp;aacute;gina haya terminado de cargar por lo que rodearemos nuestro c&amp;oacute;digo de la funci&amp;oacute;n &lt;code&gt;$&lt;/code&gt; de jQuery de forma que el &lt;em&gt;script&lt;/em&gt; no sea ejecutado hasta entonces.  Dentro de la funci&amp;oacute;n crearemos el c&amp;oacute;digo necesario para generar una gr&amp;aacute;fica b&amp;aacute;sica.&lt;/p&gt;

&lt;p class="codeFilePath"&gt;/app/views/orders/index.html.erb&lt;/p&gt;
&lt;pre class="javascript"&gt;
&amp;lt;script type=&amp;quot;text/javascript&amp;quot; charset=&amp;quot;utf-8&amp;quot;&amp;gt;
  $(function () {
    new Highcharts.Chart({
      chart: { renderTo: &amp;#x27;orders_chart&amp;#x27; }
    });
  });
&amp;lt;/script&amp;gt;
&lt;/pre&gt;

&lt;p&gt;Hemos creado la gr&amp;aacute;fica con un nuevo objeto &lt;code&gt;Highcharts.Chart&lt;/code&gt; y le hemos pasado un &lt;em&gt;hash&lt;/em&gt; de opciones.  Hay m&amp;uacute;ltiples opciones que se le pueden pasar, merece la pena mirar la  &lt;a href="http://www.highcharts.com/ref"&gt;referencia de la web Highcharts&lt;/a&gt; para ver qu&amp;eacute; es lo que podemos usar.&lt;/p&gt;

&lt;p&gt;Para empezar vamos a a&amp;ntilde;adir la opci&amp;oacute;n &lt;code&gt;chart&lt;/code&gt;.  Esta opci&amp;oacute;n tiene a su vez una opci&amp;oacute;n &lt;code&gt;renderTo&lt;/code&gt; que recibe el &lt;code&gt;id&lt;/code&gt; de una capa, por lo que le pasaremos el identificador de nuestra capa &lt;code&gt;orders_chart&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Si ahora recargamos la p&amp;aacute;gina de pedidos deber&amp;iacute;amos ver en la parte superior una gr&amp;aacute;fica vac&amp;iacute;a, lo que nos indica que estamos en el buen camino.&lt;/p&gt;

&lt;div class="imageWrapper"&gt;
  &lt;img src="/system/photos/424/original/E223I02.png" width="817" height="462" alt="Se muestra una gr&#225;fica vac&#237;a en la parte superior de la p&#225;gina."/&gt;
&lt;/div&gt;

&lt;p&gt;Ahora le vamos a&amp;ntilde;adir m&amp;aacute;s opciones a la gr&amp;aacute;fica y por supuesto algunos datos que dibujar.  Vamos a a&amp;ntilde;adir una opci&amp;oacute;n &lt;code&gt;title&lt;/code&gt; que tiene su propia opci&amp;oacute;n &lt;code&gt;text&lt;/code&gt; para establecer el t&amp;iacute;tulo de la gr&amp;aacute;fica, una opci&amp;oacute;n &lt;code&gt;xAxis&lt;/code&gt; que tiene su opci&amp;oacute;n &lt;code&gt;type&lt;/code&gt; puesta a  &lt;code&gt;&amp;#x27;datetime&amp;#x27;&lt;/code&gt; porque nuestra gr&amp;aacute;fica mostrar&amp;aacute; fechas en su eje X, una opci&amp;oacute;n &lt;code&gt;yAxis&lt;/code&gt; con el t&amp;iacute;tulo &lt;code&gt;&amp;#x27;Dollars&amp;#x27;&lt;/code&gt; y por &amp;uacute;ltimo los datos propiamente dichos.&lt;/p&gt;

&lt;p&gt;Una gr&amp;aacute;fica puede visualizar varias series de datos por lo que tendremos que pasarle un &lt;em&gt;array&lt;/em&gt; de &lt;em&gt;hashes&lt;/em&gt; a la opci&amp;oacute;n &lt;code&gt;series&lt;/code&gt;.  Cada uno de estos &lt;em&gt;hashes&lt;/em&gt; puede contener cualquier n&amp;uacute;mero de puntos por lo que su valor es un &lt;em&gt;array&lt;/em&gt;.  En el c&amp;oacute;digo de abajo hemos creado una serie de datos con cinco valores para probar que nuestra gr&amp;aacute;fica funciona antes de a&amp;ntilde;adir los datos de verdad.&lt;/p&gt;

&lt;p class="codeFilePath"&gt;/app/views/orders/index.html.erb&lt;/p&gt;
&lt;pre class="javascript"&gt;
$(function () {
  new Highcharts.Chart({
    chart: { renderTo: &amp;#x27;orders_chart&amp;#x27; },
    title: { text: &amp;#x27;Orders by Day&amp;#x27; },
    xAxis: { type: &amp;#x27;datetime&amp;#x27; },
    yAxis: {
      title: { text: &amp;#x27;Dollars&amp;#x27;}
    },
    series: [{
      data: [1, 2, 5, 7, 3]
    }]
  });
});
&lt;/pre&gt;

&lt;p&gt;Si recargamos la p&amp;aacute;gina veremos que ahora se dibuja la gr&amp;aacute;fica con sus cinco puntos de datos obtenidos del &lt;em&gt;array&lt;/em&gt;.  Todav&amp;iacute;a tenemos que trabajar en la l&amp;iacute;nea temporal del eje X pero por ahora estamos avanzando adecuadamente.&lt;/p&gt;


&lt;div class="imageWrapper"&gt;
  &lt;img src="/system/photos/425/original/E223I03.png" width="817" height="462" alt="La gr&#225;fica con cinco puntos de prueba."/&gt;
&lt;/div&gt;

&lt;p&gt;En la opci&amp;oacute;n &lt;code&gt;series&lt;/code&gt; podemos activar un par de opciones para especificar el punto de inicio y el intervalo entre puntos en el eje X.  La primera es  &lt;code&gt;pointInterval&lt;/code&gt; que recibe un n&amp;uacute;mero que representa el tiempo entre fechas en milisegundos.  Utilizaremos esta opci&amp;oacute;n para que entre cada punto haya un d&amp;iacute;a, y utilizaremos un poco de c&amp;oacute;digo ERb para calcular el n&amp;uacute;mero de milisegundos que hay en un d&amp;iacute;a.  El c&amp;oacute;digo Ruby &lt;code&gt;1.day&lt;/code&gt; nos dar&amp;aacute; el n&amp;uacute;mero de segundos que hay en un d&amp;iacute;a, que tenemos que multiplicar por 1000 para obtener el valor en milisegundos que Highcharts necesita.&lt;/p&gt;

&lt;p&gt;La segunda opci&amp;oacute;n es &lt;code&gt;pointStart&lt;/code&gt;, que define la fecha y hora del primer punto.  Esta opci&amp;oacute;n requiere, igual que antes, un n&amp;uacute;mero de milisegundos y si bien en una aplicaci&amp;oacute;n &amp;quot;de verdad&amp;quot; utilizar&amp;iacute;amos algo m&amp;aacute;s din&amp;aacute;mico para definir la fecha de comienzo aqu&amp;iacute; utilizaremos una fecha de hace tres semanas, nuevamente utilizando ERb para recuperar ese valor en segundos y multiplic&amp;aacute;ndolo por 1000.&lt;/p&gt;


&lt;p class="codeFilePath"&gt;/app/views/orders/index.html.erb&lt;/p&gt;
&lt;pre class="javascript"&gt;
$(function () {
  new Highcharts.Chart({
    chart: { renderTo: &amp;#x27;orders_chart&amp;#x27; },
    title: { text: &amp;#x27;Orders by Day&amp;#x27; },
    xAxis: { type: &amp;#x27;datetime&amp;#x27; },
    yAxis: {
      title: { text: &amp;#x27;Dollars&amp;#x27; }
    },
    series: [{
      pointInterval: &amp;lt;%= 1.day * 1000 %&amp;gt;,
      pointStart: &amp;lt;%= 3.weeks.ago.at_midnight.to_i * 1000 %&amp;gt;,
      data: [1, 2, 5, 7, 3]
    }]
  });
});
&lt;/pre&gt;

&lt;p&gt;Si  ahora  recargamos  la p&amp;aacute;gina  de  pedidos  el  eje  X ya muestra  las  fechas.&lt;/p&gt;

&lt;div class="imageWrapper"&gt;
  &lt;img src="/system/photos/432/original/E223I04.png" width="817" height="462" alt="En el eje X ahora aparecen las fechas."/&gt;
&lt;/div&gt;

&lt;h3&gt;Inclusi&amp;oacute;n de datos reales&lt;/h3&gt;

&lt;p&gt;Una vez que tenemos la gr&amp;aacute;fica configurada a nuestro gusto podemos reemplazar los datos de prueba con datos reales obtenidos de nuestra tabla de pedidos.  Primero veremos la manera ineficiente de hacerlo porque es m&amp;aacute;s f&amp;aacute;cil escribir el c&amp;oacute;digo de esta manera y luego veremos c&amp;oacute;mo optimizarlo todo al final del episodio.&lt;/p&gt;

&lt;p&gt;Necesitaremos la suma del todal de los pedidos de cada d&amp;iacute;a, as&amp;iacute; que tendremos que escribir un m&amp;eacute;todo de clase en el modelo &lt;code&gt;Order&lt;/code&gt; para recuperar todas las compras de un d&amp;iacute;a determinado y calcular la suma del atributo &lt;code&gt;total_price&lt;/code&gt; de cada una de ellas.&lt;/p&gt;


&lt;p class="codeFilePath"&gt;/app/models/order.rb&lt;/p&gt;
&lt;pre class="ruby"&gt;
class Order &amp;lt; ActiveRecord::Base
  def self.total_on(date)
    where(&amp;quot;date(purchased_at) = ?&amp;quot;,date).sum(:total_price)
  end
end
&lt;/pre&gt;

&lt;p&gt;Ahora ya podemos usar este m&amp;eacute;todo para recuperar los datos de cada uno de los d&amp;iacute;as que aparece en la gr&amp;aacute;fica y podemos reemplazar los datos de prueba con el siguiente c&amp;oacute;digo:&lt;/p&gt;

&lt;p class="codeFilePath"&gt;/app/views/orders/index.html.erb&lt;/p&gt;
&lt;pre class="javascript"&gt;
series: [{
  pointInterval: &amp;lt;%= 1.day * 1000 %&amp;gt;,
  pointStart: &amp;lt;%= 3.weeks.ago.at_midnight.to_i * 1000 %&amp;gt;,
  data: &amp;lt;%= (3.weeks.ago.to_date..Date.today).map { |date| Order.total_on(date).to_f}.inspect %&amp;gt;
 }]
&lt;/pre&gt; 

&lt;p&gt;En el c&amp;oacute;digo ERb anterior creamos un rango de fechas entre hoy y tres semanas antes y luego usamos el m&amp;eacute;todo &lt;code&gt;map&lt;/code&gt; para iterar sobre ese rango y recuperar el total de los pedidos de cada d&amp;iacute;a.  Aplicamos &lt;code&gt;inspect&lt;/code&gt; sobre el resultado para convertirlo a algo que pueda usarse desde JavaScript.&lt;/p&gt;

&lt;p&gt;Si recargamos la p&amp;aacute;gina otra vez veremos que ya no salen nuestros datos de prueba y que ahora aparecen los datos reales de las &amp;uacute;ltimas tres semanas, con un punto para cada d&amp;iacute;a y los ingresos totales de los pedidos de cada d&amp;iacute;a en d&amp;oacute;lares.&lt;/p&gt;


&lt;div class="imageWrapper"&gt;
  &lt;img src="/system/photos/426/original/E223I05.png" width="816" height="478" alt="Se muestran los datos de nuestros pedidos"/&gt;
&lt;/div&gt;

&lt;h3&gt;Tooltips&lt;/h3&gt;

&lt;p&gt;Aunque nuestra gr&amp;aacute;fica tiene buen aspecto la informaci&amp;oacute;n que aparece cuando ponemos el rat&amp;oacute;n sobre uno de los puntos es mejorable.  Podemos hacerlo a&amp;ntilde;adiendo la opci&amp;oacute;n &lt;code&gt;tooltip&lt;/code&gt; al c&amp;oacute;digo de la gr&amp;aacute;fica.&lt;/p&gt;


&lt;p class="codeFilePath"&gt;/app/views/orders/index.html.erb&lt;/p&gt;
&lt;pre class="javascript"&gt;
$(function () {
  new Highcharts.Chart({
    chart: { renderTo: &amp;#x27;orders_chart&amp;#x27; },
    title: { text: &amp;#x27;Orders by Day&amp;#x27; },
    xAxis: { type: &amp;#x27;datetime&amp;#x27; },
    yAxis: {
      title: { text: &amp;#x27;Dollars&amp;#x27; }
    },
    tooltip: {
      formatter: function () {
        return Highcharts.dateFormat(&amp;quot;%B %e %Y&amp;quot;, this.x) + &amp;#x27;: &amp;#x27; +
          &amp;#x27;$&amp;#x27; + Highcharts.numberFormat(this.y, 2);
      }
    },    
    series: [{
      pointInterval: &amp;lt;%= 1.day * 1000 %&amp;gt;,
      pointStart: &amp;lt;%= 3.weeks.ago.at_midnight.to_i * 1000 %&amp;gt;,
      data: &amp;lt;%= (3.weeks.ago.to_date..Date.today).map { |date| Order.total_on(date).to_f}.inspect %&amp;gt;
    }]
  });
});
&lt;/pre&gt;

&lt;p&gt;Esta opci&amp;oacute;n tiene su propia opci&amp;oacute;n  &lt;code&gt;formatter&lt;/code&gt; que recibe una funci&amp;oacute;n como argumento, la cual debe devolver una cadena que es lo que aparecer&amp;aacute; como texto del &lt;em&gt;tooltip&lt;/em&gt;.  Para esto se pueden utilizar algunos formateadores que proporciona Highcharts, por ejemplo en el c&amp;oacute;digo anterior hemos usado dos: uno para formatear la fecha del eje X y otro para el valor num&amp;eacute;rico del eje Y.&lt;/p&gt;

&lt;p&gt;Si volvemos a recargar la p&amp;aacute;gina y ponemos el rat&amp;oacute;n sobre uno de los puntos veremos nuestro &lt;em&gt;tooltip&lt;/em&gt; pulcramente formateado.&lt;/p&gt;

&lt;div class="imageWrapper"&gt;
  &lt;img src="/system/photos/427/original/E223I06.png" width="816" height="478" alt="Cada punto tiene ahora un tooltip personalizado."/&gt;
&lt;/div&gt;

&lt;h3&gt;Visualizaci&amp;oacute;n de m&amp;uacute;ltiples series&lt;/h3&gt;

&lt;p&gt;Con esta gr&amp;aacute;fica de nuestros pedidos es f&amp;aacute;cil ver tendencias en los datos.  En la gr&amp;aacute;fica anterior puede verse que ha habido un incremento de &amp;oacute;rdenes desde el 19 de Julio.  Para determinar qu&amp;eacute; es lo que ha causado este alza en las ventas podemos reemplazar la serie que muestra las ventas totales por dos que muestren las ventas de descargas (las que tienen un valor &lt;code&gt;shipping&lt;/code&gt; a &lt;code&gt;false&lt;/code&gt;) y las ventas f&amp;iacute;sicas.&lt;/p&gt;

&lt;p&gt;Vamos a tener que distinguir los pedidos que fueron enviados de los que fueron descargados as&amp;iacute; que lo primero que haremos ser&amp;aacute; a&amp;ntilde;adir dos &amp;aacute;mbitos a nuestra clase &lt;code&gt;Order&lt;/code&gt; para que podamos recuperar f&amp;aacute;cilmente cada tipo de pedido.&lt;/p&gt;

&lt;p class="codeFilePath"&gt;/app/models/order.rb&lt;/p&gt;
&lt;pre class="ruby"&gt;
class Order &amp;lt; ActiveRecord::Base
  scope :shipping, where(:shipping =&amp;gt; true)
  scope :download, where(:shipping =&amp;gt; false)

  def self.total_on(date)
    where(&amp;quot;date(purchased_at) = ?&amp;quot;,date).sum(:total_price)
  end
end
&lt;/pre&gt;

&lt;p&gt;De vuelta en el c&amp;oacute;digo JavaScript de la vista de pedidos tenemos que a&amp;ntilde;adir otra opci&amp;oacute;n &lt;code&gt;series&lt;/code&gt; de forma que se dibujen dos conjuntos de datos.  Para distinguir las dos series les hemos dado a cada una de ellas un nombre diferente.&lt;/p&gt;

&lt;p class="codeFilePath"&gt;/app/views/orders/index.html.erb&lt;/p&gt;
&lt;pre class="javascript"&gt;
$(function () {
  new Highcharts.Chart({
    chart: { renderTo: &amp;#x27;orders_chart&amp;#x27; },
    title: { text: &amp;#x27;Orders by Day&amp;#x27; },
    xAxis: { type: &amp;#x27;datetime&amp;#x27; },
    yAxis: {
      title: { text: &amp;#x27;Dollars&amp;#x27; }
    },
    tooltip: {
      formatter: function () {
        return Highcharts.dateFormat(&amp;quot;%B %e %Y&amp;quot;, this.x) + &amp;#x27;: &amp;#x27; +
          &amp;#x27;$&amp;#x27; + Highcharts.numberFormat(this.y, 2);
      }
    },    
    series: [{
      name: &amp;quot;Shipping&amp;quot;,
      pointInterval: &amp;lt;%= 1.day * 1000 %&amp;gt;,
      pointStart: &amp;lt;%= 3.weeks.ago.at_midnight.to_i * 1000 %&amp;gt;,
      data: &amp;lt;%= (3.weeks.ago.to_date..Date.today).map { |date| Order.shipping.total_on(date).to_f}.inspect %&amp;gt;
    },
    {
      name: &amp;quot;Download&amp;quot;,
      pointInterval: &amp;lt;%= 1.day * 1000 %&amp;gt;,
      pointStart: &amp;lt;%= 3.weeks.ago.at_midnight.to_i * 1000 %&amp;gt;,
      data: &amp;lt;%= (3.weeks.ago.to_date..Date.today).map { |date| Order.download.total_on(date).to_f}.inspect %&amp;gt;
    }]
  });
});
&lt;/pre&gt;

&lt;p&gt;Ahora tenemos dos series y hemos usado el correspondiente &amp;aacute;mbito en cada uno de ellos para recuperar los datos relevantes para cada d&amp;iacute;a.  L&amp;aacute; gr&amp;aacute;fica ahora mostrar&amp;aacute; ambas series, con sus nombres en la leyenda inferior.&lt;/p&gt;

&lt;div class="imageWrapper"&gt;
  &lt;img src="/system/photos/428/original/E223I07.png" width="816" height="478" alt="Ahora en la gr&#225;fica aparecen dos series separadas para los pedidos descargados y los enviados."/&gt;
&lt;/div&gt;

&lt;p&gt;Ahora podemos ver los totales de ventas f&amp;iacute;sicas y descargadas y podemos ver que el pico de pedidos de los &amp;uacute;ltimos d&amp;iacute;as se corresponde con un incremento en las ventas tanto f&amp;iacute;sicas como de descargas.  Obs&amp;eacute;rvese que cuando un gr&amp;aacute;fico muestra m&amp;aacute;s de una serie podemos hacer clic en la leyenda para ocultar o mostrar dicha serie.&lt;/p&gt;

&lt;h3&gt;Eliminando la duplicaci&amp;oacute;n&lt;/h3&gt;

&lt;p&gt;Al a&amp;ntilde;adir una segunda serie a la gr&amp;aacute;fica hemos introducido cierta duplicidad en el c&amp;oacute;digo JavaScript con el problema de que si quisi&amp;eacute;ramos a&amp;ntilde;adir m&amp;aacute;s series tendr&amp;iacute;amos que repetirnos todav&amp;iacute;a m&amp;aacute;s.  Podemos atajar este problema utilizando un poco de c&amp;oacute;digo Ruby para generar din&amp;aacute;micamente el c&amp;oacute;digo JavaScript de cada serie.&lt;/p&gt;

&lt;p class="codeFilePath"&gt;/app/views/orders/index.html.erb&lt;/p&gt;
&lt;pre class="javascript"&gt;
$(function () {
  new Highcharts.Chart({
    chart: { renderTo: &amp;#x27;orders_chart&amp;#x27; },
    title: { text: &amp;#x27;Orders by Day&amp;#x27; },
    xAxis: { type: &amp;#x27;datetime&amp;#x27; },
    yAxis: {
      title: { text: &amp;#x27;Dollars&amp;#x27; }
    },
    tooltip: {
      formatter: function () {
        return Highcharts.dateFormat(&amp;quot;%B %e %Y&amp;quot;, this.x) + &amp;#x27;: &amp;#x27; +
          &amp;#x27;$&amp;#x27; + Highcharts.numberFormat(this.y, 2);
      }
    },    
    series: [
      &amp;lt;% { &amp;quot;Download&amp;quot; =&amp;gt; Order.download, &amp;quot;Shipping&amp;quot; =&amp;gt; Order.shipping }.each do |name, order| %&amp;gt;
    {
      name: &amp;quot;&amp;lt;%= name %&amp;gt;&amp;quot;,
      pointInterval: &amp;lt;%= 1.day * 1000 %&amp;gt;,
      pointStart: &amp;lt;%= 3.weeks.ago.at_midnight.to_i * 1000 %&amp;gt;,
      data: &amp;lt;%= (3.weeks.ago.to_date..Date.today).map { |date| order.total_on(date).to_f}.inspect %&amp;gt;
    },
    &amp;lt;% end %&amp;gt;]
  });
});
&lt;/pre&gt;

&lt;p&gt;En lugar de definir cada serie por separado ahora tenemos un &lt;em&gt;hash&lt;/em&gt; que define el nombre de cada serie y el c&amp;oacute;digo que hay que usar para recuperar los pedidos de cada serie.  Luego podemos iterar por ese &lt;em&gt;hash&lt;/em&gt; y generar el JavaScript de la serie correspondiente.&lt;/p&gt;

&lt;h3&gt;Optimizaci&amp;oacute;n de la consulta&lt;/h3&gt;

&lt;p&gt;La gr&amp;aacute;fica ya muestra la informaci&amp;oacute;n que queremos pero el SQL que se est&amp;aacute; usando para recuperar los datos es bastante ineficiente.  El &lt;em&gt;log&lt;/em&gt; de desarrollo nos revela que se hace una consulta separada para cada nodo de la gr&amp;aacute;fica.&lt;/p&gt;

&lt;pre class="terminal"&gt;
  SQL (0.6ms)  SELECT SUM(&amp;quot;orders&amp;quot;.&amp;quot;total_price&amp;quot;) AS sum_id FROM &amp;quot;orders&amp;quot; WHERE (&amp;quot;orders&amp;quot;.&amp;quot;shipping&amp;quot; = &amp;#x27;t&amp;#x27;) AND (date(purchased_at) = &amp;#x27;2010-07-18&amp;#x27;)
  SQL (0.6ms)  SELECT SUM(&amp;quot;orders&amp;quot;.&amp;quot;total_price&amp;quot;) AS sum_id FROM &amp;quot;orders&amp;quot; WHERE (&amp;quot;orders&amp;quot;.&amp;quot;shipping&amp;quot; = &amp;#x27;t&amp;#x27;) AND (date(purchased_at) = &amp;#x27;2010-07-19&amp;#x27;)
  SQL (0.6ms)  SELECT SUM(&amp;quot;orders&amp;quot;.&amp;quot;total_price&amp;quot;) AS sum_id FROM &amp;quot;orders&amp;quot; WHERE (&amp;quot;orders&amp;quot;.&amp;quot;shipping&amp;quot; = &amp;#x27;t&amp;#x27;) AND (date(purchased_at) = &amp;#x27;2010-07-20&amp;#x27;)
  SQL (0.6ms)  SELECT SUM(&amp;quot;orders&amp;quot;.&amp;quot;total_price&amp;quot;) AS sum_id FROM &amp;quot;orders&amp;quot; WHERE (&amp;quot;orders&amp;quot;.&amp;quot;shipping&amp;quot; = &amp;#x27;t&amp;#x27;) AND (date(purchased_at) = &amp;#x27;2010-07-21&amp;#x27;)
  SQL (0.7ms)  SELECT SUM(&amp;quot;orders&amp;quot;.&amp;quot;total_price&amp;quot;) AS sum_id FROM &amp;quot;orders&amp;quot; WHERE (&amp;quot;orders&amp;quot;.&amp;quot;shipping&amp;quot; = &amp;#x27;t&amp;#x27;) AND (date(purchased_at) = &amp;#x27;2010-07-22&amp;#x27;)
&lt;/pre&gt;  

&lt;p&gt;Podemos mejorar esto de forma que se lance una &amp;uacute;nica llamada para cada serie utilizando los m&amp;eacute;todos &lt;code&gt;group&lt;/code&gt; y &lt;code&gt;select&lt;/code&gt;.  Queremos recuperar los pedidos del modelo &lt;code&gt;Order&lt;/code&gt; y agruparlos por su &lt;code&gt;purchase_date&lt;/code&gt; y para cada grupo de &amp;oacute;rdenes devuelto queremos la fecha y el precio total.  El siguiente c&amp;oacute;digo  hace eso:&lt;/p&gt;

&lt;pre class="ruby"&gt;
Order.group(&amp;quot;date(purchased_at)&amp;quot;).select(&amp;quot;purchased_at, sum(total_price) as total_price&amp;quot;)
&lt;/pre&gt;

&lt;p&gt;Podemos comprobarlo en la consola y recuperar el &lt;code&gt;total_price&lt;/code&gt; del primer &amp;iacute;tem devuelto por la consulta.&lt;/p&gt;

&lt;pre class="terminal"&gt;
&amp;gt; Order.group(&amp;quot;date(purchased_at)&amp;quot;).select(&amp;quot;purchased_at, sum(total_price) as total_price&amp;quot;).first.total_price.to_f
 =&amp;gt; 403.0
&lt;/pre&gt;

&lt;p&gt;Pero hay un ligero problema con este enfoque.  Si no hay pedidos en un d&amp;iacute;a determinado tendremos un punto ausente en los datos devueltos por lo que el resto de puntos posteriores estar&amp;aacute;n desplazados un d&amp;iacute;a en la gr&amp;aacute;fica.  Tenemos que tener en cuenta esta posibilidad, lo que va a complicar el c&amp;oacute;digo.  Para organizarlo lo mejor posible, vamos a llevar esto a un m&amp;eacute;todo &lt;em&gt;helper&lt;/em&gt;.&lt;/p&gt;

&lt;p class="codeFilePath"&gt;/app/views/orders/index.html.erb&lt;/p&gt;
&lt;pre class="javascript"&gt;
$(function () {
  new Highcharts.Chart({
    chart: { renderTo: &amp;#x27;orders_chart&amp;#x27; },
    title: { text: &amp;#x27;Orders by Day&amp;#x27; },
    xAxis: { type: &amp;#x27;datetime&amp;#x27; },
    yAxis: {
      title: { text: &amp;#x27;Dollars&amp;#x27; }
    },
    tooltip: {
      formatter: function () {
        return Highcharts.dateFormat(&amp;quot;%B %e %Y&amp;quot;, this.x) + &amp;#x27;: &amp;#x27; +
          &amp;#x27;$&amp;#x27; + Highcharts.numberFormat(this.y, 2);
      }
    },    
    series: [
      &amp;lt;% { &amp;quot;Both&amp;quot; =&amp;gt; Order, &amp;quot;Download&amp;quot; =&amp;gt; Order.download, &amp;quot;Shipping&amp;quot; =&amp;gt; Order.shipping }.each do |name, order| %&amp;gt;
    {
      name: &amp;quot;&amp;lt;%= name %&amp;gt;&amp;quot;,
      pointInterval: &amp;lt;%= 1.day * 1000 %&amp;gt;,
      pointStart: &amp;lt;%= 3.weeks.ago.at_midnight.to_i * 1000 %&amp;gt;,
      data: &amp;lt;%= orders_chart_series(orders, 3.weeks.ago) %&amp;gt;
    },
    &amp;lt;% end %&amp;gt;]
  });
});
&lt;/pre&gt;

&lt;p&gt;Hemos reemplazado el c&amp;oacute;digo que recupera los datos por una llamada a un m&amp;eacute;todo llamado &lt;code&gt;orders_chart_series&lt;/code&gt; que recibe como argumentos el &amp;aacute;mbito relevante del modelo &lt;code&gt;Order&lt;/code&gt; y la fecha de inicio de la serie.  Vamos a escribir este nuevo m&amp;eacute;todo en el m&amp;oacute;dulo &lt;code&gt;OrdersHelper&lt;/code&gt;.&lt;/p&gt;

&lt;p class="codeFilePath"&gt;/app/helpers/orders_helper.rb&lt;/p&gt;
&lt;pre class="ruby"&gt;
module OrdersHelper
  def orders_chart_series(orders, start_time)
    orders_by_day = orders.where(:purchased_at =&amp;gt; start_time.beginning_of_day..Time.zone.now.end_of_day).
                    group(&amp;quot;date(purchased_at)&amp;quot;).
                    select(&amp;quot;purchased_at, sum(total_price) as total_price&amp;quot;)
    (start_time.to_date..Date.today).map do |date|
      order = orders_by_day.detect { |order| order.purchased_at.to_date == date }
      order &amp;amp;&amp;amp; order.total_price.to_f || 0
    end.inspect
  end
end
&lt;/pre&gt;

&lt;p&gt;En el m&amp;eacute;todo &lt;code&gt;orders_chart_series&lt;/code&gt; pasamos el &amp;aacute;mbito &lt;code&gt;orders&lt;/code&gt; y recuperamos los pedidos que entran dentro del rango de fechas entre &lt;code&gt;start_time&lt;/code&gt; y la fecha de hoy.  Despu&amp;eacute;s agrupamos los resultados por d&amp;iacute;a y escogemos la fecha y el total de pedidos de dicho d&amp;iacute;a.  Por &amp;uacute;ltimo iteramos sobre el rango de fechas para recuperar el total de pedidos de cada d&amp;iacute;a, introduciendo un &lt;code&gt;0&lt;/code&gt; si no se hicieron pedidos ese d&amp;iacute;a.&lt;/p&gt;

&lt;p&gt;Si recargamos la p&amp;aacute;gina y miramos el &lt;em&gt;log&lt;/em&gt; de desarrollo veremos que ahora hemos reducido a dos el n&amp;uacute;mero de consultas a la base de datos.&lt;/p&gt;

&lt;pre class="terminal"&gt;
  Order Load (2.6ms)  SELECT purchased_at, sum(total_price) as total_price FROM &amp;quot;orders&amp;quot; WHERE (&amp;quot;orders&amp;quot;.&amp;quot;shipping&amp;quot; = &amp;#x27;f&amp;#x27;) AND (&amp;quot;orders&amp;quot;.&amp;quot;purchased_at&amp;quot; BETWEEN &amp;#x27;2010-07-01 00:00:00.000000&amp;#x27; AND &amp;#x27;2010-07-22 23:59:59.999999&amp;#x27;) GROUP BY date(purchased_at)
  Order Load (1.5ms)  SELECT purchased_at, sum(total_price) as total_price FROM &amp;quot;orders&amp;quot; WHERE (&amp;quot;orders&amp;quot;.&amp;quot;shipping&amp;quot; = &amp;#x27;t&amp;#x27;) AND (&amp;quot;orders&amp;quot;.&amp;quot;purchased_at&amp;quot; BETWEEN &amp;#x27;2010-07-01 00:00:00.000000&amp;#x27; AND &amp;#x27;2010-07-22 23:59:59.999999&amp;#x27;) GROUP BY date(purchased_at)
&lt;/pre&gt;  
  
&lt;h3&gt;Alternativas&lt;/h3&gt;

&lt;p&gt;Highcharts es una librer&amp;iacute;a de gr&amp;aacute;ficas excelente pero puede que no sea exactamente lo que estamos buscando, por lo que finalizaremos este episodio recorriendo algunas alternativas.&lt;/p&gt;

&lt;p&gt;La primera de ellas es  &lt;a href="http://people.iola.dk/olau/flot/examples/graph-types.html"&gt;Flot&lt;/a&gt;. Tambi&amp;eacute;n utiliza JavaScript y puede usarse para generar gr&amp;aacute;ficos bastante bonitos.&lt;/p&gt;

&lt;div class="imageWrapper"&gt;
  &lt;img src="/system/photos/429/original/E223I08.png" width="828" height="512" alt="Flot"/&gt;
&lt;/div&gt;

&lt;p&gt;Otra librer&amp;iacute;a basada en JavaScript es &lt;a href="http://g.raphaeljs.com"&gt;gRrapha&amp;euml;l&lt;/a&gt;. Es muy &amp;uacute;til para generar diagramas de barra o tarta, por lo que se recomienda echarle un vistazo si tenemos que generar este tipo de gr&amp;aacute;ficas.&lt;/p&gt;

&lt;div class="imageWrapper"&gt;
  &lt;img src="/system/photos/430/original/E223I09.png" width="828" height="512" alt="gRrapha&amp;euml;l"/&gt;
&lt;/div&gt;

&lt;p&gt;Y por &amp;uacute;ltimo tenemos &lt;a href="http://bluff.jcoglan.com"&gt;Bluff&lt;/a&gt;. Est&amp;aacute; basada en la librer&amp;iacute;a Gruff de Ruby.  Es una soluci&amp;oacute;n muy bonita y sencilla, por lo que si buscamos una soluci&amp;oacute;n ligera podr&amp;iacute;a interesarnos.&lt;/p&gt;

&lt;div class="imageWrapper"&gt;
  &lt;img src="/system/photos/431/original/E223I10.png" width="828" height="512" alt="Bluff"/&gt;
&lt;/div&gt;

&lt;p&gt;Y eso es todo por este episodio.  Si necesitamos mostrar gr&amp;aacute;ficas en nuestra aplicaci&amp;oacute;n tenemos muchas opciones donde escoger.&lt;/p&gt;
</description>
      <pubDate>Thu, 29 Jul 2010 07:51:51 +0000</pubDate>
      <guid>http://es.asciicasts.com/episodes/223-graficas</guid>
      <link>http://es.asciicasts.com/episodes/223-graficas</link>
    </item>
    <item>
      <title>Rack y Rails 3</title>
      <description>&lt;p&gt;En el episodio 203 [&lt;a href="http://railscasts.com/episodes/203-routing-in-rails-3"&gt;verlo&lt;/a&gt;, &lt;a href="http://es.asciicasts.com/episodes/203-rutas-en-rails-3"&gt;leerlo&lt;/a&gt;] le dimos un repaso al sistema de rutas de Rails 3, pero se nos quedaron en el tintero algunas funcionalidades avanzadas que pueden sacar todo el partido a la flexibilidad del enrutador de Rails 3.  Veremos algunas de ellas en este episodio.&lt;/p&gt;

&lt;h3&gt;Rutas para aplicaciones Rack&lt;/h3&gt;

&lt;p&gt;Comenzaremos con un ejemplo sencillo.  Nuestra aplicaci&amp;oacute;n de ejemplo tiene una ruta que apunta su URL ra&amp;iacute;z a la acci&amp;oacute;n &lt;code&gt;index&lt;/code&gt; del controlador &lt;code&gt;HomeController&lt;/code&gt;&amp;rsquo;s &lt;code&gt;index&lt;/code&gt;.&lt;/p&gt;

&lt;p class="codeFilePath"&gt;/config/routes.rb&lt;/p&gt;
&lt;pre class="ruby"&gt;
Store::Application.routes.draw do |map|
  root :to =&amp;gt; &amp;quot;home#index&amp;quot;
end
&lt;/pre&gt;

&lt;p&gt;Lo importante aqu&amp;iacute; es saber que la cadena &lt;code&gt;&amp;quot;home#index&amp;quot;&lt;/code&gt; es una abreviatura y por debajo dicha cadena se convierte a:&lt;/p&gt;

&lt;pre class="ruby"&gt;
HomeController.action(:index)
&lt;/pre&gt;

&lt;p&gt;El m&amp;eacute;todo &lt;code&gt;action&lt;/code&gt; devuelve una aplicaci&amp;oacute;n Rack, y por tanto es f&amp;aacute;cil obtener una aplicaci&amp;oacute;n Rack a partir de cualquier controlador Rails.  Como demostraci&amp;oacute;n, vamos a crear una aplicaci&amp;oacute;n Rack sencilla pas&amp;aacute;ndole el &lt;em&gt;hash&lt;/em&gt; de entorno a un nuevo objeto &lt;code&gt;proc&lt;/code&gt;, devolviendo un &lt;em&gt;array&lt;/em&gt; con un c&amp;oacute;digo de estado, un &lt;em&gt;hash&lt;/em&gt; de cabeceras vac&amp;iacute;o y el contenido.&lt;/p&gt;

&lt;p class="codeFilePath"&gt;/config.routes.rb&lt;/p&gt;
&lt;pre class="ruby"&gt;
Store::Application.routes.draw do |map|
  root :to =&amp;gt; proc { |env| [200, {}, [&amp;quot;Welcome&amp;quot;]] }
end
&lt;/pre&gt;

&lt;p&gt;Si visitamos la URL ra&amp;iacute;z de nuestra aplicaci&amp;oacute;n veremos dicha respuesta.&lt;/p&gt;

&lt;div class="imageWrapper"&gt;
  &lt;img src="/system/photos/417/original/E222I01.png" width="798" height="280" alt="La salida de nuestra aplicaci&#243;n Rack."/&gt;
&lt;/div&gt;

&lt;p&gt;Toda esta flexibilidad hace que sea muy f&amp;aacute;cil integrar cualquier aplicaci&amp;oacute;n basada en Rack dentro de una aplicaci&amp;oacute;n Rails.  Ve&amp;aacute;moslo con Sinatra.&lt;/p&gt;

&lt;p&gt;Primero tenemos que a&amp;ntilde;adir una referencia a la gema Sinatra en el fichero &lt;code&gt;Gemfile&lt;/code&gt; de nuestra aplicaci&amp;oacute;n.&lt;/p&gt;


&lt;p class="codeFilePath"&gt;/Gemfile&lt;/p&gt;
&lt;pre class="ruby"&gt;
gem &amp;#x27;sinatra&amp;#x27;
&lt;/pre&gt;

&lt;p&gt;Nos aseguraremos de que la gema est&amp;aacute; instalada ejecutando&lt;/p&gt;

&lt;pre class="ruby"&gt;
bundle install
&lt;/pre&gt;

&lt;p&gt;Ya podemos escribir nuestra aplicaci&amp;oacute;n Sinatra.  La vamos a poner en el directorio &lt;code&gt;/lib&lt;/code&gt;, y la llamaremos &lt;code&gt;home_app.rb&lt;/code&gt;.  Tenemos que crear una clase que herede de &lt;code&gt;Sinatra::Base&lt;/code&gt; con un m&amp;eacute;todo &lt;code&gt;get&lt;/code&gt; para la URL ra&amp;iacute;z que devuelve una cadena.&lt;/p&gt;

&lt;p class="codeFilePath"&gt;/lib/home_app.rb&lt;/p&gt;
&lt;pre class="ruby"&gt;
class HomeApp &amp;lt; Sinatra::Base
  get &amp;quot;/&amp;quot; do
    &amp;quot;Hello from Sinatra&amp;quot;
  end
end
&lt;/pre&gt;

&lt;p&gt;Ahora ya podemos actualizar el fichero de rutas para que la ruta ra&amp;iacute;z apunte a nuestra aplicaci&amp;oacute;n Rack &lt;code&gt;HomeApp&lt;/code&gt;.&lt;/p&gt;

&lt;p class="codeFilePath"&gt;/config/routes.rb&lt;/p&gt;
&lt;pre class="ruby"&gt;
Store::Application.routes.draw do |map|
  root :to =&amp;gt; HomeApp
end
&lt;/pre&gt;

&lt;p&gt;Si recargamos la portada de la aplicaci&amp;oacute;n ahora veremos la respuesta de la aplicaci&amp;oacute;n Sinatra.&lt;/p&gt;

&lt;div class="imageWrapper"&gt;
  &lt;img src="/system/photos/418/original/E222I02.png" width="798" height="280" alt="La salida de nuestra aplicaci&#243;n Sinatra."/&gt;
&lt;/div&gt;

&lt;h3&gt;Redirecciones&lt;/h3&gt;

&lt;p&gt;Uno de los beneficios de la integraci&amp;oacute;n de Rack con Rails es el nuevo m&amp;eacute;todo &lt;code&gt;redirect&lt;/code&gt;, que veremos a continuaci&amp;oacute;n c&amp;oacute;mo funciona.  Supongamos que nuestra aplicaci&amp;oacute;n tiene una ruta para mostra una p&amp;aacute;gina de informaci&amp;oacute;n con la URL &lt;code&gt;/about&lt;/code&gt;, que est&amp;aacute; mapeada a la acci&amp;oacute;n &lt;code&gt;about&lt;/code&gt; del controlador &lt;code&gt;InfoController&lt;/code&gt;.&lt;/p&gt;

&lt;p class="codeFilePath"&gt;/config/routes.rb&lt;/p&gt;
&lt;pre class="ruby"&gt;
Store::Application.routes.draw do |map|
  root :to =&amp;gt; HomeApp
  match &amp;quot;/about&amp;quot; =&amp;gt; &amp;quot;info#about&amp;quot; 
end
&lt;/pre&gt;

&lt;p&gt;Si por ejemplo queremos cambiar la URL de &lt;code&gt;/about&lt;/code&gt; a &lt;code&gt;/aboutus&lt;/code&gt; podr&amp;iacute;amos cambiar f&amp;aacute;cilmente la ruta, pero entonces tendr&amp;iacute;amos que ver qu&amp;eacute; hacemos con la URL antigua que ya no mapea a ninguna acci&amp;oacute;n.  Pero ahora con Rails 3 podemos hacer redirecciones f&amp;aacute;cilmente con el m&amp;eacute;todo &lt;code&gt;redirect&lt;/code&gt;.&lt;/p&gt;

&lt;p class="codeFilePath"&gt;/config/routes.rb&lt;/p&gt;
&lt;pre class="ruby"&gt;
Store::Application.routes.draw do |map|
  root :to =&amp;gt; HomeApp
  match &amp;quot;/about&amp;quot; =&amp;gt; redirect(&amp;quot;/aboutus&amp;quot;)
  match &amp;quot;/aboutus&amp;quot; =&amp;gt; &amp;quot;info#about&amp;quot; 
end
&lt;/pre&gt;

&lt;p&gt;Con esta redirecci&amp;oacute;n la antigua ruta redirigir&amp;aacute; a la nueva, por tanto si visitamos la URL &lt;code&gt;/about&lt;/code&gt; seremos redirigidos a &lt;code&gt;/aboutus&lt;/code&gt;.&lt;/p&gt;

&lt;div class="imageWrapper"&gt;
  &lt;img src="/system/photos/419/original/E222I03.png" width="800" height="337" alt="Aqu&#237; se muestra la p&#225;gina redirigida."/&gt;
&lt;/div&gt;

&lt;p&gt;Veamos a continuaci&amp;oacute;n un ejemplo del uso de &lt;code&gt;redirect&lt;/code&gt; un poco m&amp;aacute;s complicado.  Supongamos que  tenemos un controlador &lt;code&gt;ProductsController&lt;/code&gt; y queremos tener una URL de atajo de la forma &lt;code&gt;/p/:id&lt;/code&gt; para cada producto.  Para eso podemos modificar las rutas de esta forma:&lt;/p&gt;

&lt;p class="codeFilePath"&gt;/config/routes.rb&lt;/p&gt;
&lt;pre class="ruby"&gt;
Store::Application.routes.draw do |map|
  get &amp;quot;info/about&amp;quot;

  root :to =&amp;gt; HomeApp
  match &amp;quot;/about&amp;quot; =&amp;gt; redirect(&amp;quot;/aboutus&amp;quot;)
  match &amp;quot;/aboutus&amp;quot; =&amp;gt; &amp;quot;info#about&amp;quot; 
  
  resources :products
  match &amp;quot;/p/:id&amp;quot; =&amp;gt; redirect(&amp;quot;/products/%{id}&amp;quot;)
end
&lt;/pre&gt;

&lt;p&gt;La ruta abreviada de un producto es la &amp;uacute;ltima ruta del archivo.  Redirige la URL abreviada a la URL normal de un producto utilizando otra vez el m&amp;eacute;todo &lt;code&gt;redirect&lt;/code&gt; pero esta vez lo hace a una URL din&amp;aacute;mica.  Para poder poner el &lt;code&gt;id&lt;/code&gt; del producto en la URL redirigida tenemos que usar un signo de porcentaje seguido del par&amp;aacute;metro &lt;code&gt;id&lt;/code&gt; rodeado de llaves.&lt;/p&gt;

&lt;p&gt;Si ahora visitamos la URL &lt;code&gt;http://localhost:3000/p/1&lt;/code&gt; seremos redirigidos a la p&amp;aacute;gina de ese producto.&lt;/p&gt;

&lt;div class="imageWrapper"&gt;
  &lt;img src="/system/photos/420/original/E222I04.png" width="800" height="337" alt="La p&#225;gina redirigida de producto."/&gt;
&lt;/div&gt;

&lt;p&gt;Aunque no lo veremos por ahora, si necesitamos m&amp;aacute;s control sobre la redirecci&amp;oacute;n le podemos pasar un bloque al m&amp;eacute;todo &lt;code&gt;redirect&lt;/code&gt;.  Los detalles sobre c&amp;oacute;mo hacerlo est&amp;aacute;n en el &lt;a href="http://guides.rails.info/routing.html#redirection"&gt;sitio de rails.info&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;Rails Metal&lt;/h3&gt;

&lt;p&gt;En el episodio 150 repasamos Rails Metal [&lt;a href="http://railscasts.com/episodes/150-rails-metal"&gt;verlo&lt;/a&gt;, &lt;a href="http://asciicasts.com/episodes/150-rails-metal"&gt;leero&lt;/a&gt;] y creamos una p&amp;aacute;gina que listaba los procesos en ejecuci&amp;oacute;n en un momento dado.  En Rails 3 la t&amp;eacute;cnica es bastante diferente pero es mucho m&amp;aacute;s f&amp;aacute;cil de usar gracias a la integraci&amp;oacute;n de Rack.  Primero tenemos que crear la nueva ruta y hacer que apunte a la nueva aplicaci&amp;oacute;n Rack que bautizaremos como &lt;code&gt;ProcessesApp&lt;/code&gt;.&lt;/p&gt;

&lt;p class="codeFilePath"&gt;/config/routes.rb&lt;/p&gt;
&lt;pre class="ruby"&gt;
match &amp;quot;/processes&amp;quot; =&amp;gt; ProcessesApp
&lt;/pre&gt;

&lt;p&gt;Crearemos la aplicaci&amp;oacute;n en el directorio &lt;code&gt;/lib&lt;/code&gt;, donde editaremos el fichero &lt;code&gt;processes_app.rb&lt;/code&gt;.  La clase tendr&amp;aacute; un m&amp;eacute;todo de clase llamado  &lt;code&gt;call&lt;/code&gt; que recibe un &lt;em&gt;hash&lt;/em&gt; de entorno, en el cual ejecutaremos el mismo comando que en el episodio 150 y devolveremos el resultado.&lt;/p&gt;

&lt;p class="codeFilePath"&gt;/lib/processes_app.rb&lt;/p&gt;
&lt;pre class="ruby"&gt;
class ProcessesApp
  def self.call(env)
    [200, {}, [`ps -axcr -o &amp;quot;pid,pcpu, pmem, time, comm&amp;quot;`]]
  end
end
&lt;/pre&gt;

&lt;p&gt;Si vamos a la p&amp;aacute;gina de procesos en el navegador, veremos el listado de procesos, y se puede comprobar que la p&amp;aacute;gina se recarga muy r&amp;aacute;pidamente porque estamos usando una sencilla aplicaci&amp;oacute;n Rack.&lt;/p&gt;

&lt;div class="imageWrapper"&gt;
  &lt;img src="/system/photos/421/original/E222I05.png" width="801" height="335" alt="Nuestra aplicaci&#243;n de procesos."/&gt;
&lt;/div&gt;

&lt;p&gt;La clase &lt;code&gt;ProcessApp&lt;/code&gt; no tiene nada de especial (en realidad se trata de una aplicaci&amp;oacute;n Rack sencilla), pero queda claro que con unas pocas l&amp;iacute;neas de c&amp;oacute;digo hemos reproducido el comportamiento del episodio 150 de una manera mucho m&amp;aacute;s f&amp;aacute;cil.&lt;/p&gt;

&lt;p&gt;No tenemos control alguno sobre el aspecto de la p&amp;aacute;gina de procesos porque la respuesta no pasa por ninguna plantilla.  Sin embargo podemos expandir nuestra aplicaci&amp;oacute;n Rack para que incluya el comportamiento de un controlador Rails, lo que permitir&amp;iacute;a usar plantillas y hacer que la p&amp;aacute;gina fuese menos plana.&lt;/p&gt;

&lt;p&gt;Para que la p&amp;aacute;gina de procesos pueda mostrar una plantilla erb en lugar de la salida en crudo del comando &lt;code&gt;ps&lt;/code&gt;  tan s&amp;oacute;lo tenemos que asegurarnos de que la clase &lt;code&gt;ProcessesApp&lt;/code&gt; herede de &lt;code&gt;ActionController::Metal&lt;/code&gt;, con lo que adquirir&amp;aacute; ciertos comportamientos de los controladores de nuestra aplicaci&amp;oacute;n Rails, pero lo har&amp;aacute; a un nivel tan bajo que tenemos tambi&amp;eacute;n que incluir  &lt;code&gt;ActionController::Rendering&lt;/code&gt; para tener todo lo que nos hace falta.&lt;/p&gt;

&lt;p&gt;Dado que el c&amp;oacute;digo que estamos usando funciona a un nivel m&amp;aacute;s bajo que un controlador Rails normal tenemos que especificar la ubicaci&amp;oacute;n de las vistas invocando al m&amp;eacute;todo de Rails  &lt;code&gt;append_view_path&lt;/code&gt; y pas&amp;aacute;ndole el &lt;em&gt;path&lt;/em&gt; al directorio  &lt;code&gt;views&lt;/code&gt; de nuestra aplicaci&amp;oacute;n, con lo que nuestro controlador busque sus plantillas en el mismo lugar que un controlador normal.&lt;/p&gt;

&lt;p&gt;Queremos que el controlador tenga una acci&amp;oacute;n &lt;code&gt;index&lt;/code&gt; por lo que hemos cambiado el m&amp;eacute;todo  &lt;code&gt;self.call&lt;/code&gt; por un m&amp;eacute;todo  &lt;code&gt;index&lt;/code&gt; que hace la misma llamada a &lt;code&gt;ps&lt;/code&gt; pero asignando la salida a una variable de instancia.  Despu&amp;eacute;s el m&amp;eacute;todo llama a  &lt;code&gt;render&lt;/code&gt; para mostrar la plantilla.  Al contrario que en un controlador Rails normal la plantilla se muestra autom&amp;aacute;ticamente.&lt;/p&gt;

&lt;p class="codeFilePath"&gt;/lib/processes_app.rb&lt;/p&gt;
&lt;pre class="ruby"&gt;
class ProcessesApp &amp;lt; ActionController::Metal
  include ActionController::Rendering
  
  append_view_path &amp;quot;#{Rails.root}/app/views&amp;quot;
  
  def index
    @processes = `ps -axcr -o &amp;quot;pid,pcpu, pmem, time, comm&amp;quot;`
    render
  end
end
&lt;/pre&gt;

&lt;p&gt;Ahora tenemos que crear la plantilla.  En el directorio &lt;code&gt;views&lt;/code&gt; de nuestra aplicaci&amp;oacute;n crearemos el directorio &lt;code&gt;process_app&lt;/code&gt; en el que pondremos nuestra nueva plantilla, en la que podemos escribir el c&amp;oacute;digo que queramos en este archivo como har&amp;iacute;amos con una plantilla erb normal.&lt;/p&gt;

&lt;p class="codeFilePath"&gt;/app/views/processes_app/index.html.erb&lt;/p&gt;
&lt;pre class="ruby"&gt;
&amp;lt;h1&amp;gt;Processes&amp;lt;/h1&amp;gt;
&amp;lt;pre&amp;gt;&amp;lt;%= @processes %&amp;gt;&amp;lt;/pre&amp;gt;
&lt;/pre&gt;

&lt;p&gt;Ya casi hemos terminado.  Lo &amp;uacute;nico que tenemos que hacer es modificar la ruta para que apunte a esta acci&amp;oacute;n espec&amp;iacute;fica.&lt;/p&gt;

&lt;p class="codeFilePath"&gt;/config/routes.rb&lt;/p&gt;
&lt;pre class="ruby"&gt;
match &amp;quot;/processes&amp;quot; =&amp;gt; ProcessesApp.action(:index)
&lt;/pre&gt;

&lt;p&gt;Si reiniciamos el servidor y visitamos otra vez la p&amp;aacute;gina de procesos veremos que ya se usa nuestra plantilla.&lt;/p&gt;

&lt;div class="imageWrapper"&gt;
  &lt;img src="/system/photos/422/original/E222I06.png" width="801" height="335" alt="La p&#225;gina de procesos con su plantilla."/&gt;
&lt;/div&gt;

&lt;p&gt;Tal y como acabamos de ver, Rails 3 es muy modular y nos permite poner en nuestras aplicaciones Rack justo s&amp;oacute;lo la funcionalidad que queramos.  Hay que tener en cuenta que escribir un controlador de esta manera s&amp;oacute;lo nos ahorrar&amp;aacute; un par de milisegundos en el mejor de los casos, por lo que lo mejor es crear primero un controlador Rails normal y luego hacer pruebas de rendimiento antes de escribir una aplicaci&amp;oacute;n Rack tal y como hemos hecho en este episodio.&lt;/p&gt;
</description>
      <pubDate>Thu, 29 Jul 2010 07:48:39 +0000</pubDate>
      <guid>http://es.asciicasts.com/episodes/222-rack-y-rails-3</guid>
      <link>http://es.asciicasts.com/episodes/222-rack-y-rails-3</link>
    </item>
    <item>
      <title>Subdominios con Rails 3</title>
      <description>&lt;p&gt;Ya han pasado casi dos a&amp;ntilde;os desde que &lt;a href="http://railscasts.com/episodes/123-subdomains"&gt;tratamos de los subdominios&lt;/a&gt; por &amp;uacute;ltima vez.  En Rails 3 se ha a&amp;ntilde;adido soporte para los subdominios y en este episodio veremos qu&amp;eacute; cosas nuevas podemos hacer.&lt;/p&gt;

&lt;p&gt;Vamos a trabajar con la misma aplicaci&amp;oacute;n del episodio dedicado a los subdominios: se trata de un sencilla aplicaci&amp;oacute;n que soporta m&amp;uacute;ltiples blogs.  Debajo se muestra la portada en la que aparece un listado de todos los blogs.  Cada blog tiene varios art&amp;iacute;culos asociados.&lt;/p&gt;


&lt;div class="imageWrapper"&gt;
  &lt;img src="/system/photos/408/original/E221I01.png" width="800" height="331" alt="Nuestra aplicaci&#243;n de blogs"/&gt;
&lt;/div&gt;

&lt;p&gt;Si hacemos clic en el enlace para editar un blog veremos que cada uno de ellos tiene dos atributos: &lt;code&gt;name&lt;/code&gt; y &lt;code&gt;subdomain&lt;/code&gt;.  Queremos utilizar el atributo  &lt;code&gt;subdomain&lt;/code&gt; como subdominio de la URL de dicho blog.&lt;/p&gt;

&lt;div class="imageWrapper"&gt;
  &lt;img src="/system/photos/409/original/E221I02.png" width="800" height="388" alt="P&#225;gina de edici&#243;n de un blog"/&gt;
&lt;/div&gt;

&lt;h3&gt;Configuraci&amp;oacute;n de subdominios en el entorno de desarrollo&lt;/h3&gt;

&lt;p&gt;Lo primero que tenemos que resolver es c&amp;oacute;mo encontrar una forma de gestionar los subdominios en el entorno de desarrollo.  La URL de nuestra aplicaci&amp;oacute;n es  &lt;a href="http://localhost:3000"&gt;http://localhost:3000&lt;/a&gt; que no permite subdominios, por lo que tendremos que usar alg&amp;uacute;n m&amp;eacute;todo alternativo.&lt;/p&gt;

&lt;p&gt;Una forma de hacerlo podr&amp;iacute;a ser configurando nuestra aplicaci&amp;oacute;n para utilizar 
 &lt;a href="http://www.modrails.com"&gt;Passenger&lt;/a&gt;.  Podr&amp;iacute;amos entonces darle un dominio de tipo  &lt;code&gt;http://blog.local&lt;/code&gt; y editar el fichero  &lt;code&gt;/etc/hosts&lt;/code&gt; para configurar subdominios de esta manera:&lt;/p&gt;

&lt;p class="codeFilePath"&gt;/etc/hosts&lt;/p&gt;
&lt;pre class="css"&gt;
127.0.0.1 personal.blog.local company.blog.local
&lt;/pre&gt;

&lt;p&gt;Lo ideal ser&amp;iacute;a poder utilizar un car&amp;aacute;cter comod&amp;iacute;n que casase con cualquier subdominio de forma que no tuvi&amp;eacute;semos que estar continuamente cambiando este archivo durante el desarrollo de nuestra aplicaci&amp;oacute;n, pero por desgracia esto no es posible as&amp;iacute; que tenemos que buscar otra posibilidad.&lt;/p&gt;

&lt;p&gt;Una buena soluci&amp;oacute;n es la que que ha presentado 
&lt;a href="http://tbaggery.com/2010/03/04/smack-a-ho-st.html"&gt;Tim Pope en su blog&lt;/a&gt;.  Ha comprado el dominio &lt;code&gt;smackaho.st&lt;/code&gt; y ha hecho que sea comod&amp;iacute;n de &lt;code&gt;localhost&lt;/code&gt;.  Uno de los comentaristas en ese blog ha hecho algo parecido con un dominio similar, llamado &lt;code&gt;lvh.me&lt;/code&gt;, y &amp;eacute;ste es el dominio que vamos a usar.&lt;/p&gt;

&lt;p&gt;Si abrimos &lt;a href="http://lvh.me:3000/"&gt;http://lvh.me:3000/&lt;/a&gt; veremos la p&amp;aacute;gina de nuestra aplicaci&amp;oacute;n porque el dominio &lt;code&gt;lvh.me&lt;/code&gt; resuelve a la direcci&amp;oacute;n IP 127.0.0.1.  La diferencia es que ahora podemos a&amp;ntilde;adir delante cualquier subdominio a la URL y este seguir&amp;aacute; apuntando a la misma aplicaci&amp;oacute;n.&lt;/p&gt;

&lt;div class="imageWrapper"&gt;
  &lt;img src="/system/photos/410/original/E221I03.png" width="800" height="388" alt="Ahora el blog ya tiene subdominios."/&gt;
&lt;/div&gt;

&lt;p&gt;Como ya podemos usar subdominios en nuestra aplicaci&amp;oacute;n, ya podemos configurar sus rutas para que el subdominio &lt;code&gt;personal&lt;/code&gt; vaya a la acci&amp;oacute;n &lt;code&gt;show&lt;/code&gt; del blog correspondiente.  Este es el aspecto de nuestro fichero de rutas:&lt;/p&gt;

&lt;p class="codeFilePath"&gt;/config/routes.rb&lt;/p&gt;
&lt;pre class="ruby"&gt;
Bloggit::Application.routes.draw do |map|
  resources :comments
  resources :articles
  resources :blogs
  root :to =&amp;gt; &amp;quot;blogs#index&amp;quot;
end
&lt;/pre&gt;

&lt;p&gt;Ahora mismo la ruta ra&amp;iacute;z va a la acci&amp;oacute;n &lt;code&gt;blogs/index&lt;/code&gt;.  Queremos que esto ocurra s&amp;oacute;lo si no se ha especificado ning&amp;uacute;n subdominio, si lo hubiera habr&amp;iacute;a que mostrar la acci&amp;oacute;n  &lt;code&gt;blogs/show&lt;/code&gt;.  Esto en Rails 3 puede hacerse a&amp;ntilde;adiendo una restricci&amp;oacute;n.&lt;/p&gt;

&lt;p class="codeFilePath"&gt;/config/routes.rb&lt;/p&gt;
&lt;pre class="ruby"&gt;
Bloggit::Application.routes.draw do |map|
  resources :comments
  resources :articles
  resources :blogs
  match &amp;#x27;/&amp;#x27; =&amp;gt; &amp;#x27;blogs#show&amp;#x27;, :constraints =&amp;gt; { :subdomain =&amp;gt; /.+/ }
  root :to =&amp;gt; &amp;quot;blogs#index&amp;quot;
end
&lt;/pre&gt;

&lt;p&gt;Esta funcionalidad (que ya viene incorporada en Rails 3) habr&amp;iacute;a requerido el uso de un &lt;em&gt;plugin&lt;/em&gt; si nuestra aplicaci&amp;oacute;n utilizase Rails 2.  La opc&amp;iacute;on &lt;code&gt;:subdomain&lt;/code&gt; recibe o bien una cadena o una expresi&amp;oacute;n regular, que es lo que hemos usado nosotros, para aceptar  cualquier subdominio que tenga al menos una letra.  Obs&amp;eacute;rvese que es importante que la ruta ra&amp;iacute;z aparezca despu&amp;eacute;s de la ruta de subdominio o de lo contrario la ruta ra&amp;iacute;z tendr&amp;aacute; preferencia sobre las rutas de subdominios, por las que nunca pasaremos.  Como regla general cuanto m&amp;aacute;s espec&amp;iacute;fica es una ruta deber&amp;iacute;a aparecer m&amp;aacute;s arriba en la lista de rutas.&lt;/p&gt;

&lt;p&gt;Si ahora visitamos el subdominio &lt;code&gt;personal&lt;/code&gt; veremos un error que era de esperar porque estamos en la acci&amp;oacute;n &lt;code&gt;show&lt;/code&gt; de &lt;code&gt;BlogsController&lt;/code&gt; a la que no le hemos pasado un &lt;code&gt;id&lt;/code&gt;.&lt;/p&gt;


&lt;div class="imageWrapper"&gt;
  &lt;img src="/system/photos/411/original/E221I04.png" width="800" height="312" alt="La p&#225;gina muestra un error porque el controlador espera un identificador."/&gt;
&lt;/div&gt;

&lt;p&gt;Esto es f&amp;aacute;cil de arreglar.  Tan s&amp;oacute;lo tenemos que modificar la acci&amp;oacute;n &lt;code&gt;show&lt;/code&gt; de forma que encuentre un blog por su identificador de subdominio en lugar de por su &lt;code&gt;id&lt;/code&gt;.&lt;/p&gt;

&lt;p class="codeFilePath"&gt;/app/controllers/blogs_controller.rb&lt;/p&gt;
&lt;pre class="ruby"&gt;
def show
  @blog = Blog.find_by_subdomain!(request.subdomain)
end
&lt;/pre&gt;

&lt;p&gt;Si ahora recargamos la p&amp;aacute;gina veremos lo art&amp;iacute;culos de nuestro blog &amp;ldquo;Personal&amp;rdquo; .&lt;/p&gt;

&lt;div class="imageWrapper"&gt;
  &lt;img src="/system/photos/412/original/E221I05.png" width="815" height="427" alt="Ahora el blog se encuentra por su subdominio."/&gt;
&lt;/div&gt;

&lt;p&gt;Si quitamos el subdominio veremos otra vez la p&amp;aacute;gina de inicio que ten&amp;iacute;amos antes.&lt;/p&gt;

&lt;div class="imageWrapper"&gt;
  &lt;img src="/system/photos/413/original/E221I06.png" width="810" height="341" alt="Aparece la portada si no se especifica un subdominio."/&gt;
&lt;/div&gt;

&lt;p&gt;A&amp;uacute;n nos queda un peque&amp;ntilde;o problema con las rutas.  Si visitamos &lt;a href="http://www.lvh.me:3000"&gt;http://www.lvh.me:3000&lt;/a&gt; nuestra aplicaci&amp;oacute;n buscar&amp;aacute; un blog con el subdominio  &lt;code&gt;www&lt;/code&gt; cuando claramente deber&amp;iacute;a redirigirnos a la portada de los blogs.  Podr&amp;iacute;amos intentar arreglar esto haciendo algo astuto con la expresi&amp;oacute;n regular del archivo de rutas, pero para tener m&amp;aacute;s flexibilidad y control sobre los subodminios lo haremos en c&amp;oacute;digo Ruby, porque en Rails 3 podemos escribir una clase que gestione las restricciones de las rutas.&lt;/p&gt;

&lt;p&gt;En primer lugar vamos a modificar el archivo de rutas para que utilice una nueva clase llamada &lt;code&gt;Subdomain&lt;/code&gt;.  Lo haremos utilizando el m&amp;eacute;todo &lt;code&gt;constraints&lt;/code&gt; y pas&amp;aacute;ndole la clase como argumento.&lt;/p&gt;

&lt;p class="codeFilePath"&gt;/config/routes.rb&lt;/p&gt;
&lt;pre class="ruby"&gt;
Bloggit::Application.routes.draw do |map|
  resources :comments
  resources :articles
  resources :blogs
  constraints(Subdomain) do
    match &amp;#x27;/&amp;#x27; =&amp;gt; &amp;#x27;blogs#show&amp;#x27;  
  end
  root :to =&amp;gt; &amp;quot;blogs#index&amp;quot;
end
&lt;/pre&gt;

&lt;p&gt;A continuaci&amp;oacute;n tenemos que crear la clase &lt;code&gt;Subdomain&lt;/code&gt; que pondremos en el directorio &lt;code&gt;lib&lt;/code&gt;.  Esta clase tendr&amp;aacute; que tener un m&amp;eacute;todo de clase llamado &lt;code&gt;matches?&lt;/code&gt; que reciba un objeto de tipo &lt;code&gt;request&lt;/code&gt; como par&amp;aacute;metro.  Este objeto es el mismo objeto al que tenemos acceso en nuestros controladores y vistas y por tanto podemos invocar los mismos m&amp;eacute;todos que estamos acostumbrados a utilizar.  El m&amp;eacute;todo tiene que devolver un valor booleano cuyo valor dependa de si la ruta dada casa con la petici&amp;oacute;n.  En nuestro caso queremos que el m&amp;eacute;todo devuelva &lt;code&gt;true&lt;/code&gt; si la petici&amp;oacute;n tiene un subdominio siempre que dicho subdominio no sea &lt;code&gt;www&lt;/code&gt;.  Nuestra clase, por tanto, tendr&amp;aacute; este aspecto:&lt;/p&gt;


&lt;p class="codeFilePath"&gt;/lib/subdomain.rb&lt;/p&gt;
&lt;pre class="ruby"&gt;
class Subdomain
  def self.matches?(request)
    request.subdomain.present? &amp;amp;&amp;amp; request.subdomain != &amp;#x27;www&amp;#x27;
  end
end
&lt;/pre&gt;

&lt;p&gt;Si visitamos &lt;a href="http://www.lvh.me:3000"&gt;http://www.lvh.me:3000&lt;/a&gt; veremos la portada tal y como quer&amp;iacute;amos.&lt;/p&gt;


&lt;div class="imageWrapper"&gt;
  &lt;img src="/system/photos/414/original/E221I07.png" width="800" height="333" alt="En el subdominio www ahora aparece la portada."/&gt;
&lt;/div&gt;

&lt;h3&gt;Para arreglar los enlaces&lt;/h3&gt;

&lt;p&gt;A continuaci&amp;oacute;n vamos a corregir los enlaces a cada blog que aparecen en la portada para que apunten al subdominio correspondiente en lugar de a la acci&amp;oacute;n &lt;code&gt;show&lt;/code&gt; de dicho blog (lo que dar&amp;aacute; un error porque dicha acci&amp;oacute;n ahora espera recibir un subdominio)&lt;/p&gt;

&lt;p&gt;En el c&amp;oacute;digo de la vista de la acci&amp;oacute;n &lt;code&gt;index&lt;/code&gt; tenemos un enlace est&amp;aacute;ndar a cada blog rodeado por etiquetas &lt;code&gt;h2&lt;/code&gt;.&lt;/p&gt;

&lt;p class="codeFilePath"&gt;/app/views/blogs/index.html.erb&lt;/p&gt;
&lt;pre class="ruby"&gt;
&amp;lt;% title &amp;quot;Blogs&amp;quot; %&amp;gt;

&amp;lt;% for blog in @blogs %&amp;gt;
  &amp;lt;div&amp;gt;
    &amp;lt;h2&amp;gt;&amp;lt;%= link_to blog.name, blog %&amp;gt;&amp;lt;/h2&amp;gt;
    &amp;lt;div class=&amp;quot;actions&amp;quot;&amp;gt;
      &amp;lt;%= link_to &amp;quot;Edit&amp;quot;, edit_blog_path(blog) %&amp;gt; | 
      &amp;lt;%= link_to &amp;quot;Destroy&amp;quot;, blog, :confirm =&amp;gt; &amp;#x27;Are you sure?&amp;#x27;, :method =&amp;gt; :delete %&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;% end %&amp;gt;
&lt;/pre&gt;

&lt;p&gt;Queremos cambiar dicho enlace para que apunte a la URL ra&amp;iacute;z con el subdominio apropiado.  Por desgracia a &lt;code&gt;root_url&lt;/code&gt; no le podemos pasar la opci&amp;oacute;n &lt;code&gt;subdomain&lt;/code&gt; como s&amp;iacute; que podr&amp;iacute;amos hacer con &lt;strong&gt;subdomain_fu&lt;/strong&gt;.  En su lugar tendremos que crear el nombre de &lt;em&gt;host&lt;/em&gt; partiendo de cero e incluyendo el subdominio.  Esto lo haremos a partir del atributo &lt;code&gt;subdomain&lt;/code&gt; del blog y el dominio y puerto de la petici&amp;oacute;n actual.&lt;/p&gt;

&lt;p class="codeFilePath"&gt;/app/views/blogs/index.html.erb&lt;/p&gt;
&lt;pre class="ruby"&gt;
&amp;lt;h2&amp;gt;&amp;lt;%= link_to blog.name, root_url(:host =&amp;gt; blog.subdomain + &amp;#x27;.&amp;#x27; + request.domain + request.port_string) %&amp;gt;&amp;lt;/h2&amp;gt;
&lt;/pre&gt;

&lt;p&gt;Si ahora recargamos la portada veremos que los enlaces de los blogs ahora muestran correctamente el subdominio.&lt;/p&gt;

&lt;h3&gt;Limpieza del c&amp;oacute;digo que cambia el subdominio&lt;/h3&gt;

&lt;p&gt;Todo funciona correctamente ahora pero el c&amp;oacute;digo que crea el enlace al otro subdominio podr&amp;iacute;a ser un poco m&amp;aacute;s claro, lo que nos interesar&amp;aacute; especialmente si vamos a tener que usarlo mucho. Lo haremos movi&amp;eacute;ndolo a su propio &lt;em&gt;helper&lt;/em&gt;, que llamaremos &lt;code&gt;with_subdomain&lt;/code&gt; y que recibir&amp;aacute; un argumento &lt;code&gt;subdomain&lt;/code&gt;.  Primero cambiaremos el c&amp;oacute;digo en la vista para que invoque al m&amp;eacute;todo que estamos a punto de escribir.&lt;/p&gt;

&lt;p class="codeFilePath"&gt;/app/views/blogs/index.html.erb&lt;/p&gt;
&lt;pre class="ruby"&gt;
&amp;lt;h2&amp;gt;&amp;lt;%= link_to blog.name, root_url(:host =&amp;gt; with_subdomain(blog.subdomain)) %&amp;gt;&amp;lt;/h2&amp;gt;
&lt;/pre&gt;

&lt;p&gt;Pondremos el m&amp;eacute;todo &lt;em&gt;helper&lt;/em&gt; en su propio m&amp;oacute;dulo, en fichero llamado &lt;code&gt;url_helper.rb&lt;/code&gt;.&lt;/p&gt;

&lt;p class="codeFilePath"&gt;/app/helpers/url_helper.rb&lt;/p&gt;
&lt;pre class="ruby"&gt;
module UrlHelper
  def with_subdomain(subdomain)
    subdomain = (subdomain || &amp;quot;&amp;quot;)
    subdomain += &amp;quot;.&amp;quot; unless subdomain.empty?
    [subdomain, request.domain, request.port_string].join
  end
end
&lt;/pre&gt;

&lt;p&gt;El c&amp;oacute;digo del m&amp;oacute;dulo primero asigna &lt;code&gt;subdomain&lt;/code&gt; a una cadena vac&amp;iacute;a si el valor pasado es  &lt;code&gt;nil&lt;/code&gt; y le a&amp;ntilde;ade un punto en caso contrario.  Finalmente une esta variable &lt;code&gt;subdomain&lt;/code&gt;, con el dominio y el puerto de la petici&amp;oacute;n actual y devuelve dicho valor.&lt;/p&gt;

&lt;p&gt;Vamos a a&amp;ntilde;adir este m&amp;oacute;dulo a &lt;code&gt;ApplicationController&lt;/code&gt; para que todos los controladores de nuestra aplicaci&amp;oacute;n puedan utilizarlo.&lt;/p&gt;

&lt;p class="codeFilePath"&gt;/app/controllers/application_controller.rb&lt;/p&gt;
&lt;pre class="ruby"&gt;
class ApplicationController &amp;lt; ActionController::Base
  include UrlHelper
  protect_from_forgery
  layout &amp;#x27;application&amp;#x27;
end
&lt;/pre&gt;

&lt;p&gt;Aunque hemos limpiado el c&amp;oacute;digo de la vista, a&amp;uacute;n podr&amp;iacute;amos mejorarlo a&amp;uacute;n m&amp;aacute;s si pudi&amp;eacute;semos a&amp;ntilde;adir la opci&amp;oacute;n  &lt;code&gt;:subdomain&lt;/code&gt; al m&amp;eacute;todo &lt;code&gt;root_url&lt;/code&gt;, y esto s&amp;oacute;lo es posible hacerlo si reescribimos el m&amp;eacute;todo &lt;code&gt;url_for&lt;/code&gt;.  Tan s&amp;oacute;lo tenemos que a&amp;ntilde;adir el siguiente m&amp;eacute;todo a nuestro m&amp;oacute;dulo &lt;code&gt;UrlHelper&lt;/code&gt;.&lt;/p&gt;

&lt;p class="codeFilePath"&gt;/app/helpers/url_helper.rb&lt;/p&gt;
&lt;pre class="ruby"&gt;
def url_for(options = nil)
  if options.kind_of?(Hash) &amp;amp;&amp;amp; options.has_key?(:subdomain)
    options[:host] = with_subdomain(options.delete(:subdomain))
  end
  super
end
&lt;/pre&gt;

&lt;p&gt;Nuestra versi&amp;oacute;n redefinida de &lt;code&gt;url_for&lt;/code&gt;  comprueba si en el &lt;em&gt;hash&lt;/em&gt; de opciones aparece una clave llamada &lt;code&gt;:subdomain&lt;/code&gt; y, de ser as&amp;iacute;, pone la opci&amp;oacute;n &lt;code&gt;:host&lt;/code&gt; al valor que devuelva la funci&amp;oacute;n &lt;code&gt;with_subdomain&lt;/code&gt; para dicho subdominio.  Finalmente, llama a &lt;code&gt;super&lt;/code&gt; para que se ejecute el c&amp;oacute;digo por defecto del m&amp;eacute;todo y se genere correctamente el resto de la URL.  No nos hace falta pasar por &lt;code&gt;alias_method_chain&lt;/code&gt;, bastar&amp;aacute; con llamar a &lt;code&gt;super&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Ya podemos actualizar el c&amp;oacute;digo de nuestra vista para que utilice la opci&amp;oacute;n &lt;code&gt;:subdomain&lt;/code&gt;.&lt;/p&gt;


&lt;p class="codeFilePath"&gt;/app/views/blogs/index.html.erb&lt;/p&gt;
&lt;pre class="ruby"&gt;
&amp;lt;h2&amp;gt;&amp;lt;%= link_to blog.name, root_url(:subdomain =&amp;gt; blog.subdomain) %&amp;gt;&amp;lt;/h2&amp;gt;
&lt;/pre&gt;

&lt;p&gt;Si recargamos la portada y hacemos clic en uno de los enlaces de los blogs, el enlace sigue dirigiendo a la URL correcta, con su subdominio.&lt;/p&gt;

&lt;div class="imageWrapper"&gt;
  &lt;img src="/system/photos/415/original/E221I08.png" width="801" height="338" alt="La opci&#243;n :subdomain funciona."/&gt;
&lt;/div&gt;

&lt;p&gt;Ser&amp;iacute;a conveniente poder tener en cada blog un enlace de vuelta a la p&amp;aacute;gina principal, podemos hacerlo llamando a &lt;code&gt;root_url&lt;/code&gt; con &lt;code&gt;:subdomain&lt;/code&gt; a &lt;code&gt;false&lt;/code&gt;.&lt;/p&gt;

&lt;p class="codeFilePath"&gt;/app/views/blogs/show.html.erb&lt;/p&gt;
&lt;pre class="ruby"&gt;
&amp;lt;p&amp;gt;&amp;lt;%= link_to &amp;quot;All Blogs&amp;quot;, root_url(:subdomain =&amp;gt; false) %&amp;gt;&amp;lt;/p&amp;gt;
&lt;/pre&gt;

&lt;p&gt;Con esto tendremos el enlace que nos llevar&amp;aacute; de vuelta a la portada.&lt;/p&gt;

&lt;h3&gt;C&amp;oacute;mo gestionar diferentes dominios de nivel superior&lt;/h3&gt;

&lt;p&gt;Una cosa que a&amp;uacute;n no hemos visto es c&amp;oacute;mo gestionar nombres de dominio que tengan m&amp;aacute;s de dos partes.  As&amp;iacute;, si bien nuestro c&amp;oacute;digo de subdominios funcionar&amp;aacute; para dominios .com, no lo har&amp;aacute; con dominios que terminen en .co.uk. Para poder trabajar con estos dominios tendremos que cambiar nuestra aplicaci&amp;oacute;n y donde quiera que se llame a 
 &lt;code&gt;request.domain&lt;/code&gt; o &lt;code&gt;request.subdomain&lt;/code&gt; tendremos que especificar el n&amp;uacute;mero de puntos que contiene el dominio (sin subdominios).  Rails por defecto asume el valor 1, pero  tendremos que pasar el valor 2 para dominios como .co.uk.&lt;/p&gt;

&lt;p&gt;Tenemos que hacer cambios en dos sitios de nuestra aplicaci&amp;oacute;n: en el m&amp;eacute;todo &lt;code&gt;UrlHelper&lt;/code&gt; y en la clase &lt;code&gt;Subdomain&lt;/code&gt; que estamos usando en nuestras rutas.&lt;/p&gt;

&lt;p class="codeFilePath"&gt;/app/helpers/url_helper.rb&lt;/p&gt;
&lt;pre class="ruby"&gt;
def with_subdomain(subdomain)
  subdomain = (subdomain || &amp;quot;&amp;quot;)
  subdomain += &amp;quot;.&amp;quot; unless subdomain.empty?
  [subdomain, request.domain(2), request.port_string].join
end
&lt;/pre&gt;  

&lt;p class="codeFilePath"&gt;/lib/subdomains.rb&lt;/p&gt;
&lt;pre class="ruby"&gt;
class Subdomain
  def self.matches?(request)
    request.subdomain(2).present? &amp;amp;&amp;amp; request.subdomain(2) != &amp;quot;www&amp;quot;
  end
end
&lt;/pre&gt;

&lt;p&gt;Obviamente no nos interesa que el valor est&amp;eacute; grabado a fuego, tendremos que hacerlo din&amp;aacute;mico de forma que en desarrollo pueda valer 1 (para dominios como &lt;code&gt;lvh.me&lt;/code&gt;) mientras que en producci&amp;oacute;n utilizar&amp;iacute;amos el valor 2.  Este valor se puede poner en un archivo externo de configuraci&amp;oacute;n que ser&amp;iacute;a cargado por el entorno.&lt;/p&gt;

&lt;h3&gt;Cookies&lt;/h3&gt;

&lt;p&gt;Hay un aspecto &amp;uacute;ltimo que nos hemos dejado para el final de este episodio.  Si miramos las &lt;em&gt;cookies&lt;/em&gt; de nuestro navegador y filtramos por el nombre de dominio que hemos venido usando para nuestra aplicaci&amp;oacute;n veremos que se guarda una &lt;em&gt;cookie&lt;/em&gt; de sesi&amp;oacute;n diferente para cada subdominio que hemos visitado.  No queremos que esto ocurra, porque esto significa que los subdominios no comparten la sesi&amp;oacute;n.&lt;/p&gt;

&lt;div class="imageWrapper"&gt;
  &lt;img src="/system/photos/416/original/E221I09.png" width="805" height="246" alt="Tenemos una cookie de sesi&#243;n distinta para cada subdominio."/&gt;
&lt;/div&gt;

&lt;p&gt;En el episodio 123 se present&amp;oacute; una soluci&amp;oacute;n a este problema pero existe una forma mejor de hacerlo en Rails 3.  S&amp;oacute;lo tenemos que a&amp;ntilde;adir la opci&amp;oacute;n &lt;code&gt;:domain&lt;/code&gt; en el fichero &lt;code&gt;/config/initializers/session_store.rb&lt;/code&gt; de nuestra aplicaci&amp;oacute;n en el m&amp;eacute;todo &lt;code&gt;Rails.application.config.session_store&lt;/code&gt; d&amp;aacute;ndole el valor &lt;code&gt;:all&lt;/code&gt;.&lt;/p&gt;

&lt;p class="codeFilePath"&gt;/config/initializers/sesion_store.rb&lt;/p&gt;
&lt;pre class="ruby"&gt;
Rails.application.config.session_store :cookie_store, :key =&amp;gt; &amp;#x27;_bloggit_session&amp;#x27;, :domain =&amp;gt; :all
&lt;/pre&gt;

&lt;p&gt;De todas formas, esto todav&amp;iacute;a no funcionar&amp;aacute;.  La opci&amp;oacute;n &lt;code&gt;:domain&lt;/code&gt; ha sido a&amp;ntilde;adida posteriormente a Rails 3.0 beta 4, que es la &amp;uacute;ltima versi&amp;oacute;n de Rails a fecha de escritura de este art&amp;iacute;culo.  Tendremos que pasar a &lt;em&gt;Edge Rails&lt;/em&gt; o bien esperar a la siguiente versi&amp;oacute;n candidata para que tener esta funcionalidad.  Una vez que tengamos una versi&amp;oacute;n de Rails que soporte esta opci&amp;oacute;n nuestra aplicaci&amp;oacute;n podr&amp;aacute; compartir sesiones a lo largo de m&amp;uacute;ltiples subodminos.  La opci&amp;oacute;n &lt;code&gt;:all&lt;/code&gt; asume que nuestra aplicaci&amp;oacute;n tiene s&amp;oacute;lo un punto en su dominio de nivel superior.  De no ser as&amp;iacute; podemos especificar un nombre de dominio que ser&amp;aacute; utilizado como base para la sesi&amp;oacute;n.&lt;/p&gt;

&lt;p&gt;Con esto cerramos este episodio.  La gesti&amp;oacute;n de subdominios sin tener que usar &lt;em&gt;plugins&lt;/em&gt; adicionales es una gran mejora a Rails que seguramente tendr&amp;aacute; muchos usos en nuestras aplicaciones.&lt;/p&gt;
</description>
      <pubDate>Tue, 27 Jul 2010 09:49:06 +0000</pubDate>
      <guid>http://es.asciicasts.com/episodes/221-subdominios-con-rails-3</guid>
      <link>http://es.asciicasts.com/episodes/221-subdominios-con-rails-3</link>
    </item>
    <item>
      <title>PDFKit</title>
      <description>&lt;p&gt;Existen varias librer&amp;iacute;as para generar archivos PDF en Ruby.  Una de las m&amp;aacute;s populares es &lt;a href="http://prawn.majesticseacreature.com/"&gt;Prawn&lt;/a&gt;, que es una excelente manera de generar PDFs desde cero con Ruby y que ya estudiamos en el Episodio 153 [&lt;a href="http://railscasts.com/episodes/153-pdfs-with-prawn"&gt;verlo&lt;/a&gt;, &lt;a href="http://asciicasts.com/episodes/153-pdfs-with-prawn"&gt;leero&lt;/a&gt;].  Sin embargo hay situaciones en las que nos ser&amp;iacute;a mucho m&amp;aacute;s f&amp;aacute;cil generar un PDF a partir de un documento HTML ya existente, que es el caso que veremos en este episodio.&lt;/p&gt;

&lt;p&gt;La generaci&amp;oacute;n de PDFs a partir de HTML no es una idea precisamente nueva pero hasta ahora las soluciones disponibles costaban dinero, pero ha habido mucha actividad en este campo a partir de una soluci&amp;oacute;n propuesta por &lt;a href="http://thinkrelevance.com/blog/2010/06/15/rethinking-pdf-creation-in-ruby.html"&gt;Jared Pace&lt;/a&gt; con su nueva librer&amp;iacute;a &lt;a href="http://github.com/jdpace/PDFKit"&gt;PDFKit&lt;/a&gt;.  La gema depende de &lt;a href="http://code.google.com/p/wkhtmltopdf/"&gt;wkhtmltopdf&lt;/a&gt;, una herramienta que utiliza el motor de WebKit para generar documentos PDF, as&amp;iacute; que para instalar PDFKit tenemos primero que instalar wkhtmltopdf.  PDFKit ya viene con wkhtmltopdf as&amp;iacute; que, dependiendo de nuestro entorno, puede que esto no sea necesario.&lt;/p&gt;

&lt;p&gt;La gema de PDFKit se instala de la forma habitual:&lt;/p&gt;

&lt;pre class="terminal"&gt;
$ gem install pdfkit
&lt;/pre&gt;

&lt;p&gt;Tras su instalaci&amp;oacute;n PDFKit puede generar un archivo PDF si se le pasa un archivo HTML o la direcci&amp;oacute;n de una p&amp;aacute;gina web.  La gema tambi&amp;eacute;n viene con un &lt;em&gt;middleware&lt;/em&gt; de Rack que se puede usar para generar PDFs a partir de cualquier p&amp;aacute;gina de un sitio simplemente a&amp;ntilde;adiendo &lt;code&gt;.pdf&lt;/code&gt; a la URL.  Este es el enfoque que usaremos en nuestra aplicaci&amp;oacute;n.&lt;/p&gt;

&lt;h3&gt;Creaci&amp;oacute;n de PDFs de facturas&lt;/h3&gt;

&lt;p&gt;La aplicaci&amp;oacute;n que vamos a usar para demostrar el uso de PDFKit es la misma con la que trabajamos en el episodio sobre Prawn.  Debajo se muestra la p&amp;aacute;gina de pedido de una aplicaci&amp;oacute;n sencilla de comercio electr&amp;oacute;nico.   Queremos a&amp;ntilde;adir un enlace a esta p&amp;aacute;gina que permita descargar la versi&amp;oacute;n en PDF del pedido, y para eso es para lo que vamos a usar PDFKit.&lt;/p&gt;

&lt;div class="imageWrapper"&gt;
  &lt;img src="/system/photos/403/original/E220I01.png" width="800" height="390" alt="P&#225;gina de un pedido."/&gt;
&lt;/div&gt;

&lt;p&gt;Lo primero que haremos ser&amp;aacute; a&amp;ntilde;adir una referencia a PDFKit en nuestra aplicaci&amp;oacute;n.  Dado que estamos con una aplicaci&amp;oacute;n Rails 3 podemos modificar el archivo &lt;code&gt;Gemfile&lt;/code&gt;:&lt;/p&gt;


&lt;p class="codeFilePath"&gt;/Gemfile&lt;/p&gt;
&lt;pre class="ruby"&gt;
gem &amp;quot;pdfkit&amp;quot;
&lt;/pre&gt;

&lt;p&gt;Y despu&amp;eacute;s, para asegurarnos de que la gema est&amp;aacute; instalada:&lt;/p&gt;

&lt;pre class="terminal"&gt;
$ bundle install
&lt;/pre&gt;

&lt;p&gt;A continuaci&amp;oacute;n tenemos que a&amp;ntilde;adir el &lt;em&gt;middleware&lt;/em&gt;.  En una aplicaci&amp;oacute;n Rails 3 esto hay que hacerlo en el fichero  &lt;code&gt;/config/application.rb&lt;/code&gt; (si estuvi&amp;eacute;ramos con una aplicaci&amp;oacute;n Rails 2 ir&amp;iacute;a en el fichero de configuraci&amp;oacute;n &lt;code&gt;environment.rb&lt;/code&gt;).   Modificaremos el entorno para que la aplicaci&amp;oacute;n utilice el &lt;em&gt;middleware&lt;/em&gt; de PDFKit.&lt;/p&gt;
	
&lt;p class="codeFilePath"&gt;/config/application.rb&lt;/p&gt;
&lt;pre class="ruby"&gt;
require File.expand_path(&amp;#x27;../boot&amp;#x27;, __FILE__)

require &amp;#x27;rails/all&amp;#x27;

# Auto-require default libraries and those for the current Rails environment.
Bundler.require :default, Rails.env

module Store
  class Application &amp;lt; Rails::Application
    config.secret_token = &amp;#x27;6f22fa632e18b338b4babfa5fca632f5454fc97317cb52f372fa0f0fdd7f4d5bd95a060ff412c7230627b5c17906c9762c09208624bc1ab97f8d5344d8d4f467&amp;#x27;
    config.filter_parameters &amp;lt;&amp;lt; :password
    config.middleware.use &amp;quot;PDFKit::Middleware&amp;quot;
  end
end
&lt;/pre&gt;

&lt;p&gt;Podemos ejecutar &lt;code&gt;rake middleware&lt;/code&gt; para ver qu&amp;eacute; &lt;em&gt;middlewares&lt;/em&gt; est&amp;aacute;n siendo utilizados por nuestra aplicaci&amp;oacute;n, con lo que podemos comprobar si aparece el de PDFKit:&lt;/p&gt;

&lt;pre class="terminal"&gt;
$ rake middleware
(in /Users/eifion/rails/apps_for_asciicasts/ep220/store)
use ActionDispatch::Static
use Rack::Lock
use ActiveSupport::Cache::Strategy::LocalCache
use Rack::Runtime
use Rails::Rack::Logger
use ActionDispatch::ShowExceptions
use ActionDispatch::RemoteIp
use Rack::Sendfile
use ActionDispatch::Callbacks
use ActiveRecord::ConnectionAdapters::ConnectionManagement
use ActiveRecord::QueryCache
use ActionDispatch::Cookies
use ActionDispatch::Session::CookieStore
use ActionDispatch::Flash
use ActionDispatch::ParamsParser
use Rack::MethodOverride
use ActionDispatch::Head
use PDFKit::Middleware
run Store::Application.routes
&lt;/pre&gt;

&lt;p&gt;Como en el listado aparece &lt;code&gt;PDFKit::Middleware&lt;/code&gt; podemos continuar.  Ya es posible a&amp;ntilde;adir la extensi&amp;oacute;n &lt;code&gt;.pdf&lt;/code&gt; a cualquier URL de nuestra aplicaci&amp;oacute;n para obtener una versi&amp;oacute;n en PDF de la misma.  Si hacemos as&amp;iacute; para una de las facturas de la aplicaci&amp;oacute;n veremos su versi&amp;oacute;n en PDF:&lt;/p&gt;

&lt;div class="imageWrapper"&gt;
  &lt;img src="/system/photos/404/original/E220I02.png" width="810" height="368" alt="La version PDF de una factura."/&gt;
&lt;/div&gt;

&lt;p&gt;Y eso es todo.  Pr&amp;aacute;cticamente sin tener que hacer nada hemos creado una versi&amp;oacute;n PDF del pedido.  Podr&amp;iacute;a tener mejor aspecto, pero ya tenemos una base sobre la que trabajar.&lt;/p&gt;

&lt;h3&gt;Personalizando el aspecto de la factura en PDF&lt;/h3&gt;

&lt;p&gt;Lo primero que haremos con la factura es quitar el fondo azul de la versi&amp;oacute;n en PDF.  Tenemos que ser capaces de distinguir la versi&amp;oacute;n PDF de forma que podamos aplicar diferentes reglas de estilo y esto puede hacerse modificando la llamada al &lt;em&gt;middleware&lt;/em&gt; para que PDFKit utilice el tipo de medio impreso.&lt;/p&gt;

&lt;p class="codeFilePath"&gt;/config/application.rb&lt;/p&gt;
&lt;pre class="ruby"&gt;
config.middleware.use &amp;quot;PDFKit::Middleware&amp;quot;, :print_media_type =&amp;gt; true
&lt;/pre&gt;

&lt;p&gt;El fichero de &lt;em&gt;layout&lt;/em&gt; de nuestra aplicaci&amp;oacute;n tenemos una etiqueta &lt;code&gt;stylesheet_link_tag&lt;/code&gt; que incluye la hoja de estilos  &lt;code&gt;application.css&lt;/code&gt;.  Por defecto, esta hoja tiene el tipo de medio  &lt;code&gt;screen&lt;/code&gt; lo que significa que con el cambio que acabamos de hacer al &lt;em&gt;middleware&lt;/em&gt; ninguno de los estilos de esa hoja ser&amp;aacute; aplicado en la versi&amp;oacute;n en PDF.  Vamos a modificar la etiqueta &lt;code&gt;stylesheet_link_tag&lt;/code&gt; para que se corresponda con el tipo de medio &lt;code&gt;all&lt;/code&gt;, con lo o ue los estilos se aplicar&amp;aacute;n tanto a versi&amp;oacute;n de pantalla (HTML) como a la de impresi&amp;oacute;n (PDF).&lt;/p&gt;

&lt;p class="codeFilePath"&gt;/application/views/layouts/application.html.erb&lt;/p&gt;
&lt;pre class="ruby"&gt;
&amp;lt;%= stylesheet_link_tag &amp;quot;application&amp;quot;, :media =&amp;gt; &amp;#x27;all&amp;#x27; %&amp;gt;
&lt;/pre&gt;

&lt;p&gt;Podr&amp;iacute;amos haber creado una hoja de estilos completamente separada para la hoja de impresi&amp;oacute;n pero en vez de eso vamos a incluir las reglas que aplican para la versi&amp;oacute;n de impresi&amp;oacute;n en la misma hoja de estilos.  Al final del archivo CSS podemos a&amp;ntilde;adir una regla de tipo de medio, y las reglas definidas en dicha secci&amp;oacute;n s&amp;oacute;lo ser&amp;aacute;n aplicables a la vista en PDF.  Por tanto para eliminar el fondo azul de nuestra vista de impresi&amp;oacute;n podemos a&amp;ntilde;adir la siguiente regla CSS al final de la hoja de estilos de nuestra aplicaci&amp;oacute;n.&lt;/p&gt;

&lt;p class="codeFilePath"&gt;/public/stylesheets/application.css&lt;/p&gt;
&lt;pre class="css"&gt;
@media print {
	body { background-color: #FFF; }
  #container {
		width: auto;
		margin: 0;
		padding: 0;
		border: none;
	}
}
&lt;/pre&gt;

&lt;p&gt;Cuando recarguemos la vista de p&amp;aacute;gina de nuestro pedido veremos que el fondo azul habr&amp;aacute; desaparecido.&lt;/p&gt;


&lt;div class="imageWrapper"&gt;
  &lt;img src="/system/photos/405/original/E220I03.png" width="800" height="435" alt="Ya hemos quitado el fondo azul"/&gt;
&lt;/div&gt;

&lt;p&gt;El PDF que tenemos ahora es muy parecido al que creamos en el episodio sobre Prawn, pero hemos tenido que escribir mucho menos c&amp;oacute;digo para conseguir el mismo resultado.  Prawn tiene sus ventajas, porque nos da mucho m&amp;aacute;s control sobre la forma en que la se genera el fichero PDF, especialmente en documentos con varias p&amp;aacute;ginas.  Con PDFKit si la tabla del pedido ocupase varias p&amp;aacute;ginas tendr&amp;iacute;amos problemas para controlar la forma en la que se muestran en cada p&amp;aacute;gina las cabeceras y pies.&lt;/p&gt;

&lt;h3&gt;Control de saltos de p&amp;aacute;gina&lt;/h3&gt;

&lt;p&gt;Dicho esto, con PDFKit s&amp;iacute; que podemos tener cierto control sobre los saltos de p&amp;aacute;gina.  Si a&amp;ntilde;adimos unos p&amp;aacute;rrafos de texto al principio del pedido (para que la tabla quede m&amp;aacute;s abajo) veremos que la tabla ahora aparece en dos p&amp;aacute;ginas distintas.&lt;/p&gt;

&lt;div class="imageWrapper"&gt;
  &lt;img src="/system/photos/406/original/E220I04.png" width="800" height="435" alt="La tabla ocupando dos p&#225;ginas."/&gt;
&lt;/div&gt;

&lt;p&gt;Podemos evitar este problema modificando la hoja de estilos de impresi&amp;oacute;n para que se inserte un salto de p&amp;aacute;gina justo antes de la tabla y que siempre aparezca en la parte de arriba de la p&amp;aacute;gina, con la propiedad   &lt;code&gt;page-break-before&lt;/code&gt;.&lt;/p&gt;

&lt;p class="codeFilePath"&gt;/public/stylesheets/application.css&lt;/p&gt;
&lt;pre class="css"&gt;
@media print {
	body { background-color: #FFF; }
  #container {
		width: auto;
		margin: 0;
		padding: 0;
		border: none;
	}
	
	#line_items {
		page-break-before: always;
	}
}
&lt;/pre&gt;

&lt;p&gt;Si recargamos el documento PDF de nuevo, la tabla volver&amp;aacute; a aparecer en su propia p&amp;aacute;gina.&lt;/p&gt;

&lt;div class="imageWrapper"&gt;
  &lt;img src="/system/photos/407/original/E220I05.png" width="802" height="490" alt="Si se inserta un salto de p&#225;gina nos aseguramos de que la tabla quede entera"/&gt;
&lt;/div&gt;

&lt;p&gt;A pesar de tener este control b&amp;aacute;sico de los saltos de p&amp;aacute;gina con PDFKit, si nuestros documentos
son m&amp;aacute;s complicados deberemos quedarnos con Prawn.&lt;/p&gt;
	
&lt;p&gt;Hay todav&amp;iacute;a un &amp;uacute;ltimo cambio que queremos hacer antes de acabar este episodio.  Se trata de a&amp;ntilde;adir un enlace de la versi&amp;oacute;n PDF en la p&amp;aacute;gina HTML de la factura.  Primero a&amp;ntilde;adiremos un enlace al archivo PDF al final de la p&amp;aacute;gina.&lt;/p&gt;

&lt;p class="codeFilePath"&gt;/app/views/orders/show.html.erb&lt;/p&gt;
&lt;pre class="ruby"&gt;
&amp;lt;p&amp;gt;&amp;lt;%= link_to &amp;quot;Download Invoice (PDF)&amp;quot;, order_path(@order, :format =&amp;gt; &amp;quot;pdf&amp;quot;) %&amp;gt;&amp;lt;/p&amp;gt;
&lt;/pre&gt;

&lt;p&gt;Con esto veremos un enlace en el pedido en HTML a la versi&amp;oacute;n PDF, pero este enlace tambi&amp;eacute;n aparecer&amp;aacute; en la versi&amp;oacute;n PDF (cosa que no queremos)  Podemos ocultar este enlace en el PDF dando un identificador al elemento de p&amp;aacute;rrafo que contiene el enlace:&lt;/p&gt;

&lt;p class="codeFilePath"&gt;/app/views/orders/show.html.erb&lt;/p&gt;
&lt;pre class="ruby"&gt;
&amp;lt;p id=&amp;quot;pdf_link&amp;quot;&amp;gt;&amp;lt;%= link_to &amp;quot;Download Invoice (PDF)&amp;quot;, order_path(@order, :format =&amp;gt; &amp;quot;pdf&amp;quot;) %&amp;gt;&amp;lt;/p&amp;gt;
&lt;/pre&gt;

&lt;p&gt;Y modificando despu&amp;eacute;s la hoja de estilos para que oculte dicho enlace en la versi&amp;oacute;n impresa.&lt;/p&gt;


&lt;p class="codeFilePath"&gt;/public/stylesheets/application.css&lt;/p&gt;
&lt;pre class="css"&gt;
@media print {
	body { background-color: #FFF; }
  #container {
		width: auto;
		margin: 0;
		padding: 0;
		border: none;
	}
	
	#line_items {
		page-break-before: always;
	}
	
	#pdf_link {
		display: none;
	}
}
&lt;/pre&gt;

&lt;p&gt;Ahora tendremos un enlace al PDF en la versi&amp;oacute;n HTML de la factura, pero dicho no enlace no aparecer&amp;aacute; en el PDF.&lt;/p&gt;

&lt;h3&gt;Una alternativa&lt;/h3&gt;

&lt;p&gt;Antes de cerrar este episodio mencionaremos una alternativa a PDFKit, &lt;a href="http://github.com/mileszs/wicked_pdf"&gt;Wicked PDF&lt;/a&gt;.  Este &lt;em&gt;plugin&lt;/em&gt; tiene un enfoque distinto del de PDFKit, aunque tambi&amp;eacute;n usa wkhtmltopdf.  Wicked PDF no es un &lt;em&gt;middlware&lt;/em&gt; sino que es un &lt;em&gt;renderer&lt;/em&gt; de Rails.   Wicked PDF nos da m&amp;aacute;s control sobre qu&amp;eacute; acciones pueden mostrarse como PDFs, por lo que es una opci&amp;oacute;n que merece la pena tener en cuenta.&lt;/p&gt;

&lt;p&gt;Y eso es todo por este episodio.  Ya hemos cubierto dos maneras muy distintas de generar documentos PDF en nuestras aplicaciones Rails, cada una de ellas con sus ventajas y desventajas.   Si bien PDFKit nos da la facilidad de generar PDFs a partir del HTML, con Prawn tendremos m&amp;aacute;s control sobre los archivos generales.  Hay que considerar bien ambos enfoques.&lt;/p&gt;
</description>
      <pubDate>Tue, 27 Jul 2010 09:44:00 +0000</pubDate>
      <guid>http://es.asciicasts.com/episodes/220-pdfkit</guid>
      <link>http://es.asciicasts.com/episodes/220-pdfkit</link>
    </item>
    <item>
      <title>Active Model</title>
      <description>&lt;p&gt;El episodio 193 [&lt;a href="http://railscasts.com/episodes/193-tableless-model"&gt;verlo&lt;/a&gt;, &lt;a href="http://es.asciicasts.com/episodes/193-modelos-sin-tablas"&gt;leerlo&lt;/a&gt;] trataba de los modelos sin tablas.  En ese episodio creamos un modelo que utilizaba algunas de las caracter&amp;iacute;sticas de ActiveRecord pero que no ten&amp;iacute;a una base de datos por detr&amp;aacute;s.  Podr&amp;iacute;a decirse que esta t&amp;eacute;cnica era un &lt;em&gt;hack&lt;/em&gt;, dado que no era algo para lo que ActiveRecord estuviera dise&amp;ntilde;ado pero ahora en Rails 3.0 tenemos una nueva funcionalidad llamada ActiveModel con la que  se pueden hacer f&amp;aacute;cilmente este tipo de cosas.&lt;/p&gt;

&lt;p&gt;Antes de entrar en detalle con ActiveModel vamos a describir la secci&amp;oacute;n de la aplicaci&amp;oacute;n que vamos a modificar:
&lt;/p&gt;

&lt;div class="imageWrapper"&gt;
  &lt;img src="/system/photos/399/original/E219I01.png" width="801" height="465" alt="Formulario para crear un nuevo mensaje."/&gt;
&lt;/div&gt;

&lt;p&gt;En la captura de pantalla anterior vemos un formulario de contacto que ha sido creado utilizando el andamiaje de Rails.  La aplicaci&amp;oacute;n tiene un modelo llamado  &lt;code&gt;Message&lt;/code&gt; y 
 que ActiveRecord se encarga de guardar en la base de datos.  Vamos a cambiar este comportamiento para que tan s&amp;oacute;lo env&amp;iacute;e el correo electr&amp;oacute;nico sin almacenar nada en ninguna tabla.&lt;/p&gt;

&lt;p&gt;Por supuesto, siempre que pensemos acometer este tipo de soluciones es buena idea plantearnos nuestros requerimientos y asegurarnos de que realmente no nos interesa almacenar estos datos en la base de datos  porque por lo general s&amp;iacute; que nos interesa hacerlo: en primer lugar la base de datos puede actuar de copia de respaldo y hace que sea m&amp;aacute;s f&amp;aacute;cil pasar el env&amp;iacute;o de mensajes a una cola de tareas en un proceso &lt;em&gt;background&lt;/em&gt;.  Como no vamos a necesitar nada de esto en nuestro ejemplo, vamos a seguir adelante y haremos que este sea un modelo sin tabla.&lt;/p&gt;

&lt;p&gt;El c&amp;oacute;digo de la clase &lt;code&gt;Message&lt;/code&gt; tiene el siguiente aspecto:&lt;/p&gt;

&lt;p class="codeFilePath"&gt;/app/models/message.rb&lt;/p&gt;
&lt;pre class="ruby"&gt;
class Message &amp;lt; ActiveRecord::Base
 validates_presence_of :name
 validates_format_of :email, :with =&amp;gt; /^[-a-z0-9_+\.]+\@([-a-z0-9]+\.)+[a-z0-9]{2,4}$/i
 validates_length_of :content, :maximum =&amp;gt; 500
end
&lt;/pre&gt;

&lt;p&gt;Como es de esperar de una clase de modelo, &lt;code&gt;Message&lt;/code&gt; hereda de &lt;code&gt;ActiveRecord::Base&lt;/code&gt;, herencia que tendremos que romper porque no queremos que este modelo est&amp;eacute; respaldado por una base de datos.  En cuanto lo hagamos el formulario dejar&amp;aacute; de funcionar porque es ActiveRecord quien est&amp;aacute; haciendo las validaciones.  Por suerte podemos recuperar esta funcionalidad utilizando ActiveModel.&lt;/p&gt;

&lt;p&gt;Si echamos un vistazo al &lt;a href="http://github.com/rails/rails/"&gt;c&amp;oacute;digo fuente de Rails 3&lt;/a&gt; veremos que hay un par de directorios, &lt;code&gt;activerecord&lt;/code&gt; y &lt;code&gt;activemodel&lt;/code&gt;.  El equipo de Rails ha extra&amp;iacute;do de ActiveRecord todo lo que no era espec&amp;iacute;fico del soporte de base de datos y lo ha puesto en ActiveModel, cuyas funcionalidades est&amp;aacute;n muy bien probadas y es ideal para ser usado fuera de ActiveRecord (si bien ActiveRecord se apoya en ActiveModel para todo lo que no tiene que ver con la base de datos).&lt;/p&gt;

&lt;p&gt;Si echamos un vistazo al directorio que contiene &lt;a href="http://github.com/rails/rails/tree/master/activemodel/lib/active_model/"&gt;el c&amp;oacute;digo de ActiveModel&lt;/a&gt; podemos ver toda la funcionalidad que suministra.&lt;/p&gt;

&lt;div class="imageWrapper"&gt;
  &lt;img src="/system/photos/400/original/E219I02.png" width="800" height="644" alt="Listado de los archivos del directorio ActiveModel."/&gt;
&lt;/div&gt;

&lt;p&gt;Tal y como puede verse del listado anterior ActiveModel incluye entre otras cosas el c&amp;oacute;digo para gestionar &lt;em&gt;callbacks&lt;/em&gt;, serializaci&amp;oacute;n y validaci&amp;oacute;n.  Esto &amp;uacute;ltimo es justamente lo que est&amp;aacute;bamos buscando.&lt;/p&gt;

&lt;p&gt;En el &lt;a href="http://github.com/rails/rails/blob/master/activemodel/lib/active_model/validations.rb"&gt;c&amp;oacute;digo para las validaciones &lt;/a&gt; aparece el siguiente comentario al principio, de donde podemos ver que es muy f&amp;aacute;cil a&amp;ntilde;adir validaciones a cualquier modelo.  Lo &amp;uacute;nico que tenemos que hacer es incluir el m&amp;oacute;dulo  &lt;code&gt;Validations&lt;/code&gt; y proporcionar m&amp;eacute;todos de acceso para los atributos sobre los que vamos a aplicar validadores.&lt;/p&gt;


&lt;pre class="ruby"&gt;
&amp;nbsp;&amp;nbsp;# == Validaciones de ActiveModel
&amp;nbsp;&amp;nbsp;#   
&amp;nbsp;&amp;nbsp;# Suministra un marco completo de validaci&amp;oacute;n a tus objetos
&amp;nbsp;&amp;nbsp;# 
&amp;nbsp;&amp;nbsp;# Una implementaci&amp;oacute;n m&amp;iacute;nima ser&amp;iacute;a
&amp;nbsp;&amp;nbsp;# 
&amp;nbsp;&amp;nbsp;#   class Person
&amp;nbsp;&amp;nbsp;#     include ActiveModel::Validations
&amp;nbsp;&amp;nbsp;# 
&amp;nbsp;&amp;nbsp;#     attr_accessor :first_name, :last_name
&amp;nbsp;&amp;nbsp;#
&amp;nbsp;&amp;nbsp;#     validates_each :first_name, :last_name do |record, attr, value|
&amp;nbsp;&amp;nbsp;#       record.errors.add attr, &amp;#x27;starts with z.&amp;#x27; if value.to_s[0] == ?z
&amp;nbsp;&amp;nbsp;#     end
&amp;nbsp;&amp;nbsp;#   end
&amp;nbsp;&amp;nbsp;# 
&lt;/pre&gt;

&lt;p&gt;Una vez que sabemos esto podemos aplicarlo a nuestro modelo &lt;code&gt;Message&lt;/code&gt;.&lt;/p&gt;

&lt;p class="codeFilePath"&gt;/app/models/message.rb&lt;/p&gt;
&lt;pre class="ruby"&gt;
class Message
  include ActiveModel::Validations
  
  attr_accessor :name, :email, :content
 
  validates_presence_of :name
  validates_format_of :email, :with =&amp;gt; /^[-a-z0-9_+\.]+\@([-a-z0-9]+\.)+[a-z0-9]{2,4}$/i
  validates_length_of :content, :maximum =&amp;gt; 500
end
&lt;/pre&gt;

&lt;p&gt;Pero con esto no es suficiente para que nuestro modelo se comporte como espera el controlador, a&amp;uacute;n tenemos dos problemas en el m&amp;eacute;todo &lt;code&gt;create&lt;/code&gt;.  Primero, la llamada a  &lt;code&gt;Message.new&lt;/code&gt; no funciona porque el modelo &lt;code&gt;Message&lt;/code&gt; ya no tiene un inicializador que reciba el &lt;em&gt;hash&lt;/em&gt; de atributos como argumento.  Y en segundo lugar el m&amp;eacute;todo &lt;code&gt;save&lt;/code&gt; no funcionar&amp;aacute; porque no tenemos un servidor de base de datos en el que almacenar el nuevo mensaje.&lt;/p&gt;

&lt;p class="codeFilePath"&gt;/apps/controllers/messages_controller.rb&lt;/p&gt;
&lt;pre class="ruby"&gt;
class MessagesController &amp;lt; ApplicationController
  def new
    @message = Message.new
  end

  def create
    @message = Message.new(params[:message])
    if @message.save
      # TODO send message here
      flash[:notice] = &amp;quot;Message sent! Thank you for contacting us.&amp;quot;
      redirect_to root_url
    else
      render :action =&amp;gt; &amp;#x27;new&amp;#x27;
    end
  end
end
&lt;/pre&gt;

&lt;p&gt;Vamos a empezar corrigiendo el segundo de estos problemas.  Si bien no podemos guardar el mensaje s&amp;iacute; que podemos comprobar si es v&amp;aacute;lido, as&amp;iacute; que vamos a cambiar  &lt;code&gt;@message.save&lt;/code&gt; con &lt;code&gt;@message.valid?&lt;/code&gt;.&lt;/p&gt;

&lt;p class="codeFilePath"&gt;/app/controllers/messages_controllers.rb&lt;/p&gt;
&lt;pre class="ruby"&gt;
def create
  @message = Message.new(params[:message])
  if @message.valid?
    # TODO send message here
    flash[:notice] = &amp;quot;Message sent! Thank you for contacting us.&amp;quot;
    redirect_to root_url
  else
    render :action =&amp;gt; &amp;#x27;new&amp;#x27;
  end
end
&lt;/pre&gt;

&lt;p&gt;Y para arreglar el primer problema, vamos a escribir un m&amp;eacute;todo 
&lt;code&gt;initialize&lt;/code&gt; en el modelo &lt;code&gt;Message&lt;/code&gt; que recibir&amp;aacute; un &lt;em&gt;hash&lt;/em&gt; como par&amp;aacute;metro.  Este m&amp;eacute;todo recorrer&amp;aacute; cada elemento de este &lt;em&gt;hash&lt;/em&gt; y asignar&amp;aacute; su valor al atributo correspondiente, utilizando el m&amp;eacute;todo &lt;code&gt;send&lt;/code&gt;.&lt;/p&gt;

&lt;p class="codeFilePath"&gt;/app/models/message.rb&lt;/p&gt;
&lt;pre class="ruby"&gt;
class Message
  include ActiveModel::Validations
  
  attr_accessor :name, :email, :content
 
  validates_presence_of :name
  validates_format_of :email, :with =&amp;gt; /^[-a-z0-9_+\.]+\@([-a-z0-9]+\.)+[a-z0-9]{2,4}$/i
  validates_length_of :content, :maximum =&amp;gt; 500
  
  def initialize(attributes = {})
    attributes.each do |name, value|
      send(&amp;quot;#{name}=&amp;quot;, value)
    end
  end
end
&lt;/pre&gt;

&lt;p&gt;Si recargamos el formulario veremos que a&amp;uacute;n nos falta un poco m&amp;aacute;s para terminar.&lt;/p&gt;

&lt;div class="imageWrapper"&gt;
  &lt;img src="/system/photos/401/original/E219I03.png" width="796" height="510" alt="El formulario devuelve un error."/&gt;
&lt;/div&gt;
 
&lt;p&gt;Esta vez el error viene provocado porque al modelo &lt;code&gt;Message&lt;/code&gt; le falta el m&amp;eacute;todo &lt;code&gt;to_key&lt;/code&gt;.  El error ha sido devuelto por el m&amp;eacute;todo  &lt;code&gt;form_for&lt;/code&gt;, as&amp;iacute; que esto parece indicar que Rails espera que nuestro modelo tenga cierta funcionalidad que a&amp;uacute;n no soporta.  A&amp;ntilde;ad&amp;aacute;mosla ahora.&lt;/p&gt;

&lt;p&gt;En vez de tener que adivinar todo lo que Rails espera que nuestro modelo tenga, podemos usar una &lt;a href="http://github.com/rails/rails/blob/master/activemodel/lib/active_model/lint.rb"&gt; herramienta de chequeo&lt;/a&gt; incluida con ActiveModel que nos permite comprobar si nuestro modelo cumple con todo lo que Rails espera de &amp;eacute;l.  Si incluimos el m&amp;oacute;dulo  &lt;code&gt;ActiveModel::Lint::Tests&lt;/code&gt; en un test del modelo, comprobar&amp;aacute; que &amp;eacute;ste tiene toda la funcionalidad necesaria.&lt;/p&gt;

&lt;p&gt;El c&amp;oacute;digo fuente del m&amp;oacute;dulo &lt;code&gt;Lint::Tests&lt;/code&gt; muestra los m&amp;eacute;todos a los que el m&amp;eacute;todo tiene que responder para que todo funcione como deber&amp;iacute;a (incluyendo &lt;code&gt;to_key&lt;/code&gt;).  Por tanto podemos hacer que nuestro modelo funcione incluyendo un par de m&amp;oacute;dulos de ActiveModel.  El primero de ellos es &lt;a href="http://github.com/rails/rails/blob/master/activemodel/lib/active_model/conversion.rb"&gt;&lt;code&gt;Conversion&lt;/code&gt;&lt;/a&gt;, y es el que proporciona el m&amp;eacute;todo &lt;code&gt;to_key&lt;/code&gt; y algunos m&amp;aacute;s.  El otro m&amp;oacute;dulo se llama &lt;a href="http://github.com/rails/rails/blob/master/activemodel/lib/active_model/naming.rb"&gt;&lt;code&gt;Naming&lt;/code&gt;&lt;/a&gt; pero en este caso en lugar de incluirlo en nuestra clase tendremos que usar &lt;code&gt;extend&lt;/code&gt; porque incluye algunos m&amp;eacute;todos de clase.&lt;/p&gt;

&lt;p&gt;Adem&amp;aacute;s de incluir el m&amp;oacute;dulo &lt;code&gt;Conversion&lt;/code&gt; tenemos que definir el m&amp;eacute;todo  &lt;code&gt;persisted?&lt;/code&gt; en nuestro modelo, que tiene que devolver  &lt;code&gt;false&lt;/code&gt; dado que no estamos guardando nuestro modelo en la base datos.  Con estos cambios, este es el aspecto que tiene nuestro modelo &lt;code&gt;Message&lt;/code&gt;:&lt;/p&gt;

&lt;p class="codeFilePath"&gt;/app/models/message.rb&lt;/p&gt;
&lt;pre class="ruby"&gt;
class Message
  include ActiveModel::Validations
  include ActiveModel::Conversion
  extend ActiveModel::Naming
  
  attr_accessor :name, :email, :content
 
  validates_presence_of :name
  validates_format_of :email, :with =&amp;gt; /^[-a-z0-9_+\.]+\@([-a-z0-9]+\.)+[a-z0-9]{2,4}$/i
  validates_length_of :content, :maximum =&amp;gt; 500
  
  def initialize(attributes = {})
    attributes.each do |name, value|
      send(&amp;quot;#{name}=&amp;quot;, value)
    end
  end
  
  def persisted?
    false
  end
end
&lt;/pre&gt;

&lt;p&gt;Si recargamos el formulario volver&amp;aacute; a funcionar de nuevo, lo que significa que el modelo  &lt;code&gt;Message&lt;/code&gt; ya satisface todos lo que Rails 3 requiere de un modelo.  Si intentamos enviar el formulario veremos que los validadores ya est&amp;aacute;n funcionando:&lt;/p&gt;

&lt;div class="imageWrapper"&gt;
  &lt;img src="/system/photos/402/original/E219I04.png" width="800" height="631" alt="El formulario funciona, incluyendo los validadores."/&gt;
&lt;/div&gt;

&lt;p&gt;En este episodio hemos visto s&amp;oacute;lo un poco de todo lo que ActiveModel nos ofrece, pero deber&amp;iacute;a ser suficiente para despertar nuestro inter&amp;eacute;s y darnos una raz&amp;oacute;n para examinar su documentaci&amp;oacute;n y aprender todo lo que puede hacer.  El c&amp;oacute;digo est&amp;aacute; bien documentado y estructurado, as&amp;iacute; que si vemos algo que nos parece interesante seguro que encontraremos suficiente informaci&amp;oacute;n en los comentarios del archivo como para poder ponernos manos a la obra.&lt;/p&gt;</description>
      <pubDate>Fri, 23 Jul 2010 21:27:33 +0000</pubDate>
      <guid>http://es.asciicasts.com/episodes/219-active-model</guid>
      <link>http://es.asciicasts.com/episodes/219-active-model</link>
    </item>
    <item>
      <title>C&#243;mo escribir nuestros propios generadores en Rails 3</title>
      <description>&lt;p&gt;En el episodio 216 [&lt;a href="http://railscasts.com/episodes/216-generators-in-rails-3"&gt;verlo&lt;/a&gt;, &lt;a href="http://es.asciicasts.com/episodes/216-generadores-en-rails-3"&gt;leerlo&lt;/a&gt;] vimos que en Rails 3 los generadores son mucho m&amp;aacute;s modulares y personalizables que en Rails 2.  Pero hay veces, sin embargo, que nos interesar&amp;iacute;a escribir un generador desde cero porque los que vienen con Rails no se ajustan a nuestras necesidades.  Ya vimos c&amp;oacute;mo escribir nuestros propios generadores en el &lt;a href="http://railscasts.com/episodes/58-how-to-make-a-generator"&gt;episodio 58&lt;/a&gt;, pero han sido tantos los cambios que vamos a darles otro repaso, viendo c&amp;oacute;mo escribir el mismo generador de ese episodio en Rails 3.&lt;/p&gt;

&lt;h3&gt;Empecemos&lt;/h3&gt;

&lt;p&gt;Como la forma m&amp;aacute;s sencilla de escribir un generador es desde dentro de una aplicaci&amp;oacute;n Rails 3, vamos a crear una.  Se acaba de liberar la beta 4 de Rails 3.0, y la sintaxis para generar una nueva aplicaci&amp;oacute;n ha cambiado, as&amp;iacute; que para crear la aplicaci&amp;oacute;n de tareas sobre la que vamos a desarrollar nuestro generador en lugar de ejecutar&lt;/p&gt;

&lt;pre class="terminal"&gt;
$ rails todo
&lt;/pre&gt;

&lt;p&gt;tenemos que ejecutar&lt;/p&gt;

&lt;pre class="terminal"&gt;
$ rails new todo
&lt;/pre&gt;

&lt;p&gt;Una vez que hemos creado nuestra nueva aplicaci&amp;oacute;n empezaremos el desarrollo de nuestro generador usando uno de los generadores incluidos.  El comando &lt;code&gt;rails g generator&lt;/code&gt; crear&amp;aacute; los ficheros b&amp;aacute;sicos que hacen falta.  Podemos ver qu&amp;eacute; par&amp;aacute;metros acepta este comando:&lt;/p&gt;

&lt;pre class="terminal"&gt;
$ rails g generator --help
&lt;/pre&gt;

&lt;p&gt;Este generador es bastante sencillo, y s&amp;oacute;lo hace falta pasarle el nombre del nuevo generador que queremos crear.  Como queremos que genere un fichero de &lt;em&gt;layout&lt;/em&gt; de la aplicaci&amp;oacute;n, lo bautizaremos como &lt;code&gt;layout&lt;/code&gt;.&lt;/p&gt;

&lt;pre class="terminal"&gt;
$ rails g generator layout
      &lt;span class="passed"&gt;create&lt;/span&gt;  lib/generators/layout
      &lt;span class="passed"&gt;create&lt;/span&gt;  lib/generators/layout/layout_generator.rb
      &lt;span class="passed"&gt;create&lt;/span&gt;  lib/generators/layout/USAGE
      &lt;span class="passed"&gt;create&lt;/span&gt;  lib/generators/layout/templates
&lt;/pre&gt;      

&lt;p&gt;El generador crear&amp;aacute; una serie de archivos dentro del directorio &lt;code&gt;/lib&lt;/code&gt; lo que quiere decir que nuestro generador s&amp;oacute;lo podr&amp;aacute; ser usado en esta aplicaci&amp;oacute;n.  Para poder usarlo en cualquier otra aplicaci&amp;oacute;n tendremos que hacer algunas cosas m&amp;aacute;s que veremos m&amp;aacute;s adelante.&lt;/p&gt;

&lt;p&gt;El fichero del generador as&amp;iacute; generado (valga la redundancia) es bastante sencillo y tan s&amp;oacute;lo contiene una clase.  En dicha clase hay una l&amp;iacute;nea de c&amp;oacute;digo que le dice al generador que mire en el directorio de plantillas en busca de ficheros adicionales.&lt;/p&gt;

&lt;p class="codeFilePath"&gt;/lib/generators/layout/layout_generator.rb&lt;/p&gt;
&lt;pre class="rails"&gt;
class LayoutGenerator &amp;lt; Rails::Generators::NamedBase
  source_root File.expand_path(&amp;#x27;../templates&amp;#x27;, __FILE__)
end
&lt;/pre&gt;

&lt;p&gt;El generador &lt;code&gt;generator&lt;/code&gt; tambi&amp;eacute;n crea un archivo llamado &lt;code&gt;USAGE&lt;/code&gt; donde podemos definir la documentaci&amp;oacute;n de nuestro generador.  Dicha documentaci&amp;oacute;n se mostrar&amp;aacute; cuando se ejecute la opci&amp;oacute;n &lt;code&gt;--help&lt;/code&gt; de nuestro generador.&lt;/p&gt;

&lt;p&gt;La clase creada por el generador hereda de &lt;code&gt;Rails::Generators::NamedBase&lt;/code&gt;.  Esto significa que har&amp;aacute; falta proporcionar un nombre cuando se ejecute el generador de la misma manera que tuvimos que poner un nombre cuando ejecutamos &lt;code&gt;generator&lt;/code&gt;.  Podemos ver esto ejecutando nuestro nuevo generador con la opci&amp;oacute;n &lt;code&gt;--help&lt;/code&gt;.&lt;/p&gt;

&lt;pre class="terminal"&gt;
$ rails g layout --help
Usage:
  rails generate layout NAME [options]

Runtime options:
  -f, [--force]    # Overwrite files that already exist
  -p, [--pretend]  # Run but do not make any changes
  -s, [--skip]     # Skip files that already exist
  -q, [--quiet]    # Supress status output

Description:
    Explain the generator

Example:
    rails generate layout Thing

    This will create:
        what/will/it/create
&lt;/pre&gt;  

&lt;p&gt;N&amp;oacute;tese que los contenidos del fichero &lt;code&gt;USAGE&lt;/code&gt; se muestran al final.&lt;/p&gt;

&lt;p&gt;Queremos que la opci&amp;oacute;n &lt;code&gt;NAME&lt;/code&gt; sea opcional y que por defecto tenga el valor de &lt;code&gt;application&lt;/code&gt;.  Podemos hacerlo haciendo que la clase herede de  &lt;code&gt;Rails::Generators::Base&lt;/code&gt;, en lugar de &lt;code&gt;NamedBase&lt;/code&gt;.  Esto har&amp;aacute; que todos los argumentos sean opcionales y por tanto tendremos m&amp;aacute;s flexibilidad a la hora de personalizar nuestro generador.&lt;/p&gt;

&lt;p&gt;Podemos definir qu&amp;eacute; argumentos recibe el generador utilizando el m&amp;eacute;todo &lt;code&gt;argument&lt;/code&gt;.  Si definimos nuestros propios argumentos en lugar de usar la opci&amp;oacute;n &lt;code&gt;NAME&lt;/code&gt; podremos definir un valor por defecto para cada argumento.  A&amp;ntilde;adiremos un argumento &lt;code&gt;layout_name&lt;/code&gt; cuyo valor por defecto ser&amp;aacute; &lt;code&gt;application&lt;/code&gt;.&lt;/p&gt;

&lt;p class="codeFilePath"&gt;/lib/generators/layout/layout_generator.rb&lt;/p&gt;
&lt;pre class="ruby"&gt;
class LayoutGenerator &amp;lt; Rails::Generators::Base
  source_root File.expand_path(&amp;#x27;../templates&amp;#x27;, __FILE__)
  argument :layout_name, :type =&amp;gt; :string, :default =&amp;gt; &amp;quot;application&amp;quot;
end
&lt;/pre&gt;

&lt;p&gt;Si ejecutamos una vez m&amp;aacute;s el comando de generador con la opci&amp;oacute;n &lt;code&gt;--help&lt;/code&gt; veremos que ahora aparece nuestro nuevo argumento.  Adem&amp;aacute;s lo hace entre corchetes, y esto significa que es un par&amp;aacute;metro opcional.&lt;/p&gt;

&lt;pre class="terminal"&gt;
$ rails g layout --help
Usage:
  rails generate layout [LAYOUT_NAME] [options]
&lt;/pre&gt;  

&lt;p&gt;En Rails 3 los generadores se basan en la librer&amp;iacute;a &lt;a href="http://github.com/wycats/thor"&gt;Thor&lt;/a&gt;, que es similar a rake.  Muchos de los metodos accesibles desde nuestra clase del generador son definidos en la propia librer&amp;iacute;a Thor as&amp;iacute; que podemos echar un vistazo a la documentaci&amp;oacute;n y el c&amp;oacute;digo fuente de Thor si queremos profundizar m&amp;aacute;s acerca de dichos m&amp;eacute;todos.&lt;/p&gt;

&lt;h3&gt;Creando los ficheros que utilizar&amp;aacute; el generador&lt;/h3&gt;

&lt;p&gt;Recordemos que quer&amp;iacute;amos que nuestro generador crease dos archivos: un archivo de &lt;em&gt;layout&lt;/em&gt; de la apliccaion y una hoja de estilos.  Para esto crearemos dos archivos en el directorio &lt;code&gt;templates&lt;/code&gt; del generador y haremos que cuando el generador se ejecute los copie a los directorios correspondientes en nuestra aplicaci&amp;oacute;n.&lt;/p&gt;


&lt;p&gt;&amp;iquest;Y c&amp;oacute;mo definimos el comportamiento del generador?  La forma de hacerlo es, por as&amp;iacute; decirlo, bastante inusual.  Cualquier m&amp;eacute;todo p&amp;uacute;blico que sea definido en la clase del generador ser&amp;aacute; invocado cuando se ejecute el generador.  Por tanto, podremos definir un m&amp;eacute;todo llamado &lt;code&gt;generate_layout&lt;/code&gt; que ser&amp;aacute; ejecutado autom&amp;aacute;ticamente.   Se trata de un concepto un poco extra&amp;ntilde;o pero resulta ser una buena forma de organizar el c&amp;oacute;digo del generador.&lt;/p&gt;

&lt;p&gt;Lo primero que har&amp;aacute; nuestro generador ser&amp;aacute; copiar una hoja de estilos desde el directorio &lt;code&gt;templates&lt;/code&gt; al directorio &lt;code&gt;/public/stylesheets&lt;/code&gt; de nuestra aplicaci&amp;oacute;n y para esto podemos usar el m&amp;eacute;todo &lt;code&gt;copy_file&lt;/code&gt;.&lt;/p&gt;

&lt;p class="codeFilePath"&gt;/lib/generators/layout/layout_generator.rb&lt;/p&gt;
&lt;pre class="ruby"&gt;
class LayoutGenerator &amp;lt; Rails::Generators::Base
  source_root File.expand_path(&amp;#x27;../templates&amp;#x27;, __FILE__)
  argument :layout_name, :type =&amp;gt; :string, :default =&amp;gt; &amp;quot;application&amp;quot;
  
  def generate_layout
    copy_file &amp;quot;stylesheet.css&amp;quot;, &amp;quot;public/stylesheets/#{layout_name.underscore}.css&amp;quot;
  end
  
end
&lt;/pre&gt;

&lt;p&gt;El m&amp;eacute;todo &lt;code&gt;copy_file&lt;/code&gt; recibe dos argumentos.  El primero es el nombre del fichero del directorio &lt;code&gt;templates&lt;/code&gt; que deber&amp;aacute; copiarse y el segundo es el &lt;em&gt;path&lt;/em&gt; de destino.  Obs&amp;eacute;rvese que el nombre del fichero de destino se basa en el argumento &lt;code&gt;layout_name&lt;/code&gt; que se pas&amp;oacute; al generador.  El m&amp;eacute;todo &lt;code&gt;argument&lt;/code&gt; crea un m&amp;eacute;todo &lt;code&gt;layout_name&lt;/code&gt; qu epodemos usar, pero dado que el nombre recibido podr&amp;iacute;a contener may&amp;uacute;sculas y min&amp;uacute;sculas tendremos que invocar el m&amp;eacute;todo &lt;code&gt;underscore&lt;/code&gt; para asegurarnos que est&amp;aacute; en un formato apto para nombres de fichero.&lt;/p&gt;

&lt;p&gt;Por &amp;uacute;ltimo, tenemos que crear el fichero &lt;code&gt;stylesheet.css&lt;/code&gt; en el directorio &lt;code&gt;templates&lt;/code&gt; y pegar la &lt;a href="http://github.com/ryanb/railscasts-episodes/blob/master/episode-218/todo/lib/generators/layout/templates/stylesheet.css"&gt;hoja de estilos por defecto&lt;/a&gt; que queremos que tengan nuestras aplicaciones.&lt;/p&gt;

&lt;p&gt;Si ahora ejecutamos nuestro generador sin argumentos deber&amp;iacute;a crear un archivo &lt;code&gt;application.css&lt;/code&gt;.&lt;/p&gt;

&lt;pre class="terminal"&gt;
$ rails g layout
      &lt;span class="passed"&gt;create&lt;/span&gt;  public/stylesheets/application.css
&lt;/pre&gt;

&lt;p&gt;A continuaci&amp;oacute;n tenemos que hacer lo mismo con el archivo de &lt;em&gt;layout&lt;/em&gt;.  
Vamos a crear un m&amp;eacute;todo auxiliar llamado &lt;code&gt;file_name&lt;/code&gt; 	porque nos va a hacer falta obtener el nombre de layout con guiones bajos en varios sitios.  Como mencion&amp;aacute;bamos antes, cualquier m&amp;eacute;todo p&amp;uacute;blico definidio en la clase del generador ser&amp;aacute; ejecutado as&amp;iacute; que tenemos que asegurarnos de que este m&amp;eacute;todo sea privado.&lt;/p&gt;

&lt;p&gt;Lo siguiente que tenemos que hacer en nuestro generador es crear el propio fichero de &lt;em&gt;layout&lt;/em&gt;.  Podr&amp;iacute;amos otra vez ejecutar &lt;code&gt;copy_file&lt;/code&gt; pero esto efectuar&amp;iacute;a una simple copia del fichero de la plantilla, y queremos poder pasar este archivo por &lt;strong&gt;ERb&lt;/strong&gt; de forma que sea modificado cuando se ejecute el generador.&lt;/p&gt;

&lt;p&gt;En su lugar podemos usar el m&amp;eacute;todo &lt;code&gt;template&lt;/code&gt;, que recibe argumentos similares a &lt;code&gt;copy_file&lt;/code&gt;, pero ejecutar&amp;aacute; cualquier c&amp;oacute;digo erb que exista en la plantilla antes de copiarlo al directorio de destino.&lt;/p&gt;

&lt;p class="codeFilePath"&gt;/lib/generators/layout/layout_generator.rb&lt;/p&gt;
&lt;pre class="ruby"&gt;
class LayoutGenerator &amp;lt; Rails::Generators::Base
  source_root File.expand_path(&amp;#x27;../templates&amp;#x27;, __FILE__)
  argument :layout_name, :type =&amp;gt; :string, :default =&amp;gt; &amp;quot;application&amp;quot;
  
  def generate_layout
    copy_file &amp;quot;stylesheet.css&amp;quot;, &amp;quot;public/stylesheets/#{file_name}.css&amp;quot;
    template &amp;quot;layout.html.erb&amp;quot;, &amp;quot;app/views/layouts/#{file_name}.html.erb&amp;quot;
  end
  
  private
  def file_name
    layout_name.underscore
  end
  
end
&lt;/pre&gt;

&lt;p&gt;A continuaci&amp;oacute;n crearemos la plantilla propiamente dicha&lt;/p&gt;

&lt;p class="codeFilePath"&gt;/lib/generators/layout/templates/layout.html.erb&lt;/p&gt;
&lt;pre class="ruby"&gt;
&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
  &amp;lt;head&amp;gt;
    &amp;lt;title&amp;gt;Untitled&amp;lt;/title&amp;gt;
    &amp;lt;%%= stylesheet_link_tag &amp;quot;&amp;lt;%= file_name %&amp;gt;&amp;quot; %&amp;gt;
    &amp;lt;%%= javascript_include_tag :defaults %&amp;gt;
    &amp;lt;%%= csrf_meta_tag %&amp;gt;
    &amp;lt;%%= yield(:head) %&amp;gt;
  &amp;lt;/head&amp;gt;
  &amp;lt;body&amp;gt;
    &amp;lt;div id=&amp;quot;container&amp;quot;&amp;gt;
      &amp;lt;%% flash.each do |name, msg| %&amp;gt;
        &amp;lt;%%= content_tag :div, msg, :id =&amp;gt; &amp;quot;flash_#{name}&amp;quot; %&amp;gt;
      &amp;lt;%% end %&amp;gt;
      &amp;lt;%%= yield %&amp;gt;
    &amp;lt;/div&amp;gt;
  &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/pre&gt;

&lt;p&gt;Lo primero que notamos es que, como estamos usando el m&amp;eacute;todo &lt;code&gt;template&lt;/code&gt;, se ejecutar&amp;aacute;n todas las etiquetas erb del c&amp;oacute;digo.  Si queremos que el fichero generado contenga erb tendremos que escapar el signo de porcentaje al comienzo de la etiqueta erb tal y como hemos hecho en el c&amp;oacute;digo anterior.&lt;/p&gt;

&lt;p&gt;Por tanto, para incluir contenido din&amp;aacute;mico en la plantilla podemos usar c&amp;oacute;digo erb normal.  Podemos acceder m&amp;eacute;todos del fichero del generador (aunque sean privados) y podemos llamar a &lt;code&gt;file_name&lt;/code&gt; para poner el nombre correcto del archivo de la hoja de estilos en la llamada a &lt;code&gt;stylesheet_link_tag&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Podemos ejecutar una vez m&amp;aacute;s el generador para comprobar que todo funciona correctamente, esta vez pasando un archivo de &lt;em&gt;layout&lt;/em&gt;.&lt;/p&gt;


&lt;pre class="terminal"&gt;
$ rails g layout admin
      &lt;span class="passed"&gt;create&lt;/span&gt;  public/stylesheets/admin.css
      &lt;span class="passed"&gt;create&lt;/span&gt;  app/views/layouts/admin.html.erb
&lt;/pre&gt;

&lt;p&gt;Esta vez el generador ha creado dos archivos, cada uno de ellos con un nombre basado en el par&amp;aacute;metro recibido, as&amp;iacute; que todo parece estar funcionando.  Si miramos el fichero generado veremos que se llam&amp;oacute; al m&amp;eacute;todo &lt;code&gt;file_name&lt;/code&gt; cuando se gener&amp;oacute; el archivo por lo que le m&amp;eacute;todo &lt;code&gt;stylesheet_link_tag&lt;/code&gt; tiene el argumento correto.  Las otras etiquetas erb que estaban escapadas s&amp;iacute; que aparecen tal cual en el fichero generado.&lt;/p&gt;

&lt;p class="codeFilePath"&gt;/app/views/layouts/admin.html.erb&lt;/p&gt;
&lt;pre class="ruby"&gt;
&amp;lt;head&amp;gt;
  &amp;lt;title&amp;gt;Untitled&amp;lt;/title&amp;gt;
  &amp;lt;%= stylesheet_link_tag &amp;quot;admin&amp;quot; %&amp;gt;
  &amp;lt;%= javascript_include_tag :defaults %&amp;gt;
  &amp;lt;%= csrf_meta_tag %&amp;gt;
  &amp;lt;%= yield(:head) %&amp;gt;
&amp;lt;/head&amp;gt;
&lt;/pre&gt;

&lt;h3&gt;Opciones opcionales&lt;/h3&gt;

&lt;p&gt;Hay una &amp;uacute;ltima funcionalidad que queremos a&amp;ntilde;adir al generador: la posibilidad de pasarle una opci&amp;oacute;n que haga que no se genere el fichero con la hoja de estilos.  Podemos hacerlo utilizando el m&amp;eacute;todo &lt;code&gt;class_option&lt;/code&gt;.&lt;/p&gt;


&lt;p class="codeFilePath"&gt;/lib/generators/layout/layout_generator.rb&lt;/p&gt;
&lt;pre class="ruby"&gt;
class LayoutGenerator &amp;lt; Rails::Generators::Base
  source_root File.expand_path(&amp;#x27;../templates&amp;#x27;, __FILE__)
  argument :layout_name, :type =&amp;gt; :string, :default =&amp;gt; &amp;quot;application&amp;quot;
  class_option :stylesheet, :type =&amp;gt; :boolean, :default =&amp;gt; true, :description =&amp;gt; &amp;quot;Include stylesheet file&amp;quot;
  
  def generate_layout
    copy_file &amp;quot;stylesheet.css&amp;quot;, &amp;quot;public/stylesheets/#{file_name}.css&amp;quot; if options.stylesheet?
    template &amp;quot;layout.html.erb&amp;quot;, &amp;quot;app/views/layouts/#{file_name}.html.erb&amp;quot;
  end
  
  private
  def file_name
    layout_name.underscore
  end
  
end
&lt;/pre&gt;

&lt;p&gt;Hemos llamado a nuestra opci&amp;oacute;n &lt;code&gt;stylesheet&lt;/code&gt;, de tipo &lt;code&gt;boolean&lt;/code&gt; y valor por defecto &lt;code&gt;true&lt;/code&gt;.  Tambi&amp;eacute;n le hemos dado una descripci&amp;oacute;n.  En la l&amp;iacute;nea  &lt;code&gt;copy_file&lt;/code&gt; ahora podemos a&amp;ntilde;adir una sentencia &lt;code&gt;if&lt;/code&gt; para que la hoja de estilos se copie s&amp;oacute;lo si dicha opci&amp;oacute;n est&amp;aacute; a &lt;code&gt;true&lt;/code&gt;.  Igualmente, en el fichero de plantilla para el &lt;em&gt;layout&lt;/em&gt; s&amp;oacute;lo queremos que aparezca la l&amp;iacute;nea &lt;code&gt;stylesheet_link_tag&lt;/code&gt; si dicha opci&amp;oacute;n est&amp;aacute; tambi&amp;eacute;n a &lt;code&gt;true&lt;/code&gt;.&lt;/p&gt;

&lt;p class="codeFilePath"&gt;/lib/generators/layout/templates/layout.html.erb&lt;/p&gt;
&lt;pre class="ruby"&gt;
&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
  &amp;lt;head&amp;gt;
    &amp;lt;title&amp;gt;Untitled&amp;lt;/title&amp;gt;
    &amp;lt;%- if options.stylesheet? -%&amp;gt;
    &amp;lt;%%= stylesheet_link_tag &amp;quot;&amp;lt;%= file_name %&amp;gt;&amp;quot; %&amp;gt;
    &amp;lt;%- end -%&amp;gt;
    &amp;lt;%%= javascript_include_tag :defaults %&amp;gt;
    &amp;lt;%%= csrf_meta_tag %&amp;gt;
    &amp;lt;%%= yield(:head) %&amp;gt;
  &amp;lt;/head&amp;gt;
  &amp;lt;body&amp;gt;
    &amp;lt;div id=&amp;quot;container&amp;quot;&amp;gt;
      &amp;lt;%% flash.each do |name, msg| %&amp;gt;
        &amp;lt;%%= content_tag :div, msg, :id =&amp;gt; &amp;quot;flash_#{name}&amp;quot; %&amp;gt;
      &amp;lt;%% end %&amp;gt;
      &amp;lt;%%= yield %&amp;gt;
    &amp;lt;/div&amp;gt;
  &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/pre&gt;

&lt;p&gt;Si vemos la documentaci&amp;oacute;n de ayuda del generador una vez m&amp;aacute;s, veremos que ahora aparece la opci&amp;oacute;n &lt;code&gt;stylesheet&lt;/code&gt;.&lt;/p&gt;

&lt;pre class="terminal"&gt;
$ rails g layout --help
Usage:
  rails generate layout [LAYOUT_NAME] [options]

Options:
  [--stylesheet]  # Indicates when to generate stylesheet
                  # Default: true
&lt;/pre&gt;            

&lt;p&gt;Ahora podemos generar un &lt;em&gt;layout&lt;/em&gt; sin una hoja de estilos utilizando la opci&amp;oacute;n &lt;code&gt;--skip-stylesheet&lt;/code&gt; (o &lt;code&gt;--no-stylesheet&lt;/code&gt;).  En tal caso s&amp;oacute;lo se generar&amp;aacute; el archivo de &lt;em&gt;layout&lt;/em&gt;.&lt;/p&gt;

&lt;pre class="terminal"&gt;
$ rails g layout foo --skip-stylesheet
      &lt;span class="passed"&gt;create&lt;/span&gt;  app/views/layouts/foo.html.erb
&lt;/pre&gt;      

&lt;p&gt;Y con esto terminamos este episodio dedicado a la escritura de generadores en Rails 3.  Siempre que en nuestras aplicaciones acabemos generando el mismo c&amp;oacute;digo (o similar) podemos utilizar un generador.  Para compartir con otros nuestros generadores tan s&amp;oacute;lo tenemos que crear un &lt;em&gt;plugin&lt;/em&gt; o gema y ponerlo en un directorio &lt;code&gt;lib/generators&lt;/code&gt;.  Cualquiera que incluya dicha gema o &lt;em&gt;plugin&lt;/em&gt; tendr&amp;aacute; disponible ese generador.&lt;/p&gt;</description>
      <pubDate>Fri, 23 Jul 2010 21:11:38 +0000</pubDate>
      <guid>http://es.asciicasts.com/episodes/218-como-escribir-nuestros-propios-generadores-en-rails-3</guid>
      <link>http://es.asciicasts.com/episodes/218-como-escribir-nuestros-propios-generadores-en-rails-3</link>
    </item>
    <item>
      <title>Fomularios con m&#250;ltiples pasos</title>
      <description>&lt;p&gt;Un formulario multipaso, tambi&amp;eacute;n conocido como &lt;em&gt;wizard&lt;/em&gt; o asistente, es un formulario que debido a su tama&amp;ntilde;o ha sido dividido en una serie de p&amp;aacute;ginas o pantallas por las cuales los usuarios deben navegar hasta completarlo.   Este enfoque tiene sus detractores: hay quien prefiere mostrar el formulario completo o quienes prefieren dividir dicho formulario en diferentes recursos y modelos.  Pero hay casos en los que son la mejor soluci&amp;oacute;n, por ejemplo un formulario de declaraci&amp;oacute;n de impuestos que necesita que el usuario introduzca muchos datos y donde el camino de pasos a seguir no es lineal sino que depende de los datos introducidos.&lt;/p&gt;

&lt;p&gt;Un formulario multipaso debe recordar los datos introducidos de un paso a otro de forma que si el usuario se mueve adelante y atr&amp;aacute;s a lo largo de sus p&amp;aacute;ginas no deber&amp;iacute;a perder la informaci&amp;oacute;n que ha introducido.  Si tan s&amp;oacute;lo queremos separar un formulario largo para simplificar la interfaz de usuario podr&amp;iacute;amos usar una combinaci&amp;oacute;n de JavaScript y CSS para mostrar y ocultar las diferentes partes del formulario seg&amp;uacute;n se pulsen los botones &amp;ldquo;anterior&amp;rdquo; y &amp;ldquo;siguiente&amp;rdquo;. En el sitio de desarrolladores de Apple se puede encontrar &lt;a href="http://developer.apple.com/internet/webcontent/examples/wizard.html"&gt;un ejemplo de esto&lt;/a&gt; .  Si nos hace falta algo m&amp;aacute;s din&amp;aacute;mico y orientado al servidor no nos quedar&amp;aacute; m&amp;aacute;s remedio que pasar por Rails.  En este episodio veremos c&amp;oacute;mo se hace.&lt;/p&gt;

&lt;h3&gt;Un formulario de pedido multipaso&lt;/h3&gt;

&lt;p&gt;Como demostraci&amp;oacute;n de formulario multipaso vamos a a&amp;ntilde;adir un proceso de compra a una aplicaci&amp;oacute;n de tienda. Los usuarios rellenar&amp;aacute;n primero los datos de env&amp;iacute;o, luego la direcci&amp;oacute;n de facturaci&amp;oacute;n y por &amp;uacute;ltimo, en el tercer paso, ver&amp;aacute;n un resumen del pedido para su confirmaci&amp;oacute;n.&lt;/p&gt;

&lt;p&gt;Como no tenemos todav&amp;iacute;a un modelo para el pedido ni su controlador usaremos uno de los &lt;a href="http://github.com/ryanb/nifty-generators"&gt;nifty generators&lt;/a&gt; de Ryan Bates para crear un andamiaje para el formulario del pedido.  N&amp;oacute;tese que esta aplicaci&amp;oacute;n est&amp;aacute; escrita con Rails 2, por lo que usaremos  &lt;code&gt;script/generate&lt;/code&gt;.   Para los prop&amp;oacute;sitos de nuestro ejemplo, el pedido tendr&amp;aacute; dos datos (en el mundo real un modelo para un pedido tendr&amp;iacute;a muchos m&amp;aacute;s atributos).  Tambi&amp;eacute;n crearemos las acciones  &lt;code&gt;index&lt;/code&gt;, &lt;code&gt;show&lt;/code&gt; y &lt;code&gt;new&lt;/code&gt; de este controlador.&lt;/p&gt;

&lt;pre class="terminal"&gt;
$ script/generate nifty_scaffold order shipping_name:string billing_name:string index show new
&lt;/pre&gt;

&lt;p&gt;A continuaci&amp;oacute;n migraremos la base de datos para crear la nueva tabla.&lt;/p&gt;


&lt;pre class="terminal"&gt;
$ rake db:migrate
&lt;/pre&gt;

&lt;p&gt;El formulario de pedido que se generar&amp;aacute; tendr&amp;aacute; todos los campos amontonados en una s&amp;oacute;la p&amp;aacute;gina, por lo que lo primero que tenemos que hacer es separarlo en m&amp;uacute;ltiples pasos.&lt;/p&gt;

&lt;div class="imageWrapper"&gt;
  &lt;img src="/system/photos/391/original/E217I01.png" width="800" height="387" alt="The scaffold-generated new order form."/&gt;
&lt;/div&gt;

&lt;p&gt;Empezaremos moviendo cada uno de los tres elementos de p&amp;aacute;rrafo y sus campos de formulario en sus propios parciales.&lt;/p&gt;

&lt;p class="codeFilePath"&gt;/app/views/orders/new.html.erb&lt;/p&gt;
&lt;pre class="ruby"&gt;
&amp;lt;% title &amp;quot;New Order&amp;quot; %&amp;gt;

&amp;lt;% form_for @order do |f| %&amp;gt;
  &amp;lt;%= f.error_messages %&amp;gt;
  &amp;lt;p&amp;gt;
    &amp;lt;%= f.label :shipping_name %&amp;gt;&amp;lt;br /&amp;gt;
    &amp;lt;%= f.text_field :shipping_name %&amp;gt;
  &amp;lt;/p&amp;gt;
  &amp;lt;p&amp;gt;
    &amp;lt;%= f.label :billing_name %&amp;gt;&amp;lt;br /&amp;gt;
    &amp;lt;%= f.text_field :billing_name %&amp;gt;
  &amp;lt;/p&amp;gt;
  &amp;lt;p&amp;gt;&amp;lt;%= f.submit &amp;quot;Submit&amp;quot; %&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;% end %&amp;gt;

&amp;lt;p&amp;gt;&amp;lt;%= link_to &amp;quot;Back to List&amp;quot;, orders_path %&amp;gt;&amp;lt;/p&amp;gt;
&lt;/pre&gt;

&lt;p&gt;Antes de hacerlo tendremos que a&amp;ntilde;adir una cabecera a cada secci&amp;oacute;n y modificar la secci&amp;oacute;n final para que muestre un resumen del pedido.&lt;/p&gt;

&lt;p class="codeFilePath"&gt;/app/views/orders/new.html.erb&lt;/p&gt;
&lt;pre class="ruby"&gt;
&amp;lt;% title &amp;quot;New Order&amp;quot; %&amp;gt;

&amp;lt;% form_for @order do |f| %&amp;gt;
  &amp;lt;%= f.error_messages %&amp;gt;
  &amp;lt;h2&amp;gt;Shipping Information&amp;lt;/h2&amp;gt;
  &amp;lt;p&amp;gt;
    &amp;lt;%= f.label :shipping_name %&amp;gt;&amp;lt;br /&amp;gt;
    &amp;lt;%= f.text_field :shipping_name %&amp;gt;
  &amp;lt;/p&amp;gt;
  &amp;lt;h2&amp;gt;Billing Information&amp;lt;/h2&amp;gt;
  &amp;lt;p&amp;gt;
    &amp;lt;%= f.label :billing_name %&amp;gt;&amp;lt;br /&amp;gt;
    &amp;lt;%= f.text_field :billing_name %&amp;gt;
  &amp;lt;/p&amp;gt;
  &amp;lt;h2&amp;gt;Confirm Information&amp;lt;/h2&amp;gt;
  &amp;lt;p&amp;gt;
    &amp;lt;strong&amp;gt;Shipping Name:&amp;lt;/strong&amp;gt;
    &amp;lt;%= h @order.shipping_name %&amp;gt;
  &amp;lt;/p&amp;gt;
  &amp;lt;p&amp;gt;
    &amp;lt;strong&amp;gt;Billing Name:&amp;lt;/strong&amp;gt;
    &amp;lt;%= h @order.billing_name %&amp;gt;
  &amp;lt;/p&amp;gt;
  &amp;lt;p&amp;gt;&amp;lt;%= f.submit &amp;quot;Submit&amp;quot; %&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;% end %&amp;gt;

&amp;lt;p&amp;gt;&amp;lt;%= link_to &amp;quot;Back to List&amp;quot;, orders_path %&amp;gt;&amp;lt;/p&amp;gt;
&lt;/pre&gt;

&lt;p&gt;Con esto podemos empezar a mover cada seccion a su propio parcial.  Crearemos tres nuevos parciales llamados &lt;code&gt;shipping_step&lt;/code&gt;, &lt;code&gt;billing_step&lt;/code&gt; y &lt;code&gt;confirmation_step&lt;/code&gt;, copiando lo que haga falta del formulario.  Despu&amp;eacute;s de esto nuestro formulario de nuevo pedido tendr&amp;aacute; este aspecto:&lt;/p&gt;

&lt;p class="codeFilePath"&gt;/app/views/orders/new.html.erb&lt;/p&gt;
&lt;pre class="terminal"&gt;
&amp;lt;% title &amp;quot;New Order&amp;quot; %&amp;gt;

&amp;lt;% form_for @order do |f| %&amp;gt;
  &amp;lt;%= f.error_messages %&amp;gt;
  &amp;lt;%= render &amp;#x27;shipping_step&amp;#x27;, :f =&amp;gt; f %&amp;gt;
  &amp;lt;%= render &amp;#x27;billing_step&amp;#x27;, :f =&amp;gt; f %&amp;gt;
  &amp;lt;%= render &amp;#x27;confirmation_step&amp;#x27;, :f =&amp;gt; f %&amp;gt;
  &amp;lt;p&amp;gt;&amp;lt;%= f.submit &amp;quot;Submit&amp;quot; %&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;% end %&amp;gt;

&amp;lt;p&amp;gt;&amp;lt;%= link_to &amp;quot;Back to List&amp;quot;, orders_path %&amp;gt;&amp;lt;/p&amp;gt;
&lt;/pre&gt;

&lt;p&gt;Y los tres parciales ser&amp;aacute;n as&amp;iacute;:&lt;/p&gt;


&lt;p class="codeFilePath"&gt;/app/views/orders/_billing_step.html.erb&lt;/p&gt;
&lt;pre class="ruby"&gt;
&amp;lt;h2&amp;gt;Billing Information&amp;lt;/h2&amp;gt;
&amp;lt;p&amp;gt;
  &amp;lt;%= f.label :billing_name %&amp;gt;&amp;lt;br /&amp;gt;
  &amp;lt;%= f.text_field :billing_name %&amp;gt;
&amp;lt;/p&amp;gt;
&lt;/pre&gt;

&lt;p class="codeFilePath"&gt;/app/views/orders/_shipping_step.html.erb&lt;/p&gt;
&lt;pre class="ruby"&gt;
&amp;lt;h2&amp;gt;Shipping Information&amp;lt;/h2&amp;gt;
&amp;lt;p&amp;gt;
  &amp;lt;%= f.label :shipping_name %&amp;gt;&amp;lt;br /&amp;gt;
  &amp;lt;%= f.text_field :shipping_name %&amp;gt;
&amp;lt;/p&amp;gt;
&lt;/pre&gt;

&lt;p class="codeFilePath"&gt;/app/views/orders/confirmation_step.html.erb&lt;/p&gt;
&lt;pre class="ruby"&gt;
&amp;lt;h2&amp;gt;Confirm Information&amp;lt;/h2&amp;gt;
&amp;lt;p&amp;gt;
  &amp;lt;strong&amp;gt;Shipping Name:&amp;lt;/strong&amp;gt;
  &amp;lt;%= h @order.shipping_name %&amp;gt;
&amp;lt;/p&amp;gt;
&amp;lt;p&amp;gt;
  &amp;lt;strong&amp;gt;Billing Name:&amp;lt;/strong&amp;gt;
  &amp;lt;%= h @order.billing_name %&amp;gt;
&amp;lt;/p&amp;gt;
&lt;/pre&gt;

&lt;p&gt;Tambi&amp;eacute;n tendremos qie modificar el modelo &lt;code&gt;Order&lt;/code&gt; para que sepa acerca de cada uno de los pasos y pueda identificar cu&amp;aacute;l es el paso actual.   Una posibilidad ser&amp;iacute;a utilizar un &lt;em&gt;plugin&lt;/em&gt; de m&amp;aacute;quina de estados, pero dado que este ejemplo es muy sencillo podemos escribir el c&amp;oacute;digo desde cero.&lt;/p&gt;

&lt;p&gt;El modelo &lt;code&gt;Order&lt;/code&gt; tendr&amp;aacute; que saber cu&amp;aacute;les son los pasos y el orden en que deber&amp;iacute;an aparecer, para eso escribiremos un m&amp;eacute;otod &lt;code&gt;steps&lt;/code&gt; que devolver&amp;aacute; la lista de pasos.  Esta lista no ser&amp;aacute; m&amp;aacute;s que un  &lt;em&gt;array&lt;/em&gt; pero en el caso de formularios m&amp;aacute;s complejos podr&amp;iacute;amos hacer que este m&amp;eacute;todo fuese m&amp;aacute;s din&amp;aacute;mico y controlase casos en los que la lista de pasos cambiase dependiendo de alg&amp;uacute;n dato.&lt;/p&gt;

&lt;p&gt;Para obtener y cambiar el paso actual de un pedido crearemos un m&amp;eacute;todo de escritura llamado &lt;code&gt;current_step&lt;/code&gt; y su correspondiente m&amp;eacute;todo de acceso que devolver&amp;aacute; el paso actual (o el primero si es que hemos empezado).  Tras estos cambios, el modelo &lt;code&gt;Order&lt;/code&gt; tiene lo siguiente:&lt;/p&gt;

&lt;p class="codeFilePath"&gt;/app/models/order.rb&lt;/p&gt;
&lt;pre class="ruby"&gt;
class Order &amp;lt; ActiveRecord::Base
  attr_accessible :shipping_name, :billing_name
  attr_writer :current_step
  
  def current_step
    @current_step || steps.first
  end
  
  def steps
    %w[shipping billing confirmation]
  end
end
&lt;/pre&gt;

&lt;p&gt;Ahora que sabemos en qu&amp;eacute; paso estamos, podemos utilizar dicha informaci&amp;oacute;n para cambiar el parcial que se muestra modificando el formulario de nuevo pedido:&lt;/p&gt;

&lt;p class="codeFilePath"&gt;/app/views/orders/new.html.erb&lt;/p&gt;
&lt;pre class="ruby"&gt;
&amp;lt;% title &amp;quot;New Order&amp;quot; %&amp;gt;

&amp;lt;% form_for @order do |f| %&amp;gt;
  &amp;lt;%= f.error_messages %&amp;gt;
  &amp;lt;%= render &amp;quot;#{@order.current_step}_step&amp;quot;, :f =&amp;gt; f %&amp;gt;
  &amp;lt;p&amp;gt;&amp;lt;%= f.submit &amp;quot;Submit&amp;quot; %&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;% end %&amp;gt;

&amp;lt;p&amp;gt;&amp;lt;%= link_to &amp;quot;Back to List&amp;quot;, orders_path %&amp;gt;&amp;lt;/p&amp;gt;
&lt;/pre&gt;

&lt;p&gt;Si ahora recargamos el formulario veremos que se muestra tan s&amp;oacute;lo el primer paso.&lt;/p&gt;


&lt;div class="imageWrapper"&gt;
  &lt;img src="/system/photos/392/original/E217I02.png" width="800" height="373" alt="The order form now only shows the first step."/&gt;
&lt;/div&gt;

&lt;h3&gt;Movimiento por los pasos&lt;/h3&gt;

&lt;p&gt;Si ahora pulsamos el bot&amp;oacute;n de env&amp;iacute;o el formulario invocar&amp;aacute; la acci&amp;oacute;n &lt;code&gt;create&lt;/code&gt;, creando un nuevo pedido.  No queremos que sea esto lo que ocurra sino que se muestre el siguente paso del formulario, as&amp;iacute; que tenemos que cambiar el comportamiento del controlador.&lt;/p&gt;

&lt;p&gt;Podemos enfocar este problema de distintas maneras.  Por ejemplo podr&amp;iacute;amos crear una acci&amp;oacute;n separada para cada uno de los pasos y utilizar  &lt;code&gt;create&lt;/code&gt;  para el paso inicial y las acciones &lt;code&gt;edit&lt;/code&gt; y &lt;code&gt;update&lt;/code&gt; para los otros pasos, lo que significar&amp;iacute;a que tendr&amp;iacute;amos en la base de datos un modelo a medio completar o podr&amp;iacute;amos usar solo las acciones &lt;code&gt;new&lt;/code&gt; y &lt;code&gt;create&lt;/code&gt;, guardando los datos de entrada del usuario en la sesi&amp;oacute;n.  Esta t&amp;eacute;cnica no est&amp;aacute; libre de problemas, los veremos (y sus alternativas) poco a poco.&lt;/p&gt;

&lt;p&gt;La soluci&amp;oacute;n m&amp;aacute;s sencilla parasar&amp;iacute;a por modificar la acci&amp;oacute;n &lt;code&gt;create&lt;/code&gt;.  Lo primero que haremos ser&amp;aacute; cambiarlo de forma que no guarde el pedido sino que en su lugar muestre el primer paso del formulario.&lt;/p&gt;

&lt;p class="codeFilePath"&gt;/app/controllers/orders_controller.rb&lt;/p&gt;
&lt;pre class="ruby"&gt;
def create
  @order = Order.new(params[:order])
  @order.next_step
  render &amp;#x27;new&amp;#x27;
end
&lt;/pre&gt;

&lt;p&gt;En el controlador, invocaremos el m&amp;eacute;todo &lt;code&gt;next_step&lt;/code&gt; sobre &lt;code&gt;Order&lt;/code&gt;:&lt;/p&gt;

&lt;p class="codeFilePath"&gt;/app/models/order.rb&lt;/p&gt;
&lt;pre class="ruby"&gt;
def next_step
  self.current_step = steps[steps.index(current_step)+1]
end
&lt;/pre&gt;

&lt;p&gt;Cuando ahora pulsemos el bot&amp;oacute;n de env&amp;iacute;o iremos del primer al segundo paso pero si volvemos a pulsar en el bot&amp;oacute;n permaneceremos en el segundo paso, porque no estamos guardando el paso en el que estamos cuando se env&amp;iacute;a el formulario.  En la acci&amp;oacute;n &lt;code&gt;create&lt;/code&gt; tenemos que establecer el paso actual en el modelo &lt;code&gt;Order&lt;/code&gt; y luego guardar ese valor.  Esto lo haremos recuperando el paso actual de la sesi&amp;oacute;n, pasando al siguiente y guardando el valor de nuevo en la sesi&amp;oacute;n.&lt;/p&gt;

&lt;p class="codeFilePath"&gt;/app/controllers/orders_controller.rb&lt;/p&gt;
&lt;pre class="ruby"&gt;
def create
  @order = Order.new(params[:order])
  @order.current_step = session[:order_step]
  @order.next_step
  session[:order_step] = @order.current_step
  render &amp;#x27;new&amp;#x27;
end
&lt;/pre&gt;

&lt;p&gt;Si ahora volvemos al formulario veremos que recorre las p&amp;aacute;gina como deber&amp;iacute;a y vuelve al primer paso cuando se pulsa el bot&amp;oacute;n en el &amp;uacute;ltimo paso, lo que no es precisamente lo que queremos pero ya lo corregiremos m&amp;aacute;s adelante.&lt;/p&gt;

&lt;p&gt;Antes de eso vamos a implementar la forma de volver al paso anterior.  Ahora mismo podemos movernos hacia adelante pero no hacia atr&amp;aacute;s, para hacer cambios en los campos que ya hemos completado.  Vamos a a&amp;ntilde;adirle otro bot&amp;oacute;n al formulario para poder hacerlo.&lt;/p&gt;

&lt;p class="codeFilePath"&gt;/app/views/orders/new.html.erb&lt;/p&gt;
&lt;pre class="ruby"&gt;
&amp;lt;% title &amp;quot;New Order&amp;quot; %&amp;gt;

&amp;lt;% form_for @order do |f| %&amp;gt;
  &amp;lt;%= f.error_messages %&amp;gt;
  &amp;lt;%= render &amp;quot;#{@order.current_step}_step&amp;quot;, :f =&amp;gt; f %&amp;gt;
  &amp;lt;p&amp;gt;&amp;lt;%= f.submit &amp;quot;Continue&amp;quot; %&amp;gt;&amp;lt;/p&amp;gt;
  &amp;lt;p&amp;gt;&amp;lt;%= f.submit &amp;quot;Back&amp;quot;, :name =&amp;gt; &amp;quot;previous_button&amp;quot; %&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;% end %&amp;gt;

&amp;lt;p&amp;gt;&amp;lt;%= link_to &amp;quot;Back to List&amp;quot;, orders_path %&amp;gt;&amp;lt;/p&amp;gt;
&lt;/pre&gt;

&lt;p&gt;N&amp;oacute;tese que al bot&amp;oacute;n para volver atr&amp;aacute;s le hemos dato un atributo &lt;code&gt;name&lt;/code&gt; para poder determinar qu&amp;eacute; bot&amp;oacute;n se ha pulsado cuando se env&amp;iacute;e el formulario.  Ya podemos modificar el controlador para que se comporte adecuadamente seg&amp;uacute;n el bot&amp;oacute;n que se haya pulsado.&lt;/p&gt;

&lt;p class="codeFilePath"&gt;/app/controllers/orders_controller.rb&lt;/p&gt;
&lt;pre class="ruby"&gt;
def create
  @order = Order.new(params[:order])
  @order.current_step = session[:order_step]
  if params[:back_button]
    @order.previous_step
  else
    @order.next_step
  end
  session[:order_step] = @order.current_step
  render &amp;#x27;new&amp;#x27;
end
&lt;/pre&gt;

&lt;p&gt;Para que esto funcione necesitamos un m&amp;eacute;todo &lt;code&gt;previous_step&lt;/code&gt; en el modelo &lt;code&gt;Order&lt;/code&gt; que ser&amp;aacute; muy parecido al m&amp;eacute;todo &lt;code&gt;next_step&lt;/code&gt; que escribimos antes.&lt;/p&gt;

&lt;p class="codeFilePath"&gt;/app/models/order.rb&lt;/p&gt;
&lt;pre class="ruby"&gt;
def previous_step
  self.current_step = steps[steps.index(current_step)-1]
end
&lt;/pre&gt;

&lt;p&gt;Con estos botones ya podemos movernos a lo largo de todos los pasos en ambas direcciones.&lt;/p&gt;

&lt;div class="imageWrapper"&gt;
  &lt;img src="/system/photos/393/original/E217I03.png" width="800" height="410" alt="The form now has buttons for moving forwards and backwards."/&gt;
&lt;/div&gt;

&lt;p&gt;Como no tiene sentido tener un bot&amp;oacute;n para volver atr&amp;aacute;s en la primera pantalla tendremos que ocultarlo si la orden est&amp;aacute; en el primer paso.&lt;/p&gt;

&lt;p class="codeFilePath"&gt;/app/views/orders/new.html.erb&lt;/p&gt;
&lt;pre class="ruby"&gt;
&amp;lt;% title &amp;quot;New Order&amp;quot; %&amp;gt;

&amp;lt;% form_for @order do |f| %&amp;gt;
  &amp;lt;%= f.error_messages %&amp;gt;
  &amp;lt;%= render &amp;quot;#{@order.current_step}_step&amp;quot;, :f =&amp;gt; f %&amp;gt;
  &amp;lt;p&amp;gt;&amp;lt;%= f.submit &amp;quot;Continue&amp;quot; %&amp;gt;&amp;lt;/p&amp;gt;
  &amp;lt;p&amp;gt;&amp;lt;%= f.submit &amp;quot;Back&amp;quot;, :name =&amp;gt; &amp;quot;previous_button&amp;quot; unless @order.first_step? %&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;% end %&amp;gt;

&amp;lt;p&amp;gt;&amp;lt;%= link_to &amp;quot;Back to List&amp;quot;, orders_path %&amp;gt;&amp;lt;/p&amp;gt;
&lt;/pre&gt;

&lt;p&gt;Para esto tenemos que a&amp;ntilde;adirle al modelo &lt;code&gt;Order&lt;/code&gt; el m&amp;eacute;todo &lt;code&gt;first_step?&lt;/code&gt;.&lt;/p&gt;


&lt;p class="codeFilePath"&gt;/app/models/order.rb&lt;/p&gt;
&lt;pre class="ruby"&gt;
def first_step?
  current_step == steps.first
end
&lt;/pre&gt;

&lt;p&gt;Si ahora recargamos el formulario veremos que en el primer paso ya no aparece el bot&amp;oacute;n para volver atr&amp;aacute;s.&lt;/p&gt;


&lt;div class="imageWrapper"&gt;
  &lt;img src="/system/photos/394/original/E217I04.png" width="800" height="375" alt="The first step no longer has a back button"/&gt;
&lt;/div&gt;

&lt;h3&gt;C&amp;oacute;mo hacer que los campos recuerden sus valores&lt;/h3&gt;

&lt;p&gt;Ya podemos movernos por los pasos de nuestro formulario pero si introducimos un valor en uno de los campos y abandonamos dicho paso el valor no ser&amp;aacute; recuperado si posteriormente regresamos al paso.  Nuevamente, podemos resolver este problema utilizando la sesi&amp;oacute;n.  Si bien no es recomendable guardar objetos complejos en variables de sesi&amp;oacute;n, s&amp;iacute; que podemos hacerlo con objetos sencillos como &lt;em&gt;hashes&lt;/em&gt; y listas.&lt;/p&gt;

&lt;p&gt;El primer paso es crear una nueva variable de sesi&amp;oacute;n en la acci&amp;oacute;n &lt;code&gt;new&lt;/code&gt; llamada &lt;code&gt;order_params&lt;/code&gt; y darle un valor vac&amp;iacute;o a no ser que ya exista.&lt;/p&gt;

&lt;p class="codeFilePath"&gt;/app/controllers/orders_controller.rb&lt;/p&gt;
&lt;pre class="ruby"&gt;
def new
  session[:order_params] ||= {}
  @order = Order.new
end
&lt;/pre&gt;

&lt;p&gt;En la acci&amp;oacute;n &lt;code&gt;create&lt;/code&gt; mezclamos los valores de los par&amp;aacute;metros del pedido con los valores que tiene la variable de sesi&amp;oacute;n.  N&amp;oacute;tese que estamos efectuando una copia en profundidad (&lt;em&gt;deep merge&lt;/em&gt;) por si hay valores anidados en los par&amp;aacute;metros, y adem&amp;aacute;s s&amp;oacute;lo lo haremos si existen dichos par&amp;aacute;metros A partir de este &lt;em&gt;hash&lt;/em&gt; combinado ya podemos crear nuestro objeto &lt;code&gt;Order&lt;/code&gt;.&lt;/p&gt;

&lt;p class="codeFilePath"&gt;/app/controllers/orders_controller.rb&lt;/p&gt;
&lt;pre class="ruby"&gt;
def create
  session[:order_params].deep_merge!(params[:order]) if params[:order]
  @order = Order.new(session[:order_params])
  @order.current_step = session[:order_step]
  if params[:back_button]
    @order.previous_step
  else
    @order.next_step
  end
  session[:order_step] = @order.current_step
  render &amp;#x27;new&amp;#x27;
end
&lt;/pre&gt;

&lt;p&gt;Si ahora rellenamos el formulario veremos los valores que introducimos en el paso final, lo que muestra que se han guardado.&lt;/p&gt;

&lt;div class="imageWrapper"&gt;
  &lt;img src="/system/photos/395/original/E217I05.png" width="804" height="419" alt="The final step of the form showing the saved information."/&gt;
&lt;/div&gt;

&lt;p&gt;Todav&amp;iacute;a nos queda un peque&amp;ntilde;o problema. Si cambiamos de paso mientras el formulario est&amp;aacute; a medio rellenar  luego volvemos la informaci&amp;oacute;n que hab&amp;iacute;amos introducido se perder&amp;aacute;.  Esto se puede resolver copiando a la acci&amp;oacute;n &lt;code&gt;new&lt;/code&gt; las dos l&amp;iacute;neas de la acci&amp;oacute;n &lt;code&gt;create&lt;/code&gt; que recuperan la informaci&amp;oacute;n del pedido y el paso actual desde las variables de sesi&amp;oacute;n.&lt;/p&gt;

&lt;p class="codeFilePath"&gt;/app/controllers/orders_controller.rb&lt;/p&gt;
&lt;pre class="ruby"&gt;
def new
  session[:order_params] ||= {}
  @order = Order.new(session[:order_params])
  @order.current_step = session[:order_step]
end
&lt;/pre&gt;

&lt;p&gt;Ya podemos volver al formulario, y veremos que los datos que vayamos introduciendo seguir&amp;aacute;n estando ah&amp;iacute;.  Y no s&amp;oacute;lo eso, adem&amp;aacute;s iremos al &amp;uacute;ltimo paso en el que est&amp;aacute;bamos.&lt;/p&gt;

&lt;h3&gt;Almacenamiento del Pedido&lt;/h3&gt;

&lt;p&gt;Por &amp;uacute;ltimo haremos que el formulario sea totalmente funcional haciendo que guarde el pedido cuando se finalice el &amp;uacute;ltimo paso.  Para ello vamos a modificar el controlador de forma que guarde el pedido s&amp;oacute;lo si el paso en el que estamos es el &amp;uacute;ltimo y si el bot&amp;oacute;n pulsado no era el de &amp;ldquo;paso anterior&amp;rdquo;.  Tambi&amp;eacute;n tendremos que cambiar el comportamiento de la respuesta de forma que si se guarda el pedido la aplicaci&amp;oacute;n redirige a la acci&amp;oacute;n &lt;code&gt;show&lt;/code&gt; del pedido y muestra un mensaje informativo.&lt;/p&gt;

&lt;p class="codeFilePath"&gt;/app/controllers/orders_controller.rb&lt;/p&gt;
&lt;pre class="ruby"&gt;
class OrdersController &amp;lt; ApplicationController
  def index
    @orders = Order.all
  end
  
  def show
    @order = Order.find(params[:id])
  end
  
  def new
    session[:order_params] ||= {}
    @order = Order.new(session[:order_params])
    @order.current_step = session[:order_step]
  end
  
  def create
    session[:order_params].deep_merge!(params[:order]) if params[:order]
    @order = Order.new(session[:order_params])
    @order.current_step = session[:order_step]
    if params[:back_button]
      @order.previous_step
    elsif @order.last_step?
      @order.save
    else
      @order.next_step
    end
    session[:order_step] = @order.current_step
    
    if @order.new_record?
      render &amp;#x27;new&amp;#x27;
    else
      flash[:notice] = &amp;quot;Order saved.&amp;quot;
      redirect_to @order
    end
  end
end
&lt;/pre&gt;

&lt;p&gt;Para que esto funcione tendremos que a&amp;ntilde;adir el m&amp;eacute;todo &lt;code&gt;last_step&lt;/code&gt; al modelo &lt;code&gt;Order&lt;/code&gt;.&lt;/p&gt;

&lt;p class="codeFilePath"&gt;/app/models/order.rb&lt;/p&gt;
&lt;pre class="ruby"&gt;
def last_step?
  current_step == steps.last
end
&lt;/pre&gt;

&lt;p&gt;Cuando ahora rellenemos el &amp;uacute;ltimo paso se guardar&amp;aacute; el pedido y seremos redirigidos a la p&amp;aacute;gina del nuevo pedido.&lt;/p&gt;

&lt;div class="imageWrapper"&gt;
  &lt;img src="/system/photos/396/original/E217I06.png" width="801" height="343" alt="The order has been saved."/&gt;
&lt;/div&gt;

&lt;p&gt;Pero a&amp;uacute;n no hemos terminado.  Si intentamos crear un nuevo pedido el formulario nos llevar&amp;aacute; directamente al &amp;uacute;ltimo paso y veremos los detalles del pedido anterior.  Una vez m&amp;aacute;s, tenemos que cambiar el controlador para que la informaci&amp;oacute;n del pedido que estamos guardando en la sesi&amp;oacute;n se elimine una vez que el pedido haya sido creado correctamente.  Lo haremos poniendo a &lt;code&gt;nil&lt;/code&gt; las variables de sesi&amp;oacute;n &lt;code&gt;order_step&lt;/code&gt; y &lt;code&gt;order_params&lt;/code&gt;.&lt;/p&gt;

&lt;p class="codeFilePath"&gt;/app/controllers/orders_controller.rb&lt;/p&gt;
&lt;pre class="ruby"&gt;
def create
  session[:order_params].deep_merge!(params[:order]) if params[:order]
  @order = Order.new(session[:order_params])
  @order.current_step = session[:order_step]
  if params[:back_button]
    @order.previous_step
  elsif @order.last_step?
    @order.save
  else
    @order.next_step
  end
  session[:order_step] = @order.current_step
    
  if @order.new_record?
    render &amp;#x27;new&amp;#x27;
  else
    session[:order_step] = session[:order_params] = nil
    flash[:notice] = &amp;quot;Order saved.&amp;quot;
    redirect_to @order
  end
end
&lt;/pre&gt;

&lt;h3&gt;Validaciones&lt;/h3&gt;

&lt;p&gt;Lo &amp;uacute;ltimo que nos queda por ver es c&amp;oacute;mo hacer las validaciones del formulario.  Pondremos una validaci&amp;oacute;n que compruebe la presencia de los atributos &lt;code&gt;shipping_name&lt;/code&gt; y &lt;code&gt;billing_name&lt;/code&gt;.&lt;/p&gt;

&lt;p class="codeFilePath"&gt;/app/models/order.rb&lt;/p&gt;
&lt;pre class="ruby"&gt;
validates_presence_of :shipping_name
validates_presence_of :billing_name
&lt;/pre&gt;

&lt;p&gt;Queremos que los errores de validaci&amp;oacute;n de cada atributo s&amp;oacute;lo aparezcan en su paso apropiado, lo que haremos a&amp;ntilde;adiendo una condici&amp;oacute;n en los validadores.&lt;/p&gt;

&lt;p class="codeFilePath"&gt;/app/models/order.rb&lt;/p&gt;
&lt;pre class="ruby"&gt;
validates_presence_of :shipping_name, :if =&amp;gt; lambda { |o| o.current_step == &amp;quot;shipping&amp;quot; }
validates_presence_of :billing_name, :if =&amp;gt; lambda { |o| o.current_step == &amp;quot;billing&amp;quot; }
&lt;/pre&gt;  

&lt;p&gt;Claro est&amp;aacute;, s&amp;oacute;lo queremos que el formulario cambie al paso siguiente (o  anterior) si el pedido es v&amp;aacute;lido, as&amp;iacute; que tenemos que modificar la acci&amp;oacute;n &lt;code&gt;create&lt;/code&gt; del controlador para que compruebe que el pedido es v&amp;aacute;lido,  cosa que haremos poniendo una sentencia &lt;code&gt;if&lt;/code&gt; al c&amp;oacute;digo que cambia el paso y almacena el registro.&lt;/p&gt;

&lt;p class="codeFilePath"&gt;/app/controllers/orders_controller.rb&lt;/p&gt;
&lt;pre class="ruby"&gt;
def create
  session[:order_params].deep_merge!(params[:order]) if params[:order]
  @order = Order.new(session[:order_params])
  @order.current_step = session[:order_step]
  if @order.valid?
    if params[:back_button]
      @order.previous_step
    elsif @order.last_step?
      @order.save
    else
      @order.next_step
    end
    session[:order_step] = @order.current_step
  end
  if @order.new_record?
    render &amp;#x27;new&amp;#x27;
  else
    session[:order_step] = session[:order_params] = nil
    flash[:notice] = &amp;quot;Order saved.&amp;quot;
    redirect_to @order
  end
end
&lt;/pre&gt;

&lt;p&gt;Si ahora creamos un nuevo pedido e intentamos enviar el primer paso veremos que aparece el error de un campo pero no del otro.&lt;/p&gt;

&lt;div class="imageWrapper"&gt;
  &lt;img src="/system/photos/397/original/E217I07.png" width="800" height="500" alt="Only the error for the shipping step is shown."/&gt;
&lt;/div&gt;

&lt;p&gt;Igualmente si intentamos movernos al siguiente paso y dejamos su campo vac&amp;iacute;o, s&amp;oacute;lo veremos el error correspondente.&lt;/p&gt;

&lt;div class="imageWrapper"&gt;
  &lt;img src="/system/photos/398/original/E217I08.png" width="800" height="536" alt="Likewise on the billing step only the error for the billing infomation field is shown."/&gt;
&lt;/div&gt;

&lt;p&gt;Podemos organizar un poco la validaci&amp;oacute;n cambiando las expresiones lambda por m&amp;eacute;todos llamados &lt;code&gt;shipping?&lt;/code&gt; y &lt;code&gt;billing?&lt;/code&gt;.&lt;/p&gt;

&lt;p class="codeFilePath"&gt;/app/models/order.rb&lt;/p&gt;
&lt;pre class="ruby"&gt;
class Order &amp;lt; ActiveRecord::Base
  attr_accessible :shipping_name, :billing_name
  attr_writer :current_step

  validates_presence_of :shipping_name, :if =&amp;gt; :shipping?
  validates_presence_of :billing_name, :if =&amp;gt; :billing?

  # other methods omittted.
    
  def shipping?
    current_step == &amp;quot;shipping&amp;quot;
  end
  
  def billing?
    current_step == &amp;quot;billing&amp;quot;
  end
end
&lt;/pre&gt;

&lt;p&gt;Tambi&amp;eacute;n resulta &amp;uacute;til tener un m&amp;eacute;todo que valide todos los pasos a la vez.  Podemos escribir un m&amp;eacute;todo llamado &lt;code&gt;all_valid?&lt;/code&gt; que recorra los pasos y compruebe que sean v&amp;aacute;lidos.&lt;/p&gt;

&lt;p class="codeFilePath"&gt;/app/models/order.rb&lt;/p&gt;
&lt;pre class="ruby"&gt;
def all_valid?
  steps.all? do |step|
    self.current_step = step
    valid?
  end
end
&lt;/pre&gt;

&lt;p&gt;De esta forma si alguna vez hacemos cambios en las validaciones o en la forma en que funcionan los pasos podremos asegurarnos de que no hemos invalidado ninguno de ellos.  Podemos usar este nuevo m&amp;eacute;todo en el controlador para garantizar qu&amp;eacute; s&amp;oacute;lo se guarde el pedido si todos sus pasos son v&amp;aacute;lidos.&lt;/p&gt;

&lt;p class="codeFilePath"&gt;/app/controllers/orders_controller.rb&lt;/p&gt;
&lt;pre class="ruby"&gt;
def create
  session[:order_params].deep_merge!(params[:order]) if params[:order]
  @order = Order.new(session[:order_params])
  @order.current_step = session[:order_step]
  if @order.valid?
    if params[:back_button]
      @order.previous_step
    elsif @order.last_step?
      @order.save if @order.all_valid?
    else
      @order.next_step
    end
    session[:order_step] = @order.current_step
  end
  if @order.new_record?
    render &amp;#x27;new&amp;#x27;
  else
    session[:order_step] = session[:order_params] = nil
    flash[:notice] = &amp;quot;Order saved.&amp;quot;
    redirect_to @order
  end
end
&lt;/pre&gt;

&lt;p&gt;Lo m&amp;aacute;s &amp;uacute;til del m&amp;eacute;todo &lt;code&gt;all_valid?&lt;/code&gt; es que si uno de los pasos no valida entonces se convierte en el paso actual por lo que al usuario se le mostrar&amp;aacute; el primer paso que no ha validado.&lt;/p&gt;

&lt;p&gt;Y eso es todo por este episodio.  Tenemos que tener siempre en mente que estamos almacenando los par&amp;aacute;metros del pedido en la sesi&amp;oacute;n, esto quiere decir que si un usuario tiene m&amp;uacute;ltiples ventanas o pesta&amp;ntilde;as abiertas estas compartir&amp;aacute;n la informaci&amp;oacute;n de sesi&amp;oacute;n.  Si quisi&amp;eacute;ramos tratar las ventanas o pesta&amp;ntilde;as de forma indepnediente tendr&amp;iacute;amos que almacenar los par&amp;aacute;metros del pedido en la base de datos y guardar el modelo al principio, utilizando la acci&amp;oacute;n &lt;code&gt;update&lt;/code&gt; para ir grabando cada paso.  Alternativamente se podr&amp;iacute;an guardar los par&amp;aacute;metros como campos ocultos en el formulario.&lt;/p&gt;

&lt;p&gt;Por &amp;uacute;ltimo, si le echamos un vistazo a la acci&amp;oacute;n &lt;code&gt;create&lt;/code&gt; veremos que es bastante m&amp;aacute;s larga y complicada de lo que deber&amp;iacute;a ser.  Si bien para nuestros prop&amp;oacute;sitos esto est&amp;aacute; bien as&amp;iacute;, si quisi&amp;eacute;ramos usar esta t&amp;eacute;cnica para una aplicaci&amp;oacute;n de producci&amp;oacute;n tendr&amp;iacute;amos que refactorizarla un poco.&lt;/p&gt;
</description>
      <pubDate>Sun, 20 Jun 2010 19:39:33 +0000</pubDate>
      <guid>http://es.asciicasts.com/episodes/217-fomularios-con-multiples-pasos</guid>
      <link>http://es.asciicasts.com/episodes/217-fomularios-con-multiples-pasos</link>
    </item>
    <item>
      <title>Generadores en Rails 3</title>
      <description>&lt;p&gt;A los que hayan usado Rails alguna vez les resultar&amp;aacute;n familiares los generadores y habr&amp;aacute;n utilizado el comando &lt;code&gt;script/generate&lt;/code&gt; para crear modelos, controladores, andamiajes, etc&amp;eacute;tera.  En Rails 3 los generadores han sido reescritos en su totalidad, siendo muy diferentes de los de la versi&amp;oacute;n 2.   De entrada ahora son mucho m&amp;aacute;s modulares y esto quiere decir que son m&amp;aacute;s f&amp;aacute;ciles de adaptar seg&amp;uacute;n nuestras preferencias.&lt;/p&gt;

&lt;p&gt;Si en el directorio ra&amp;iacute;z de una aplicaci&amp;oacute;n Rails 3 ejecutamos &lt;code&gt;rails g&lt;/code&gt; veremos la lista de los generadores disponibles en esa aplicaci&amp;oacute;n.  Si no hemos creado nuestros propios generadores tan s&amp;oacute;lo veremos los que vienen incluidos en Rails 3: &lt;code&gt;controller&lt;/code&gt;, &lt;code&gt;generator&lt;/code&gt;, &lt;code&gt;helper&lt;/code&gt;, &lt;code&gt;integration_test&lt;/code&gt;, &lt;code&gt;mailer&lt;/code&gt;, &lt;code&gt;metal&lt;/code&gt;, &lt;code&gt;migration&lt;/code&gt;, &lt;code&gt;model&lt;/code&gt;, &lt;code&gt;observer&lt;/code&gt;, &lt;code&gt;performance_test&lt;/code&gt;, &lt;code&gt;plugin&lt;/code&gt;, &lt;code&gt;resource&lt;/code&gt;, &lt;code&gt;scaffold&lt;/code&gt;, &lt;code&gt;scaffold_controller&lt;/code&gt;, &lt;code&gt;session_migration&lt;/code&gt; y &lt;code&gt;stylesheets&lt;/code&gt;.  En su mayor&amp;iacute;a estos generadores se comportan igual que sus equivalentes en Rails 2.&lt;/p&gt;

&lt;p&gt;Para ver la ayuda acerca de un generador en concreto podemos ejecutar el generador con la opci&amp;oacute;n &lt;code&gt;--help&lt;/code&gt;.  Si lo hacemos con el generador de andamiajes veremos que hay muchas opciones disponibles que nos dan bastante flexibilidad para personalizar el comportamiento del generador.&lt;/p&gt;

&lt;pre class="terminal"&gt;
$ rails g scaffold --help
Usage:
  rails generate scaffold NAME [field:type field:type] [options]

Options:
  -c, --scaffold-controller=NAME  # Scaffold controller to be invoked
                                  # Default: scaffold_controller
      [--singleton]               # Supply to create a singleton controller
      [--force-plural]            # Forces the use of a plural ModelName
  -y, [--stylesheets]             # Indicates when to generate stylesheets
                                  # Default: true
  -o, --orm=NAME                  # Orm to be invoked
                                  # Default: active_record

ScaffoldController options:
  -e, [--template-engine=NAME]  # Template engine to be invoked
                                # Default: erb
      [--helper]                # Indicates when to generate helper
                                # Default: true
  -t, [--test-framework=NAME]   # Test framework to be invoked
                                # Default: test_unit

Runtime options:
  -f, [--force]    # Overwrite files that already exist
  -p, [--pretend]  # Run but do not make any changes
  -q, [--quiet]    # Supress status output
  -s, [--skip]     # Skip files that already exist

TestUnit options:
  -r, [--fixture-replacement=NAME]  # Fixture replacement to be invoked
      [--fixture]                   # Indicates when to generate fixture
                                    # Default: true

ActiveRecord options:
  [--parent=PARENT]  # The parent class for the generated model
  [--timestamps]     # Indicates when to generate timestamps
                     # Default: true
  [--migration]      # Indicates when to generate migration
                     # Default: true
&lt;/pre&gt;

&lt;p&gt;N&amp;oacute;tese que algunas opciones vienen con valores por defecto.  Por ejemplo la opci&amp;oacute;n  &lt;code&gt;--stylesheets&lt;/code&gt; por defecto vale &lt;code&gt;true&lt;/code&gt;.  Pero, &amp;iquest;y si no queremos que las hojas de estilo sean generadas autom&amp;aacute;ticamente?  Pues bien, cuando tenemos una opci&amp;oacute;n booleana cuyo valor por defecto es &lt;code&gt;true&lt;/code&gt; podemos poner el prefijo &lt;code&gt;--no&lt;/code&gt; para desactivar dicha opci&amp;oacute;n.  A continuaci&amp;oacute;n crearemos un andamiaje llamado  &lt;code&gt;project&lt;/code&gt;, pero sin hoja de estilos esta vez.&lt;/p&gt;

&lt;pre class="terminal"&gt;
$ rails g scaffold project name:string --no-stylesheets
      invoke  active_record
      &lt;span class="passed"&gt;create&lt;/span&gt;    db/migrate/20100602201538_create_projects.rb
      &lt;span class="passed"&gt;create&lt;/span&gt;    app/models/project.rb
      invoke    test_unit
      &lt;span class="passed"&gt;create&lt;/span&gt;      test/unit/project_test.rb
      &lt;span class="passed"&gt;create&lt;/span&gt;      test/fixtures/projects.yml
       route  resources :projects
      invoke  scaffold_controller
      &lt;span class="passed"&gt;create&lt;/span&gt;    app/controllers/projects_controller.rb
      invoke    erb
      &lt;span class="passed"&gt;create&lt;/span&gt;      app/views/projects
      &lt;span class="passed"&gt;create&lt;/span&gt;      app/views/projects/index.html.erb
      &lt;span class="passed"&gt;create&lt;/span&gt;      app/views/projects/edit.html.erb
      &lt;span class="passed"&gt;create&lt;/span&gt;      app/views/projects/show.html.erb
      &lt;span class="passed"&gt;create&lt;/span&gt;      app/views/projects/new.html.erb
      &lt;span class="passed"&gt;create&lt;/span&gt;      app/views/projects/_form.html.erb
      invoke    test_unit
      &lt;span class="passed"&gt;create&lt;/span&gt;      test/functional/projects_controller_test.rb
      invoke    helper
      &lt;span class="passed"&gt;create&lt;/span&gt;      app/helpers/projects_helper.rb
      invoke      test_unit
      &lt;span class="passed"&gt;create&lt;/span&gt;        test/unit/helpers/projects_helper_test.rb
&lt;/pre&gt;      

&lt;p&gt;Si inspeccionamos la salida del comando veremos que no se han generado hojas de estilo porque hemos desactivado la opci&amp;oacute;n.&lt;/p&gt;

&lt;p&gt;La salida del generador muestra (adem&amp;aacute;s del listado de los archivos que se han creado) una serie de llamadas a &lt;code&gt;invoke&lt;/code&gt;.  Estas l&amp;iacute;neas indican cu&amp;aacute;ndo se est&amp;aacute;n invocando otros generadores.  Esto quiere decir que el generador de andamiajes no hace mucho por s&amp;iacute; solo, sino que delega en otros generadores como el generador &lt;code&gt;active_record&lt;/code&gt; que crea el archivo del modelo y la migraci&amp;oacute;n.  El generador &lt;code&gt;active_record&lt;/code&gt; llama a otro generador &lt;code&gt;test_unit&lt;/code&gt;, para crear un fichero de tests unitarios.  Este patr&amp;oacute;n se repite m&amp;aacute;s abajo cuando el generador &lt;code&gt;scaffold_controller&lt;/code&gt; invoca los generadores &lt;code&gt;erb&lt;/code&gt;, &lt;code&gt;test_unit&lt;/code&gt; y &lt;code&gt;helper&lt;/code&gt; para crear el resto de archivos conectados con un controlador y sus vistas.  Esto hace que este sistema sea muy modular y podamos reemplazar lo que nos interese: si por ejemplo nos interesa utilizar Haml en lugar de erb en las vistas, o Shoulda o RSpec en lugar de Test::Unit podr&amp;iacute;amos utilizar otros generadores para generar el andamiaje.&lt;/p&gt;

&lt;p&gt;Con esto ya podemos repasar otra vez la lista de opciones que nos muestra el generador de andamiajes.  Todo deber&amp;iacute;a empezar a adquirir m&amp;aacute;s sentido.  Por ejemplo la opci&amp;oacute;n  &lt;code&gt;--orm&lt;/code&gt; nos permite cambiar el mapeo objeto-relacional que generar&amp;aacute; nuestros modelos (podr&amp;iacute;amos usar DataMapper en lugar de ActiveRecord).  M&amp;aacute;s abajo en la lista de opciones hay una lista de opciones de ActiveRecord que s&amp;oacute;lo aplican a la versi&amp;oacute;n de ActiveRecord del generador as&amp;iacute; como las opciones de los generadores de Test::Unit y ScaffoldController.&lt;/p&gt;

&lt;h3&gt;Cambio de las opciones por defecto&lt;/h3&gt;

&lt;p&gt;Aunque poder pasar a los generadores opciones como &lt;code&gt;--no-stylesheets&lt;/code&gt; es bastante &amp;uacute;til, ser&amp;iacute;a mucho m&amp;aacute;s &amp;uacute;til si pudi&amp;eacute;ramos cambiar el valor por defecto de estas opciones, y eso en Rails 3 lo podemos hacer (a nivel de aplicaci&amp;oacute;n) modificando el archivo  &lt;code&gt;config/application.rb.&lt;/code&gt;, que es generado autom&amp;aacute;ticamente cuando se crea la aplicaci&amp;oacute;n Rails, y que viene con la siguiente secci&amp;oacute;n comentada:&lt;/p&gt;

&lt;p class="codeFilePath"&gt;/config/application.rb&lt;/p&gt;
&lt;pre class="ruby"&gt;
# Configure generators values. Many other options are available, be sure to check the documentation.
# config.generators do |g|
#   g.orm             :active_record
#   g.template_engine :erb
#   g.test_framework  :test_unit, :fixture =&amp;gt; true
# end
&lt;/pre&gt;

&lt;p&gt;Si quitamos los comentarios de esta secci&amp;oacute;n podemos cambiar las opciones por defecto de los generadores en nuestra aplicaci&amp;oacute;n.  Las opciones que se muestran coinciden con los valores por defecto.  Por ejemplo para desactivar la generaci&amp;oacute;n de hojas de estilo por defecto podr&amp;iacute;amos escribir:&lt;/p&gt;

&lt;p class="codeFilePath"&gt;/config/application.rb&lt;/p&gt;
&lt;pre class="ruby"&gt;
# Configure generators values. Many other options are available, be sure to check the documentation.
config.generators do |g|
  g.stylesheets false
end
&lt;/pre&gt;

&lt;p&gt;Si ahora ejecutamos el comando &lt;code&gt;help&lt;/code&gt; en el generador de andamiajes veremos que la nueva opci&amp;oacute;n por defecto entra en acci&amp;oacute;n y la opci&amp;oacute;n  &lt;code&gt;--stylesheets&lt;/code&gt; ya no tendr&amp;aacute; &lt;code&gt;true&lt;/code&gt; como valor por defecto.&lt;/p&gt;

&lt;pre class="terminal"&gt;
$ rails g scaffold --help
Usage:
  rails generate scaffold NAME [field:type field:type] [options]

Options:
  -y, [--stylesheets]             # Indicates when to generate stylesheets
  -c, --scaffold-controller=NAME  # Scaffold controller to be invoked
                                  # Default: scaffold_controller
      [--singleton]               # Supply to create a singleton controller
      [--force-plural]            # Forces the use of a plural ModelName
  -o, --orm=NAME                  # Orm to be invoked
                                  # Default: active_record
&lt;/pre&gt;

&lt;p&gt;Hagamos algo un poco m&amp;aacute;s a la aventura, cambiemos el &lt;em&gt;framework&lt;/em&gt; de pruebas para que sea Shoulda en lugar de Test::Unit, y vamos a cambiar las &lt;em&gt;fixturas&lt;/em&gt; por Factory Girl usando la opci&amp;oacute;n &lt;code&gt;fixture-replacement&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Por supuesto antes de hacer esto tenemos que detallar cu&amp;aacute;les son las gemas relevantes en el fichero &lt;code&gt;Gemfile&lt;/code&gt; de la aplicaci&amp;oacute;n.  Tan s&amp;oacute;lo necesitaremos estas gemas en el entorno de test, as&amp;iacute; que las agruparemos.&lt;/p&gt;

&lt;p class="codeFilePath"&gt;/Gemfile&lt;/p&gt;
&lt;pre class="ruby"&gt;
group :test do
  gem &amp;quot;shoulda&amp;quot;
  gem &amp;quot;factory_girl&amp;quot;
end
&lt;/pre&gt;

&lt;p&gt;Por desgracia estas dos gemas no incluyen oficialmente generadores para Rails 3 pero podemos utilizar otra gema llamada &lt;a href="http://github.com/indirect/rails3-generators"&gt;rails3-generators&lt;/a&gt; que s&amp;iacute; que proporciona generadores adaptados a Rails 3 de muchos &lt;em&gt;plugins&lt;/em&gt; y gemas, incluyendo Shoulda y Factory Girl.  Vamos a a&amp;ntilde;adir dicha gema tambi&amp;eacute;n al fichero Gemfile pero s&amp;oacute;lo lo haremos en el entorno de desarrollo.&lt;/p&gt;

&lt;p class="codeFilePath"&gt;/Gemfile&lt;/p&gt;
&lt;pre class="ruby"&gt;
gem &amp;quot;rails3-generators&amp;quot;, :group =&amp;gt; :development
&lt;/pre&gt;
&lt;p&gt;Con esto, s&amp;oacute;lo tenemos que ejecutar&lt;/p&gt;

&lt;pre class="terminal"&gt;
bundle install
&lt;/pre&gt;

&lt;p&gt;para instalar las gemas.  Una vez que se hayan instalado las gemas podemos ejecutar &lt;code&gt;rails g&lt;/code&gt; para ver una lista de los generadores que hay disponibles.  Al final de la lista ahora deber&amp;iacute;an aparecer los nuevos generadores definidos en la gema &lt;code&gt;rails3-generators&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Con estos nuevos generadores podemos modificar la configuraci&amp;oacute;n de  &lt;code&gt;application.rb&lt;/code&gt; y poner los valores por defecto de las opciones  &lt;code&gt;test_framework&lt;/code&gt; y &lt;code&gt;fixture_replacement&lt;/code&gt;.&lt;/p&gt;

&lt;p class="codeFilePath"&gt;/config/application.rb&lt;/p&gt;
&lt;pre class="ruby"&gt;
# Configure generators values. Many other options are available, be sure to check the documentation.
config.generators do |g|
  g.stylesheets false
  g.test_framework :shoulda
  g.fixture_replacement :factory_girl
end
&lt;/pre&gt;

&lt;p&gt;Ahora cuando ejecutemos la ayuda del generador de andamiajes otra vez veremos que habr&amp;aacute; cambiado para mostrar las opciones por defecto de &lt;em&gt;framework&lt;/em&gt; de pruebas.&lt;/p&gt;

&lt;pre class="terminal"&gt;
$ rails g scaffold --help
Usage:
  rails generate scaffold NAME [field:type field:type] [options]
  ...

ActiveRecord options:
  [--parent=PARENT]      # The parent class for the generated model
  [--migration]          # Indicates when to generate migration
                         # Default: true
  [--timestamps]         # Indicates when to generate timestamps
                         # Default: true
  [--textframework=NAME] # Test framework to be invoked
                         # Default: shoulda

Shoulda options:
  [--fixture-replacement=NAME]  # Fixture replacement to be invoked
                                # Default: factory_girl
  [--dir=DIR]                   # The directory where the model tests should go
                                # Default: test/unit
&lt;/pre&gt;                              

&lt;p&gt;Podemos probar todo esto generando un nuevo andamiaje para un modelo llamado &lt;code&gt;task&lt;/code&gt;.&lt;/p&gt;

&lt;pre class="terminal"&gt;
$ rails g scaffold task project_id:integer name:string
      invoke  active_record
      &lt;span class="passed"&gt;create&lt;/span&gt;    db/migrate/20100604202823_create_tasks.rb
      &lt;span class="passed"&gt;create&lt;/span&gt;    app/models/task.rb
      invoke    shoulda
      &lt;span class="passed"&gt;create&lt;/span&gt;      test/unit/task_test.rb
      invoke      factory_girl
      &lt;span class="passed"&gt;create&lt;/span&gt;        test/factories/tasks.rb
       route  resources :tasks
      invoke  scaffold_controller
      &lt;span class="passed"&gt;create&lt;/span&gt;    app/controllers/tasks_controller.rb
      invoke    erb
      &lt;span class="passed"&gt;create&lt;/span&gt;      app/views/tasks
      &lt;span class="passed"&gt;create&lt;/span&gt;      app/views/tasks/index.html.erb
      &lt;span class="passed"&gt;create&lt;/span&gt;      app/views/tasks/edit.html.erb
      &lt;span class="passed"&gt;create&lt;/span&gt;      app/views/tasks/show.html.erb
      &lt;span class="passed"&gt;create&lt;/span&gt;      app/views/tasks/new.html.erb
      &lt;span class="passed"&gt;create&lt;/span&gt;      app/views/tasks/_form.html.erb
       &lt;span class="failed"&gt;error&lt;/span&gt;    shoulda [not found]
      invoke    helper
      &lt;span class="passed"&gt;create&lt;/span&gt;      app/helpers/tasks_helper.rb
       &lt;span class="failed"&gt;error&lt;/span&gt;      shoulda [not found]
&lt;/pre&gt; 

&lt;p&gt;Al principio vemos que se invocan correctamente los generadores de &lt;code&gt;shoulda&lt;/code&gt; y &lt;code&gt;factory_girl&lt;/code&gt; pero que m&amp;aacute;s abajo, no se pudo encontrar un generador de Shoulda para erb o el fichero &lt;em&gt;helper&lt;/em&gt;.  Si vemos otra vez la lista de &lt;em&gt;helpers&lt;/em&gt; comprobaremos que s&amp;oacute;lo tenemos generadores Shoulda para los modelos y los controladores.&lt;/p&gt;

&lt;pre class="terminal"&gt;
$ rails g
  ...
Shoulda:
  shoulda:controller
  shoulda:model
&lt;/pre&gt;  

&lt;p&gt;&amp;iquest;Qu&amp;eacute; podemos hacer aqu&amp;iacute;?  Bueno, si examinamos la p&amp;aacute;gina de las &lt;a href="http://guides.rails.info/generators.html#adding-generators-fallbacks"&gt;Gu&amp;iacute;as Rails sobre generadores&lt;/a&gt; veremos que es posibile a&amp;ntilde;adir generadores de respaldo por si no se encuentran ciertos generadores.  Nuevamente, lo &amp;uacute;nico que hay que hacer es modificar el bloque de configuraci&amp;oacute;n en &lt;code&gt;application.rb&lt;/code&gt;.&lt;/p&gt;

&lt;p class="codeFilePath"&gt;/config/application.rb&lt;/p&gt;
&lt;pre class="ruby"&gt;
# Configure generators values. Many other options are available, be sure to check the documentation.
config.generators do |g|
  g.stylesheets false
  g.test_framework :shoulda
  g.fallbacks[:shoulda] = :test_unit 
  g.fixture_replacement :factory_girl
end
&lt;/pre&gt;

&lt;p&gt;Ahora ya podemos generar andamiajes con Shoulda y los generadores utilizar&amp;aacute;n los generadores correspondientes a Test::Unit si no se puede encontrar un generador adecuado de Shoulda.&lt;/p&gt;

&lt;h3&gt;Personalizaci&amp;oacute;n de las plantillas&lt;/h3&gt;

&lt;p&gt;Lo &amp;uacute;ltimo que nos queda por ver en este episodio es c&amp;oacute;mo personalizar las plantillas generadas.  Debajo se muestra la vista generada para la acci&amp;oacute;n &lt;code&gt;index&lt;/code&gt; del controlador que acabamos de generar.&lt;/p&gt;

&lt;p class="codeFilePath"&gt;/app/views/tasks/index.html.erb&lt;/p&gt;
&lt;pre class="ruby"&gt;
&amp;lt;h1&amp;gt;Listing tasks&amp;lt;/h1&amp;gt;

&amp;lt;table&amp;gt;
  &amp;lt;tr&amp;gt;
    &amp;lt;th&amp;gt;&amp;lt;/th&amp;gt;
    &amp;lt;th&amp;gt;&amp;lt;/th&amp;gt;
    &amp;lt;th&amp;gt;&amp;lt;/th&amp;gt;
  &amp;lt;/tr&amp;gt;

&amp;lt;% @tasks.each do |task| %&amp;gt;
  &amp;lt;tr&amp;gt;
    &amp;lt;td&amp;gt;&amp;lt;%= link_to &amp;#x27;Show&amp;#x27;, task %&amp;gt;&amp;lt;/td&amp;gt;
    &amp;lt;td&amp;gt;&amp;lt;%= link_to &amp;#x27;Edit&amp;#x27;, edit_task_path(task) %&amp;gt;&amp;lt;/td&amp;gt;
    &amp;lt;td&amp;gt;&amp;lt;%= link_to &amp;#x27;Destroy&amp;#x27;, task, :confirm =&amp;gt; &amp;#x27;Are you sure?&amp;#x27;, :method =&amp;gt; :delete %&amp;gt;&amp;lt;/td&amp;gt;
  &amp;lt;/tr&amp;gt;
&amp;lt;% end %&amp;gt;
&amp;lt;/table&amp;gt;

&amp;lt;br /&amp;gt;

&amp;lt;%= link_to &amp;#x27;New Task&amp;#x27;, new_task_path %&amp;gt;
&lt;/pre&gt;

&lt;p&gt;Supongamos que queremos eliminar la etiqueta &lt;code&gt;br&lt;/code&gt; al final de la plantilla y poner el enlace del final en su p&amp;aacute;rrafo.  &amp;iquest;C&amp;oacute;mo podemos cambiar la plantilla de forma que estos cambios ocurran en todas las vistas generadas?&lt;/p&gt;

&lt;p&gt;Podemos crear nuestras propias plantillas en un directorio llamado &lt;code&gt;templates&lt;/code&gt; dentro del directorio &lt;code&gt;lib&lt;/code&gt;.  Los generadores mirar&amp;aacute;n ah&amp;iacute; en busca de plantillas en lugar de utilizar las incluidas por defecto.  Para determinar qu&amp;eacute; plantilla tenemos que redefinir tendremos que mirar &lt;a href="http://github.com/rails/rails"&gt;el c&amp;oacute;digo fuente del generador&lt;/a&gt;.  El c&amp;oacute;digo de los generadores est&amp;aacute; en el directorio  &lt;a href="http://github.com/rails/rails/tree/master/railties/lib/rails/generators/"&gt;railties/lib/rails/generators&lt;/a&gt; y las plantillas por defecto est&amp;aacute;n en el directorio &lt;a href="http://github.com/rails/rails/tree/master/railties/lib/rails/generators/erb/scaffold/templates/"&gt;erb/scaffold/templates/&lt;/a&gt;.  Podemos copiar los contenidos de &lt;code&gt;index.html.erb&lt;/code&gt; de ese directorio para cambiarlo seg&amp;uacute;n nos convenga.&lt;/p&gt;

&lt;p&gt;Tenemos que crear una estructura de directorios similar por lo que nuestra plantilla de &amp;iacute;ndice personalizada tendr&amp;iacute;a que estar en  &lt;code&gt;/lib/templates/erb/scaffold/index.html.erb&lt;/code&gt;.  Ah&amp;iacute; podemos poner la plantilla por defecto sobre la que hacer nuestros cambios.  Una vez que los hayamos hecho si creamos un nuevo andamiaje, por ejemplo para un modelo llamado &lt;code&gt;Category&lt;/code&gt; veremos que la vista de &amp;iacute;ndice ya incorpora nuestra propia plantilla.&lt;/p&gt;

&lt;pre class="terminal"&gt;
rails g scaffold category name:string
&lt;/pre&gt;

&lt;p class="codeFilePath"&gt;/app/views/categories/index.html.erb&lt;/p&gt;
&lt;pre class="ruby"&gt;
&amp;lt;h1&amp;gt;Listing categories&amp;lt;/h1&amp;gt;

&amp;lt;table&amp;gt;
  &amp;lt;tr&amp;gt;
    &amp;lt;th&amp;gt;Name&amp;lt;/th&amp;gt;
    &amp;lt;th&amp;gt;&amp;lt;/th&amp;gt;
    &amp;lt;th&amp;gt;&amp;lt;/th&amp;gt;
    &amp;lt;th&amp;gt;&amp;lt;/th&amp;gt;
  &amp;lt;/tr&amp;gt;

&amp;lt;% @categories.each do |category| %&amp;gt;
  &amp;lt;tr&amp;gt;
    &amp;lt;td&amp;gt;&amp;lt;%= category.name %&amp;gt;&amp;lt;/td&amp;gt;
    &amp;lt;td&amp;gt;&amp;lt;%= link_to &amp;#x27;Show&amp;#x27;, category %&amp;gt;&amp;lt;/td&amp;gt;
    &amp;lt;td&amp;gt;&amp;lt;%= link_to &amp;#x27;Edit&amp;#x27;, edit_category_path(category) %&amp;gt;&amp;lt;/td&amp;gt;
    &amp;lt;td&amp;gt;&amp;lt;%= link_to &amp;#x27;Destroy&amp;#x27;, category, :confirm =&amp;gt; &amp;#x27;Are you sure?&amp;#x27;, :method =&amp;gt; :delete %&amp;gt;&amp;lt;/td&amp;gt;
  &amp;lt;/tr&amp;gt;
&amp;lt;% end %&amp;gt;
&amp;lt;/table&amp;gt;

&amp;lt;p&amp;gt;&amp;lt;%= link_to &amp;#x27;New Category&amp;#x27;, new_category_path %&amp;gt;&amp;lt;/p&amp;gt;
&lt;/pre&gt;

&lt;p&gt;Y eso es todo por este episodio.   En Rails 3 se han introducido muchas mejoras en los generadores que los hacen mucho m&amp;aacute;s f&amp;aacute;ciles de personalizar.&lt;/p&gt;</description>
      <pubDate>Thu, 17 Jun 2010 18:29:24 +0000</pubDate>
      <guid>http://es.asciicasts.com/episodes/216-generadores-en-rails-3</guid>
      <link>http://es.asciicasts.com/episodes/216-generadores-en-rails-3</link>
    </item>
    <item>
      <title>Consultas avanzadas con Rails 3</title>
      <description>&lt;p&gt;En este episodio trataremos el uso de consultas avanzadas con Rails 3.  En el episodio 202 [&lt;a href="http://railscasts.com/episodes/202-active-record-queries-in-rails-3"&gt;verlo&lt;/a&gt;, &lt;a href="http://es.asciicasts.com/episodes/202-consultas-activerecord-en-rails-3"&gt;leerlo&lt;/a&gt;] vimos las novedadades de ActiveRecord en Rails 3; en esta ocasi&amp;oacute;n continuaremos a partir de ah&amp;iacute; y veremos algunos usos m&amp;aacute;s avanzados.&lt;/p&gt;

&lt;h3&gt;Uso de m&amp;eacute;todos de clase en lugar de &amp;aacute;mbitos&lt;/h3&gt;

&lt;p&gt;En la aplicaci&amp;oacute;n de ejemplo que vamos a usar tenemos dos modelos: &lt;code&gt;Product&lt;/code&gt; y &lt;code&gt;Category&lt;/code&gt;,  donde un producto pertenece a una categor&amp;iacute;a.  El modelo de producto tiene dos &amp;aacute;mbitos con nombre: &lt;code&gt;discontinued&lt;/code&gt;, que devuelve todos aquellos productos cuyo atributo &lt;code&gt;discontinued&lt;/code&gt; tiene el valor &lt;code&gt;true&lt;/code&gt;, y &lt;code&gt;price&lt;/code&gt;, que devuelve todos los productos cuyo precio es inferior a un argumento dado.&lt;/p&gt;

&lt;p class="codeFilePath"&gt;/app/models/product.rb&lt;/p&gt;
&lt;pre class="ruby"&gt;
class Product &amp;lt; ActiveRecord::Base
  belongs_to :category
  scope :discontinued, where(:discontinued =&amp;gt; true)
  scope :cheaper_than, lambda { |price| where(&amp;quot;price &amp;lt; ?&amp;quot;, price) }
end
&lt;/pre&gt;

&lt;p&gt;En el segundo &amp;aacute;mbito estamos usando una funci&amp;oacute;n lambda.  Si algunas vez tenemos que usar una de estas en un &amp;aacute;mbito con nombre deber&amp;iacute;amos considerar utilizar en su lugar un m&amp;eacute;todo de clase, sobre todo si estamos pasando un gran n&amp;uacute;mero de par&amp;aacute;metros o si el contenido del &amp;aacute;mbito es complejo.   Nuestro caso es muy sencillo pero de todas formas lo vamos a poner como m&amp;eacute;todo de clase:&lt;/p&gt;

&lt;p class="codeFilePath"&gt;/app/models/product.rb&lt;/p&gt;
&lt;pre class="ruby"&gt;
class Product &amp;lt; ActiveRecord::Base
  belongs_to :category
  scope :discontinued, where(:discontinued =&amp;gt; true)
  
  def self.cheaper_than(price)
    where(&amp;quot;price &amp;lt; ?&amp;quot;, price)
  end
end
&lt;/pre&gt;

&lt;p&gt;El comportamiento del m&amp;eacute;todo de clase ser&amp;aacute; exactamente el mismo que el del &amp;aacute;mbito.  Esto tambi&amp;eacute;n se pod&amp;iacute;a hacer tambi&amp;eacute;n con Rails 2, pero el comportamiento en Rails 3 es mucho mejor, incluso podemos reutilizar este m&amp;eacute;todo en otro &amp;aacute;mbito.  Por ejemplo si queremos crear un &amp;aacute;mbito llamado &lt;code&gt;cheap&lt;/code&gt; que devuelva los productos cuyo precio sea inferior a 5&amp;euro; podemos escribirlo as&amp;iacute;:&lt;/p&gt;

&lt;p class="codeFilePath"&gt;/app/models/products.rb&lt;/p&gt;
&lt;pre class="ruby"&gt;
scope :cheap, cheaper_than(5)
&lt;/pre&gt;

&lt;p&gt;Hay que tener, sin embargo, cuidado con una trampa.  Si usamos un m&amp;eacute;todo de clase en un &amp;aacute;mbito tenemos que declarar el &amp;aacute;mbito despu&amp;eacute;s de que se haya definido el m&amp;eacute;todo de clase lo que quiere decir que tendremos que poner el &amp;aacute;mbito un poco m&amp;aacute;s abajo de lo que es habitual en el fichero de la clase.&lt;/p&gt;

&lt;p&gt;Podemos ver la consulta SQL que genera nuestro &amp;aacute;mbito con la consola de Rails.&lt;/p&gt;


&lt;pre class="terminal"&gt;
ruby-1.8.7-p249 &amp;gt; Product.cheap.to_sql
 =&amp;gt; &amp;quot;SELECT    \&amp;quot;products\&amp;quot;.* FROM      \&amp;quot;products\&amp;quot; WHERE    (price &amp;lt; 5)&amp;quot;
&lt;/pre&gt; 

&lt;p&gt;Si aplicamos el &amp;aacute;mbito veremos el listado de productos baratos.&lt;/p&gt;

&lt;pre class="terminal"&gt;
&amp;gt;Product.cheap
 =&amp;gt; [#&amp;lt;Product id: 1, name: &amp;quot;Top&amp;quot;, price: 4.99, discontinued: nil, category_id: 3, created_at: &amp;quot;2010-05-24 21:01:59&amp;quot;, updated_at: &amp;quot;2010-05-24 21:01:59&amp;quot;&amp;gt;, #&amp;lt;Product id: 2, name: &amp;quot;Milk&amp;quot;, price: 2.99, discontinued: nil, category_id: 2, created_at: &amp;quot;2010-05-24 21:02:38&amp;quot;, updated_at: &amp;quot;2010-05-24 21:02:38&amp;quot;&amp;gt;]
&lt;/pre&gt;

&lt;h3&gt;Asociaciones&lt;/h3&gt;

&lt;p&gt;Aprovechando que estamos en la consola veamos otro truco que tiene que ver con el uso de &amp;aacute;mbitos sobre asociaciones.  Antes dec&amp;iacute;amos que en nuestra aplicaci&amp;oacute;n un producto pertenece a una categor&amp;iacute;a.  Esto quiere decir que podemos usar &lt;code&gt;joins&lt;/code&gt; para devolver una consulta SQL que devuelve un &lt;em&gt;inner join&lt;/em&gt; sobre la tabla de productos y categor&amp;iacute;as.&lt;/p&gt;

&lt;pre class="terminal"&gt;
ruby-1.8.7-p249 &amp;gt; Category.joins(:products).to_sql
 =&amp;gt; &amp;quot;SELECT     \&amp;quot;categories\&amp;quot;.* FROM       \&amp;quot;categories\&amp;quot; INNER JOIN \&amp;quot;products\&amp;quot; ON \&amp;quot;products\&amp;quot;.\&amp;quot;category_id\&amp;quot; = \&amp;quot;categories\&amp;quot;.\&amp;quot;id\&amp;quot;&amp;quot;
&lt;/pre&gt; 

&lt;p&gt;Queremos encontrar todas las categor&amp;iacute;as con poductos que pertenecen a un cierto &amp;aacute;mbito, por ejemplo todas las categor&amp;iacute;as que contienen productos que cuestan menos de cinco euros.  Podemos emplear dos m&amp;eacute;todos distintos para hacerlo.  El primero ser&amp;iacute;a usar &lt;code&gt;merge&lt;/code&gt; de esta manera:&lt;/p&gt;

&lt;pre class="terminal"&gt;
&amp;gt; Category.joins(:products).merge(Product.cheap)
 =&amp;gt; [#&amp;lt;Category id: 3, name: &amp;quot;Games&amp;quot;, created_at: &amp;quot;2010-05-24 21:00:57&amp;quot;, updated_at: &amp;quot;2010-05-25 18:30:18&amp;quot;&amp;gt;, #&amp;lt;Category id: 2, name: &amp;quot;Groceries&amp;quot;, created_at: &amp;quot;2010-05-24 21:00:50&amp;quot;, updated_at: &amp;quot;2010-05-25 18:30:39&amp;quot;&amp;gt;]
&lt;/pre&gt; 

&lt;p&gt;O podr&amp;iacute;amos utilizar el s&amp;iacute;mbolo &lt;code&gt;&amp;amp;&lt;/code&gt; que es un alias de lo mismo y por tanto devolver&amp;aacute; los mismos resultados:&lt;/p&gt;

&lt;pre class="terminal"&gt;
&amp;gt; Category.joins(:products) &amp;amp; Product.cheap
 =&amp;gt; [#&amp;lt;Category id: 3, name: &amp;quot;Games&amp;quot;, created_at: &amp;quot;2010-05-24 21:00:57&amp;quot;, updated_at: &amp;quot;2010-05-25 18:30:18&amp;quot;&amp;gt;, #&amp;lt;Category id: 2, name: &amp;quot;Groceries&amp;quot;, created_at: &amp;quot;2010-05-24 21:00:50&amp;quot;, updated_at: &amp;quot;2010-05-25 18:30:39&amp;quot;&amp;gt;]
&lt;/pre&gt; 

&lt;p&gt;Con estos m&amp;eacute;todos podemos encadenar otras consultas incluso aunque no sean sobre los mismo modelos, y esto es lo que nos permitir&amp;aacute; encontrar las categor&amp;iacute;as con productos que cuestan menos de cinco euros.  Si llamamos  &lt;code&gt;to_sql&lt;/code&gt; en las consultas veremos que se ha hecho un &lt;em&gt;inner join&lt;/em&gt; y se ha a&amp;ntilde;adido  al final la cl&amp;aacute;sula &lt;code&gt;WHERE&lt;/code&gt; del &amp;aacute;mbito.&lt;/p&gt;

&lt;pre class="terminal"&gt;
&amp;gt; (Category.joins(:products) &amp;amp; Product.cheap).to_sql
 =&amp;gt; &amp;quot;SELECT     \&amp;quot;categories\&amp;quot;.* FROM       \&amp;quot;categories\&amp;quot; INNER JOIN \&amp;quot;products\&amp;quot; ON \&amp;quot;products\&amp;quot;.\&amp;quot;category_id\&amp;quot; = \&amp;quot;categories\&amp;quot;.\&amp;quot;id\&amp;quot; WHERE     (price &amp;lt; 5)&amp;quot;
&lt;/pre&gt; 

&lt;p&gt;Esta t&amp;eacute;cnica es bastante potente si se utiliza para definir un &amp;aacute;mbito.  Podemos usarla, por ejemplo, para crear un &amp;aacute;mbito en el modelo &lt;code&gt;Category&lt;/code&gt; que encuentre las categor&amp;iacute;as que tengan productos baratos.&lt;/p&gt;

&lt;p class="codeFilePath"&gt;/app/models/category.rb&lt;/p&gt;
&lt;pre class="ruby"&gt;
class Category &amp;lt; ActiveRecord::Base
  has_many :products
  scope :with_cheap_products, joins(:products) &amp;amp; Product.cheap
end
&lt;/pre&gt;

&lt;p&gt;Este nuevo &amp;aacute;mbito devolver&amp;aacute; las mismas categor&amp;iacute;as que la consulta que escribimos anteriormente.&lt;/p&gt;

&lt;pre class="terminal"&gt;
&amp;gt; Category.with_cheap_products
 =&amp;gt; [#&amp;lt;Category id: 3, name: &amp;quot;Games&amp;quot;, created_at: &amp;quot;2010-05-24 21:00:57&amp;quot;, updated_at: &amp;quot;2010-05-25 18:30:18&amp;quot;&amp;gt;, #&amp;lt;Category id: 2, name: &amp;quot;Groceries&amp;quot;, created_at: &amp;quot;2010-05-24 21:00:50&amp;quot;, updated_at: &amp;quot;2010-05-25 18:30:39&amp;quot;&amp;gt;]
&lt;/pre&gt; 

&lt;p&gt;Cuando trabajemos con estas condiciones que cubren diferentes asociaciones hay que tener en cuenta el nombre de la tabla.  Si examinamos el SQL generado al llamar al &amp;aacute;mbito veremos que no hay un nombre de tabla en la cl&amp;aacute;usula &lt;code&gt;WHERE&lt;/code&gt;.&lt;/p&gt;

&lt;pre class="terminal"&gt;
&amp;gt; Category.with_cheap_products.to_sql
 =&amp;gt; &amp;quot;SELECT     \&amp;quot;categories\&amp;quot;.* FROM       \&amp;quot;categories\&amp;quot; INNER JOIN \&amp;quot;products\&amp;quot; ON \&amp;quot;products\&amp;quot;.\&amp;quot;category_id\&amp;quot; = \&amp;quot;categories\&amp;quot;.\&amp;quot;id\&amp;quot; WHERE     (price &amp;lt; 5)&amp;quot;
&lt;/pre&gt; 

&lt;p&gt;Esto podr&amp;iacute;a ser un problema si ambas tablas tienen una columna llamada &lt;code&gt;price&lt;/code&gt; porque en tal caso la consulta SQL ser&amp;iacute;a ambigua.  Podemos eliminar esta ambiguedad definiendo expl&amp;iacute;citamente el nombre de la tabla en el modelo &lt;code&gt;Product&lt;/code&gt;.&lt;/p&gt;

&lt;p class="codeFilePath"&gt;/app/models/product.rb&lt;/p&gt;
&lt;pre class="ruby"&gt;
def self.cheaper_than(price) 
  where(&amp;quot;products.price &amp;lt; ?&amp;quot;, price)
end
&lt;/pre&gt;

&lt;p&gt;Con esto ya no hay peligro de que el nombre de la columna sea ambiguo.  Siempre hay que especificar el nombre de las tablas cuando se construyen cadenas SQL en las condiciones de una consulta.  Si estamos usando un &lt;em&gt;hash&lt;/em&gt; (como en el &amp;aacute;mbito) entonces no hay que preocuparse de la tabla porque Rails autom&amp;aacute;ticamente lo pone.&lt;/p&gt;

&lt;h3&gt;Construcci&amp;oacute;n de registros a trav&amp;eacute;s de &amp;aacute;mbitos&lt;/h3&gt;

&lt;p&gt;Lo siguiente que veremos es c&amp;oacute;mo crear registros con &amp;aacute;mbitos.  Tenemos un &amp;aacute;mbito definido sobre nuestro modelo &lt;code&gt;Product&lt;/code&gt; llamado &lt;code&gt;discontinued&lt;/code&gt; que encuentra todos los productos descatalogados.&lt;/p&gt;

&lt;pre class="terminal"&gt;
&amp;gt; Product.discontinued
 =&amp;gt; [#&amp;lt;Product id: 3, name: &amp;quot;Some DVD&amp;quot;, price: 13.49, discontinued: true, category_id: 1, created_at: &amp;quot;2010-05-25 19:45:05&amp;quot;, updated_at: &amp;quot;2010-05-25 19:45:05&amp;quot;&amp;gt;]
&lt;/pre&gt; 

&lt;p&gt;Como el &amp;aacute;mbito utiliza un &lt;em&gt;hash&lt;/em&gt; podemos invocar el m&amp;eacute;todo &lt;code&gt;build&lt;/code&gt; sobre &amp;eacute;l para construir un nuevo producto, que tendr&amp;aacute; el atributo &lt;code&gt;discontinued&lt;/code&gt; puesto a &lt;code&gt;true&lt;/code&gt;.&lt;/p&gt;


&lt;pre class="terminal"&gt;
&amp;gt; p = Product.discontinued.build
 =&amp;gt; #&amp;lt;Product id: nil, name: nil, price: nil, discontinued: true, category_id: nil, created_at: nil, updated_at: nil&amp;gt;
&lt;/pre&gt; 

&lt;p&gt;Esto es as&amp;iacute; porque el atributo &lt;code&gt;discontinued&lt;/code&gt; forma parte de la condici&amp;oacute;n &lt;code&gt;where&lt;/code&gt;, funcionando de forma parecida a una asociaci&amp;oacute;n en el sentido de que si se invoca &lt;code&gt;build&lt;/code&gt; sobre una asociaci&amp;oacute;n autom&amp;aacute;ticamente se pone la clave for&amp;aacute;nea que est&amp;aacute; asociada con dicho registro.  En nuestro caso estamos tan s&amp;oacute;lo asignando valores a atributos que aparecen en la condici&amp;oacute;n &lt;code&gt;where&lt;/code&gt;.  Es algo muy &amp;uacute;til a tener en cuenta si necesitamos crear registros que cumplan un cierto &amp;aacute;mbito.&lt;/p&gt;

&lt;h3&gt;Arel&lt;/h3&gt;

&lt;p&gt;Como colof&amp;oacute;n a este episodio presentaremos  &lt;a href="http://github.com/rails/arel"&gt;Arel&lt;/a&gt;.  Arel se encarga de generar las consultas de ActiveRecord, ser&amp;aacute; poco frecuente que tengamos que interactuar directamente con Arel pero es bueno tener un conocimiento de c&amp;oacute;mo funciona y qu&amp;eacute; puede hacer en caso de que nos haga falta.&lt;/p&gt;

&lt;p&gt;Para trabajar con Arel directamente en Rails 3 se puede acceder a la &lt;code&gt;arel_table&lt;/code&gt; de un modelo.  Vamos a recuperar la tabla de Arel para el modelo de producto en la consola y se la asignaremos a una varialbe.&lt;/p&gt;

&lt;pre class="terminal"&gt;
&amp;gt; t = Product.arel_table
&lt;/pre&gt;

&lt;p&gt;Se trata de una representaci&amp;oacute;n de la tabla de productos.  Podemos acceder a las columnas de la tabla as&amp;iacute;:&lt;/p&gt;


&lt;pre class="terminal"&gt;
&amp;gt;t[:price]
 =&amp;gt; &amp;lt;Attribute price&amp;gt;
&lt;/pre&gt; 

&lt;p&gt;Y sobre este objeto vamos a invocar m&amp;eacute;todos sobre un atributo para obtener condiciones de b&amp;uacute;squeda.  Por ejemplo, si queremos encontrar todos los productos que cuestan 2.99&amp;euro; podemos ejecutar&lt;/p&gt;

&lt;pre class="terminal"&gt;
&amp;gt; t[:price].eq(2.99)
 =&amp;gt; #&amp;lt;Arel::Predicates::Equality:0x1040dd9f0 @operand1=&amp;lt;Attribute price&amp;gt;, @operand2=2.99&amp;gt;
&lt;/pre&gt; 

&lt;p&gt;Esto devuelve un predicado (que b&amp;aacute;sicamente es la condici&amp;oacute;n de b&amp;uacute;squeda)  Existen tambi&amp;eacute;n otros m&amp;eacute;todos que podr&amp;iacute;amos llamar, como por ejemplo &lt;code&gt;matches&lt;/code&gt; que ejecutar&amp;aacute; una consulta &lt;code&gt;LIKE&lt;/code&gt;.A  Al igual que con otras consultas podemos ejecutar  &lt;code&gt;to_sql&lt;/code&gt; para examinar la sentencia SQL que generar&amp;aacute; esta consulta.&lt;/p&gt;

&lt;pre class="terminal"&gt;
&amp;gt; t[:name].matches(&amp;#x27;%lore&amp;#x27;).to_sql
 =&amp;gt; &amp;quot;\&amp;quot;products\&amp;quot;.\&amp;quot;name\&amp;quot; LIKE &amp;#x27;%lore&amp;#x27;&amp;quot;
&lt;/pre&gt; 

&lt;p&gt;Podemos usar el m&amp;eacute;todo &lt;code&gt;or&lt;/code&gt; para encadenar predicados, por ejemplo para encontrar los productos que cuestan 2.99&amp;euro; y cuyo nombre termina en &amp;lsquo;lore&amp;rsquo;.&lt;/p&gt;

&lt;pre class="terminal"&gt;
t[:price].eq(2.99).or(t[:name].matches(&amp;#x27;%lore&amp;#x27;))
&lt;/pre&gt;

&lt;p&gt;Esto devolver&amp;aacute; un predicado combinado, y podemos ver la consulta SQL al igual que antes invocando &lt;code&gt;to_sql&lt;/code&gt;.&lt;/p&gt;

&lt;pre class="terminal"&gt;
&amp;gt; t[:price].eq(2.99).or(t[:name].matches(&amp;#x27;%lore&amp;#x27;)).to_sql
 =&amp;gt; &amp;quot;(\&amp;quot;products\&amp;quot;.\&amp;quot;price\&amp;quot; = 2.99 OR \&amp;quot;products\&amp;quot;.\&amp;quot;name\&amp;quot; LIKE &amp;#x27;%lore&amp;#x27;)&amp;quot;
&lt;/pre&gt; 

&lt;p&gt;Podemos pasar estos predicados como argumentos al m&amp;eacute;todo &lt;code&gt;where&lt;/code&gt; de ActiveRecord.  Esto significa que podemos pasar el predicado que acabamos de crear a &lt;code&gt;Product.where&lt;/code&gt; para recuperar todos los productos cuyo precio sea de 2.99&amp;euro; o cuyo nombre termine en &amp;lsquo;lore&amp;rsquo;.&lt;/p&gt;

&lt;pre class="terminal"&gt;
&amp;gt;   Product.where(t[:price].eq(2.99).or(t[:name].matches(&amp;#x27;%lore&amp;#x27;)))
 =&amp;gt; [#&amp;lt;Product id: 2, name: &amp;quot;Milk&amp;quot;, price: 2.99, discontinued: nil, category_id: 2, created_at: &amp;quot;2010-05-24 21:02:38&amp;quot;, updated_at: &amp;quot;2010-05-24 21:02:38&amp;quot;&amp;gt;, #&amp;lt;Product id: 4, name: &amp;quot;Knight Lore&amp;quot;, price: 2.99, discontinued: nil, category_id: nil, created_at: &amp;quot;2010-05-26 19:36:02&amp;quot;, updated_at: &amp;quot;2010-05-26 19:36:02&amp;quot;&amp;gt;]
&lt;/pre&gt;

&lt;p&gt;Tan s&amp;oacute;lo hemos ara&amp;ntilde;ado la superficie que nos revela ActiveRecord de Arel, que es capaz de mucho m&amp;aacute;s.  Hay varios &lt;em&gt;plugins&lt;/em&gt; disponibles que aprovechan la potencia de Arel y le dan una nterfaz m&amp;aacute;s conveniente como  &lt;a href="http://metautonomo.us/projects/metawhere/"&gt;MetaWhere&lt;/a&gt; con el cual podemos acceder a m&amp;eacute;todos de Arel como  &lt;code&gt;matches&lt;/code&gt; y &lt;code&gt;eq&lt;/code&gt; dentro del &lt;em&gt;hash&lt;/em&gt; de condiciones as&amp;iacute;:&lt;/p&gt;

&lt;pre class="ruby"&gt;
Product.where(:price.eq =&amp;gt; 2.99, :name.matches =&amp;gt; &amp;#x27;%lore&amp;#x27;)
&lt;/pre&gt;

&lt;p&gt;Esto nos da mucha m&amp;aacute;s flexibilidad a la hora de definir el &lt;em&gt;hash&lt;/em&gt; de condiciones, y merece la pena tenerlo en cuenta si vamos a necesitar hacer consultas m&amp;aacute;s complejas sobre nuestros modelos.&lt;/p&gt;
</description>
      <pubDate>Sat, 29 May 2010 08:36:39 +0000</pubDate>
      <guid>http://es.asciicasts.com/episodes/215-consultas-avanzadas-con-rails-3</guid>
      <link>http://es.asciicasts.com/episodes/215-consultas-avanzadas-con-rails-3</link>
    </item>
    <item>
      <title>Tests A/B con A/Bingo</title>
      <description>&lt;p&gt;Los tests A/B son una forma de experimentar con variantes de una aplicaci&amp;oacute;n y determinar cu&amp;aacute;les de ellas son m&amp;aacute;s efectivas.  A continuaci&amp;oacute;n se muestra el formulario de registro de una aplicaci&amp;oacute;n.  Al comienzo del formulario hay un p&amp;aacute;rrafo que explica lo que tiene que hacer el usuario y queremos determinar c&amp;oacute;mo de efectivo es este p&amp;aacute;rrafo a la hora de convencer a los usuarios para que se registren, y para esto utilizaremos un test  A/B.&lt;/p&gt;

&lt;div class="imageWrapper"&gt;
  &lt;img src="/system/photos/386/original/E214I01.png" width="808" height="495" alt="La pagina de registro de la aplicaci&#243;n."/&gt;
&lt;/div&gt;

&lt;p&gt;El funcionamiento es el siguiente: a cada visitante de la p&amp;aacute;gina se le mostrar&amp;aacute; o no el p&amp;aacute;rrafo de la parte superior, y tenemos que determinar qu&amp;eacute; opci&amp;oacute;n da los mejores resultados.  Para esto tenemos que definir un evento que, cuando ocurra, nos indicar&amp;aacute; que la opci&amp;oacute;n ha tenido &amp;eacute;xito.  En nuestro caso este evento ocurrir&amp;aacute; cuando el usuario rellene el formulario y lo env&amp;iacute;e.&lt;/p&gt;

&lt;h3&gt;C&amp;oacute;mo escoger la herramienta adecuada&lt;/h3&gt;

&lt;p&gt;Hay varias herramientas disponibles para hacer tests A/B.  Una de las que podr&amp;iacute;amos utiliar es &lt;a href="http://www.google.com/websiteoptimizer/b/index.html"&gt;Google Website Optimizer&lt;/a&gt;.  No es una tecnolog&amp;iacute;a espec&amp;iacute;fica de Rails por lo que puede usarse con cualquier tipo de aplicaci&amp;oacute;n web, incluso con sitios est&amp;aacute;ticos.  Con esta herramienta podemos dar diferentes URLs, o utilizar JavaScript para rastrear las conversiones.  Se trata de una opci&amp;oacute;n bastante buena pero si queremos hacer nuestros tests sobre una aplicaci&amp;oacute;n Rails tendremos que usar algo que se integre dentro de la aplicaci&amp;oacute;n para gestionar el seguimiento propiamente dicho.&lt;/p&gt;

&lt;p&gt;Una de las soluciones espec&amp;iacute;ficas de Rails m&amp;aacute;s populares es&lt;a href="http://vanity.labnotes.org/"&gt;Vanity&lt;/a&gt;.   Devuelve informes de resultados muy vistosos e incluye bastantes funcionalidades pero hoy no vamos a tratar de ella (si bien puede que lo hagamos en alg&amp;uacute;n pr&amp;oacute;ximo episodio).  Puede resultar un poco dif&amp;iacute;cil de configurar porque por motivos de rendimuento necesita una base de datos  &lt;a href="http://code.google.com/p/redis/"&gt;Redis&lt;/a&gt;  pero merece la pena echarle un vistazo para ver si se ajusta a nuestras necesidades.&lt;/p&gt;

&lt;p&gt;El &lt;em&gt;plugin&lt;/em&gt; que vamos a usar es &lt;a href="http://www.bingocardcreator.com/abingo/"&gt;A/Bingo&lt;/a&gt;.  El DSL que trae viene bastante bien para definir experimentos y es f&amp;aacute;cil de usar.  La aplicaci&amp;oacute;n sobre la que haremos nuestros tests est&amp;aacute; escrita con Rails 2, por lo que instalaremos el &lt;em&gt;plugin&lt;/em&gt; con &lt;/p&gt;

&lt;pre class="terminal"&gt;
script/plugin install git://git.bingocardcreator.com/abingo.git
&lt;/pre&gt;

&lt;p&gt;en el directorio de nuestra aplicaci&amp;oacute;n.  Una vez que haya terminado generaremos una migraci&amp;oacute;n con &lt;/p&gt;

&lt;pre class="terminal"&gt;
script/generate abingo_migration
&lt;/pre&gt;

&lt;p&gt;y la ejecutaremos con&lt;/p&gt;
&lt;pre class="terminal"&gt;
rake db:migrate
&lt;/pre&gt;

&lt;p&gt;Con esto se generar&amp;aacute;n dos tablas nuevas en la base de datos en la que se guardar&amp;aacute;n los resultados de nuestros tests: &lt;code&gt;experiments&lt;/code&gt; y &lt;code&gt;alternatives&lt;/code&gt; .&lt;/p&gt;

&lt;p&gt;En el entorno de producci&amp;oacute;n es recomendable que configuremos la cach&amp;eacute; en A/Bingo para que utilice algo como &lt;code&gt;memcached&lt;/code&gt; pero nos saltaremos esa parte porque s&amp;oacute;lo vamos a usar el entorno de desarrollo.&lt;/p&gt;

&lt;h3&gt;Nuestro primer test&lt;/h3&gt;

&lt;p&gt;Como ya hemos instalado A/Bingo podemos escribir nuestro primer test.  Queremos mostrar u ocultar el p&amp;aacute;rrafo de la parte superior del formulario de registro de forma que aparezca para algunos usuarios y otros no lo vean.  Esto lo podemos hacer con el m&amp;eacute;todo &lt;code&gt;ab_test&lt;/code&gt;:&lt;/p&gt;


&lt;p class="codeFilePath"&gt;/app/views/users/new.html.erb&lt;/p&gt;
&lt;pre class="ruby"&gt;
&amp;lt;% title (&amp;quot;Sign up&amp;quot;) %&amp;gt;

&amp;lt;% if ab_test &amp;quot;signup_intro&amp;quot; %&amp;gt;
&amp;lt;p&amp;gt;Complete the form below to create a new user account. You will then be able 
to create projects and tasks, and mark them as complete when finishing them.&amp;lt;/p&amp;gt;
&amp;lt;% end %&amp;gt;
&amp;lt;p&amp;gt;Already have an account? &amp;lt;%= link_to &amp;quot;Log in&amp;quot;, new_user_session_path %&amp;gt;.&amp;lt;/p&amp;gt;

&amp;lt;% form_for @user do |form| %&amp;gt;
 &amp;lt;!-- se omite el formulario --&amp;gt;
&amp;lt;% end %&amp;gt;
&lt;/pre&gt;

&lt;p&gt;El primer argumento que se pasa a &lt;code&gt;ab_test&lt;/code&gt; es el nombre del test, que en este caso hemos llamado  &lt;code&gt;signup-intro&lt;/code&gt;.  Si no proporcionamos m&amp;aacute;s argumentos este m&amp;eacute;todo devolver&amp;aacute; aleatoriamente &lt;code&gt;true&lt;/code&gt; o &lt;code&gt;false&lt;/code&gt; para los diferentes visitantes del sitio por lo que el p&amp;aacute;rrafo se mostrar&amp;aacute; u ocultar&amp;aacute; dependiendo de qui&amp;eacute;n cargue la p&amp;aacute;gina.&lt;/p&gt;

&lt;p&gt;A continuaci&amp;oacute;n a&amp;ntilde;adiremos el evento para poder rastrear los resultados.  Queremos que se registre dicho evento cuando se guarde el usuario correctamente, para lo que modificaremos la acci&amp;oacute;n &lt;code&gt;create&lt;/code&gt; del controlador &lt;code&gt;UsersController&lt;/code&gt;.&lt;/p&gt;

&lt;p class="codeFilePath"&gt;/app/controllers/users_controller.rb&lt;/p&gt;
&lt;pre class="ruby"&gt;
def create
  @user = User.new(params[:user])
  if @user.save
    bingo! &amp;quot;signup_intro&amp;quot;
    session[:user_id] = @user.id
    flash[:notice] = &amp;quot;Thank you for signing up. You are now logged in.&amp;quot;
    redirect_to root_url
  else
    render :action =&amp;gt; &amp;#x27;new&amp;#x27;
  end
end
&lt;/pre&gt;

&lt;p&gt;Tan s&amp;oacute;lo tenemos que invocar el m&amp;eacute;todo &lt;code&gt;bingo!&lt;/code&gt; en la acci&amp;oacute;n &lt;code&gt;create&lt;/code&gt; y pasarle el nombre del test.&lt;/p&gt;

&lt;p&gt;Si recargamos el formulario de registro veremos que esta vez  &lt;code&gt;ab_test&lt;/code&gt; ha devuelto &lt;code&gt;false&lt;/code&gt; por lo que no vemos el p&amp;aacute;rrafo de la parte de superior.  Podemos recargar la p&amp;aacute;gina todas las veces que queramos pero el p&amp;aacute;rrafo no se va a mostrar porque se ha registrado nuestra identidad (en breve explicaremos c&amp;oacute;mo).&lt;/p&gt;

&lt;div class="imageWrapper"&gt;
  &lt;img src="/system/photos/387/original/E214I02.png" width="808" height="495" alt="El parrafo de la parte superior no aparece."/&gt;
&lt;/div&gt;

&lt;p&gt;Si completamos el formulario y lo enviamos se activar&amp;aacute; el evento que hemos definido.&lt;/p&gt;


&lt;div class="imageWrapper"&gt;
  &lt;img src="/system/photos/388/original/E214I03.png" width="808" height="280" alt="Cuando nos registremos se dispara nuestro evento."/&gt;
&lt;/div&gt;

&lt;h3&gt;Visualizaci&amp;oacute;n de resultados&lt;/h3&gt;

&lt;p&gt;Podemos ver los resultados de nuestro test creando un controlador para ver dichos resultados.   Nosotros lo llamaremos &lt;code&gt;abingo_dashboard&lt;/code&gt;.&lt;/p&gt;

&lt;pre class="terminal"&gt;
script/generate controller abingo_dashboard
&lt;/pre&gt;

&lt;p&gt;En este controlador s&amp;oacute;lo tenemos que incluir el m&amp;oacute;dulo correspondiente de A/Bingo.&lt;/p&gt;

&lt;p class="codeFilePath"&gt;/app/controllers/abingo_dashboard_controller.rb&lt;/p&gt;
&lt;pre class="ruby"&gt;
class AbingoDashboardController &amp;lt; ApplicationController
  # PENDIENTE poner autorizacion
  include Abingo::Controller::Dashboard
end
&lt;/pre&gt;

&lt;p&gt;Queda claro que si pusi&amp;eacute;semos esta aplicaci&amp;oacute;n en producci&amp;oacute;n no querr&amp;iacute;amos que cualquiera viese este panel de control por lo que tendr&amp;iacute;amos que a&amp;ntilde;adir autorizaci&amp;oacute;n al controlador.&lt;/p&gt;

&lt;p&gt;Tambi&amp;eacute;n tenemos que a&amp;ntilde;adir una nueva ruta para que podamos acceder a este nuevo controlador.&lt;/p&gt;


&lt;p class="codeFilePath"&gt;/config/routes.rb&lt;/p&gt;
&lt;pre class="ruby"&gt;
map.abingo_dashboard &amp;quot;/abingo/:action/:id&amp;quot;, :controller =&amp;gt; :abingo_dashboard
&lt;/pre&gt;

&lt;p&gt;Una vez que est&amp;eacute; todo ya podemos visitar &lt;a href="http://localhost:3000/abingo"&gt;http://localhost:3000/abingo&lt;/a&gt; y veremos nuestro panel.&lt;/p&gt;

&lt;div class="imageWrapper"&gt;
  &lt;img src="/system/photos/389/original/E214I04.png" width="823" height="433" alt="El cuadro de mandos de A/Bingo."/&gt;
&lt;/div&gt;

&lt;p&gt;Es f&amp;aacute;cil modificar las plantillas de vistas para darle los estilos que queramos a este cuadro de mandos.  Sin embargo incluso con los estilos por defecto podemos ver los resultados de los experimentos, as&amp;iacute; que por ahora no los cambiaremos.  Si nos fijamos en los resultados veremos que tenemos un experimento con un participante, que hemos sido nosotros mismos en la visita anterior a la p&amp;aacute;gina de registro, y una conversi&amp;oacute;n finalizada que ocurri&amp;oacute; cuando enviamo el formulario.  El p&amp;aacute;rrafo de la parte superior del formulario no se estaba mostrando por lo que el participante y su conversi&amp;oacute;n aparecen en la secci&amp;oacute;n de resultados &amp;quot;false&amp;quot;.&lt;/p&gt;


&lt;h3&gt;C&amp;oacute;mo se identifica a los usuarios&lt;/h3&gt;

&lt;p&gt;Antes vimos que si volv&amp;iacute;amos a visitar el formulario de registro no ve&amp;iacute;amos nunca el p&amp;aacute;rrafo porque A/Bingo siempre consideraba que nuestra identidad era la misma.  Para esto tenemos que darle instrucciones sobre c&amp;oacute;mo distinguir unos usuarios de otros.  Esto se hace en el controlador de aplicaci&amp;oacute;n escribiendo un  &lt;code&gt;before_filter&lt;/code&gt;.&lt;/p&gt;

&lt;p class="codeFilePath"&gt;/app/controllers/application_controller.rb&lt;/p&gt;
&lt;pre class="ruby"&gt;
class ApplicationController &amp;lt; ActionController::Base
  helper :all # include all helpers, all the time
  protect_from_forgery # See ActionController::RequestForgeryProtection for details
  before_filter :set_abingo_identity

  private
  def set_abingo_identity
    session[:abingo_identity] ||= rand(10 ** 10)
    Abingo.identity = session[:abingo_identity]
  end
end
&lt;/pre&gt;

&lt;p&gt;Hemos a&amp;ntilde;adido el filtro llamado  &lt;code&gt;set_abingo_identity&lt;/code&gt; y en dicho m&amp;eacute;todo hemos puesto el c&amp;oacute;digo que determina la identidad de cada usuario.  En primer lugar se comprueba la existencia de una variable de sesi&amp;oacute;n llamada  &lt;code&gt;abingo_identity&lt;/code&gt; y si no existe la crea con un valor num&amp;eacute;rico aleatorio lo que significa que mientra el usuario mantenga su sesi&amp;oacute;n ser&amp;aacute; identificado como la misma persona por A/Bingo y ver&amp;aacute;n o no el p&amp;aacute;rrafo siempre de la misma forma.&lt;/p&gt;

&lt;p&gt;Si tuvi&amp;eacute;ramos autenticaci&amp;oacute;n de usuarios en nuestro aplicaci&amp;oacute;n querriamos que un usuario registrado viera siempre lo mismo independientemente de que cambiase su sesi&amp;oacute;n o est&amp;eacute;n utilizando un ordenador o navegador distinto.  Podemos cambiar el m&amp;eacute;todo  &lt;code&gt;set_abingo_identity&lt;/code&gt; para que utilice el id del usuario si hay una sesi&amp;oacute;n iniciada o volver al m&amp;eacute;todo basado en &lt;em&gt;cookie&lt;/em&gt; de sesi&amp;oacute;n para usuarios an&amp;oacute;nimos.&lt;/p&gt;

&lt;p class="codeFilePath"&gt;/app/controllers/application_controller.rb&lt;/p&gt;
&lt;pre class="ruby"&gt;
def set_abingo_identity
  if current_user
    Abingo.identity = current_user.id
  else
    session[:abingo_identity] ||= rand(10 ** 10)
    Abingo.identity = session[:abingo_identity]
  end
end
&lt;/pre&gt;      

&lt;p&gt;Tambi&amp;eacute;n podemos comprobar si la visita viene de una ara&amp;ntilde;a web y darle a cada robot la misma identidad para que los resultados no se vean afectados por estos visitantes no humanos.&lt;/p&gt;

&lt;p class="codeFilePath"&gt;/app/controllers/application_controller.rb&lt;/p&gt;
&lt;pre class="ruby"&gt;
def set_abingo_identity
  if request.user_agent =~ /\b(Baidu|Gigabot|Googlebot|libwww-perl|lwp-trivial|msnbot|SiteUptime|Slurp|WordPress|ZIBB|ZyBorg)\b/i
     Abingo.identity = &amp;quot;robot&amp;quot;
   elsif current_user
     Abingo.identity = current_user.id
   else
     session[:abingo_identity] ||= rand(10 ** 10)
     Abingo.identity = session[:abingo_identity]
   end
end
&lt;/pre&gt;

&lt;p&gt;Comprobamos si se trata de un robot comparando la cadena de agente de usuario contra una lista de nombres conocidos y si coincide, se establece el valor de identidad igual a la cadena &amp;ldquo;robot&amp;rdquo;.  De esta forma todos los robots que visiten la p&amp;aacute;gina ser&amp;aacute;n identificados como el mismo usuario.&lt;/p&gt;

&lt;p&gt;Durante el desarrollo de la aplicaci&amp;oacute;n, he visitado la p&amp;aacute;gina de alta con diferentes identidades y me he registrado un par de veces, lo que queda reflejado en el cuadro de mandos de A/Bingo.  Ahora aparecen ocho participantes, dos de los cuales vieron el p&amp;aacute;rrafo y seis no.&lt;/p&gt;

&lt;div class="imageWrapper"&gt;
  &lt;img src="/system/photos/390/original/E214I05.png" width="823" height="545" alt="Ya aparecen mas resultados."/&gt;
&lt;/div&gt;

&lt;h3&gt;Tests m&amp;aacute;s complejos&lt;/h3&gt;

&lt;p&gt;Vamos a terminar el episodio mostrando c&amp;oacute;mo se pueden introducir m&amp;uacute;ltiples opciones diferentes para un &amp;uacute;nico test.  El test &lt;code&gt;signup_into&lt;/code&gt; es un test sencillo pero tambi&amp;eacute;n nos puede interesar tener m&amp;aacute;s de dos posibilidades (por ejemplo si queremos probar con muchos t&amp;iacute;tulos diferentes de p&amp;aacute;gina).&lt;/p&gt;

&lt;p&gt;Es importante tener siempre en cuenta que si tenemos m&amp;uacute;ltiples tests en una &amp;uacute;nica p&amp;aacute;gina nuestros resultados pueden sesgarse hacia las opciones que aparezcan con m&amp;aacute;s frecuencia. Esto es muy importante en aplicaciones en producci&amp;oacute;n pero como esto es tan s&amp;oacute;lo una aplicaci&amp;oacute;n de ejemplo no nos preocupa.&lt;/p&gt;

&lt;p&gt;Podemos llamar a &lt;code&gt;ab_test&lt;/code&gt; con dos argumentos para crear un test con m&amp;uacute;ltiples opciones.  El segundo par&amp;aacute;metro ser&amp;aacute; un &lt;em&gt;array&lt;/em&gt; con las diferentes opciones que queremos testar.  En nuestro caso queremos probar con tres t&amp;iacute;tulos diferentes de p&amp;aacute;gina y podemos cambiar la p&amp;aacute;gina de alta para mostrar uno de ellos :&lt;/p&gt;

&lt;p class="codeFilePath"&gt;/app/views/users/new.html.erb&lt;/p&gt;
&lt;pre class="ruby"&gt;
&amp;lt;% title ab_test(&amp;quot;signup_title&amp;quot;, [&amp;quot;Sign up&amp;quot;, &amp;quot;Registration&amp;quot;, &amp;quot;Free Sign up&amp;quot;]) %&amp;gt;
&lt;/pre&gt;

&lt;p&gt;Con esto cada usuario que visite la p&amp;aacute;gina ver&amp;aacute; uno de estos t&amp;iacute;tulos de forma aleatoria.  Por supuesto, esto es trivial para algo tan sencillo como poner el t&amp;iacute;tulo de la p&amp;aacute;gina pero si queremos utilizar el valor aleatorio m&amp;aacute;s de una vez en la misma p&amp;aacute;gina podemos pasarle un bloque a &lt;code&gt;ab_test&lt;/code&gt;.&lt;/p&gt;

&lt;p class="codeFilePath"&gt;/app/views/users/new.html.erb&lt;/p&gt;
&lt;pre class="ruby"&gt;
&amp;lt;% ab_test(&amp;quot;signup_title&amp;quot;, [&amp;quot;Sign up&amp;quot;, &amp;quot;Registration&amp;quot;, &amp;quot;Free Sign up&amp;quot;]) do |signup_title| %&amp;gt;
&amp;lt;% title signup_title %&amp;gt;
&amp;lt;% end %&amp;gt;
&lt;/pre&gt;

&lt;p&gt;Es posible, si no estamos usando la &amp;uacute;ltima versi&amp;oacute;n de A/Bingo, que veamos el mensaje &lt;code&gt;&amp;ldquo;can&amp;rsquo;t modify frozen array&amp;rdquo;&lt;/code&gt;.  Ryan Bates ha enviado un parche para corregir este problema que ha sido aceptado, por lo que si vemos este error podemos intentarlo otra vez tras actualizar la versi&amp;oacute;n del &lt;em&gt;plugin&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Cuando visitemos la p&amp;aacute;gina de registro veremos una de las opciones del t&amp;iacute;tulo y al p&amp;aacute;gina mostrar&amp;aacute; o no el p&amp;aacute;rrafo de la parte superior.  Pero a&amp;uacute;n nos queda una cosa por hacer.  Aunque hemos configurado el test en la vista, no estamos registrando si ha tenido &amp;eacute;xito o no en el controlador, lo que podemos hacer a&amp;ntilde;adiendo otra llamada a  &lt;code&gt;bingo!&lt;/code&gt; in la acci&amp;oacute;n &lt;code&gt;create&lt;/code&gt;:&lt;/p&gt;

&lt;p class="codeFilePath"&gt;/app/controllers/users_controller.rb&lt;/p&gt;
&lt;pre class="ruby"&gt;
def create
  @user = User.new(params[:user])
  if @user.save
    bingo! &amp;quot;signup_intro&amp;quot;
    bingo! &amp;quot;signup_title&amp;quot;
    session[:user_id] = @user.id
    flash[:notice] = &amp;quot;Thank you for signing up. You are now logged in.&amp;quot;
    redirect_to root_url
  else
    render :action =&amp;gt; &amp;#x27;new&amp;#x27;
  end
end
&lt;/pre&gt;      

&lt;p&gt;Cabr&amp;iacute;a sospechar que esto se nos puede ir de las manos si acabamos con muchos tests sobre una acci&amp;oacute;n determinada.  En estos casos conviene hacer una &amp;uacute;nica llamada al m&amp;eacute;todo &lt;code&gt;bingo!&lt;/code&gt; con el nombre de una conversi&amp;oacute;n y utilizar el nombre de dicha conversi&amp;oacute;n como un argumento extra del m&amp;eacute;todo &lt;code&gt;ab_test&lt;/code&gt; en la vista, y de esta forma podemos reemplazar las dos llamadas a &lt;code&gt;bingo!&lt;/code&gt; por una:&lt;/p&gt;

&lt;p class="codeFilePath"&gt;/app/controllers/users_controller.rb&lt;/p&gt;
&lt;pre class="ruby"&gt;
def create
  @user = User.new(params[:user])
  if @user.save
    bingo! &amp;quot;signup&amp;quot;
    session[:user_id] = @user.id
    flash[:notice] = &amp;quot;Thank you for signing up. You are now logged in.&amp;quot;
    redirect_to root_url
  else
    render :action =&amp;gt; &amp;#x27;new&amp;#x27;
  end
end
&lt;/pre&gt;

&lt;p&gt;... siempre que en la vista le pasemos el nombre de la conversi&amp;oacute;n como parte del &lt;em&gt;hash&lt;/em&gt; de argumentos.&lt;/p&gt;

&lt;p class="codeFilePath"&gt;/app/views/users/new.html.erb&lt;/p&gt;
&lt;pre class="ruby"&gt;
&amp;lt;% ab_test(&amp;quot;signup_title&amp;quot;, [&amp;quot;Sign up&amp;quot;, &amp;quot;Registration&amp;quot;, &amp;quot;Free Sign up&amp;quot;], :conversion =&amp;gt; &amp;quot;signup&amp;quot;) do |signup_title| %&amp;gt;
  &amp;lt;% title signup_title %&amp;gt;
&amp;lt;% end %&amp;gt;

&amp;lt;% if ab_test &amp;quot;signup_intro&amp;quot;, nil, :conversion =&amp;gt; &amp;quot;signup&amp;quot; %&amp;gt;
&amp;lt;!-- resto de la vista --&amp;gt;
&lt;/pre&gt;

&lt;p&gt;N&amp;oacute;tese que como el segundo test es de tipo booleano simple hemos pasado &lt;code&gt;nil&lt;/code&gt; como segundo argumento.&lt;/p&gt;

&lt;p&gt;Y con esto cerramos este episodio.  Los tests A/B son una forma excelente de experimentar con variantes de una aplicaci&amp;oacute;n web y medir el &amp;eacute;xito de sus resultados.  Desde aqu&amp;iacute; os aninamos a probarlo en vuestras aplicaciones usando Google Website Optimizer, Vanity o A/Bingo.&lt;/p&gt;</description>
      <pubDate>Thu, 20 May 2010 21:54:45 +0000</pubDate>
      <guid>http://es.asciicasts.com/episodes/214-tests-a-b-con-a-bingo</guid>
      <link>http://es.asciicasts.com/episodes/214-tests-a-b-con-a-bingo</link>
    </item>
  </channel>
</rss>
