#222 Rack in Rails 3
- Download:
- source codeProject Files in Zip (99.9 KB)
- mp4Full Size H.264 Video (12.9 MB)
- m4vSmaller H.264 Video (8.73 MB)
- webmFull Size VP8 Video (21.4 MB)
- ogvFull Size Theora Video (16.2 MB)
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
.
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.
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.
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.
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.
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
.
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.
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
.
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
.
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
.
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:
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.
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
.
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.
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.
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.
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.
<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.
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.
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.