Cómo Escribir un Plugin para Rails
Donde intento demostrar como escribir un plugin para Ruby on Rails. En este caso, usaré zodiac como ejemplo. Zodiac es un helper que retorna el signo zodiacal de una fecha dada:
zodiac_sign_for(Time.utc(2000, "jun", 12))
# => "Gemini"
Generando un esqueleto
Necesitamos crear una aplicación de Rails para ejecutar algunos generadores:
$ rails ejemplo
$ cd ejemplo
Ahora, es hora de usar el generador estándar de Rails para crear el esqueleto del plugin:
$ script/generate plugin zodiac
create vendor/plugins/zodiac/lib
create vendor/plugins/zodiac/tasks
create vendor/plugins/zodiac/test
create vendor/plugins/zodiac/README
create vendor/plugins/zodiac/MIT-LICENSE
create vendor/plugins/zodiac/Rakefile
create vendor/plugins/zodiac/init.rb
create vendor/plugins/zodiac/install.rb
create vendor/plugins/zodiac/uninstall.rb
create vendor/plugins/zodiac/lib/zodiac.rb
create vendor/plugins/zodiac/tasks/zodiac_tasks.rake
create vendor/plugins/zodiac/test/zodiac_test.rb
Desde ahora podemos trabajar dentro de esta misma aplicación o podemos mover el plugin afuera y trabajar sobre el. Inicialmente el generador crea los siguientes archivos/directorios:
lib: Aquí es donde ira todo nuestro código.
tasks: Tareas que Rake podrá ejecutar, por ejemplo, mover archivos o
ejecutar un set de RSpec.
test: Directorio para nuestros tests.
README: Un README por defecto que podemos usar para explicar el
funcionamiento de nuestro plugin.
MIT-LICENSE: El generador crea por defecto un archivo para la licencia del
plugin usando la misma licencia de Rails (MIT). Nada nos impide cambiarlo
para usar nuestra licencia preferida.
Rakefile: Un archivo de Rake con unas tareas bastante básicas: Correr los
tests y generar la documentación RDoc.
init.rb: El “punto de enganche” de nuestra aplicación. Rails ejecutará este
archivo cada vez que sea cargado, así que aquí hacemos las
inclusiones/”mixins” necesarios.
install.rb/uninstall.rb: Estos scripts serán ejecutados a la hora de
instalar/desinstalar nuestro plugin. Podemos incluir aquí migraciones o
copias de archivos necesarias para la instalación del plugin.
No es necesario que usemos todos estos archivos/directorios para cada plugin que hagamos, así que podemos eliminar tranquilamente lo que no usaremos. En el caso de Zodiac, no necesitamos tareas adicionales para Rake así que podemos deshacernos del directorio tasks/. Si pensamos usar RSpec en vez de Test/Unit podemos eliminar también el directorio test/ y crear un directorio spec/. No haremos ninguna modificación a la hora de instalar/desinstalar el plugin así que podemos olvidarlos de install.rb/uninstall.rb
Primeros Pasos
Empezaremos por llenar nuestro archivo README con información útil sobre nuestro plugin:
Zodiac
========
A Rails plugin to get the zodiac sign on a given date.
Example
-------
zodiac_sign_for(Time.utc(2000, "jun", 12))
# => "Gemini"
Copyright (c) 2008 Federico Builes, released under the MIT license.
Fork freely!
Además, intentaremos seguir un acercamiento TDD, así que escribamos algunos tests. Por gusto personal prefiero usar RSpec a Test/Unit, así que me he olvidado del directorio test/, he creado un directorio specs/ y dentro de este tengo tres archivos:
spec_helper.rb
Este es el helper que se encargara de ejecutar las especificaciones cuando
hagamos uso de Rake:
require File.dirname(__FILE__) + "/../lib/zodiac_helper"
spec.opts
Algunas opciones para modificar la salida de RSpec:
--colour
--format
specdoc
--loadby
mtime
zodiac_helper_spec.rb
La especificación de nuestro helper:
Finalmente, he añadido una pequeña tarea a Rakefile para ejecutar las specs:
Rakefile
desc "Runs the spec suite"
task :spec do
spec_path = "#{File.dirname(__FILE__)}/spec"
sh "spec #{spec_path} -O #{spec_path}/spec.opts "
end
Perfecto, podemos correr nuestras specs con rake spec y veremos
4 errores, perfecto para empezar.
Escribiendo el código
Es hora de programar nuestro pequeño helper. Ya que nuestro plugin es bastante simple, podemos insertarlo todo en lib/zodiac.rb
Para empezar, queremos incluir todo nuestro trabajo dentro de un modulo para el plugin, en este caso, ZodiacHelper:
module ZodiacHelper
end
La idea de escribir todo dentro de un modulo es poder hacer un mixin con los módulos de Rails cuando estemos cargando el plugin. Rails está divido en varias partes, así que solo queremos hacer la inclusión con ciertos módulos de nuestra aplicación. Un mixin nos permite incluir nuestros módulos dentro de los módulos de Rails, así que terminamos añadiendo nuestro código al código de uno (o varios) subsistemas de nuestro framework.
Si estuviésemos escribiendo un plugin para modificar modelos podríamos hace la inclusión dentro de ActiveRecord::Base mientras que si estamos escribiendo un par de helpers para las vistas haremos entonces la inclusión en ActionView::Base.
Nuestro plugin tiene un único método, zodiac_sign_for, que se
encarga de recibir una fecha (como un Time o como un entero) y devolver el
signo zodiacal que corresponde a este día. Lo que haremos entonces es :
-
Recibir una fecha. Si esta fecha responde al método strftime entonces lo convertiremos a un formato númerico compuesto por mes-día:
Octubre 12 => 1012
Julio 7 => 707
Si la fecha dada no responde a este método entonces supondremos que ya está en este formato.
Vale la pena resaltar que el ejemplo Julio 7 se resume a 707 y no 0707. Esto es porque Ruby utiliza el prefijo 0 para referirse a números en base octal, así que 0708 es realmente 455 en base 10.
Teniendo esta combinación de mes/día usaremos una expresión
casepara saber que signo zodiacal corresponde a la fecha dada. El rango de número entonces va desde 11 (Enero 1 — que también puede ser representado como 101) hasta 1231 (Diciembre 31). Cualquier número menor a 11 o por encima de 1231 resultará entonces en error.
Además de esto, debemos comprobar que los rangos para los días siempre estén entre 1 y 31, ningún mes tiene más días.Si el número está entre los rangos mencionados en el paso anterior devolveremos una cadena con el signo zodiacal. Sino, lanzaremos una excepción con una pequeña descripción del problema.
zodiac_helper.rb
Añadimos además un comentario explicando un poco el funcionamiento de nuestro método.
Probando, probando
Teniendo listo nuestro código, es hora de probarlo con las especificaciones que escribimos previamente:
$ rake spec
Zodiac
- returns the zodiac sign for a given Time
- returns the zodiac sign for a given number
- receives a number that represents a month and day
- raises if the date's invalid
Finished in 0.045702 seconds
4 examples, 0 failures
Excelente, todo parece estar funcionando, podemos pasar a insertarlo dentro de Rails.
ee-neat
Previamente mencionamos que init.rb es el punto de enganche de
nuestro plugin con Rails. Es aquí donde debemos crear el mixin
de nuestro modulo con los módulos del framework. Para el caso de Zodiac
queremos asegurarnos de que el programador pueda usarlo tanto en sus vistas
(tal vez para mostrar la información de un perfil en su próxima red social
de coleccionistas de estampillas de Sri Lanka) como en su modelo (en caso de
que simplemente quiera guardarlo en la base de datos y vender toda esta
información al futuro Google Astrologer). Así pues, necesitamos incluir
nuestro modulo en dos lugares diferentes:
- ActionView::Base — que se encargara de hacer disponible el método a nuestras vistas.
- ActiveRecord::Base — para poder usarlo dentro de nuestros modelos.
Incluir nuestro módulo en estos dos es tan fácil como:
init.rb
ActionView::Base.send :include, ZodiacHelper
ActiveRecord::Base.send :include, ZodiacHelper
Trivial. De ahora en adelante podemos usar nuestro modulo en cualquiera de estos dos lugares sin necesidad de hacer nada mágico.
¿Qué pasa si nos saltamos este paso? Rails no tendrá manera de saber a que
nos referimos cuando le pidamos el método zodiac_sign_for así
que tendremos que incluir a mano ZodiacHelper (include
ZodiacHelper será suficiente) en cada lugar que queramos usarlo. Por
comodidad haremos el enganche desde init.rb como mencionamos
anteriormente.
Only wimps use tape backup: real men just upload their important stuff on ftp, and let the rest of the world mirror it.
Finalmente, nuestro plugin no será de utilidad a menos de que podamos distribuirlo a otros programadores. En este caso lo único que debemos hacer es subir nuestro plugin a nuestro repositorio de código fuente favorito (o sea, GitHub, y este punto no es discutible).
Luego de tener el plugin (y únicamente el plugin, no toda la aplicación de Rails) en nuestro repositorio, podremos instalarlo desde otras máquinas o aplicaciones así:
$ script/plugin install git://github.com/febuiles/zodiac.git
Reemplazando la dirección web usada por nuestro link para clone en GitHub podremos distribuir el plugin a todo el mundo y hacernos millonarios prediciendo el zodiaco.
Postroducción
Algunas ideas de lo que podemos hacer después de haber publicado nuestro plugin:
-
install.rbes un excelente punto para incluir la documentación inicial que será mostrada al usuario cuando instale el plugin por primera vez, es usual imprimir el README en este script. - Ruby on Rails Plugins es tal vez el repositorio más grande de plugins para Rails, podemos añadir nuestro plugin a este sitio web.
- Working With Rails Es un buen sitio para autopromoción y listar nuestro plugin en el perfil personal hará subir un poco el “rating profesional”.
De salida
El código fuente completo de este mini-proyecto se puede encontrar en: http://github.com/febuiles/zodiac.
Para cualquier correción, insulto por mala gramática, pregunta, o sugerencia, federico.builes@gmail.com