homeASCIIcasts

222: Rack y Rails 3 

(view original Railscast)

Other translations: En It

Other formats:

Written by Juan Lupión

En el episodio 203 [verlo, leerlo] 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.

Rutas para aplicaciones Rack

Comenzaremos con un ejemplo sencillo. Nuestra aplicación de ejemplo tiene una ruta que apunta su URL raíz a la acción index del controlador HomeController’s index.

/config/routes.rb

Store::Application.routes.draw do |map|
  root :to => "home#index"
end

Lo importante aquí es saber que la cadena "home#index" es una abreviatura y por debajo dicha cadena se convierte a:

HomeController.action(:index)

El método action devuelve una aplicación Rack, y por tanto es fácil obtener una aplicación Rack a partir de cualquier controlador Rails. Como demostración, vamos a crear una aplicación Rack sencilla pasándole el hash de entorno a un nuevo objeto proc, devolviendo un array con un código de estado, un hash de cabeceras vacío y el contenido.

/config.routes.rb

Store::Application.routes.draw do |map|
  root :to => proc { |env| [200, {}, ["Welcome"]] }
end

Si visitamos la URL raíz de nuestra aplicación veremos dicha respuesta.

La salida de nuestra aplicación Rack.

Toda esta flexibilidad hace que sea muy fácil integrar cualquier aplicación basada en Rack dentro de una aplicación Rails. Veámoslo con Sinatra.

Primero tenemos que añadir una referencia a la gema Sinatra en el fichero Gemfile de nuestra aplicación.

/Gemfile

gem 'sinatra'

Nos aseguraremos de que la gema está instalada ejecutando

bundle install

Ya podemos escribir nuestra aplicación Sinatra. La vamos a poner en el directorio /lib, y la llamaremos home_app.rb. Tenemos que crear una clase que herede de Sinatra::Base con un método get para la URL raíz que devuelve una cadena.

/lib/home_app.rb

class HomeApp < Sinatra::Base
  get "/" do
    "Hello from Sinatra"
  end
end

Ahora ya podemos actualizar el fichero de rutas para que la ruta raíz apunte a nuestra aplicación Rack HomeApp.

/config/routes.rb

Store::Application.routes.draw do |map|
  root :to => HomeApp
end

Si recargamos la portada de la aplicación ahora veremos la respuesta de la aplicación Sinatra.

La salida de nuestra aplicación Sinatra.

Redirecciones

Uno de los beneficios de la integración de Rack con Rails es el nuevo método redirect, que veremos a continuación cómo funciona. Supongamos que nuestra aplicación tiene una ruta para mostra una página de información con la URL /about, que está mapeada a la acción about del controlador InfoController.

/config/routes.rb

Store::Application.routes.draw do |map|
  root :to => HomeApp
  match "/about" => "info#about" 
end

Si por ejemplo queremos cambiar la URL de /about a /aboutus podríamos cambiar fácilmente la ruta, pero entonces tendríamos que ver qué hacemos con la URL antigua que ya no mapea a ninguna acción. Pero ahora con Rails 3 podemos hacer redirecciones fácilmente con el método redirect.

/config/routes.rb

Store::Application.routes.draw do |map|
  root :to => HomeApp
  match "/about" => redirect("/aboutus")
  match "/aboutus" => "info#about" 
end

Con esta redirección la antigua ruta redirigirá a la nueva, por tanto si visitamos la URL /about seremos redirigidos a /aboutus.

Aquí se muestra la página redirigida.

Veamos a continuación un ejemplo del uso de redirect un poco más complicado. Supongamos que tenemos un controlador ProductsController y queremos tener una URL de atajo de la forma /p/:id para cada producto. Para eso podemos modificar las rutas de esta forma:

/config/routes.rb

Store::Application.routes.draw do |map|
  get "info/about"

  root :to => HomeApp
  match "/about" => redirect("/aboutus")
  match "/aboutus" => "info#about" 
  
  resources :products
  match "/p/:id" => redirect("/products/%{id}")
end

La ruta abreviada de un producto es la última ruta del archivo. Redirige la URL abreviada a la URL normal de un producto utilizando otra vez el método redirect pero esta vez lo hace a una URL dinámica. Para poder poner el id del producto en la URL redirigida tenemos que usar un signo de porcentaje seguido del parámetro id rodeado de llaves.

Si ahora visitamos la URL http://localhost:3000/p/1 seremos redirigidos a la página de ese producto.

La página redirigida de producto.

Aunque no lo veremos por ahora, si necesitamos más control sobre la redirección le podemos pasar un bloque al método redirect. Los detalles sobre cómo hacerlo están en el sitio de rails.info.

Rails Metal

En el episodio 150 repasamos Rails Metal [verlo, leero] y creamos una página que listaba los procesos en ejecución en un momento dado. En Rails 3 la técnica es bastante diferente pero es mucho más fácil de usar gracias a la integración de Rack. Primero tenemos que crear la nueva ruta y hacer que apunte a la nueva aplicación Rack que bautizaremos como ProcessesApp.

/config/routes.rb

match "/processes" => ProcessesApp

Crearemos la aplicación en el directorio /lib, donde editaremos el fichero processes_app.rb. La clase tendrá un método de clase llamado call que recibe un hash de entorno, en el cual ejecutaremos el mismo comando que en el episodio 150 y devolveremos el resultado.

/lib/processes_app.rb

class ProcessesApp
  def self.call(env)
    [200, {}, [`ps -axcr -o "pid,pcpu, pmem, time, comm"`]]
  end
end

Si vamos a la página de procesos en el navegador, veremos el listado de procesos, y se puede comprobar que la página se recarga muy rápidamente porque estamos usando una sencilla aplicación Rack.

Nuestra aplicación de procesos.

La clase ProcessApp no tiene nada de especial (en realidad se trata de una aplicación Rack sencilla), pero queda claro que con unas pocas líneas de código hemos reproducido el comportamiento del episodio 150 de una manera mucho más fácil.

No tenemos control alguno sobre el aspecto de la página de procesos porque la respuesta no pasa por ninguna plantilla. Sin embargo podemos expandir nuestra aplicación Rack para que incluya el comportamiento de un controlador Rails, lo que permitiría usar plantillas y hacer que la página fuese menos plana.

Para que la página de procesos pueda mostrar una plantilla erb en lugar de la salida en crudo del comando ps tan sólo tenemos que asegurarnos de que la clase ProcessesApp herede de ActionController::Metal, con lo que adquirirá ciertos comportamientos de los controladores de nuestra aplicación Rails, pero lo hará a un nivel tan bajo que tenemos también que incluir ActionController::Rendering para tener todo lo que nos hace falta.

Dado que el código que estamos usando funciona a un nivel más bajo que un controlador Rails normal tenemos que especificar la ubicación de las vistas invocando al método de Rails append_view_path y pasándole el path al directorio views de nuestra aplicación, con lo que nuestro controlador busque sus plantillas en el mismo lugar que un controlador normal.

Queremos que el controlador tenga una acción index por lo que hemos cambiado el método self.call por un método index que hace la misma llamada a ps pero asignando la salida a una variable de instancia. Después el método llama a render para mostrar la plantilla. Al contrario que en un controlador Rails normal la plantilla se muestra automáticamente.

/lib/processes_app.rb

class ProcessesApp < ActionController::Metal
  include ActionController::Rendering
  
  append_view_path "#{Rails.root}/app/views"
  
  def index
    @processes = `ps -axcr -o "pid,pcpu, pmem, time, comm"`
    render
  end
end

Ahora tenemos que crear la plantilla. En el directorio views de nuestra aplicación crearemos el directorio process_app en el que pondremos nuestra nueva plantilla, en la que podemos escribir el código que queramos en este archivo como haríamos con una plantilla erb normal.

/app/views/processes_app/index.html.erb

<h1>Processes</h1>
<pre><%= @processes %></pre>

Ya casi hemos terminado. Lo único que tenemos que hacer es modificar la ruta para que apunte a esta acción específica.

/config/routes.rb

match "/processes" => ProcessesApp.action(:index)

Si reiniciamos el servidor y visitamos otra vez la página de procesos veremos que ya se usa nuestra plantilla.

La página de procesos con su plantilla.

Tal y como acabamos de ver, Rails 3 es muy modular y nos permite poner en nuestras aplicaciones Rack justo sólo la funcionalidad que queramos. Hay que tener en cuenta que escribir un controlador de esta manera sólo nos ahorrará 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ón Rack tal y como hemos hecho en este episodio.