#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)
Come già fatto vedere nell’episodio 216 [guardalo, leggilo] i generatori in Rails 3 sono molto più modulari e possono essere personalizzati in modi che non erano pensabili con Rails 2. Ciononostante ci sono ancora delle volte in cui si ha ugualmente la necessità di creare un generatore completamente nuovo, perchè magari quelli forniti non vengono incontro appieno alle vostre necessità. La creazione di un generatore personalizzato è stata discussa nell’episodio 58 ma la tecnica è cambiata sostanzialmente da allora, per cui riprendiamo il discorso e mostriamo come creare un generatore analogo a quello di tale episodio in Rails 3.
Incominciamo
Il modo più semplice per creare un generatore è farlo da una nuova applicazione Rails 3, er cui partiamo creandone una nuova. Rails 3.0 beta 4 è appena stato rilasciato e la sintassi per la creazione di nuove applicazioni è cambiata, per cui, per creare l’applicazione todo che useremo per scrivere il nostro generatore, anzichè lanciare il comando:
$ rails todo
dovremo lanciare:
$ rails new todo
Dopo che abbiamo creato la nostra nuova applicazione, possiamo partire con la creazione di del nostro generatore, usando uno dei generatori forniti. Il comando rails g generator
crea i file di base di cui avremo bisogno per il nostro nuovo generatore. Possiamo esaminare i parametri che il comando accetta, lanciando:
$ rails g generator --help
Il generatore è piuttosto semplice e ha bisogno solo del nome del generatore che si vuole creare. Vogliamo che il nostro generatore generi un file di layout applicativo, per cui lo chiamiamo 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/templates
Il generatore crea i file sotto la cartella /lib
dell’applicazione, il che significa che il generatore sarà disponibile esclusivamente per questa applicazione. Per renderlo disponibile a qualunque applicazione, dovremo fare un po’ di lavoro extra; vedremo come fare anche questa cosa più avanti nel corso dell’episodio.
La struttura creata dal generatore è piuttosto semplice e contiene esclusivamente una classe. Nella classe c’è una linea di codice che dice al generatore di ispezionare nella cartella dei template alla ricerca di ulteriori file:
class LayoutGenerator < Rails::Generators::NamedBase source_root File.expand_path('../templates', __FILE__) end
Il genratore generator
crea anche un file USAGE
che serve a descrivere la documentazione per il nostro generatore. Questa documentazione verrà mostrata se si lancierà il nostro generatore con l’opzione --help
.
La classe creata dal generatore eredita da Rails::Generators::NamedBase
. Questo implica che si dovrà fornire un nome come parametro al lancio del nostro generatore, allo stesso modo di come si è fatto al lancio del generatore generator
. Possiamo averne conferma, lanciando il nostro nuovo generatore con l’opzione --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/create
Si note che il contenuto del file USAGE
viene mostrato in fondo all’output.
Vogliamo rendere opzionale il parametro NAME
impostando un valore di default pari a application
. Possiamo farlo rendendo la nostra classe figlia di Rails::Generators::Base
, anzichè di NamedBase
. Ciò renderà opzionali tutti gli argomenti e di conseguenza ci darà più flessibilità nell’adattare il generatore alle nostra esigenze.
Possiamo dichiarare gli argomenti accettati dal nostro generatore mediante il metodo argument
. Definire gli argomenti per conto nostro anzichè usare l’opzione di default NAME
significa che possiamo stabilire un valore di default per ciascuno argomento. Aggiungiamo un argomento layout_name
con un valore di default pari a application
:
class LayoutGenerator < Rails::Generators::Base source_root File.expand_path('../templates', __FILE__) argument :layout_name, :type => :string, :default => "application" end
Se lanciamo nuovamente il generatore con l’opzione --help
, vedremo il nostro nuovo argomento in lista. E’ mostrato fra parentesi quadre, ad indicare che è un parametro opzionale.
$ rails g layout --help Usage: rails generate layout [LAYOUT_NAME] [options]
I generatori in Rails 3 si basano sulla libreria Thor, che somiglia a rake. Molti dei metodi che chiamiamo nella classe del nostro generatore sono metodi definiti all’interno dello stesso Thor, per cui se vorrete saperne di più potrete consultare il codice sorgente di Thor e la documentazione relativa.
Creiamo i file che verranno usati dal generatore
Vogliamo che il nostro generatore crei due file: un file di layout di applicazione e un foglio di stile. Per fare ciò, creiamo due file nella cartella templates
del nostro generatore e facciamo in modo che quest’ultimo copi questi file nelle cartelle corrette dell’applicazione su cui verrà lanciato.
Come definiamo il comportamento del generatore? Ebbene, il modo in cui lo si fa è piuttosto anomalo. Tutti i metodi pubblici definiti nella classe del generatore verranno eseguiti al lancio del generatore. Quindi possiamo definire un metodo chiamato generate_layout
e questo sarà automaticamente eseguito al lancio del generatore. E’ un concetto piuttosto strano inizialmente, ma alla fine si rivela un buon sistema per poter organizzare il codice all’interno del generatore.
La prima cosa che facciamo fare al nostro generatore è copiare il foglio di stile dalla cartella templates
alla cartella /public/stylesheets
dell’applicazione. Possiamo usare il metodo copy_file
per questo:
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
Il metodo copy_file
richiede due parametri. Il primo è il nome del file nella cartella templates
da copiare e il secondo è il percorso di destinazione. Si noti che il nome del file di destinazione è basato sul nome dell’argomento layout_name
che viene passato al generatore. Il metodo argument
crea un metodo layout_name
che possiamo utilizzare, ma siccome il nome del layout passato potrebbe essere in camel-case, invochiamo sul quest’ultimo il metodo underscore
per essere certi che sia in un formato adatto ai nomi di file.
Infine dobbiamo creare il file stylesheet.css
nella cartella templates
e incollarvi il contenuto del default CSS che vogliamo per la nostra applicazione.
Se ora invochiamo il nostro generatore senza argomenti, dovrebbe creare un file application.css
:
$ rails g layout <span class="passed">create</span> public/stylesheets/application.css
Poi dobbiamo fare la stessa cosa per il file di layout. Useremo la versione con gli underscore del nome del layout un paio di volte, per cui è meglio fattorizzare la conversione in un metodo che chiamiamo file_name
. Come detto poco fa, ogni metodo pubblico definito nel generatore sarà invocato al lancio del generatore, per cui dobbiamo dichiarare il metodo file_name
, che è di servizio, privato.
La prossima cosa che dobbiamo fare nel nostro generatore è creare il file di layout stesso. Potremmo usare nuovamente il metodo copy_file
, ma in questo modo avremmo semplicemente una copia esatta del file di template, mentre invece vorremmo far girare un qualche codice erb sul template, in modo che venga modificato al lancio del generatore.
Per fare questo, possiamo usare il metodo template
, che accetta argomenti simili a quelli di copy_file
, ma che interpreta tutto l’erb che trova all’interno del template prima di copiarlo nella cartella di destinazione:
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
Poi creiamo il vero e proprio file di template nella cartella templates
. Il codice contenutovi sarà il seguente:
<!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>
La prima cosa degna di nota è che dal momento che stiamo usando il metodo template
, tutti i tag erb nel codice verranno eseguiti al lancio del generatore. Se vogliamo includere dell’erb nel file generato, dobbiamo farne l’escape col simbolo percentuale all’inizio di ciascun tag erb e ciò è quanto abbiamo fatto per la maggior parte del codice erb qui sopra.
Per includere del contenuto dinamico nel template, possiamo invece usare del normale codice erb. Possiamo accedere ai metodi del generatore, anche privati, per cui chiamiamo file_name
per inserire il nome corretto del foglio di stile nella chiamata a stylesheet_link_tag
.
Per vedere se tutto funziona, rilanciamo il nostro generatore, questa volta passandogli un nome per il layout:
$ rails g layout admin <span class="passed">create</span> public/stylesheets/admin.css <span class="passed">create</span> app/views/layouts/admin.html.erb
Questa volta il generatore ha creato due file, ciascuno col nome basato sul nome di layout passato come parametro al lancio, per cui tutto sembra funzionare. Se diamo uno sguardo al file di layout generato, notiamo che il metodo file_name
è stato chiamato quando il file è stato generato, per cui il metodo stylesheet_link_tag
ha l’argomento corretto. Per gli altri tag erb era stato applicato l’escape, per cui ora li ritroviamo correttamente nel file di layout:
<head> <title>Untitled</title> <%= stylesheet_link_tag "admin" %> <%= javascript_include_tag :defaults %> <%= csrf_meta_tag %> <%= yield(:head) %> </head>
Rendere le opzioni facoltative
C’è ancora una piccola feature che vorremmo aggiungere al nostro generatore: la possibilità di passare un’opzione che prevenga la generazione del foglio di stile. Possiamo fare questa cosa, usando il metodo 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
Abbiamo chiamato la nostra option stylesheet
, dandole il tipo boolean
con un valore di default pari a true
e fornendole una descrizione. Nella linea copy_file
possiamo ora aggiungere una condizione if
per cui il foglio di stile venga copiato solo se l’opzione stylesheet
vale true
.
Analogamente, nel template di layout, vogliamo includere la linea stylesheet_link_tag
solo se l’opzione stylesheet
è true
. Racchiudiamo dunque quella riga di codice in un if
affinchè tutto ciò avvenga:
<!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>
Se lanciamo la documentazione di help per il nostro generatore nuovamente, ora vedremo che abbiamo una nuova opzione (facoltativa) stylesheet.
$ rails g layout --help Usage: rails generate layout [LAYOUT_NAME] [options] Options: [--stylesheet] # Indicates when to generate stylesheet # Default: true
Ora siamo in grado di generare un layout privo di foglio di stile, usando l’opzione --skip-stylesheet
(o --no-stylesheet
). Così facendo, verrà creato il solo file di layout:
$ rails g layout foo --skip-stylesheet <span class="passed">create</span> app/views/layouts/foo.html.erb
E’ tutto per questo episodio sulla creazione di generatori in Rails 3. Siete invitati a provare per conto vostro la creazione di nuovi generatori. Ogniqualvolta dobbiate riprodurre del codice uguale (o molto simile) nelle vostre applicazioni Rails, questo processo può essere convertito e reso più rapido e meno noioso dall’uso di un generatore. Per condividere il vostro generatore con altri, tutto quello che dovrete fare sarà di creare un plugin Rails o un gem e mettere il vostro generatore in una cartella lib/generators
. Così facendo, chiunque includa tale gem o plugin nella propria applicazione, avrà, a disposizione anche il vostro generatore.