lunes, 13 de junio de 2011

Scoping de rutas en Rails 3

En sistema de rutas de Rails es el que se encarga de:
  • Identificar peticiones entrantes y redireccionarlas a una acción concreta de un controlador
  • Definir patrones que servirán para generar URLs dirigidas hacia nuestras acciones

Rails nos permite gran cantidad de opciones de configuración para definir nuestras rutas.
He pensado que puede ser útil tomar aquí nota de las principales opciones de scoping que suelen ser más utilizadas.

El método scope

El método scope nos permite aplicar configuraciones a conjuntos de rutas que compartan dichas opciones de configuración.

Por ejemplo, supongamos que queremos definir las siguientes rutas:


match 'timelords/new' => 'timelords#new'
match 'timelords/edit/:id' => 'timelords#edit'
match 'timelords/revive/:id' => 'timelords#revive'


Como vemos, las tres rutas comparten una serie de puntos en común, como son la composición de su URL (siempre entran por 'timelords/*') y el controlador al que dirigen sus peticiones (TimelordsController).

El método scope posee diversas opciones que nos permiten ahorrar código en casos como este:

Controller


scope :controller => :timelords do
match 'timelords/new' => 'new'
match 'timelords/edit/:id' => 'edit'
match 'timelords/revive/:id' => 'revive'
end


La opción controller nos permite especificar el controlador al que pertenecen las acciones a las que vamos a redirigir estas rutas. Además, con un poco de azúcar sintáctico, podemos hacer uso del método scope de una forma más simple:

scope :timelords do
...
end


E incluso más simple aún:

controller :timelords do
...
end




Path prefix

De forma análoga, si lo que queremos es definir el path que van a seguir todas nuestras rutas, podemos utilizar la opción path:

scope :path => '/timelords' do
match 'new' => 'timelords#new'
match 'edit/:id' => 'timelords#edit'
match 'revive/:id' => 'timelords#revive'
end


Para ponernos las cosas mucho más simples, de nuevo, podemos hacer:

scope '/timelords' do
match 'new' => 'timelords#new'
match 'edit/:id' => 'timelords#edit'
match 'revive/:id' => 'timelords#revive'
end


Name prefix

Podemos hacer que todos los helper que genera el sistema de rutas tengan el prefijo que queramos (por ejemplo, admin) utilizando la opción as:

scope :path => '/timelords', :as => 'original' do
match 'new' => 'timelords#new'
match 'edit/:id' => 'timelords#edit'
match 'revive/:id' => 'timelords#revive'
end


Con esto obtendremos helpers como "original_timelords_path", "edit_original_timelord_path" etc.

Module

En caso de que queramos expresar que todas nuestras acciones hacen referencia a un controlador perteneciente a un módulo concreto, usaremos la opción module:

scope :module => 'drwho' do
match 'timelords/new' => 'timelords#new'
match 'timelords/edit/:id' => 'timelords#edit'
match 'timelords/revive/:id' => 'timelords#revive'
end


En este caso, asumimos que existe un controlador DrWho::TimelordsController, al que hacen referencia todas las acciones de nuestras rutas.

Namespace

El método namespace nos permite definir de una sóla vez, las opciones :module, :as y :path, de forma que:

namespace :drwho do
match 'new' => 'timelords#new'
match 'edit/:id' => 'timelords#edit'
match 'revive/:id' => 'timelords#revive'
end


Define rutas como "drwho_timelords_path", "edit_drwho_timelord_path" etc

Nótese que namespace también tiene la opción :controller, de forma que podríamos haberla usado para definir el controlador al que hacen referencia las acciones de dicho namespace. En el ejemplo no se utiliza porque en cada ruta definimos el controlador al que pertenece la acción (a este controlador hay que añadirle el módulo que define el namespace).

Por supuesto, aunque en aquí sólo hemos definido rutas clásicas, el scoping funciona igualmente para rutas REST definidas con resources. Después de todo, resources no es más que un atajo para definir las 7  rutas REST (index, show, new, create, edit, update, destroy).


Extra: Podemos consultar en cualquier momento la ruta que nos devuelve un helper, desde la consola de rails, utilizando el objeto app:

Loading development environment (Rails 3.0.7)
ruby-1.8.7-p248 :001 > app.timelords_path



Y eso es todo por ahora.
En breve ampliaremos el tema con rutas REST y cómo se abordan en Rails :)

domingo, 12 de junio de 2011

Cambio automático de gemsets con RVM

RVM es una herramienta que nos permite fácilmente gestionar distintas versiones de Ruby en el mismo equipo, de forma que podemos trabajar en distintos proyectos que usen distintas versiones de Ruby en nuestra misma máquina sin quebraderos de cabeza.

Además, RVM nos permite definir gemsets.

Los gemsets son conjuntos de gemas que definimos para una versión de Ruby, y que existen (gracias a RVM) aislados unos de otros.
De esta forma, podemos tener al mismo tiempo proyectos que utilicen distintas versiones de una misma gema, cada uno con su correspondiente gemset, sin que se estorben entre ellos.

Es una buena práctica definir un gemset para cada proyecto con el que trabajamos aislando así las instalaciones de gemas que dicho proyecto requiera de las que requieren otros proyectos en nuestra misma máquina (evitando conflictos).

Para crear un gemset para la versión de ruby que tenga configurada RVM en ese momento, hacemos:
$ rvm gemset create NOMBRE_GEMSET

Para utilizar un gemset que exista para la versión de ruby que tenga configurada RVM en ese momento:
$ rvm gemset use NOMBRE_GEMSET

También podemos especificar la versión de ruby y el gemset al que queremos cambiar:
$ rvm gemset use VERSION@NOMBRE_GEMSET

Con esto, conseguimos definir distintos gemsets (como hemos dicho, es buena práctica definir uno por proyecto). Sin embargo, si definimos uno por proyecto, tendremos que cambiar de gemset cada vez que cambiemos de proyecto, lo cual puede ser algo molesto.

Podemos automatizar el cambio de gemset entre proyectos, de nuevo gracias a RVM.

Para hacer esto, primero tenemos que crear un fichero .rvmrc en la raíz de nuestro proyecto. RVM recorre de forma automática la estructura de carpetas hasta que encuentra este fichero, y lo utiliza.
En dicho fichero, basta poner la versión de ruby y el gemset al que queremos cambiar para que RVM cambie automáticamente a dicho gemset cuando entremos en la carpeta del proyecto.

Contenido de .rvmrc:
rvm --create use default@NOMBRE_PROYECTO > /dev/null

Con esto conseguimos:
  • Utilizar automáticamente el gemset NOMBRE_PROYECTO definido para la versión default de ruby que RVM tenga configurada en dicha máquina.
  • Si no existe dicho gemset, lo creará
  • La redirección a /dev/null es cuestión de gustos, para evitar que RVM imprima por pantalla cada vez que cambiemos de directorio


¡Y ya está! Ya tenemos cambio automático de gemset cada vez que nos situemos en la carpeta correspondiente a cada proyecto.
A partir de aquí es cuestión personal el aplicar la práctica de definir un gemset para cada proyecto con el que trabajamos (como ya hemos dicho, es considerado buena práctica).



Enlaces de interés