#216 Generators in Rails 3
- Download:
- source codeProject Files in Zip (108 KB)
- mp4Full Size H.264 Video (29.7 MB)
- m4vSmaller H.264 Video (15 MB)
- webmFull Size VP8 Video (35.8 MB)
- ogvFull Size Theora Video (34.3 MB)
A los que hayan usado Rails alguna vez les resultarán familiares los generadores y habrán utilizado el comando script/generate
para crear modelos, controladores, andamiajes, etcétera. En Rails 3 los generadores han sido reescritos en su totalidad, siendo muy diferentes de los de la versión 2. De entrada ahora son mucho más modulares y esto quiere decir que son más fáciles de adaptar según nuestras preferencias.
Si en el directorio raíz de una aplicación Rails 3 ejecutamos rails g
veremos la lista de los generadores disponibles en esa aplicación. Si no hemos creado nuestros propios generadores tan sólo veremos los que vienen incluidos en Rails 3: controller
, generator
, helper
, integration_test
, mailer
, metal
, migration
, model
, observer
, performance_test
, plugin
, resource
, scaffold
, scaffold_controller
, session_migration
y stylesheets
. En su mayoría estos generadores se comportan igual que sus equivalentes en Rails 2.
Para ver la ayuda acerca de un generador en concreto podemos ejecutar el generador con la opción --help
. Si lo hacemos con el generador de andamiajes veremos que hay muchas opciones disponibles que nos dan bastante flexibilidad para personalizar el comportamiento del generador.
$ rails g scaffold --help Usage: rails generate scaffold NAME [field:type field:type] [options] Options: -c, --scaffold-controller=NAME # Scaffold controller to be invoked # Default: scaffold_controller [--singleton] # Supply to create a singleton controller [--force-plural] # Forces the use of a plural ModelName -y, [--stylesheets] # Indicates when to generate stylesheets # Default: true -o, --orm=NAME # Orm to be invoked # Default: active_record ScaffoldController options: -e, [--template-engine=NAME] # Template engine to be invoked # Default: erb [--helper] # Indicates when to generate helper # Default: true -t, [--test-framework=NAME] # Test framework to be invoked # Default: test_unit Runtime options: -f, [--force] # Overwrite files that already exist -p, [--pretend] # Run but do not make any changes -q, [--quiet] # Supress status output -s, [--skip] # Skip files that already exist TestUnit options: -r, [--fixture-replacement=NAME] # Fixture replacement to be invoked [--fixture] # Indicates when to generate fixture # Default: true ActiveRecord options: [--parent=PARENT] # The parent class for the generated model [--timestamps] # Indicates when to generate timestamps # Default: true [--migration] # Indicates when to generate migration # Default: true
Nótese que algunas opciones vienen con valores por defecto. Por ejemplo la opción --stylesheets
por defecto vale true
. Pero, ¿y si no queremos que las hojas de estilo sean generadas automáticamente? Pues bien, cuando tenemos una opción booleana cuyo valor por defecto es true
podemos poner el prefijo --no
para desactivar dicha opción. A continuación crearemos un andamiaje llamado project
, pero sin hoja de estilos esta vez.
$ rails g scaffold project name:string --no-stylesheets invoke active_record <span class="passed">create</span> db/migrate/20100602201538_create_projects.rb <span class="passed">create</span> app/models/project.rb invoke test_unit <span class="passed">create</span> test/unit/project_test.rb <span class="passed">create</span> test/fixtures/projects.yml route resources :projects invoke scaffold_controller <span class="passed">create</span> app/controllers/projects_controller.rb invoke erb <span class="passed">create</span> app/views/projects <span class="passed">create</span> app/views/projects/index.html.erb <span class="passed">create</span> app/views/projects/edit.html.erb <span class="passed">create</span> app/views/projects/show.html.erb <span class="passed">create</span> app/views/projects/new.html.erb <span class="passed">create</span> app/views/projects/_form.html.erb invoke test_unit <span class="passed">create</span> test/functional/projects_controller_test.rb invoke helper <span class="passed">create</span> app/helpers/projects_helper.rb invoke test_unit <span class="passed">create</span> test/unit/helpers/projects_helper_test.rb
Si inspeccionamos la salida del comando veremos que no se han generado hojas de estilo porque hemos desactivado la opción.
La salida del generador muestra (además del listado de los archivos que se han creado) una serie de llamadas a invoke
. Estas líneas indican cuándo se están invocando otros generadores. Esto quiere decir que el generador de andamiajes no hace mucho por sí solo, sino que delega en otros generadores como el generador active_record
que crea el archivo del modelo y la migración. El generador active_record
llama a otro generador test_unit
, para crear un fichero de tests unitarios. Este patrón se repite más abajo cuando el generador scaffold_controller
invoca los generadores erb
, test_unit
y helper
para crear el resto de archivos conectados con un controlador y sus vistas. Esto hace que este sistema sea muy modular y podamos reemplazar lo que nos interese: si por ejemplo nos interesa utilizar Haml en lugar de erb en las vistas, o Shoulda o RSpec en lugar de Test::Unit podríamos utilizar otros generadores para generar el andamiaje.
Con esto ya podemos repasar otra vez la lista de opciones que nos muestra el generador de andamiajes. Todo debería empezar a adquirir más sentido. Por ejemplo la opción --orm
nos permite cambiar el mapeo objeto-relacional que generará nuestros modelos (podríamos usar DataMapper en lugar de ActiveRecord). Más abajo en la lista de opciones hay una lista de opciones de ActiveRecord que sólo aplican a la versión de ActiveRecord del generador así como las opciones de los generadores de Test::Unit y ScaffoldController.
Cambio de las opciones por defecto
Aunque poder pasar a los generadores opciones como --no-stylesheets
es bastante útil, sería mucho más útil si pudiéramos cambiar el valor por defecto de estas opciones, y eso en Rails 3 lo podemos hacer (a nivel de aplicación) modificando el archivo config/application.rb.
, que es generado automáticamente cuando se crea la aplicación Rails, y que viene con la siguiente sección comentada:
# Configure generators values. Many other options are available, be sure to check the documentation. # config.generators do |g| # g.orm :active_record # g.template_engine :erb # g.test_framework :test_unit, :fixture => true # end
Si quitamos los comentarios de esta sección podemos cambiar las opciones por defecto de los generadores en nuestra aplicación. Las opciones que se muestran coinciden con los valores por defecto. Por ejemplo para desactivar la generación de hojas de estilo por defecto podríamos escribir:
# Configure generators values. Many other options are available, be sure to check the documentation. config.generators do |g| g.stylesheets false end
Si ahora ejecutamos el comando help
en el generador de andamiajes veremos que la nueva opción por defecto entra en acción y la opción --stylesheets
ya no tendrá true
como valor por defecto.
$ rails g scaffold --help Usage: rails generate scaffold NAME [field:type field:type] [options] Options: -y, [--stylesheets] # Indicates when to generate stylesheets -c, --scaffold-controller=NAME # Scaffold controller to be invoked # Default: scaffold_controller [--singleton] # Supply to create a singleton controller [--force-plural] # Forces the use of a plural ModelName -o, --orm=NAME # Orm to be invoked # Default: active_record
Hagamos algo un poco más a la aventura, cambiemos el framework de pruebas para que sea Shoulda en lugar de Test::Unit, y vamos a cambiar las fixturas por Factory Girl usando la opción fixture-replacement
.
Por supuesto antes de hacer esto tenemos que detallar cuáles son las gemas relevantes en el fichero Gemfile
de la aplicación. Tan sólo necesitaremos estas gemas en el entorno de test, así que las agruparemos.
group :test do gem "shoulda" gem "factory_girl" end
Por desgracia estas dos gemas no incluyen oficialmente generadores para Rails 3 pero podemos utilizar otra gema llamada rails3-generators que sí que proporciona generadores adaptados a Rails 3 de muchos plugins y gemas, incluyendo Shoulda y Factory Girl. Vamos a añadir dicha gema también al fichero Gemfile pero sólo lo haremos en el entorno de desarrollo.
gem "rails3-generators", :group => :development
Con esto, sólo tenemos que ejecutar
bundle install
para instalar las gemas. Una vez que se hayan instalado las gemas podemos ejecutar rails g
para ver una lista de los generadores que hay disponibles. Al final de la lista ahora deberían aparecer los nuevos generadores definidos en la gema rails3-generators
.
Con estos nuevos generadores podemos modificar la configuración de application.rb
y poner los valores por defecto de las opciones test_framework
y fixture_replacement
.
# Configure generators values. Many other options are available, be sure to check the documentation. config.generators do |g| g.stylesheets false g.test_framework :shoulda g.fixture_replacement :factory_girl end
Ahora cuando ejecutemos la ayuda del generador de andamiajes otra vez veremos que habrá cambiado para mostrar las opciones por defecto de framework de pruebas.
$ rails g scaffold --help Usage: rails generate scaffold NAME [field:type field:type] [options] ... ActiveRecord options: [--parent=PARENT] # The parent class for the generated model [--migration] # Indicates when to generate migration # Default: true [--timestamps] # Indicates when to generate timestamps # Default: true [--textframework=NAME] # Test framework to be invoked # Default: shoulda Shoulda options: [--fixture-replacement=NAME] # Fixture replacement to be invoked # Default: factory_girl [--dir=DIR] # The directory where the model tests should go # Default: test/unit
Podemos probar todo esto generando un nuevo andamiaje para un modelo llamado task
.
$ rails g scaffold task project_id:integer name:string invoke active_record <span class="passed">create</span> db/migrate/20100604202823_create_tasks.rb <span class="passed">create</span> app/models/task.rb invoke shoulda <span class="passed">create</span> test/unit/task_test.rb invoke factory_girl <span class="passed">create</span> test/factories/tasks.rb route resources :tasks invoke scaffold_controller <span class="passed">create</span> app/controllers/tasks_controller.rb invoke erb <span class="passed">create</span> app/views/tasks <span class="passed">create</span> app/views/tasks/index.html.erb <span class="passed">create</span> app/views/tasks/edit.html.erb <span class="passed">create</span> app/views/tasks/show.html.erb <span class="passed">create</span> app/views/tasks/new.html.erb <span class="passed">create</span> app/views/tasks/_form.html.erb <span class="failed">error</span> shoulda [not found] invoke helper <span class="passed">create</span> app/helpers/tasks_helper.rb <span class="failed">error</span> shoulda [not found]
Al principio vemos que se invocan correctamente los generadores de shoulda
y factory_girl
pero que más abajo, no se pudo encontrar un generador de Shoulda para erb o el fichero helper. Si vemos otra vez la lista de helpers comprobaremos que sólo tenemos generadores Shoulda para los modelos y los controladores.
$ rails g ... Shoulda: shoulda:controller shoulda:model
¿Qué podemos hacer aquí? Bueno, si examinamos la página de las Guías Rails sobre generadores veremos que es posibile añadir generadores de respaldo por si no se encuentran ciertos generadores. Nuevamente, lo único que hay que hacer es modificar el bloque de configuración en application.rb
.
# Configure generators values. Many other options are available, be sure to check the documentation. config.generators do |g| g.stylesheets false g.test_framework :shoulda g.fallbacks[:shoulda] = :test_unit g.fixture_replacement :factory_girl end
Ahora ya podemos generar andamiajes con Shoulda y los generadores utilizarán los generadores correspondientes a Test::Unit si no se puede encontrar un generador adecuado de Shoulda.
Personalización de las plantillas
Lo último que nos queda por ver en este episodio es cómo personalizar las plantillas generadas. Debajo se muestra la vista generada para la acción index
del controlador que acabamos de generar.
<h1>Listing tasks</h1> <table> <tr> <th></th> <th></th> <th></th> </tr> <% @tasks.each do |task| %> <tr> <td><%= link_to 'Show', task %></td> <td><%= link_to 'Edit', edit_task_path(task) %></td> <td><%= link_to 'Destroy', task, :confirm => 'Are you sure?', :method => :delete %></td> </tr> <% end %> </table> <br /> <%= link_to 'New Task', new_task_path %>
Supongamos que queremos eliminar la etiqueta br
al final de la plantilla y poner el enlace del final en su párrafo. ¿Cómo podemos cambiar la plantilla de forma que estos cambios ocurran en todas las vistas generadas?
Podemos crear nuestras propias plantillas en un directorio llamado templates
dentro del directorio lib
. Los generadores mirarán ahí en busca de plantillas en lugar de utilizar las incluidas por defecto. Para determinar qué plantilla tenemos que redefinir tendremos que mirar el código fuente del generador. El código de los generadores está en el directorio railties/lib/rails/generators y las plantillas por defecto están en el directorio erb/scaffold/templates/. Podemos copiar los contenidos de index.html.erb
de ese directorio para cambiarlo según nos convenga.
Tenemos que crear una estructura de directorios similar por lo que nuestra plantilla de índice personalizada tendría que estar en /lib/templates/erb/scaffold/index.html.erb
. Ahí podemos poner la plantilla por defecto sobre la que hacer nuestros cambios. Una vez que los hayamos hecho si creamos un nuevo andamiaje, por ejemplo para un modelo llamado Category
veremos que la vista de índice ya incorpora nuestra propia plantilla.
rails g scaffold category name:string
<h1>Listing categories</h1> <table> <tr> <th>Name</th> <th></th> <th></th> <th></th> </tr> <% @categories.each do |category| %> <tr> <td><%= category.name %></td> <td><%= link_to 'Show', category %></td> <td><%= link_to 'Edit', edit_category_path(category) %></td> <td><%= link_to 'Destroy', category, :confirm => 'Are you sure?', :method => :delete %></td> </tr> <% end %> </table> <p><%= link_to 'New Category', new_category_path %></p>
Y eso es todo por este episodio. En Rails 3 se han introducido muchas mejoras en los generadores que los hacen mucho más fáciles de personalizar.