Sunday, 8 June 2014

¿Retrocompatible? Si, cuando me pinta.

Hoy voy a hablar un poco de algo que me pasó en el laburo. Tuve un problema que resolver, y la primera vez, lo tiré abajo de la alfombra por un rato, hasta que volvió a surgir. Y lo tuve que atacar.

No me voy a extender mucho en los detalles, porque a quien le interese el tema, va a saber más o menos de que hablo, y al que no, ni siquiera lo va a leer. Si querés ir directamente al problema (y a la solución), podés saltar la próxima sección (el versito no fue intencional).



Con que estaba laburando
Sinatra 1.3.2
Sinatra is a DSL for quickly creating web applications in Ruby with minimal effort.
dice la documentación de Sinatra. Y es cierto. Usándolo para un par de cosas sencillas, pueden salir cosas muy fácil y rápido. Por otro lado:
Padrino is a ruby framework built upon the Sinatra web library. Sinatra is a DSL for creating simple web applications in Ruby. Padrino was created to make it fun and easy to code more advanced web applications while still adhering to the spirit that makes Sinatra great!
dice la documentación de Padrino. Pero más importante, dicen que Padrino tiene Sinatra Core, y que por lo tanto...:
Many people love the simplicity and expressiveness of Sinatra [...].
[...] Our goal with Padrino is to stay true to the core principles of Sinatra including a focus on simplicity and modularity.
Starting from this assumption, we have developed a different approach to a web development framework. We expand on Sinatra through the addition of standard libraries including helpers, components, and other functionality that are needed in a framework suitable for arbitrarily complex web applications.
¿Y qué es lo importante de todo esto? Se preguntarán. Que dicen que usa Sinatra Core y que expande Sinatra, es decir, que agarra Sinatra y le agrega cosas o lo mejora, pero no aclaran que también lo rompe un poco.
Que parezca un accidente, supongo. Padrino 0.10.3.
Así es. Uno espera que algo que extiende otra cosa, no rompa mediante dicha extensión, la primera. Es decir, lo mínimo que uno espera tener, es retrocompatibilidad (o compatibilidad hacia atrás). Si tengo una aplicación de Sinatra que anda, pero necesito algo más, y me paso a Padrino para tratar de rehacer lo menos posible, espero que al menos de entrada, lo que tenía, siga funcionando.



El problema

Supongamos que tenemos una ruta a una página. La misma tiene algunos parámetros que llamamosparamparam_opt y param_opt2(por ejemplo, si queremos listar posts de un blog, podemos hacer una ruta del estilo /:year/:year/:month/:year/:month/:day donde el mes y el día son opcionales, porque con dar sólo el año ya se puede obtener un resultado, y el mes y el día son para acotar los resultados). Como sus nombres declarativos (?) lo sugieren, el primer parámetro es obligatorio y los otros 2 son opcionales.

Óptimamente, uno no querría tener que atajar las rutas
hostname.com/:param
hostname.com/:param/:param_opt
hostname.com/:param/:param_opt/:param_opt2 
por diversas razones de comodidad y buenas prácticas. Hasta ahora lo único (y bastante) molesto es la repetición de los prefijos, pero supongan (y me pasó) que quieren agregar otro parámetro opcional, pero antes de param. Hay que agregar otros 3 casos para atajar dichas rutas. Y eso no está bueno, más aun si queremos mantenernos con filosofía DRY.



Como resuelve Sinatra

Realmente es sencillo. Luego de cada parámetro o caracter opcional, se puede agregar un "?" y eso determina que el parámetro (que puede ser un símbolo como lo describí antes) o caracter anterior, es opcional. Veamos un ejemplo. En:
hostname.com/:param/?:param_opt?
la segunda “/“ y el símbolo :param_opt son opcionales. En:
hostname.com/:param/?:param_opt?/?:param_opt2?
la segunda y tercer “/“ son opcionales, al igual que los símbolos :param_opt y :param_opt2.
Exactamente como queríamos. Sencillo, ¿no?

Seguro hago esto así en Sinatra, me queda re compacto y lindo, me paso a Padrino y anda, ¿no?

No.



Como resuelve Padrino

No tengo ni idea. Bueno, en realidad si. Como Rails. Un gran conocedor del mundo Ruby y compañero de trabajo (Demian) me lo hizo notar (y le estoy muy agradecido), luego de ver la cantidad incontable de horas que estuve buscando como hacer parámetros opcionales, sin éxito alguno.

No se si a nadie en toda la internet le pasó esto o que, pero no hay nada al respecto. Google te manda a resultados de Stack Overflow, relacionados con Sinatra, entre otras cosas, pero nadie dice que Padrino reemplaza, aparentemente, el router de Sinatra por el de Rails.

Demian sabía que en una aplicación de Rails que hizo había necesitado parámetros de URL opcionales, entonces como no había nada que perder, se fijó como se hacía, y lo probó así. Y funcionó.

La solución es poner parámetros opcionales estilo Rails 3, con paréntesis (sección 3.1 Bound Parameters).

Entonces teniendo:
hostname.com/:param
hostname.com/:param/:param_opt
hostname.com/:param/:param_opt/:param_opt2 
se soluciona aplicando los paréntesis correspondientes, quedando:
hostname.com/:param(/:param_opt(/:param_opt2))
De esta forma, /:param_opt2 es opcional dentro de /:param_opt, y a su vez, esta composición es opcional en la dirección entera. En este caso /:param_opt2 no puede aparecer si antes no hubo un /:param_opt. Si lo que queremos es tener varios parámetros independientes entre sí, en lugar de anidarlos o componerlos como hice ahí, simplemente se pone paréntesis individual a cada parámetro, quedando:
hostname.com/:param(/:param_opt)(/:param_opt2)


Conclusión

No se ustedes, pero para mi, cuando alguien basa un producto en otra cosa, la idea no es romper el producto original. O al menos, avisar y documentar bien en caso de haber alguna excepción insalvable.

Todavía no considero tener mucha experiencia en el área, y cabe la posibilidad de que el que se está equivocando al usarlo sea yo, pero para eso están ustedes y sus comentarios, para aclararme el panorama, o que nos juntemos a tomar mate e insultar a los desarrolladores.