#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)
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 <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
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:
# 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:
# 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:
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:
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
:
# 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 <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]
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
:
# 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.
<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
<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.