#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/templatesEl 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/createNó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.cssA 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.erbEsta 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: trueAhora 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.erbY 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.


