homeASCIIcasts

216: Generatori in Rails 3 

(view original Railscast)

Other translations: En Es

Other formats:

Written by Andrea Salicetti

Se avete usato a fondo Rails, sarete avvezzi all’uso dei generatori e avrete sicuramente utilizzato il comando script/generate per creare modelli, controller, scaffold e roba simile. I generatori in Rails 3 sono stati riscritti e sono completamente diversi da quelli che c’erano in Rails 2. Da un lato, ora sono molto più modulari, il che significa che potete personalizzarli per adattarli alle vostre esigenze.

Se lanciamo rails g dalla cartella radice di un applicazione Rails 3, vediamo l’elenco dei generatori disponibili per tale applicazione. Se non abbiamo ancora creato alcun generatore per conto nostro, vedremo solo i generatori inclusi in Rails 3: controller, generator, helper, integration_test, mailer, metal, migration, model, observer, performance_test, plugin, resource, scaffold, scaffold_controller, session_migration e stylesheets. Per la maggior parte dei casi, questi generatori funzionano allo stesso modo in cui funzionavano le rispettive controparti Rails 2.

Se lanciamo un generatore con l’opzione --help, ci vengono mostrate le informazioni di supporto all’uso di tale generatore, unitamente ad un elenco di opzioni. Se proviamo questo comando con il generatore scaffold, vediamo che ci sono molte opzioni a disposizione e che queste ci danno parecchia flessibilità nella personalizzazione del comportamento del generatore.

$ 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

Si noti che certe opzioni hanno un valore di default. Per esempio l’opzione --stylesheets di default vale true. Ma che succede se non vogliamo il foglio di stile creato alla generazione dello scaffold? Beh, essendoci un default booleano a true, possiamo prefissare l’opzione con un --no per disabilitare la stessa. Per farvi vedere il tutto con un esempio, creiamo un nuovo scaffold chiamato project privo di foglio di stile.

$ 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

Se guardiamo l’output del comando, vediamo che non sono stati generati fogli di stile dal momento che abbiamo disabilitato l’opzione relativa.

Oltre all’elenco dei file creati, l’output generato dal lancio del generatore mostra una serie di chiamate ad invoke: queste linee indicano l’invocazione di altri generatori. Ciò significa che il generatore dello scaffold non fa in pratica un granchè di per sè, ma piuttosto delega ad altri generatori come active_record, che crea il file di modello e il file di magration. Il generatore active_record a sua volta chiama un altro generatore, test_unit, per creare i file dei test unitari e una fixture. Questo pattern si ripete anche oltre nel generatore scaffold_controller, che invoca i generatori erb, test_unit e helper per creare tutti i file associati al controller e alle viste. Il pattern rende il sistema molto modulare, con la conseguenza che potremmo sostituire alcuni generatori con altri. Se volessimo usare Haml, anzichè erb, nelle viste, o Shoulda o RSpec al posto di Test::Unit per i test, potremmo agganciare questi generatori ed essere già a posto con tutto.

Ora che sappiamo tutto questo, possiamo riguardare l’elenco di opzioni fornite dall’help del generatore scaffold: dovrebbero apparire più sensate. Per esempio, l’opzione --orm ci consente di cambiare l’ORM che genera i modelli, in modo tale da poter usare, ad esempio, DataMapper al posto del default ActiveRecord. Più in giù nella lista delle opzioni c’è una lista di opzioni specifiche per ActiveRecord, che ovviamente riguardano la versione ActiveRecord del generatore, unitamente alle opzioni per i generatori TestUnit e ScaffoldController.

Cambiamo le opzioni di default

Anche se è utile poter passare opzioni tipo --no-stylesheets ai generatori, lo sarebbe ancor di più se potessimo cambiare proprio il comportamento di default delle opzioni, e in Rails 3 ciò è possibile, a livello di singola applicazione, modificando il file application.rb. Il file di default application.rb generato alla creazione dell’applicazione mostra le seguenti sezioni al suo interno, commentate:

/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

Se de-commentiamo questa sezione, possiamo ridefinire il comportamento di default delle opzioni per tutti i generatori dell’applicazione. Le opzioni fornite a titolo di esempio rispecchiano anche i valori di default, ma possiamo sostituire i valori con quelli che vogliamo noi. Per rendere l’opzione stylesheets false per default, possiamo scrivere:

/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

Se lanciamo nuovamente il comando help per il generatore scaffold, saranno riflessi i nuovi valori di default e l’opzione --stylesheets non comparirà più con un default a true:

$ 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

Ora proviamo qualcosa di più ardito: cambiamo il framework di test in modo che sia Shoulda anzichè Test::Unit e sostituiamo le fixture con Factory Girl, cambiando l’opzione di default di fixture-replacement.

Per fare ciò, per prima cosa dobbiamo dichiarare i rispettivi gem nel Gemfile dell’applicazione. Abbiamo bisogno di questi gem solamente nell’ambiente di test, per cui li aggiungiamo all’interno del gruppo specifico:

/Gemfile

group :test do
  gem "shoulda"
  gem "factory_girl"
end

Sfortunatamente questi due gem non includono i generatori per Rails 3, ma possiamo usare un altro gem chiamato rails3-generators che include i generatori per una serie di plugin e gem di Rails 3, incluso Factory Girl e Shoulda. Aggiungeremo anche questi gem al Gemfile, ma solo per il gruppo development, in modo tale che siano disponibili solo in tale ambiente:

/Gemfile

gem "rails3-generators", :group => :development

Fatto ciò lanciamo:

bundle install

per installare i gem. Una volta che i gem sono stati installati, possiamo lanciare rails g per vedere un elenco dei generatori disponibili. In fondo all’elenco, ci dovrebbero ora essere tutti i generatori che sono stati creati dal gem rails3-generators.

Ora che abbiamo questi nuovi geneatori, possiamo modificare la configurazione dei generatori nell’application.rb e aggiungere i default che vogliamo per le opzioni test_framework e 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

Al lancio del comando di help per il generatore di scaffold, ora, vedremo le nostre modifiche ai valori di default.

$ 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

Possiamo testare il tutto generando un nuovo scaffold per un modello 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]

In cima all’output vediamo che i generatori shoulda e factory_girl sono stati invocati correttamente, ma che un po’ dopo, nel generatore scaffold_controller, non si trova un generatore shoulda per il file erb o per l’helper. Se diamo nuovamente un’occhiata all’elenco degli helper, possiamo notare come di fatto abbiamo a disposizione i generatori shoulda solamente per i modelli e i controller:

$ rails g
  ...
Shoulda:
  shoulda:controller
  shoulda:model

Come dobbiamo procedere, dunque? Beh, leggendo la pagina delle Rails Guides per i generatori vediamo che è possibile aggiungere dei generatori in cascata in modo tale che se un determinato generatore non viene trovato, ne venga usato uno alternativo. Tutto ciò che dobbiamo fare, dunque, è modificare nuovamente il blocco config in 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

Ora potremo finalmente generare senza errori gli scaffold con Shoulda e i generatori usati saranno quelli di Test::Unit, nel caso in cui quelli propri di Shoulda non vengano rintracciati.

Personalizzazione dei template

L’ultima cosa che trattiamo in questo episodio è il modo per personalizzare i template generati. Di sotto è riportata il codice di default della vista per l’action index per il controller dei task che abbiamo appena generato.

/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 %>

Poniamo di voler togliere il tag di ritorno a capo presente alla fine del template e di voler anche racchiudere l’ultimo link all’interno di un elemento di paragrafo. Come possiamo cambiare il template affinchè ogni vista index generata sia personalizzata in questo modo?

Per far ciò, possiamo creare un nuovo template personalizzato all’interno di una nuova cartella templates, sotto la cartella lib dell’applicazione. I generatori andranno a vedere qui dentro alla ricerca dei file di template prima di utilizzare i loro default. Al fine di capire quale template sia da ridefinire, dovremo dare un’occhiata al codice sorgente di Rails. Il codice per i generatori è contenuto nella cartella railties/lib/rails/generators ed i template di default delle viste sono in una sottocartella erb/scaffold/templates/. Possiamo copiare il contenuto del file index.html.erb in questa cartella e personalizzarlo secondo il nostro volere.

Dobbiamo creare una strutturas di cartelle analoga sotto alla cartella dei generatori e perciò il nostro template personalizzato index deve trovarsi in /lib/templates/erb/scaffold/index.html.erb. Dopodichè possiamo incollargli dentro il contenuto del file di template di default, modificato come volevamo. Un volta fatto ciò, alla creazione di un nuovo scaffold, per esempio per un modello chiamato Category, vedremo la vista index generata a partire dal nostro template personalizzato.

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>

E’ tutto per questo episodio. I generatori in Rails 3 si sono nettamente evoluti rispetto a quelli presenti in Rails 2 e sono anche molto più semplici da personalizzare.