martes, 14 de junio de 2011

El misterio de la variable controller en la definición de rutas

Routes - mapeo de URLs para aplicaciones web

En este artículo les hablaré acerca de una característica del módulo  PyRoutes que al menos yo no encontré bien documentada. Se trata del manejo de la variable controller dentro de la definición de las rutas en una aplicación web. Le invito a continuar leyendo este artículo si quiere conocer qué es lo que hace este módulo, la naturaleza del problema (si es que se le puede llamar así) y cómo se resuelve.

Acerca del módulo PyRoutes

El módulo  Routes ha sido diseñado para resolver un problema muy frecuente en el desarrollo de aplicaciones web, que es cómo relacionar las URLs con acciones concretas. Por ejemplo, blog/2008/01/08 podría mostrar una lista de artículos publicados en cierta fecha, mientras que login se utilizaría para autentificar los usuarios, ... y así sucesivamente.

Es característico de  Routes el poder definir las acciones del sitio y la jerarquía de URLs de forma independiente, para luego enlazarlas del modo que sea más conveniente. Si en algún momento se cambia de idea acerca de una URL solo se cambia la línea donde se define la regla, y no será necesario tocar la definición de la acción. Incluso es posible tener múltiples caminos para lograr el mismo resultado.

El diseño de  Routes ha sido inspirado por  Ruby On Rails, pero en su estado actual existen divergencias significativas. Con el tiempo se ha convertido en la base de frameworks como  Pylons,  CherryPy, y permitió la integración de  TurboGears 2 con  Pylons.

El problema

 Routes forma parte también de un framework  WSGI muy sencillo que desarrollo desde hace poco tiempo. Al utilizarlo siempre me llamó la atención que la documentación ofrecía ejemplos donde se utilizaba la variable controller en la definición de las rutas. Por ejemplo en este caso :

>>> from routes import Mapper
>>> mapper = Mapper()
>>> mapper.connect('test', '/{controller}/{action}')
>>>
>>> print mapper.routematch('/hello/world')
None

Cómo se puede apreciar este código no funciona. Por otra parte , si se cambia ligeramente el código anterior entonces sí funciona.

>>> from routes import Mapper
>>> mapper = Mapper()
>>> mapper.connect('test', '/hello/{action}', controller='hello')
>>>
>>> mapper.match('/hello/world')
{'action': u'world', 'controller': u'hello'}

¿Entonces cuál es la causa de esta situación aparentemente tan extraña?

Utilizando la variable controller en definiciones de rutas

Pues bien la cuestión es que en el Mapper() no se especifican los controladores. Todo parece indicar que hay que hacer esto explícitamente. La implementación predeterminada utiliza los nombres de los ficheros en un directorio. Sucede que por defecto, si no encuentra el controlador adecuado al procesar la ruta siempre se devuelve None. Ese es también el motivo por el que el segundo ejemplo funciona al especificar un controlador.

Para solucionarlo, existen dos opciones:

  1. Pasar a Mapper() la ruta (absoluta) a los controladores
mapper = Mapper(directory='/ruta/a/los/controllers']

  1. Pasar a Mapper() una función controler_scan que devuelva una lista con los nombres de los controladores disponibles. Por ejemplo:
>>> def controller_scan(directory=None):
...        return 'hello', 'auth'
... 
>>> mapper = Mapper(controller_scan=controller_scan)
>>> mapper.connect('default', '/{controller}/{action}')
>>> mapper.connect('login', '/login', controller='auth', action='login')
>>> mapper.connect('logout', '/logout', controller='auth', action='logout')
>>> mapper.match('/hello/world')
{'action': u'world', 'controller': u'hello'}

En mi caso particular, la segunda opción es la más apropiada. Así que muchas gracias  Chema Cortes por  aclararme esta duda ... ;o) Espero que este artículo le sirva a Usted también de referencia y quizás le ahorre un buen tiempo tratando de hacer que esto funcione.

No hay comentarios:

Publicar un comentario