homeASCIIcasts

216: Generadores en Rails 3 

(view original Railscast)

Other translations: En It

Other formats:

Written by Juan Lupión

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
      create    db/migrate/20100602201538_create_projects.rb
      create    app/models/project.rb
      invoke    test_unit
      create      test/unit/project_test.rb
      create      test/fixtures/projects.yml
       route  resources :projects
      invoke  scaffold_controller
      create    app/controllers/projects_controller.rb
      invoke    erb
      create      app/views/projects
      create      app/views/projects/index.html.erb
      create      app/views/projects/edit.html.erb
      create      app/views/projects/show.html.erb
      create      app/views/projects/new.html.erb
      create      app/views/projects/_form.html.erb
      invoke    test_unit
      create      test/functional/projects_controller_test.rb
      invoke    helper
      create      app/helpers/projects_helper.rb
      invoke      test_unit
      create        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:

/config/application.rb

# 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:

/config/application.rb

# 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.

/Gemfile

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.

/Gemfile

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.

/config/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.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
      create    db/migrate/20100604202823_create_tasks.rb
      create    app/models/task.rb
      invoke    shoulda
      create      test/unit/task_test.rb
      invoke      factory_girl
      create        test/factories/tasks.rb
       route  resources :tasks
      invoke  scaffold_controller
      create    app/controllers/tasks_controller.rb
      invoke    erb
      create      app/views/tasks
      create      app/views/tasks/index.html.erb
      create      app/views/tasks/edit.html.erb
      create      app/views/tasks/show.html.erb
      create      app/views/tasks/new.html.erb
      create      app/views/tasks/_form.html.erb
       error    shoulda [not found]
      invoke    helper
      create      app/helpers/tasks_helper.rb
       error      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.

/config/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.

/app/views/tasks/index.html.erb

<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

/app/views/categories/index.html.erb

<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.