#218 Making Generators in Rails 3
- Download:
- source codeProject Files in Zip (95.2 KB)
- mp4Full Size H.264 Video (15 MB)
- m4vSmaller H.264 Video (10.4 MB)
- webmFull Size VP8 Video (28 MB)
- ogvFull Size Theora Video (20.9 MB)
En el episodio 216 [verlo, leerlo] vimos que en Rails 3 los generadores son mucho más modulares y personalizables que en Rails 2. Pero hay veces, sin embargo, que nos interesaría escribir un generador desde cero porque los que vienen con Rails no se ajustan a nuestras necesidades. Ya vimos cómo escribir nuestros propios generadores en el episodio 58, pero han sido tantos los cambios que vamos a darles otro repaso, viendo cómo escribir el mismo generador de ese episodio en Rails 3.
Empecemos
Como la forma más sencilla de escribir un generador es desde dentro de una aplicación Rails 3, vamos a crear una. Se acaba de liberar la beta 4 de Rails 3.0, y la sintaxis para generar una nueva aplicación ha cambiado, así que para crear la aplicación de tareas sobre la que vamos a desarrollar nuestro generador en lugar de ejecutar
$ rails todo
tenemos que ejecutar
$ rails new todo
Una vez que hemos creado nuestra nueva aplicación empezaremos el desarrollo de nuestro generador usando uno de los generadores incluidos. El comando rails g generator
creará los ficheros básicos que hacen falta. Podemos ver qué parámetros acepta este comando:
$ rails g generator --help
Este generador es bastante sencillo, y sólo hace falta pasarle el nombre del nuevo generador que queremos crear. Como queremos que genere un fichero de layout de la aplicación, lo bautizaremos como layout
.
$ rails g generator layout <span class="passed">create</span> lib/generators/layout <span class="passed">create</span> lib/generators/layout/layout_generator.rb <span class="passed">create</span> lib/generators/layout/USAGE <span class="passed">create</span> lib/generators/layout/templates
El generador creará una serie de archivos dentro del directorio /lib
lo que quiere decir que nuestro generador sólo podrá ser usado en esta aplicación. Para poder usarlo en cualquier otra aplicación tendremos que hacer algunas cosas más que veremos más adelante.
El fichero del generador así generado (valga la redundancia) es bastante sencillo y tan sólo contiene una clase. En dicha clase hay una línea de código que le dice al generador que mire en el directorio de plantillas en busca de ficheros adicionales.
class LayoutGenerator < Rails::Generators::NamedBase source_root File.expand_path('../templates', __FILE__) end
El generador generator
también crea un archivo llamado USAGE
donde podemos definir la documentación de nuestro generador. Dicha documentación se mostrará cuando se ejecute la opción --help
de nuestro generador.
La clase creada por el generador hereda de Rails::Generators::NamedBase
. Esto significa que hará falta proporcionar un nombre cuando se ejecute el generador de la misma manera que tuvimos que poner un nombre cuando ejecutamos generator
. Podemos ver esto ejecutando nuestro nuevo generador con la opción --help
.
$ rails g layout --help Usage: rails generate layout NAME [options] Runtime options: -f, [--force] # Overwrite files that already exist -p, [--pretend] # Run but do not make any changes -s, [--skip] # Skip files that already exist -q, [--quiet] # Supress status output Description: Explain the generator Example: rails generate layout Thing This will create: what/will/it/create
Nótese que los contenidos del fichero USAGE
se muestran al final.
Queremos que la opción NAME
sea opcional y que por defecto tenga el valor de application
. Podemos hacerlo haciendo que la clase herede de Rails::Generators::Base
, en lugar de NamedBase
. Esto hará que todos los argumentos sean opcionales y por tanto tendremos más flexibilidad a la hora de personalizar nuestro generador.
Podemos definir qué argumentos recibe el generador utilizando el método argument
. Si definimos nuestros propios argumentos en lugar de usar la opción NAME
podremos definir un valor por defecto para cada argumento. Añadiremos un argumento layout_name
cuyo valor por defecto será application
.
class LayoutGenerator < Rails::Generators::Base source_root File.expand_path('../templates', __FILE__) argument :layout_name, :type => :string, :default => "application" end
Si ejecutamos una vez más el comando de generador con la opción --help
veremos que ahora aparece nuestro nuevo argumento. Además lo hace entre corchetes, y esto significa que es un parámetro opcional.
$ rails g layout --help Usage: rails generate layout [LAYOUT_NAME] [options]
En Rails 3 los generadores se basan en la librería Thor, que es similar a rake. Muchos de los metodos accesibles desde nuestra clase del generador son definidos en la propia librería Thor así que podemos echar un vistazo a la documentación y el código fuente de Thor si queremos profundizar más acerca de dichos métodos.
Creando los ficheros que utilizará el generador
Recordemos que queríamos que nuestro generador crease dos archivos: un archivo de layout de la apliccaion y una hoja de estilos. Para esto crearemos dos archivos en el directorio templates
del generador y haremos que cuando el generador se ejecute los copie a los directorios correspondientes en nuestra aplicación.
¿Y cómo definimos el comportamiento del generador? La forma de hacerlo es, por así decirlo, bastante inusual. Cualquier método público que sea definido en la clase del generador será invocado cuando se ejecute el generador. Por tanto, podremos definir un método llamado generate_layout
que será ejecutado automáticamente. Se trata de un concepto un poco extraño pero resulta ser una buena forma de organizar el código del generador.
Lo primero que hará nuestro generador será copiar una hoja de estilos desde el directorio templates
al directorio /public/stylesheets
de nuestra aplicación y para esto podemos usar el método copy_file
.
class LayoutGenerator < Rails::Generators::Base source_root File.expand_path('../templates', __FILE__) argument :layout_name, :type => :string, :default => "application" def generate_layout copy_file "stylesheet.css", "public/stylesheets/#{layout_name.underscore}.css" end end
El método copy_file
recibe dos argumentos. El primero es el nombre del fichero del directorio templates
que deberá copiarse y el segundo es el path de destino. Obsérvese que el nombre del fichero de destino se basa en el argumento layout_name
que se pasó al generador. El método argument
crea un método layout_name
qu epodemos usar, pero dado que el nombre recibido podría contener mayúsculas y minúsculas tendremos que invocar el método underscore
para asegurarnos que está en un formato apto para nombres de fichero.
Por último, tenemos que crear el fichero stylesheet.css
en el directorio templates
y pegar la hoja de estilos por defecto que queremos que tengan nuestras aplicaciones.
Si ahora ejecutamos nuestro generador sin argumentos debería crear un archivo application.css
.
$ rails g layout <span class="passed">create</span> public/stylesheets/application.css
A continuación tenemos que hacer lo mismo con el archivo de layout.
Vamos a crear un método auxiliar llamado file_name
porque nos va a hacer falta obtener el nombre de layout con guiones bajos en varios sitios. Como mencionábamos antes, cualquier método público definidio en la clase del generador será ejecutado así que tenemos que asegurarnos de que este método sea privado.
Lo siguiente que tenemos que hacer en nuestro generador es crear el propio fichero de layout. Podríamos otra vez ejecutar copy_file
pero esto efectuaría una simple copia del fichero de la plantilla, y queremos poder pasar este archivo por ERb de forma que sea modificado cuando se ejecute el generador.
En su lugar podemos usar el método template
, que recibe argumentos similares a copy_file
, pero ejecutará cualquier código erb que exista en la plantilla antes de copiarlo al directorio de destino.
class LayoutGenerator < Rails::Generators::Base source_root File.expand_path('../templates', __FILE__) argument :layout_name, :type => :string, :default => "application" def generate_layout copy_file "stylesheet.css", "public/stylesheets/#{file_name}.css" template "layout.html.erb", "app/views/layouts/#{file_name}.html.erb" end private def file_name layout_name.underscore end end
A continuación crearemos la plantilla propiamente dicha
<!DOCTYPE html> <html> <head> <title>Untitled</title> <%%= stylesheet_link_tag "<%= file_name %>" %> <%%= javascript_include_tag :defaults %> <%%= csrf_meta_tag %> <%%= yield(:head) %> </head> <body> <div id="container"> <%% flash.each do |name, msg| %> <%%= content_tag :div, msg, :id => "flash_#{name}" %> <%% end %> <%%= yield %> </div> </body> </html>
Lo primero que notamos es que, como estamos usando el método template
, se ejecutarán todas las etiquetas erb del código. Si queremos que el fichero generado contenga erb tendremos que escapar el signo de porcentaje al comienzo de la etiqueta erb tal y como hemos hecho en el código anterior.
Por tanto, para incluir contenido dinámico en la plantilla podemos usar código erb normal. Podemos acceder métodos del fichero del generador (aunque sean privados) y podemos llamar a file_name
para poner el nombre correcto del archivo de la hoja de estilos en la llamada a stylesheet_link_tag
.
Podemos ejecutar una vez más el generador para comprobar que todo funciona correctamente, esta vez pasando un archivo de layout.
$ rails g layout admin <span class="passed">create</span> public/stylesheets/admin.css <span class="passed">create</span> app/views/layouts/admin.html.erb
Esta vez el generador ha creado dos archivos, cada uno de ellos con un nombre basado en el parámetro recibido, así que todo parece estar funcionando. Si miramos el fichero generado veremos que se llamó al método file_name
cuando se generó el archivo por lo que le método stylesheet_link_tag
tiene el argumento correto. Las otras etiquetas erb que estaban escapadas sí que aparecen tal cual en el fichero generado.
<head> <title>Untitled</title> <%= stylesheet_link_tag "admin" %> <%= javascript_include_tag :defaults %> <%= csrf_meta_tag %> <%= yield(:head) %> </head>
Opciones opcionales
Hay una última funcionalidad que queremos añadir al generador: la posibilidad de pasarle una opción que haga que no se genere el fichero con la hoja de estilos. Podemos hacerlo utilizando el método class_option
.
class LayoutGenerator < Rails::Generators::Base source_root File.expand_path('../templates', __FILE__) argument :layout_name, :type => :string, :default => "application" class_option :stylesheet, :type => :boolean, :default => true, :description => "Include stylesheet file" def generate_layout copy_file "stylesheet.css", "public/stylesheets/#{file_name}.css" if options.stylesheet? template "layout.html.erb", "app/views/layouts/#{file_name}.html.erb" end private def file_name layout_name.underscore end end
Hemos llamado a nuestra opción stylesheet
, de tipo boolean
y valor por defecto true
. También le hemos dado una descripción. En la línea copy_file
ahora podemos añadir una sentencia if
para que la hoja de estilos se copie sólo si dicha opción está a true
. Igualmente, en el fichero de plantilla para el layout sólo queremos que aparezca la línea stylesheet_link_tag
si dicha opción está también a true
.
<!DOCTYPE html> <html> <head> <title>Untitled</title> <%- if options.stylesheet? -%> <%%= stylesheet_link_tag "<%= file_name %>" %> <%- end -%> <%%= javascript_include_tag :defaults %> <%%= csrf_meta_tag %> <%%= yield(:head) %> </head> <body> <div id="container"> <%% flash.each do |name, msg| %> <%%= content_tag :div, msg, :id => "flash_#{name}" %> <%% end %> <%%= yield %> </div> </body> </html>
Si vemos la documentación de ayuda del generador una vez más, veremos que ahora aparece la opción stylesheet
.
$ rails g layout --help Usage: rails generate layout [LAYOUT_NAME] [options] Options: [--stylesheet] # Indicates when to generate stylesheet # Default: true
Ahora podemos generar un layout sin una hoja de estilos utilizando la opción --skip-stylesheet
(o --no-stylesheet
). En tal caso sólo se generará el archivo de layout.
$ rails g layout foo --skip-stylesheet <span class="passed">create</span> app/views/layouts/foo.html.erb
Y con esto terminamos este episodio dedicado a la escritura de generadores en Rails 3. Siempre que en nuestras aplicaciones acabemos generando el mismo código (o similar) podemos utilizar un generador. Para compartir con otros nuestros generadores tan sólo tenemos que crear un plugin o gema y ponerlo en un directorio lib/generators
. Cualquiera que incluya dicha gema o plugin tendrá disponible ese generador.