Sei sulla pagina 1di 62

Introduzione

1. 1. Introduzione
La storia di Ruby, la sua diffusione e i motivi del suo successo
2. 2. Caratteristiche di Ruby
Le caratteristiche principali del linguaggio e i principi alla base della sua
programmazione

Gli strumenti
1. 3. L'interprete ruby e Interactive Ruby
I principale attrezzi di lavoro di Ruby: l'interprete ruby e la shell di interazione irb
2. 4. Rdoc, Ri, eRuby e Testrb
La documentazione, la gestione di pagine dinamiche in HTML con Ruby e lo
strumento di test
3. 5. RubyGems e gli IDE per Ruby
Il framework Rubygems e una panoramica dei principali ambienti di sviluppo
disponibili per Ruby
4. 6. Installazione e configurazione
Come installare Ruby su Linux, Windows e Mac

Convenzioni e programmazione OO
1. 7. Convenzioni in Ruby
Le basi del linguaggio Ruby: i nomi, i commenti al codice e la documentazione
2. 8. La programmazione ad oggetti
Introduzione alla programmazione ad oggetti: le classi, i metodi e gli attributi
3. 9. Ruby e gli oggetti - I
La programmazione ad oggetti dal punto di vista di Ruby: esempi commentati di
codice
4. 10. Ruby e gli oggetti - II
La programmazione ad oggetti dal punto di vista di Ruby: uso di attr e i suoi
complementi

I tipi di dati
1. 11. Tipi di dati: numeri
I tipi fondamentali di un linguaggio di programmazione: la gestione dei numeri
2. 12. Tipi di dati: stringhe
I tipi fondamentali di un linguaggio di programmazione: le stringhe
3. 13. Blocchi e iteratori
Come eseguire cicli di codice in Ruby attraverso l'uso di blocchi e di iteratori
4. 14. Array - I
L'uso degli Array all'interno di Ruby: i metodi più comuni
5. 15. Array - II
Array in Ruby: altri metodi e i principali iteratori applicabili alle sue istanze
6. 16. Hash
Come Ruby gestisce gli Hash, gli indici per diversi tipi di dati
Strutture di controllo ed ereditarietà
1. 17. If e unless
I primi costrutti per eseguire le istruzioni condizionali in Ruby: if e unless
2. 18. Case
Eseguire le istruzioni condizionali in modo più chiaro ed elegante con case
3. 19. While, until e for
La gestione dei cicli in Ruby con While e Until e con for
4. 20. Uscire dai cicli
Come terminare l'esecuzione dei cicli con l'istruzioni break o saltare le iterazioni con
next
5. 21. Ereditarietà singola
Approfondimento sulle classi e sulla programmazione ad oggetti: l'ereditarietà singola
6. 22. Ereditarietà multipla
Approfondimento sulle classi e sulla programmazione ad oggetti: l'ereditarietà
multipla

Gestione file e directory


1. 23. Apertura e chiusura di un file
Come aprire e chiudere un file fisico utilizzando Ruby: il metodo file.new e file.open
2. 24. Lettura e scrittura
Come leggere e modificare i dati inclusi in un file: il metodo gets, gli iteratori each,
each_byte e il metodo write
3. 25. Utili metodi e gestione directory
Un elenco commentato dei metodi più utili per operare sui file con Ruby e le istruzioni
per gestire le directory

Espressioni regolare ed eccezioni


1. 26. Le espressioni regolari
Le regole generali per utilizzare le espressioni regolari all'interno del linguaggio Ruby
2. 27. Le espressioni regolari e la classe String
Eseguire espressioni regolari sulle stringhe di testo con i metodi della classe string
3. 28. Le eccezioni
Come sollevare le eccezioni in Ruby e la rappresentazione della loro gerarchia: il
metodo raise
4. 29. La gestione degli errori
Gestire le eccezioni in Ruby e verificare la presenza di errori nel codice: i metodi
rescue ed ensure

La documentazione di Ruby: Rdoc


1. 30. Rdoc: introduzione e primo utilizzo
I principi del linguaggio di markup da utilizzare per la creazione della documentazione
in Ruby con Rdoc
2. 31. Rdoc: convertire i commenti in documentazione
Come convertire i commenti inclusi nel codice di programmazione in paragrafi di
documentazione del programma
3. 32. Rdoc: documentazione automatica
Come Rdoc genera la documentazione anche senza nessun intervento da parte
dell'utente
4. 33. Rdoc: le opzioni
Le opzioni di esecuzione di Rdoc e il loro utilizzo nella gestione della documentazione

Distribuzione di pacchetti Ruby


1. 34. Introduzione a RubyGems
Cosa è RubyGems, a cosa serve e spiegazione delle principali funzioni e opzioni del
programma
2. 35. I comandi di RubyGems
Esempi pratici di utilizzo di RubyGems: dall'installazione alla rimozione delle gemme
3. 36. Creare un pacchetto gem
Come creare passo passo un pacchetto gem contenente una libreria personale
4. 37. Installazione con setup.rb
L'alternativa a RubyGems: installare pacchetti utilizzando setup.rb
5. 38. Gestire gli hook per setup.rb
Come gestire gli hook nell'installazione e nel setup di programmi Ruby

Ongaku: un'applicazione di esempio in Ruby


1. 39. La struttura
Qual è la struttura dei file e quali sono le directory della nostra applicazione d'esempio
2. 40. La classe Cd
La classe CD serve ad impostare le informazioni per la catalogazione dei nostri Cd
3. 41. La classe Applicazione
La classe applicazione gestisce le interfacce e le opzioni del nostro piccolo programma
in Ruby
4. 42. Interfaccia e YAML
Programmazione delle procedure di inserimento e cancellazione dei Cd
5. 43. L'interfaccia testuale
Programmazione dell'interfaccia testuale vera e propria del programma
6. 44. L'applicazione in azione
La nostra applicazione è terminata: vediamo alcuni esempi di utilizzo pratico
7. 45. Distribuzione e conclusioni
Cosa manca all'applicazione e cosa dovrebbe esserci prima della sua distribuzione
Introduzione
In questa guida tratteremo del linguaggio Ruby, che dopo un decennio di diffusione solo tra gli
appassionati si sta affacciando, con grande merito, sulla scena mainstream. Che il 2006 sia stato
l'anno di Ruby non c'è dubbio alcuno; abbiamo assistito all'affermazione dei framework Ruby on
Rails e Nitro, sono state pubblicate decine di volumi sui vari aspetti del linguaggio, dal celebre
Cookbook alla raccolta dei migliori RubyQuiz, e le mailing list e i forum ad esso dedicati straripano
di iscritti.
Inoltre, dal mese di Gennaio, Ruby compare nei primi dieci posti della classifica dei linguaggi più
popolari stilata mensilmente dalla TIOBE. È sicuramente un grandissimo risultato se si pensa che è
stato progettato nel 1993 da un language designer non professionista, il giapponese Yukihiro
"Matz" Matsumoto, e che quindi non ha avuto grosse realtà, commerciali o accademiche, pronte a
sostenerlo. Oggi le cose fortunatamente vanno diversamente, Ruby può contare numerose "success
stories" alla NASA, alla Motorola e alla Lucent solo per citarne alcune.
Oltre ai già citati Rails e Nitro, spiccano tra le applicazioni scritte in Ruby il Wiki Instiki, il blog
engine Typo e l'exploitation framework Metasploit. Vorrei porre l'attenzione proprio su
quest'ultimo, che dalla versione 3 è stato riscritto completamente in Ruby, non tanto per
l'importanza che riveste il progetto ma essenzialmente per le motivazioni che hanno portato a questa
scelta. Come si legge dalla documentazione è stato scelto Ruby perché:
1. Gli sviluppatori di Metasploit amano programmare in questo linguaggio. Questa può
sembrare un argomento molto soggettivo ma in realtà racchiude una grande verità: Ruby fa
riscoprire la gioia di programmare. Inoltre gli aspetti OO così come gestiti da Ruby ben si
adattano ai requisiti di un framework del genere;
2. Ha il supporto per il threading platform-independent;
3. A differenza del Perl ha un interprete nativo per piattaforme Windows, che si traduce in
prestazione notevolmente migliori delle possibili alternative (la versione Cygwin e
ActiveState del Perl).
Oltre ad essere apprezzato dai programmatori professionisti che ne utilizzano le feature più
avanzate, il Ruby ben si presta anche ad essere imparato come primo linguaggio. Ovvero quel
linguaggio che permette, per la sua semplicità, di passare dalla teoria dei libri alla pratica del
terminale senza grossi traumi, senza doversi preoccupare di innumerevoli convenzioni e aspetti non
strettamente legati alla programmazione in sé. A tale proposito non si può non citare il meraviglioso
libro Learn To Program di Chris Pine disponibile sia liberamente online sia in versione estesa in
versione cartacea.
Fino a qualche tempo fa i detrattori sconsigliavano di utilizzare Ruby per la mancanza di
documentazione (in lingue diverse dal giapponese), per la mancanza di librerie e per la scarsa base
di programmatori esperti. Tali obiezioni sono cadute una dopo l'altra. Oggi abbiamo numerosi libri,
e siti, in inglese e ultimamente anche in italiano; le librerie e le applicazioni non mancano, basta
dare uno sguardo a RubyForge e al Ruby Application Archive (RAA); e infine sul numero e sulla
qualità degli sviluppatori Ruby non credo ci sia più nessun dubbio.
Caratteristiche di Ruby
Diamo ora uno sguardo alle caratteristiche principali del Ruby. Niente paura, solo due parole in
modo da accontentare i fanatici del PLT e non annoiare tutti gli altri. Ruby è un linguaggio open
source, general purpose e interpretato. È un linguaggio orientato agli oggetti puro nel senso che,
come nello Smalltalk, ogni cosa è un oggetto con i propri metodi. Presenta però anche aspetti del
paradigma imperativo e di quello funzionale.
Ruby di per sé non ha niente di estremamente nuovo ma prende il meglio dai più rivoluzionari
linguaggi, primi su tutti lo Smalltalk e il Perl, ma anche Pyhton, LISP e Dylan. Il risultato è
estremamente potente e sintatticamente gradevole, infatti uno dei primi slogan del Ruby fu: Ruby >
(Smalltalk + Perl) / 2
Tra le altre caratteristiche che incontreremo, e approfondiremo, nel corso della guida va ricordato il
duck typing, i blocchi, gli iteratori e le chiusure e tante altre feature che rendono il linguaggio Ruby
unico.
Come si evince dall'introduzione Ruby è stato progettato per essere semplice e immediato, per
questo motivo segue il principio di minima sorpresa (Principle of least surprise, POLS), ovvero il
linguaggio si comporta così come il programmatore si aspetta, in maniera coerente e senza
imprevisti.
Ruby possiede inoltre una sintassi pulita e lineare che permette ai nuovi sviluppatori di imparare il
linguaggio, ed essere produttivi, in pochissimo tempo. È possibile infine estendere Ruby in C e in
C++ in modo estremamente semplice se confrontato con i meccanismi di estensione degli altri
linguaggi.

L'interprete ruby e Interactive Ruby


In genere tutto quello che serve per scrivere un programma è un qualsiasi editor di testo e
ovviamente un interprete. Ruby viene distribuito insieme ad una serie di utility che vanno a creare
un ambiente di sviluppo completo e flessibile.
Oltre all'interprete infatti troviamo una shell interattiva, degli strumenti per la creazione e la
visualizzazione della documentazione, e infine strumenti per il testing e per le applicazioni web.
Vediamo una veloce carrellata degli "attrezzi" che utilizzeremo durante lo svolgimento della guida.

L'interprete Ruby
Innanzitutto c'è l'interprete Ruby che ovviamente si occupa dell'esecuzione dei programmi. Queste
sono le principali opzioni, visualizzabili attraverso l'opzione -help.
$ ruby --help

Usage: ruby [switches] [--] [programfile] [arguments]


-0[octal] specify record separator (\0, if no argument)
-a autosplit mode with -n or -p (splits $_ into $F)
-c check syntax only
-Cdirectory cd to directory, before executing your script
-d set debugging flags (set $DEBUG to true)
-e 'command' one line of script. Several -e's allowed. Omit [programfile]
-Fpattern split() pattern for autosplit (-a)
-i[extension] edit ARGV files in place (make backup if extension supplied)
-Idirectory specify $LOAD_PATH directory (may be used more than once)
-Kkcode specifies KANJI (Japanese) code-set
-l enable line ending processing
-n assume 'while gets(); ... end' loop around your script
-p assume loop like -n but print line also like sed
-rlibrary require the library, before executing your script
-s enable some switch parsing for switches after script name
-S look for the script using PATH environment variable
-T[level] turn on tainting checks
-v print version number, then turn on verbose mode
-w turn warnings on for your script
-W[level] set warning level; 0=silence, 1=medium, 2=verbose (default)
-x[directory] strip off text before #!ruby line and perhaps cd to directory
--copyright print the copyright
--version print the version

Vediamone in dettaglio alcune tra quelle che ci possono tornare utili in fase di apprendimento del
linguaggio:
• -c: viene controllata la sintassi dello script, che però non viene eseguito. In caso di successo
viene fuori un rassicurante "Syntax OK".
• -d, --debug: da utilizzare in fase di debug, la variabile $DEBUG è impostata a "true".
• -v, --verbose: mostra informazioni addizionali in output (versione dell'interprete,
warning in fase di compilazione, etc.).
• -w: come -v ma non viene stampata la versione di Ruby, inoltre se non viene indicato il
nome del programma da linea di comando lo legge dallo standard input.
• -e comando: viene eseguito il comando passato come argomento all'opzione.
Per una trattazione completa di tutte le voci si faccia riferimento alla pagina del manuale. Oltre che
attraverso le opzioni da riga di comando è possibile modificare il comportamento dell'interprete
operando sulle variabili d'ambiente. Queste sono le principali variabili utilizzate da Ruby:
• RUBYLIB: lista di directory contenenti librerie aggiunte a quelle standard.
• RUBYOPT: opzioni addizionali da passare all'interprete.
• RUBYPATH: lista di directory nel quale Ruby cerca le applicazioni quando è specificato il
flag -S.
• RUBYSHELL: persorso della shell di sistema, valido solo per piattaforme Windows e OS/2.

Interactive Ruby
L'Interactive Ruby, in breve irb, è una shell che permette di sperimentare in tempo reale col
linguaggio Ruby. Risulta particolarmente utile sia in fase di apprendimento sia in fase di
"sperimentazione". Oltre ad essere un interprete interattivo fornisce alcune utili funzionalità tra le
quali spicca la "Tab Completition", la possibilità di creare sottosessioni e la presenza di alcuni
comandi di controllo (jobs, fg, cb, conf, kill, ...).
Come l'interprete anche irb prevede opzioni da linea di comando:
$ irb --help

Usage: irb.rb [options] [programfile] [arguments]


-f Suppress read of ~/.irbrc
-m Bc mode (load mathn, fraction or matrix are available)
-d Set $DEBUG to true (same as 'ruby -d')
-r load-module Same as 'ruby -r'
-I path Specify $LOAD_PATH directory
--inspect Use 'inspect' for output (default except for bc mode)
--noinspect Don't use inspect for output
--readline Use Readline extension module
--noreadline Don't use Readline extension module
--prompt prompt-mode
--prompt-mode prompt-mode
Switch prompt mode. Pre-defined prompt modes are
'default', 'simple', 'xmp' and 'inf-ruby'
--inf-ruby-mode Use prompt appropriate for inf-ruby-mode on emacs.
Suppresses --readline.
--simple-prompt Simple prompt mode
--noprompt No prompt mode
--tracer Display trace for each execution of commands.
--back-trace-limit n
Display backtrace top n and tail n. The default value is 16.
--irb_debug n Set internal debug level to n (not for popular use)
-v, --version Print the version of irb

Spendiamo qualche parola in più su irb, che utilizzeremo molto nel corso della guida. Vediamo
come va eseguito.
Gli utenti di Linux, e dei sistemi UNIX-like, devono semplicemente aprire un terminale qualsiasi e
scrivere "irb" seguito da una o più opzioni. In ambiente Windows è possibile eseguire fxri
(Interactive Ruby Help & Console) che oltre alla shell irb ci mostra un utile browser della
documentazione (come mostrato in figura 1). In alternativa è possibile accedere a irb anche
attraverso FreeRIDE, attivando la vista attraverso l'apposita icona "IRB" oppure selezionandola nel
menu "View".

Rdoc, Ri, eRuby e Testrb


RDoc e Ri
RDoc è uno strumento che produce documentazione estraendola dai file sorgente. Produce sia
documentazione html leggibile da qualsiasi browser sia documentazione formattata per l'utility ri.
Per ora è tutto ma torneremo sull'argomento in un prossimo capitolo.

eRuby
eRuby (embedded Ruby) è uno strumento che permette di inserire codice ruby all'interno di altri
file, ad esempio nei file HTML creando di fatto delle pagine dinamiche. Ci sono due
implementazioni di eRuby: erb ed eruby. Diamo uno sguardo ad erb che viene distribuito con Ruby.
In pratica erb analizza il file che gli viene passato come argomento alla ricerca di particolari
delimitatori. In particolare quando incontra qualcosa del genere
<% istruzioni ruby %>

esegue il codice presente tra <% e %>; quando incontra


<%= espressione %>

valuta l'espressione e ne restituisce il valore; infine ogni linea che inizia col carattere "%" viene
valutata come codice ruby e <# ... %> rappresenta un commento.
Ad esempio se passiamo a erb il testo seguente
% str = "aoxomoxoa"
<%= str %> al contrario si legge <%= str.reverse %>

otterremo come output:


aoxomoxoa al contrario si legge aoxomoxoa

Ovviamente questo è un esempio banale, ma eruby/erb rappresenta una valida alternativa ai


linguaggi PHP, ASP e simili per la creazione di pagine dinamiche.

Testrb
Un semplice esecutore di test (Test::Unit); dispone di diverse interfacce grafiche molto
minimali (tk, gtk, fox) e di una modalità da linea di comando.

Le librerie di base
Oltre ai programmi appena visti, installando ruby in uno dei modi che vedremo nel prossimo
capitolo, otterremo anche le librerie di base. In ambiente Linux si troveranno in
/usr/lib/ruby/ mentre sotto Windows, se si sceglie il percorso consigliato dall'installer,
saranno nella directory c:\ruby\lib\ruby\. In tali directory di norma si trovano 3
sottodirectory:
• 1.8: contiene i moduli e le estensioni standard
• gems: contiene le librerie e le applicazioni installate con l'utility RubyGems
• site_ruby: contiene i moduli e le estensioni di terze parti, aggiunte dallo sviluppatore e
che non fanno parte della distribuzione base.

RubyGems e gli IDE per Ruby


RubyGems
Oltre ai tool precedenti, distribuiti con Ruby, risulta di grande utilità RubyGems, un framework per
la gestione di librerie e applicazioni distribuite sotto forma di appositi pacchetti ("gems"). Come
tutti i package manager che si rispettano permette di cercare, installare, disinstallare e aggiornare
librerie e applicazioni, e inoltre ne permette la convivenza di diverse versioni. Anche in questo caso,
in una prossima puntata, mostreremo come distribuire una nostra applicazione sotto forma di gem.

IDE
Esistono diversi ambienti di sviluppo (IDE, Integrated Development Environments), sia pensati
espressamente per Ruby sia adattati da altri linguaggi. Tra i principali troviamo:
• FreeRIDE: progetto open source, scritto interamente in Ruby. L'unico difetto è una certa
lentezza nei rilasci di nuove versioni e nell'aggiunta di nuove caratteristiche.
• Ruby Development Tool (RDT): plug-in per Eclipse, quindi porta con se tutti i difetti e i
pregi di eclipse.
• Komodo: progetto commerciale di ActiveState, è uno degli IDE più completi e, ad oggi,
l'unico a comprendere un GUI builder integrato. Komodo Editor è invece liberamente
scaricabile.
• ArachnoRuby: progetto commerciale, non è ancora completamente stabile e richiede
requisiti minimi di sistema abbastanza alti. Inoltre lo sviluppo della versione Linux procede
più lentamente di quella per Windows, mentre la versione per Mac OS X non è stata ancora
rilasciata.
• Ruby Development Environment (RDE): progetto non commerciale ancora in fase di
sviluppo. Esiste solo per Windows.
Oltre agli IDE appena visti sono molti utilizzati dalla comunità Ruby anche alcuni editor con
caratteristiche avanzate per gli sviluppatori. A tale proposito vanno sicuramente citati, oltre ai
classici Vim ed Emacs, SciTE e TextMate il primo open source e disponibile per Windows e Linux
mentre il secondo commerciale e disponibile solo per Mac OS X.

Altre implementazioni
Concludiamo questo capitolo citando le altre implementazioni del linguaggio Ruby. Su tutte va
ricordato per completezza e importanza JRuby, un'implementazione del Ruby scritta in Java. Con
JRuby è possibile, tra l'altro, chiamare gli oggetti Java dall'interno delle applicazioni Ruby e quindi
sfruttare tutti i vantaggi del framework di casa Sun. Esistono anche implementazioni, però non
ancora mature, per il framework .NET e sono RubyCLR e Ruby.NET.
Dopo questa breve carrellata dei principali strumenti, che useremo nel corso della guida, andiamo a
vedere come installarli sul nostro sistema qualora non siano già presenti.

Installazione e configurazione
Anche se Ruby è stato sviluppato in, e per un, ambiente Linux è possibile utilizzarlo con la maggior
parte dei sistemi operativi esistenti, come ad esempio UNIX (compresi i *BSD), Microsoft
Windows e DOS, Mac OS X, BeOS, Amiga, Acorn Risc OS, OS/2 e Syllable. La seguente guida fa
riferimento a Linux, ma non ci dovrebbero essere particolari differenze con lo sviluppo in altri
ambienti. Comunque in presenza di notevoli differenze saranno illustrate le varie alternative.

Linux
Gli utenti Linux, e degli altri sistemi UNIX-like, come al solito hanno due alternative per installare
un programma, possono compilarlo dai sorgenti oppure utilizzare il pacchetto precompilato della
propria distribuzione. Sorvolando per ovvie ragioni sull'installazione dei pacchetti andiamo a vedere
come si installa Ruby a partire dai sorgenti. Innanzitutto occorre recuperare il tarball con i sorgenti
dal sito principale.
Ne esistono tre versioni, c'è la versione Stabile, la versione "Stable Snapshot" che contiene l'ultima
versione stabile del CVS e infine c'è la "Nightly Snaphot" che è l'ultima versione del CVS e
potrebbe risultare altamente instabile. Una volta scaricati i sorgenti ci si trova davanti alla classica
installazione UNIX, ovvero configure && make && make install, ad ogni modo
maggiori informazioni si trovano nel file README.

Windows
Anche gli utenti Windows hanno diverse possibilità, possono installare i binari oppure utilizzare il
One Click Installer.
Installando il "Ruby Installer for Windows", che è la scelta consigliata, avremo in poche mosse
un'ambiente di programmazione completo. Precisamente troveremo sul nostro disco, oltre
ovviamente all'interprete Ruby completo di sorgenti con le sue utility e le librerie di base, anche le
utility RubyGems e Rake, alcune utili librerie come FxRuby, OpenGL, XMLParser, HTMLParser,
RubyDBI, la prima edizione del libro "Programming Ruby", e utili tool come l'IDE FreeRIDE (in
figura 2) e l'editor SciTE.
Figura 2: FreeRIDE, l'IDE gratuito per Ruby
Mac
Dalla versione 10.5 di MacOS X Ruby è installato di default. Per le versioni precedenti è possibile
usare i DarwinPorts ed installarlo con un semplice "port install ruby".

RubyGems
I sorgenti di RubyGems sono disponibili su Internet e si installano semplicemente con il comando
"ruby setup.rb".

Altro
Infine è possibile provare Ruby senza installare nulla, basta puntare il proprio browser web su Try
Ruby!. A questo indirizzo si trova una shell interattiva di Ruby pronta all'uso con tanto di tutorial di
base per i primi passi.
Dopo questa necessaria introduzione a Ruby e ai suoi gioielli, cominceremo dalla prossima puntata
a illustrare il linguaggio vero e proprio.

Convenzioni in Ruby
Prima di introdurre i principali aspetti del linguaggio diamo uno sguardo veloce alle principali
convenzioni sui nomi e sui commenti. Altre convenzioni, e semplici suggerimenti sullo stile,
verranno introdotte durante lo svolgimento della guida.

Nomi
Iniziamo a vedere le convenzioni sui nomi delle variabili, dei metodi e delle classi. Di seguito una
rapida carrellata con degli esempi esplicativi:
• Le variabili locali devono iniziare con una lettera minuscola o col carattere underscore.
• Le variabili globali con il segno $.
• Le variabili di istanza con il segno @.
• Le variabili di classe con due segni @.
• Le costanti con una lettera maiuscola.
Ecco degli esempi:
nome = "Matz" # nome è una variabile locale
Mesi = 12 # Mesi è una costante
MESI = 12 # anche MESI è una costante
@linguaggio = "ruby" # @linguaggio è una variabile di istanza
@Linguaggio = "ruby" # anche @Linguaggio è una variabile di istanza
$anno = 2007 # $anno è una variabile globale
@@counter = 0 # @@counter è una variabile di classe

Inoltre i nomi delle classi, e dei moduli, devono iniziare con la lettera maiuscola, mentre i nomi dei
metodi e dei suoi parametri devono iniziare per lettera minuscola o per underscore "_". Ad esempio:
module Geometria
class Quadrato
def disegna

Le parole chiave del Ruby, che come accade per tutti i linguaggi non possono essere utilizzate in
nessun altro modo, sono:
BEGIN END alias and begin break case class
def defined? do else elsif end ensure
false for if in module next nil not
or redo rescue retry return self super then
true undef unless until when while yield

Ne scopriremo il significato nel corso della guida.

Commenti
I commenti sono identificati dal segno #, è considerato commento tutto quello che lo segue fino alla
fine della riga:
a = 1 # Questo è un commento.
# L'intera riga è un commento

In alternativa è possibile inserire più righe di commento tra i tag =begin e =end, in questo modo:
=begin
Questo è un commento
su più righe
=end

È possibile anche inserire dei commenti che saranno utilizzati per generare documentazione RDoc;
torneremo su questo argomento in un prossimo capitolo.

Documentazione
Come accennato prima, uno strumento fondamentale in fase di apprendimento è ri che prende come
argomento il nome di una classe, di un modulo o di un metodo e ne mostra a video una breve
descrizione con tanto di esempi. Ad esempio per sapere cosa fa e come si usa il metodo reverse
della classe String, che abbiamo usato prima, basta scrivere:
# ri String.reverse

--------------------------------------------------------- String#reverse
str.reverse => new_str
------------------------------------------------------------------------
Returns a new string with the characters from _str_ in reverse
order.

"stressed".reverse #=> "desserts"

In alternativa al punto si può utilizzare anche il carattere "#" (ri String#reverse); per i
metodi di classe si usa invece la forma con i due punti "::". Se non si conosce il nome esatto è
possibile anche fornire come argomento una indicazione parziale, ad esempio
# ri String.re

More than one method matched your request. You can refine
your search by asking for information on one of:

String#reverse!, String#replace, String#reverse

In questo caso ci viene fornito in output una lista di possibili scelte. Torniamo un attimo ai nomi dei
metodi; il lettore attento avrà di certo notato nell'output precedente la presenza, oltre a reverse,
del metodo reverse! e si starà chiedendo qual'è la differenza tra i due. In Ruby c'è una
convenzione che prevede che i metodi che terminano con il punto esclamativo siano metodi
distruttivi che non creano una nuova istanza ma modificano direttamente il valore. Analogamente ci
sono i metodi di verifica, i cui nomi terminano per punto interrogativo, che svolgono in genere
funzioni di verifica e restituiscono un valore booleano.
Infine una breve nota ripresa da Programming Ruby di Dave Thomas. Aggiungendo questa
funzione
def ri(*names)
system(%{ri #{names.map {|name| name.to_s}.join(" ")}})
end

al file di configurazione .irbrc sarà possibile utilizzare ri all'interno di irb.

La programmazione ad oggetti
Poiché in apertura abbiamo detto che in Ruby "ogni cosa è un oggetto", e dato che non è uno
slogan, prima di muoverci in qualsiasi direzione andiamo a vedere cosa sono gli oggetti.
Per permettere a tutti i lettori, anche a quelli che non sono pratici di programmazione orientata agli
oggetti, di apprezzare al meglio il linguaggio, prima di affrontare l'argomento dal punto di vista del
Ruby lo tratteremo in modo generico e molto elementare. Ovviamente chi ha già dimestichezza con
gli oggetti può saltare a piè pari questo paragrafo. Su HTML.it è presente una guida approfondita
alla programmazione ad oggetti.
Nella programmazione OO, come nel mondo reale, un oggetto ha le proprie caratteristiche e i propri
comportamenti. Ad esempio il mio router ha una marca, un modello e un numero di serie e fa delle
determinate cose (ad esempio può connettersi e disconnettersi).
È possibile "comunicare" con gli oggetti attraverso i messaggi, che non sono altro che richieste di
esecuzione di un'operazione. Un'operazione è qualcosa che l'oggetto destinatario è in grado di
eseguire, ad esempio possiamo chiedere al nostro router di connettersi e di disconnettersi.
Informaticamente parlando, per poter creare un oggetto occorre definire una classe che ne definisce
i metodi e gli attributi; in parole povere una classe è un insieme di oggetti simili che si
comportano allo stesso modo e sono caratterizzati dagli stessi attributi.
Diminuendo il grado di astrazione e tornando alla programmazione vera e propria possiamo dire che
un oggetto può essere visto come un contenitore di dati (che a questo punto possiamo identificare
con delle semplici variabili) dotato di una serie di funzioni (i metodi) che definiscono un'interfaccia
all'oggetto stesso.

Ruby e gli oggetti - I


Vediamo come è implementata in Ruby la programmazione orientata agli oggetti. Scriviamo un
semplice esempio supponendo di dover programmare un videogame di guerra dove tra le altre cose
sono presenti dei carri armati e dei camion per il trasporto dei soldati. Rappresentiamo questi
oggetti in Ruby:
class CarroArmato
def initialize
puts "Sono un nuovo carro armato"
end
end

class Camion
def initialize
puts "Sono un nuovo camion"
end
end

una volta definite le classi possiamo istanziarne degli oggetti. In irb, dopo aver creato le classi
scriviamo:
> ca = CarroArmato.new
Sono un nuovo carro armato
=> #<CarroArmato:0xb7d327fc>
> c = Camion.new
Sono un nuovo camion
=> #<Camion:0xb7d07e78>

Vediamo in ordine cosa abbiamo fatto. Innanzitutto abbiamo creato la classe usando la parola
chiave class seguita dal nome e abbiamo terminato il codice relativo alla nostra classe con la
parola chiave end. Abbiamo quindi creato il metodo initialize che viene chiamato quando
istanziamo un nuovo oggetto con il metodo new. initialize è quello che in OOP si dice un
costruttore che viene invocato ogni volta che si crea un nuovo oggetto. Alcuni linguaggi orientati
agli oggetti come il C++ contemplano anche il metodo distruttore della classe che, invocato al
momento del rilascio dell'istanza di un oggetto, si occupa tra le altre cose di de-allocare la memoria
impegnata dall'oggetto. Nei linguaggi moderni, come Ruby, Java o C#, le attività generalmente
svolte dal distruttore vengono svolte da sistemi di garbage collection, nel caso di Ruby è l'interprete
a farsene carico.
Tornando al nostro esempio, initialize è un normale metodo ed è definito dalle parole chiave
def, seguita dal nome, e dal solito end. Essendo un esempio banale il costruttore non fa altro che
dare notizia dell'avvenuta creazione dell'oggetto stampando a video una stringa utilizzando la
funzione puts (put string).
Per fare un esempio meno banale e per vedere come si passano i parametri ad un metodo
miglioriamo le nostre classi. Supponiamo di voler definire al momento della creazione degli oggetti
alcuni parametri caratteristici. Ad esempio se vogliamo indicare quanto carburante e quanti colpi
avrà il nostro carro armato al momento della creazione ci basta scrivere:
class CarroArmato
def initialize (colpi, carburante)
@carburante = carburante
@colpi = colpi
end
end

Analogamente per i camion, con la differenza che invece dei colpi ci saranno dei posti per i
passeggeri:
class Camion
def initialize (posti, carburante)
@carburante = carburante
@posti = posti
end
end

Come abbiamo visto nel capitolo precedente, @carburante, @colpi e @posti sono delle
variabili d'istanza e sono visibili da tutti i metodi della classe.
In questo modo alla creazione dei nostri nuovi oggetti vanno passati i parametri relativi al
carburante, ai colpi e ai posti:
> ca = CarroArmato.new(10, 100)
=> #<CarroArmato:0xb7d035fc @colpi=10, @carburante=100>

c = Camion.new(20, 100)
=> #<Camion:0xb7ce046c @posti=20, @carburante=100>

@carburante, @colpi e @posti sono detti anche attributi e definiscono come la classe è
vista dall'esterno. Per poter accedere, in lettura, a tali campi occorre creare dei metodi accessori del
tipo:
class CarroArmato
def carburante
@carburante
end
def colpi
@colpi
end
end

fatto ciò possiamo scrivere cose del genere:


> puts ca.carburante
=> 100
> puts ca.colpi
=> 100

Per poter accedere agli attributi anche in scrittura occorre definire anche in questo caso un apposito
metodo:
class CarroArmato
def carburante=(carburante)
@carburante = carburante
end
def colpi=(colpi)
@colpi = colpi
end
end

e quindi siamo in grado di impostare i valori degli attributi semplicemente con


> ca.colpi = 50
=> 50
> puts ca.colpi
=> 50

Ruby e gli oggetti - II


La gestione degli attributi è molto elementare e allo stesso tempo molto tediosa. Essendo però una
pratica di uso comune, il Ruby fornisce delle comode scorciatoie per la gestione degli attributi:
attr_reader, attr_writer, attr_accessor e attr. Il nostro codice diventa in
definitiva:
class CarroArmato
attr :colpi, true
attr :carburante, true

def initialize (colpi, carburante)


@colpi = colpi
@carburante = carburante
end
end

class Camion
attr :posti, true
attr :carburante, true

def initialize (posti, carburante)


@posti = posti
@carburante = carburante
end
end

Il valore true dopo il nome dell'attributo sta ad indicare che la variabile è anche scrivibile, in
alternativa avremmo potuto usare attr_reader e attr_writer oppure attr_accessor
che è sinonimo di "attr nome_simbolo, true", ovvero scrivere
attr_accessor :posti, :carburante

è lo stesso che scrivere


attr :posti, true
attr :carburante, true

Questo breve esempio mostra tutta l'eleganza del Ruby che fa di tutto per venire in contro alle
necessità dello sviluppatore senza perdere omogeneità e leggibilità.
In realtà nel nostro caso è meglio permettere solo la lettura degli attributi e creare ad esempio un
metodo per la gestione del carburante. La nostra classe Camion diventa dunque:
class Camion
attr_reader :posti, :carburante

def initialize (posti, carburante)


@posti = posti
@carburante = carburante
end

def rifornimento (quantita)


@carburante += quantita
end
end

Ora non sono più lecite istruzioni del genere


> c.carburante = 100

ma per manipolare l'attributo carburante occorre usare il metodo rifornimento:


> c.rifornimento(100)

Lo stesso discorso vale per l'altra classe e gli altri attributi.


Degli argomenti dei metodi è possibile indicare anche il valore di default, ad esempio definendo
initialize in questo modo
def initialize (colpi, carburante = 100)
@colpi = colpi
@carburante = carburante
end

possiamo istanziare nuovi oggetti anche passando un solo parametro, per il secondo argomento
viene considerato il valore di default
> ca = CarroArmato.new(50)
=> #

Quando si chiama un metodo, l'uso delle parentesi è facoltativo, ovvero è corretto anche scrivere:
> ca = Camion.new 50
=> #

Per ora, per evitare confusione fermiamoci qui, torneremo di seguito sugli oggetti trattando
l'ereditarietà, il polimorfismo e altri aspetti avanzati.

Tipi di dati: numeri


Iniziamo a vedere i tipi fondamentali di ogni linguaggio: i numeri e le stringhe. Queste ultime le
analizzeremo nella prossima lezione.
Il linguaggio Ruby prevede l'esistenza di numeri interi e di numeri a virgola mobile (floating-point).
I primi sono oggetti delle classi Fixnum o Bignum mentre i secondi sono di tipo Float. I
numeri che possono essere rappresentati in una word (meno un bit) sono oggetti della classe
Fixnum, quelli che vanno oltre questo limite sono invece istanze della classe Bignum:
f = 123
f.class
=> Fixnum
b = 1234567890
b.class
=> Bignum

Per rappresentare numeri non decimali va fatto precedere al numero vero e proprio un indicatore di
base (0b per i binari, 0 per gli ottali, 0d per i decimali e 0x per gli esadecimali), ad esempio:
> 10 # i numeri decimali sono preceduti da 0d che però può essere omesso
=> 10
> 010 # i numeri ottali sono preceduti da uno 0
=> 8
> 0x10 # i numeri esadecimali sono preceduti da 0x
=> 16
> 0b10 # i numeri binari sono preceduti da 0b
=> 2

Essendo i numeri oggetti di una determinata classe possiamo applicare ad essi i metodi previsti
dalle rispettive classi. I principali metodi previsti da Fixnum e Bignum sono:
• operazioni aritmetiche di base (+, -, *, /, div, %, modulo, **, - unario)
• operazioni sui bit (~, |, &, ^, <<, >>)
• altre operazioni aritmetiche:
• valore assoluto (abs): -123.abs -> 123
• dimensione in byte (size): 100.size -> 4
• operazioni di conversione (to_f, to_s, to_sym): 1.to_s -> "1", 1.to_f ->
1.0
Altri metodi sono ereditati dalle classi Integer e Numeric poiché entrambe le classi derivano
da Integer che a sua volta deriva da Numeric. Abbiamo ad esempio chr, floor, next, to_i,
step e molti altri.
La classe Float oltre ai metodi base visti per Fixnum e a quelli ereditati da Numeric prevede tra
l'altro:
• infinite? che restituisce -1, +1 o nil a seconda che il valore sia pari a meno infinito, più
infinito o un numero finito: (1.0).infinite? -> nil,
(+1.0/0.0).infinite? -> 1
• nan? restituisce true se il valore è un numero non valido secondo gli standard IEEE.
Per ulteriori dettagli su questi e altri metodi consiglio vivamente l'uso di ri sulle classi Fixnum,
Bignum, Integer e Numeric.

Tipi di dati: stringhe


Come abbiamo visto in precedenza una delle maggiori influenze del linguaggio Ruby è il Perl e da
questo eredita, tra l'altro, una potente e avanzata gestione del testo.
Le stringhe, sequenze di caratteri racchiusi tra virgolette (") o tra singoli apici ('), sono delle istanze
della classe String. È previsto un avanzato sistema di accesso ai singoli caratteri delle stringhe
attraverso il metodo []. Ad esempio possiamo accedere a qualsiasi carattere di una stringa
indicandone semplicemente l'indice:
> str = "Ciao Mondo!"
=> "Ciao Mondo!"
> str.class
=> String
> str[0]
=> 67

viene restituito un intero che è il valore del codice ASCII corrispondente nel nostro caso al carattere
"C". Per riconvertirlo in carattere si utilizza il metodo chr visto prima:
> str[0].chr
=> "C"

È possibile con [] anche accedere a sottostringhe:


> str[0..3]
=> "Ciao"
> str[0...3]
=> "Cia"
> str[0,4]
=> "Ciao"

Dove 0..3 e 0...3 sono dei range, le due cifre rappresentano gli estremi dell'intervallo da prendere in
considerazione; se si utilizzano i tre punti (...) il secondo estremo viene escluso. Passando invece
come argomenti due interi, ad esempio 0 e 4, estraiamo da str la sottostringa che inizia al carattere
con indice 0 (la prima lettera della prima parola) e che ha lunghezza pari a 4 caratteri. Inserendo
degli indici negativi il conteggio avviene invece dalla fine della stringa:
> str[-6..-1]
=> "Mondo!"
> str[-6,6]
=> "Mondo!"

Utilizzando una stringa come argomento viene restituito il valore nil se questa non è presente e la
stringa stessa in caso contrario:
> str["Hello"]
=> nil
> str["Mondo"]
=> "Mondo"

Infine è possibile anche passare come argomento un'espressione regolare:


> str[/\w+\s/]
=> "Ciao "

torneremo in dettaglio sull'argomento in un prossimo capitolo. In alternativa a [] è possibile usare il


metodo slice.
Analogo a [] è il metodo di assegnazione []=, l'ovvia differenza è che in questo caso viene eseguita
un'assegnazione alla porzione di stringa determinata dall'argomento di []=. Anche in questo caso
possono essere passati come argomenti un intero, due interi, un range, un'espressione regolare o una
stringa:
> str = "Ciao Mondo!"
> str[4] = ","
> str[0..3] = "Hello"
> str[-6,5] = "World"
> str
=> "Hello,World!"

Vediamo infine alcuni utili metodi applicabili alle stringhe:


• * moltiplica la nostra stringa
• "Ciao " * 2 -> "Ciao Ciao "
• + concatena due stringhe
• "Ciao " + "Mondo" -> "Ciao Mondo"
• capitalize restituisce una copia della stringa con il primo carattere maiuscolo
• "ciao".capitalize -> "Ciao"
• downcase e upcase restituiscono rispettivamente una copia della stringa con tutte le lettere
minuscole e con tutte le lettere maiuscole
• "Ciao".upcase -> "CIAO"
• "Ciao".downcase -> "ciao"
• swapcase restituisce una stringa con il case dei caratteri invertito, minuscole al posto di
maiuscole e viceversa
• chop elimina l'ultimo carattere da una stringa
• "Ciao\n".chop -> "Ciao"
• "Ciao".chop -> "Cia"
• chomp elimina dalla fine della stringa l'elemento separatore passato come argomento, se
invocato senza argomento rimuove i valori \n, \r o entrambi
• "Ciao\n".chomp -> "Ciao"
• "Ciao".chomp -> "Ciao"
• "Ciao".chomp("ao") -> "Ci"
• sub e gsub sostituiscono rispettivamente la prima occorrenza e tutte le occorrenze
dell'espressione regolare passata come primo argomento e lo sostituiscono con il valore del
secondo argomento
• "ciao mondo".gsub(/o/, '*') -> "cia* m*nd*"
• "ciao mondo".sub(/o/, '*') -> "cia* mondo"
• split divide la stringa in sottostringhe in conformità al delimitatore passato come argomento.
Come delimitatore è possibile usare sia stringhe che espressioni regolari, il valore di default
è il carattere bianco
• "ciao mondo".split -> ["ciao", "mondo"]
• "ciao mondo".split('o') -> ["cia", " m", "nd"]
• tr prende due stringhe come argomenti e sostituisce i caratteri del primo argomento con i
corrispondenti caratteri del secondo argomento
• "ciao".tr("c", "m") -> "miao"
• "ciao".tr('a-y', 'b-z') -> "djbp"
Altri metodi molto utili per la formattazione del testo sono strip, lstrip, rstrip, ljust,
rjust e center. Sono metodi molto semplici e intuitivi vediamo solo qualche esempio veloce:
> "ciao".center(20)
=> " ciao "
> "ciao".ljust(20)
=> "ciao "
> "ciao".rjust(20)
=> " ciao"
" ciao ".lstrip
=> "ciao "
" ciao ".rstrip
=> " ciao"
> " ciao ".strip
=> "ciao"

Di quasi tutti i metodi visti in questo paragrafo ne esiste anche la versione distruttiva che, invece di
restituire una copia della stringa opportunamente modificata, modifica direttamente la stringa
originaria. Torneremo sulle stringhe nel capitolo sulle espressioni regolari.

Blocchi e iteratori
In questo capitolo faremo una panoramica sui blocchi e sugli iteratori, in modo da avere gli
strumenti necessari per affrontare i prossimi capitoli. Anche se sono caratteristiche presenti in altri
linguaggi da lungo tempo, è il Ruby che ha reso l'utilizzo di questi potenti strumenti incredibilmente
semplice.

Blocchi e iteratori
Iniziamo con un paio di definizioni. Un blocco sintatticamente è una porzione di codice compresa
tra le parole chiave do e end oppure tra una coppia di parentesi graffe { e }; convenzionalmente si
utilizza la seconda forma per blocchi che occupano una sola riga. Un iteratore invece è un normale
metodo che accetta come argomento un blocco, ovvero una funzione anonima che viene eseguita
sui parametri passatigli dall'iteratore. È molto più semplice di quello che sembra, vediamo un
semplice esempio per chiarire un po' le cose:
> str = "Ciao Mondo"
> str.each_byte {|c| puts c.chr}
C
i
a
o

M
o
n
d
o

Abbiamo utilizzato l'iteratore each_byte della classe String che passa ogni byte della stringa
come parametro al blocco tra parentesi graffe. Abbiamo anche passato un parametro (c) tra il
metodo e il blocco. La variabile c, locale al blocco, assume quindi di volta in volta il valore passato
dal metodo, nel nostro esempio i singoli caratteri delle stringa.
È possibile passare dei parametri anche all'iteratore come a qualsiasi altro metodo. Ad esempio
l'iteratore each, o each_line, della classe String accetta come argomento il valore del
separatore (\n è il valore di default):
> str.each {|c| puts c}
Ciao Mondo
> str.each(' ') {|c| puts c}
Ciao
Mondo

Vedremo la grande utilità degli iteratori quando parleremo degli array e delle altre strutture dati.
Prima di concludere diamo un altro sguardo ai blocchi e vediamo come vanno chiamati dall'interno
di un metodo. Subito un esempio che poi commenteremo:
def chiama
puts "Sono nel metodo"
yield
end

chiama { puts "Sono nel blocco" }

eseguendo questo codice otterremo il seguente output:


Sono nel metodo
Sono nel blocco

Per richiamare il blocco abbiamo usato l'istruzione yield, che non fa altro richiamare il blocco
associato al metodo che lo contiene. È possibile utilizzare più volte yield all'interno di un metodo:
def chiama
yield
puts "Sono nel metodo"
yield
end

e anche passare parametri al blocco tramite yield:


def chiama
puts "Sono nel metodo"
yield(100)
end
chiama { |n| puts "Sono nel blocco e il parametro vale: #{n}" }

In questo modo avremo in output


Sono nel metodo
Sono nel blocco e il parametro vale: 100

Apriamo una breve parentesi sul significato di #{espressione} all'interno della stringa passata
a puts. In pratica il codice contenuto all'interno delle parentesi viene eseguito e il risultato
trasformato in stringa, e nel nostro esempio il tutto è stampato.
Infine possiamo creare un blocco contenente delle normali espressioni:
begin
#espressione1
#espressione2
#...
end

Il valore dell'ultima espressione valutata sarà anche il valore del blocco. Come vedremo tra qualche
capitolo begin/end risulta di grande utilità soprattutto nella gestione delle eccezioni.

Array - I
Un array può essere visto come una lista di oggetti non necessariamente dello stesso tipo. Gli array
sono istanze della classe Array e vanno dunque creati nel seguente modo:
arr = Array.new

in alternativa è possibile creare un array anche scrivendo semplicemente:


arr = []

in entrambi i casi creeremo un array vuoto. Se invece si deve creare un array non vuoto è possibile
passare una lista di valori separati da virgole:
arr = [1, 2, "tre", "IV"]

in questo modo abbiamo creato un array di quattro elementi, due interi e due stringhe. È possibile
chiamare il metodo new anche con uno o due parametri, il primo indica le dimensioni dell'array
mentre il secondo il valore di inizializzazione:
> arr = Array.new(3)
=> [nil, nil, nil]
arr = Array.new(3, 100)
=> [100, 100, 100]

Per le altre forme del metodo new rimando alla documentazione ufficiale.
Per inserire nuovi elementi nell'array o per modificare quelli esistenti si utilizza il metodo []=
> arr = [1, 2, 2]
=> [1, 2, 2]
> arr[2] = 3
=> 3
> arr
=> [1, 2, 3]

Si può anche indicare un indice superiore alle dimensioni dell'array, in tal caso l'array si estende
automaticamente e gli elementi compresi tra il l'indice indicato e l'ultimo elemento dell'array sono
impostate a nil:
> arr[8] = 9
=> 9
> arr
=> [1, 2, 3, nil, nil, nil, nil, nil, 9]

Come indice è possibile indicare due interi che rappresentano rispettivamente l'indice di inizio e il
numero di elementi da sostituire con il valore passato:
> arr = [1, 2, 3, 4, 5]
=> [1, 2, 3, 4, 5]
> arr[0,2] = ["I", "II"]
=> ["I", "II"]
> arr
=> ["I", "II", 3, 4, 5]
> arr[2,3] = "III"
=> "III"
> arr
=> ["I", "II", "III"]

in questo modo abbiamo creato un array di cinque elementi e poi abbiamo sostituito i primi due
(indice 0 e numero di elementi 2) con i valore "I" e "II". Abbiamo infine sostituito i restanti tre
elementi con il valore "III". Analogamente può essere usato anche un range con ovvio significato,
l'esempio precedente può essere riscritto così:
> arr = [1, 2, 3, 4, 5]
=> [1, 2, 3, 4, 5]
> arr[0..1] = ["I", "II"]
=> ["I", "II"]
> arr
=> ["I", "II", 3, 4, 5]
> arr[2..4] = "III"
=> "III"
> arr
=> ["I", "II", "III"]

Per il resto, per quel che riguarda i range, valgono le stesse proprietà già viste per le stringhe.
L'accesso agli elementi avviene attraverso il metodo [], o il metodo slice che prevede anche una
forma distruttiva slice! che elimina dall'array gli elementi indicati dall'indice. Come per []=,
anche [], può prendere come argomenti un intero, oppure due interi o infine un range.

Array - II
Un altro metodo per inserire elementi in un array è rappresentato dai metodi << e push che
inseriscono i nuovi valori in fondo all'array che si comporta in questo caso come una pila (stack):
> arr = [1, 2, 3]
=> [1, 2, 3]
> arr << 5 << 6
=> [1, 2, 3, 5, 6]
> arr.push(7,8)
=> [1, 2, 3, 5, 6, 7, 8]
All'opposto il metodo pop invece elimina l'ultimo elemento di un array:
> arr.pop
=> 8
> arr
=> [1, 2, 3, 5, 6, 7]

Un altro metodo per inserire valori in un array già esistente è insert che prende come argomenti
un indice ed uno o più oggetti da inserire nell'array:
> arr = [10, 30, 40]
=> [10, 30, 40]
> arr.insert(1, 20)
=> [10, 20, 30, 40]

è possibile anche indicare due o più oggetti, in questo modo il primo verrà inserito nella posizione
indicata dall'indice e gli altri di seguito:
> arr.insert(4, 50, 60, 70)
=> [10, 20, 30, 40, 50, 60, 70]

Agli array è possibile anche applicare le operazioni +, -, *, &. Il metodo concatenazione (+)
restituisce un nuovo array ottenuto dalla concatenazione di due array:
> a = ["a", "e", "i"]
=> ["a", "e", "i"]
> b = ["o", "u"]
=> ["o", "u"]
> c = a + b
=> ["a", "e", "i", "o", "u"]

Analogamente il metodo differenza restituisce un nuovo array ottenuto dalla differenza di due array:
> a = ["a", "e", "i"]
=> ["a", "e", "i"]
> b = ["a", "u"]
=> ["a", "u"]
> c = a - b
=> ["e", "i"]

Il metodo (*) invece si comporta in modo diverso a seconda dell'argomento; se gli viene passato un
intero n restituisce un array costituito da n copie dell'array originario:
> a = ["a", "e", "i"]
=> ["a", "e", "i"]
> a * 2
=> ["a", "e", "i", "a", "e", "i"]

Se invece all'argomento può essere applicato il metodo to_str, e non è un intero, avremo come
risultato una stringa costituita dagli elementi dell'array intervallati dalla stringa argomento:
> a = ["a", "e", "i"]
=> ["a", "e", "i"]
> a * "..."
=> "a...e...i"

Infine c'è il metodo intersezione (&) che restituisce un nuovo array costituito dagli elementi comuni
dei due array:
> a = ["a", "e", "i"]
=> ["a", "e", "i"]
> b = ["i", "e", "i"]
=> ["i", "e", "i"]
> a & b
=> ["e", "i"]

Altri metodi di grande utilità pratica sono:


• clear che rimuove tutti gli elementi
• compact rimuove solo gli elementi nil
• delete rimuove gli elementi passati come argomento
• empty? che restituisce true se l'array è vuoto, false in caso contrario
• include? restituisce true se l'elemento passato come argomento è presente nell'array
• index restituisce l'indice della prima occorrenza dell'oggetto passato come argomento
• join restituisce una stringa ottenuta dalla concatenazione degli elementi dell'array, è
possibile anche fornire come argomento il separatore
• [1, 2, 3].join -> "123"
• [1, 2, 3].join("...") -> "1...2...3"
• length, size e nitems restituiscono il numero di elementi con la differenza che l'ultimo
metodo non tiene conto degli elementi nil
• reverse crea un nuovo array con l'ordine degli elementi invertito
• sort restituisce un nuovo array con gli elementi ordinati
• [1, 44, 6, 2 ,4].sort -> [1, 2, 4, 6, 44]
Per la lista completa dei metodi rimando al comado ri Array.
Concludiamo con i principali iteratori applicabili alle istanze della classe Array.
Il metodo each esegue il blocco per ogni elemento dell'array passandolo come parametro.
Analogo è il comportamento di reverse_each con la differenza che gli elementi dell'array
vengono passati al blocco in ordine inverso:
> arr = ["rosso", "bianco", "nero"]
=> ["rosso", "bianco", "nero"]
> arr.each {|colore| puts colore}
rosso
bianco
nero
=> ["rosso", "bianco", "nero"]
> arr.reverse_each {|colore| puts colore}
nero
bianco
rosso
=> ["rosso", "bianco", "nero"]

each_index invece di passare gli elementi come parametri passa gli indici di tali elementi:
> arr.each_index {|indice| puts indice}
0
1
2
=> ["rosso", "bianco", "nero"]

Il metodo collect e il suo sinonimo map, ereditati da Enumerable, invocano il blocco per ogni
elemento dell'array e restituiscono un array costituito dagli elementi modificati dal blocco:
> arr = [1, 2, 3]
=> [1, 2, 3]
> arr.collect{|n| n*3}
=> [3, 6, 9]
delete_if elimina dall'array tutti quegli elementi per il quale la valutazione nel blocco
restituisce valore true. Similmente si comportano select e reject.

Hash
Gli hash, istanze della classe Hash, sono liste di coppie di chiavi e valori. A differenza degli array
invece degli indici numerici, che identificano la posizione nell'array dell'elemento, negli hash
troviamo delle chiavi che possono essere di tipo qualsiasi. In Ruby gli hash sono racchiusi tra
parentesi graffe e ogni coppia e separata dall'altra da una virgola, mentre tra le chiavi e i valori ci
deve essere il simbolo =>
Come gli array anche gli hash possono essere creati in vari modi, innanzitutto scrivendo
direttamente le coppie chiave-valore tra parentesi graffe
> hsh = {"nome" => "Lev", "patronimico" => "Nikolaevic", "cognome" => "Tolstoj"}
=> {"cognome"=>"Tolstoj", "nome"=>"Lev", "patronimico"=>"Nikolaevic"}

in alternativa si può utilizzare il metodo []


> hsh = Hash["nome", "Ivan", "patronimico", "Sergeevic", "cognome", "Turgenev"]
=> {"cognome"=>"Turgenev", "nome"=>"Ivan", "patronimico"=>"Sergeevic"}

o anche, unendo i modi precedenti, per maggiore chiarezza:


> hsh = Hash["nome" => "Fedor", "patronimico" => "Michajlovic", "cognome" =>
"Dostoevskij"]
=> {"cognome"=>"Dostoevskij", "nome"=>"Fedor", "patronimico"=>"Michajlovic"}

Infine è possibile creare un hash vuoto con il metodo new


> hsh = Hash.new
=> {}

tale hash va poi popolato, come abbiamo visto per gli array, con il metodo []= con la differenza che
invece dell'indice va indicata la chiave:
> hsh["nome"] = "Vladimir"
=> "Vladimir"
> hsh["patronimico"] = "Vladimirovic"
=> "Vladimirovic"
> hsh["cognome"] = "Majakovskij"
=> "Majakovskij"
> hsh
=> {"cognome"=>"Majakovskij", "nome"=>"Vladimir", "patronimico"=>"Vladimirovic"}

La stessa sintassi si utilizza per accedere ai valori con il metodo []. Per elencare invece tutte le
chiave e i valori di un hash si utilizzano i metodi keys e values:
> hsh.keys
=> ["cognome", "nome", "patronimico"]
> hsh.values
=> ["Majakovskij", "Vladimir", "Vladimirovic"]

È possibile inoltre indicare un valore di default che viene restituito qualora si utilizza una chiave
non esistente:
> hsh.default = "errore!"
=> "errore!"
> hsh["nazionalità"]
=> "errore!"
Tale valore può essere impostato anche al momento della creazione passando il valore di default
come argomento a new.
Oltre a buona parte degli iteratori che abbiamo visto per gli array, gli hash ne hanno di propri che
permettono di sfruttare al meglio la loro struttura.
Oltre a each, che in questo caso passa al blocco sia la chiave che il valore come parametri,
abbiamo i metodi each_key e each_value che rispettivamente passano la chiave e il valore
al blocco che viene eseguito come al solito su ogni elemento:
> hsh = {"nome" => "Lev", "patronimico" => "Nikolaevic", "cognome" => "Tolstoj"}
> hsh.each {|k, v| puts "#{k}: #{v}"}
cognome: Tolstoj
nome: Lev
patronimico: Nikolaevic
> hsh.each_key {|k| puts k}
cognome
nome
patronimico
> hsh.each_value {|v| puts v}
Tolstoj
Lev
Nikolaevic

Tipico degli hash è anche il metodo has_value? che restituisce true se il valore passato come
argomento è presente nell'hash. Analogamente, ma per le chiavi, opera has_key?. Rimando come
al solito alla documentazione ufficiale per l'elenco completo dei metodi della classe Hash.

If e unless
If
Iniziamo dando uno sguardo a if. Sorvoliamo sul significato di questo costrutto, noto a chiunque
conosca l'abc della programmazione, e passiamo direttamente alla sintassi con un esempio:
if n == 1 then
puts "N vale uno"
elsif n == 2 then
puts "N vale due"
else
puts "N non vale ne uno ne due"
end

È possibile omettere, qualora non necessario, sia il ramo elseif sia il ramo else e arrivare alla
forma base
if espressione then
istruzione
end

è anche possibile sostituire alla parola chiave then i due punti (:), o anche ometterla del tutto se il
nostro if è scritto su più linee. L'esempio di prima diventa dunque:
if n == 1
puts "N vale uno"
elsif n == 2
puts "N vale due"
else
puts "N non vale ne uno ne due"
end

Possiamo inserire un'espressione if anche in coda ad una normale istruzione


puts "N è maggiore di dieci" if n > 10

Unless
Opposto all'if abbiamo lo statement unless che esegue le istruzioni associate all'espressione che
risulta falsa:
unless n > 10
puts "N non è maggiore dieci"
else
puts "N è maggiore di dieci"
end

Anche dell'unless ne esiste una versione modificatrice di istruzione:


puts "N è maggiore di dieci" unless n < 10

Una forma ancora più ermetica prevede l'uso dell'operatore ? nel seguente modo:
segno = n >= 0 ? "positivo" : "negativo"

se la condizione è vera viene eseguita la prima espressione, se è falsa la seconda. È buona norma
usare questa sintassi solo nei casi in cui non va a scapito della leggibilità, uno dei punti di forza del
Ruby.
Infine come esercizio riscriviamo il metodo rifornimento dell'esempio che abbiamo scritto in
precedenza tenendo contro della capacità massima del serbatoio. Innanzitutto bisogna creare una
variabile per la capacità massima, e poi non ci resta che controllare se la quantità di carburante che
vogliamo immettere più quella già presente nel serbatoio non sia superiore alla capacità stessa. In
caso affermativo eroghiamo solo la quantità di carburante necessaria per un pieno.
Scrivendo il tutto in modo didattico otteniamo il codice seguente:
CAPACITA_MAX_SERBATOIO = 100

def rifornimento (quantita)


if (@carburante + quantita) > CAPACITA_MAX_SERBATOIO then
quantita_necessaria = CAPACITA_MAX_SERBATOIO - @carburante
@carburante += quantita_necessaria
puts "Carburante erogato: #{quantita_necessaria}"
else
@carburante += quantita
puts "Carburante erogato: #{quantita}"
end

puts "Carburante totale: #{@carburante}"


end

Eliminando le variabili ridondanti e le ripetizioni arriviamo ad un risultato sicuramente migliore:


def rifornimento (quantita)
if (@carburante + quantita) > CAPACITA_MAX_SERBATOIO then
quantita = CAPACITA_MAX_SERBATOIO - @carburante
end
@carburante += quantita
puts "Carburante erogato: #{quantita}"
puts "Carburante totale: #{carburante}"
end

Fatto questo possiamo scrivere:


c = Camion.new(50, 30)
c.rifornimento(90)

ed otteniamo in output il giusto risultato:


Carburante erogato: 70
Carburante totale: 100

In alternativa avremmo potuto scrivere in modo più conciso, su una unica riga, anche in questo
modo:
def rifornimento (quantita)
quantita = (@carburante + quantita) > CAPACITA_MAX_SERBATOIO ?
CAPACITA_MAX_SERBATOIO - @carburante : quantita

@carburante += quantita
puts "Carburante erogato: #{quantita}"
puts "Carburante totale: #{carburante}"
end

Case
In molti casi casi in alternativa a if, e unless, è più comodo e più chiaro utilizzare il costrutto
case. vediamolo in dettaglio. La sintassi classica è la seguente:
case command
when "start"
puts "avvio in corso..."
when "stop"
puts "arresto in corso..."
else
puts "Comandi: start/stop"
end

L'istruzione che segue l'else rappresenta il ramo di default che viene eseguito se tutte le altre
condizioni falliscono. Se la condizione è sulla stessa linea dell'espressione è possibile utilizzare la
parola chiave then o i due punti ottenendo una sintassi più compatta:
case n
when n > 10: puts "N è maggiore di dieci"
when n < 10: puts "N è minore di dieci"
else puts "N vale dieci"
end

A differenza dell'esempio precedente in questo caso abbiamo utilizzato come condizioni delle
espressioni booleane, in alternativa è possibile utilizzare anche delle espressioni regolari, dei range
o delle classi rendendo il case uno strumento incredibilmente potente. Inoltre poiché case ritorna il
valore dell'ultima espressione valutata è possibile assegnare ad una variabile il risultato del
costrutto:
value_n = case n
when n > 10: "N è maggiore di dieci"
when n < 10: "N è minore di dieci"
else "N vale dieci"
end

While, until e for


While e until
Con il costrutto while il ciclo viene eseguito fino a quando la condizione si mantiene vera, ad
esempio:
lettera = "a"

while lettera < "g"


print lettera, " "
lettera.next!
end

ci darà come output:


a b c d e f

il ciclo è stato eseguito mentre la lettera ha assunto valori minori, in ordine alfabetico, di "g". Di
natura opposta è until che esegue il ciclo finché l'espressione è falsa, ad esempio:
lettera = "a"

until lettera > "g"


print lettera, " "
lettera.next!
end

L'output è:
a b c d e f g

In questo caso il ciclo è proseguito finché la lettera non è divenuta maggiore di "g".

For
L'altro ciclo di notevole interesse è il classico for. Riprendendo l'esempio precedente possiamo
scrivere
for n in "a".."g"
print n, " "
end

ottenendo
a b c d e f g

Il ciclo viene eseguito una volta per ogni valore assunto da n. Nel nostro esempio la variabile n
assume i valori del range che ha per estremi le lettere "a" e "g". Oltre al range avremmo potuto
indicare un array, o qualsiasi altro oggetto che risponde al metodo each, e la variabile avrebbe
assunto tutti i valori degli elementi dell'array, uno per ogni ciclo proprio come un iteratore.
Uscire dai cicli
Concludiamo questa porzione della guida illustrando i costrutti che permettono di modificare la
normale esecuzione di un ciclo al verificarsi di particolari condizioni.
break: termina immediatamente il ciclo e l'esecuzione del programma viene ripresa dall'istruzione
immediatamente successiva al ciclo. Ad esempio questo codice
while lettera < "g"
break if lettera == "d"
print lettera, " "
lettera.next!
end

ci darà in output
a b c

il ciclo è stato interrotto quando la lettera ha assunto il valore "d".


Il costrutto next invece salta alla fine del ciclo eseguendo una nuova iterazione:
num = 0
while num < 5
num += 1
next if num == 3
print num, " "
end

In questo caso l'output è


1 2 4 5

Quando num ha assunto valore 3 il next ha terminato l'iterazione corrente, andando alla fine del
ciclo e saltando dunque l'istruzione di stampa, e ha iniziato l'iterazione successiva.

Ereditarietà singola
In questo e nel successivo capitolo vedremo in maggiore dettaglio gli aspetti più interessanti, e utili,
della programmazione OO in Ruby. Completeremo il discorso sulle classi iniziato nel capitolo 8
introducendo in questo capitolo l'ereditarietà singola e nel prossimo l'ereditarietà multipla.
Ricominciamo da dove eravamo rimasti. Alla fine del capitolo 8 avevamo due classi
CarroArmato e Camion:
class CarroArmato
attr_reader :colpi, :carburante

def initialize (colpi, carburante)


@colpi = colpi
@carburante = carburante
end

def rifornimento (quantita)


@carburante += quantita
end
end

class Camion
attr_reader :posti, :carburante

def initialize (posti, carburante)


@posti = posti
@carburante = carburante
end

def rifornimento (quantita)


@carburante += quantita
end
end

Anche se scritte in modo corretto le nostre due classi non sono sicuramente scritte nel modo
migliore possibile, abbiamo infatti ignorato i principi base dell'ingegneria del software. Si nota
subito infatti che ci sono delle ripetizioni che possono essere eliminate utilizzando lo strumento
dell'ereditarietà. Quindi un modo migliore di progettare le nostre classi prevede la creazione di una
superclasse che possiede gli attributi e i metodi comuni alle sottoclassi CarroArmato e
Camion. Ad esempio creando la classe Veicolo nel seguente modo:
class Veicolo
attr_reader :carburante

def initialize (carburante)


@carburante = carburante
end

def rifornimento (quantita)


@carburante += quantita
end
end

possiamo scrivere le nostre classi di conseguenza:


class CarroArmato < Veicolo
attr_reader :colpi

def initialize (carburante, colpi)


super(carburante)
@colpi = colpi
end
end

class CarroArmato < Veicolo


attr_reader :colpi

def initialize (carburante, colpi)


super(carburante)
@colpi = colpi
end
end

Vediamo cosa è successo. Innanzitutto ora abbiamo una gerarchia di classi che vede in cima la
classe-padre, o superclasse, Veicolo che ha due classi-figlie, o sottoclassi, Camion e
CarroArmato che ne ereditano il metodo rifornimento e in parte il metodo initialize. In
realtà le nostre sottoclassi specializzano il metodo initialize di Veicolo: per la variabile
d'istanza comune, @carburante, viene chiamato il metodo initialize della superclasse,
attraverso il costrutto super, mentre le altre variabili sono gestite direttamente dalle sottoclassi. In
definitiva super non fa altro che richiamare il metodo con lo stesso nome contenuto nella
superclasse.

Ereditarietà multipla
Ruby, di per sé, non permette l'ereditarietà multipla: ogni classe non può avere più di una
superclasse. È possibile però aggirare questo "problema", che a detta di molti problema non è,
attraverso l'utilizzo dei moduli e il meccanismo del mixin.
Oltre al classico utilizzo per la creazione di namespace, i moduli possono essere utilizzati per
implementare una sorta di ereditarietà multipla. Vediamo innanzitutto cosa si intende per modulo e
come si definisce.
I moduli sono dei contenitori di metodi, costanti e classi e, alla stessa maniera delle classi, devono
avere un nome che inizia per lettera maiuscola. Sono definiti dalla parola chiave module; un
esempio è il seguente:
module Net
class HTTP
def HTTP.get_print(uri_or_host, path = nil, port = nil)
...
end
end
end

Per poter accedere alle classi del modulo occorre importalo con l'istruzione require e poi fare
riferimento alle classi con il nome esteso. Ad esempio se si vuole utilizzare il metodo get_print
della classe HTTP del modulo Net occorre scrivere:
require 'net/http'
Net::HTTP.get_print 'www.google.com', '/index.html'

La creazione di un namespace permette sia di evitare problemi con i nomi, sia di rendere il codice
più coerente e pulito. Oltre a ciò, come anticipato prima, è possibile utilizzare i moduli in maniera
meno ortodossa ma molto efficace. Per ovviare alla mancanza dell'ereditarietà multipla basta
racchiudere in un modulo i metodi che vogliamo "far ereditare" alla nostra classe e quindi
includerlo con l'istruzione include. Di conseguenza possiamo utilizzare tutti i metodi del modulo
dall'interno della nostra classe come dei normali metodi che vengono mescolati, mixed in appunto, a
quelli definiti dalla classe e a quelli ereditati dalla superclasse.
Un esempio ci chiarirà tutto. Creiamo un semplice modulo con un solo metodo che non fa altro che
stampare una frase a video:
module Inutile
def hello
"Saluti da #{self.class.name}"
end
end

class Tokyo
include Inutile
end

Istanziando una nuova classe possiamo chiamare il metodo hello ereditato dal modulo ottenendo:
> tk = Tokyo.new
> tk.hello
=> "Saluti da Tokyo"

In questo modo è possibile aggiungere alle classi una gran quantità di metodi senza nessuna fatica.
Ad esempio possiamo dotare le nostre classi dei metodi di confronto includendo Comparable, o
anche aggiungere dei metodi di ricerca e ordinamento includendo Enumerable e così via.

Apertura e chiusura di un file


Una delle funzionalità più utilizzata è sicuramente la gestione dei file che in alcuni linguaggi è una
delle esperienze più frustranti per il programmatore inesperto; fortunatamente non è il caso di Ruby
che anche in questo frangente mantiene una grande immediatezza e semplicità.
A questo punto il lettore avrà notato che qualsiasi argomento in Ruby non presenta mai un'elevata
difficoltà ma è tutto molto intuitivo e di conseguenza risulta anche molto naturale. Una delle
descrizioni più appropriate lette in giro dice che "imparare Ruby non è come scalare una montagna,
ma è più come esplorare una pianura; ad ogni argomento ne segue un altro, ma nessuno porta con se
grandi difficoltà".
Torniamo alle cose pratiche e vediamo quali metodi offre la classe File per eseguire le operazioni
base sui file: apertura, chiusura, lettura e scrittura.

Apertura e chiusura di un file


Come è consueto per operare sul contenuto di un file occorre prima di tutto aprirlo. Il Ruby per
farlo mette a disposizione due metodi. Innanzitutto c'è il classico File.new, dove File è
ovviamente la classe che si occupa dei file ed ha come superclasse IO, quindi si ha ad esempio:
mio_file = File.new("test.txt", "r+")

fatto questo tutte le operazioni vanno fatte sull'oggetto mio_file di tipo File. Alla fine dopo
averlo opportunamente processato il file va chiuso con:
mio_file = File.close("test.txt", "r+")

File.new può prendere da uno a tre argomenti, il primo rappresenta il nome del file da aprire, il
secondo la modalità di apertura e il terzo i permessi da associare al file. La modalità di apertura può
essere espressa sia come stringa sia come "or" di flag. Tra le principali flag, che sono costanti della
classe File, troviamo:
• APPEND: apertura in modalità append
• CREAT: se il file da aprire non esiste viene creato
• RDONLY: apertura a sola lettura
• RDWR: apertura per lettura e scrittura
• TRUNC: apre il file e lo azzera se già esiste
• WRONLY: apertura a sola lettura
Analogamente alle flag è possibile usare le stringhe:
• r: apertura a sola lettura
• r+: apertura per lettura e scrittura dall'inizio del file
• w: apertura a sola scrittura, se il file non esiste ne viene creato uno nuovo, altrimenti viene
azzerato
• w+: apertura per lettura e scrittura, se il file non esiste ne viene creato uno nuovo, altrimenti
viene azzerato
• a: apertura a sola scrittura dalla fine del file, se il file non esiste viene creato
• a+: apertura per lettura e scrittura dalla fine del file, se il file non esiste viene creato
• b: va aggiunto ad uno dei precedenti e permette la gestione in modalità binaria, esiste solo
per sistemi Windows.
I permessi, come terzo argomento, vanno indicati, nei sistemi Unix-like, nella classica forma
Owner-Group-Other.
Oltre alla modalità classica di apertura, con la coppia new/close, il Ruby permette di aprire un file
anche con il metodo open. A differenza di new a open può essere associato un blocco al quale
viene passato il file appena aperto come parametro:
File.open("test.txt", "r+") do |mio_file|
...
end

Utilizzando open non c'è più bisogno di chiamare al termine delle operazioni close poiché il
file viene chiuso automaticamente all'uscita dal blocco; open può anche essere invocato senza
blocco e in tal caso è solo un sinonimo di new e valgono per esso tutte le considerazioni fatte in
precedenza.

Lettura e scrittura
Dopo averlo aperto le operazioni base sui file sono la lettura e la scrittura. Per leggere un file una
linea alla volta si può utilizzare il metodo gets ereditato da IO:
while linea = mio_file.gets
puts linea
end

in questo modo con un ciclo while non facciamo altro che stampare a video tutte le righe del file.
In alternativa è possibile usare anche gli iteratori che insieme a open con i blocchi rendono le
operazioni di lettura molto agevoli. Tra gli iteratori troviamo:
• each_byte che chiama il blocco per ogni byte
• each_line, e il suo sinonimo each, chiamano il blocco per ogni riga
Ad esempio supponendo che il nostro file test.txt abbia il seguente contenuto:
Vento invernale
Un monaco scinto
Cammina nel bosco

possiamo leggerne un carattere alla volta con each_byte:


File.open("test.txt") do |f|
f.each_byte {|c| print c.chr, " "}
end

in questo modo trasformiamo il byte letto, un Fixnum, in carattere con il metodo chr e poi lo
stampiamo seguito da uno spazio. L'output sarà:
V e n t o i n v e r n a l e
U n m o n a c o s c i n t o
C a m m i n a n e l b o s c o

Gli iteratori each e each_line, come detto, permettono invece di iterare su ogni linea del file.
Un esempio è:
File.open("test.txt") do |f|
f.each {|l| puts "#{f.lineno} #{l}"}
end
dove lineno è un metodo della classe IO che restituisce il numero di linea corrente. L'output sarà
dunque:
1 Vento invernale
2 Un monaco scinto
3 Cammina nel bosco

Infine un altro iteratore, che è allo stesso tempo un altro modo per aprire un file, è foreach che
quando è invocato apre il file passatogli come argomento, chiama il blocco per ogni linea e alla fine
chiude il file automaticamente:
IO.foreach("test.txt") {|linea| puts linea}

Per scrivere su di un file si utilizza il metodo write che prende come argomento la stringa da
scrivere sul file:
File.open "test.txt", "w" do |mio_file|
mio_file.write "Ciao file"
end

write restituisce il numero di byte scritti. In alternativa si può utilizzare anche l'operatore << o il
metodo puts. L'esempio precedente diventa:
File.open "test.txt", "w" do |mio_file|
mio_file << "Ciao file"
end

oppure
File.open "test.txt", "w" do |mio_file|
mio_file.puts "Ciao file"
end

Utili metodi e gestione directory


Concludiamo con una carrellata dei principali metodi utilizzabili sui file:
• atime, ctime e mtime restituiscono rispettivamente la data di ultimo accesso, di creazione e
di modifica
• chmod e chown modificano i permessi e il proprietario
• lstat restituisce delle informazioni sul file
• rename rinomina il file
• delete e unlink cancellano il file
• size restituisce le dimensioni in byte
• read restituisce il contenuto del file in una unica stringa
• readchar legge un carattere dal file
• readline legge una linea dal file
• readlines restituisce un array avente per elementi le linee del file
file = File.new("test.txt")
file.readlines => ["Vento invernale\n", "Un monaco scinto\n", "Cammina nel
bosco\n"]

poi ci sono i metodi interrogativi:


• exist? restituisce true se il file passato come argomento esiste
• executable? restituisce true se il file è eseguibile
• readable? restituisce true se il file è leggibile
• writable? restituisce true se il file è scrivibile
• file? restituisce true se è un file regolare, ovvero se non è un socket, una directory, etc.
• owned? restituisce true se l'user ID del processo è lo stesso del file

Le directory
Prima di concludere questo capitolo, per completezza, diamo uno sguardo anche alle directory. La
classe per la gestione delle directory è Dir e anche in questa classe abbiamo i metodi new, open
e close con lo stesso significato visto per i file. Ad esempio creiamo, apriamo e chiudiamo una
directory in questo modo:
Dir.mkdir("test")
test_dir = Dir.new("test")
test_dir.close

oppure con open, senza bisogno di chiamare close alla fine delle operazioni:
Dir.open("test") do |d|
...
end

Dove Dir.mkdir crea una nuova directory, è possibile passargli come secondo argomento anche i
relativi permessi. Per il resto rimando alla documentazione ufficiale per un elenco completo di tutti i
metodi.

Le espressioni regolari
Le espressioni regolari sono uno strumento fondamentale per la gestione del testo, e uno dei
linguaggi che ha fatto di questo strumento un punto di forza è sicuramente il Perl. Il Ruby, che nelle
idee di Matz nasce come suo naturale erede, non poteva non avere un supporto alle espressioni
regolari altrettanto potente.
Brevemente, rimandando a testi appositi una trattazione più approfondita, possiamo dire che le
espressioni regolari vengono utilizzate per controllare se una stringa verifica un certo schema
(pattern). O in altre parole, le espressioni regolari, forniscono dei modelli per ricercare all'interno di
un testo non solo utilizzando espressioni letterali ma anche particolari identificatori che
rappresentano delle determinate classi di caratteri.
In Ruby sono oggetti della classe Regexp che possono essere creati, come gli Array e gli Hash, in
diversi modi. Si può utilizzare il metodo new di Regexp:
expr = Regexp.new('')

passando come argomento l'espressione regolare vera e propria, oppure racchiudendola tra due /
('slash'):
expr = //

o ancora utilizzando %r:


expr = %r{}

Una volta creata la nostra espressione regolare possiamo confrontarla con qualsiasi stringa
utilizzando il metodo match della classe Regexp oppure l'operatore =~ e il suo negato !~. Ad
esempio:
> er = Regexp.new('\s\w+')
=> /\s\w+/
> stringa = "Ciao mondo"
=> "Ciao mondo"
> stringa =~ er
=> 4
> $&m
=> " mondo"
> $`
=> "Ciao"

=~ restituisce la posizione del primo carattere che verifica l'espressione regolare mentre le variabili
speciali $& e $` rappresentano rispettivamente la parte di stringa che verifica il pattern e la parte
che non lo verifica.
Come già detto, all'interno di una espressione regolare oltre ai normali caratteri è possibile usare
delle sequenze che rappresentano delle determinate classi, in Tabella1 c'è un elenco completo. In
alternativa è possibile creare delle classi racchiudendo i caratteri tra parentesi quadre, ad esempio
[A-Za-z] sta a indicare tutte le lettere dalla a alla z sia maiuscole che minuscole.
Tabella 1: classi di caratteri
. un carattere qualsiasi
\w lettera o numero, come [0-9A-Za-z]
\W il contrario di \w, ne' lettera ne' cifra
spazio (spazio vero e proprio, tabulazione e carattere di fine riga), come
\s
[\t\n\r\f]
\S carattere non spazio
\d cifra numerica, come [0-9]
\D il contrario di \d, carattere non numerico
\b backspace se si trova in una specifica di intervallo
\b limite di parola
\B non limite di parola
Oltre ai caratteri in una espressione regolare è possibile anche utilizzare due particolari elementi, ^
('accento circonflesso') e $ ('dollaro'), che indicano una precisa posizione del testo cercato. ^ va
usato se i caratteri cercati si devono trovare all'inizio del testo, $ se si devono trovare alla fine. Ci
sono quindi i simboli quantificativi:
Tabella 2: i simboli di quantità
* zero o più ripetizioni del carattere precedente
+ una o più ripetizioni del carattere precedente
{m,n} almeno m e massimo n ripetizioni del carattere precedente
? al massimo una ripetizione del precedente; lo stesso che {0,1}
Abbiamo poi | ('pipe') che rappresenta il meccanismo dell'alternanza che permette di definire più
alternative. Ad esempio /http|ftp|smtp/ permette di trovare una delle corrispondenze
indicate nell'espressione. Infine, come è consueto, è possibile catturare una parte della stringa
verificata in modo da utilizzarla sia all'interno del pattern stesso, riferendoci ad essa con \1, \2 e
così via, sia all'esterno utilizzando le variabili speciali $1, $2 ecc. Ad esempio volendo catturare la
parola "mondo" dell'esempio precedente dobbiamo scrivere:
> er = Regexp.new('\s(\w+)')
=> /\s(\w+)/
> stringa = "Ciao mondo"
=> "Ciao mondo"
> stringa =~ er
=> 4
> $1
=> "mondo"

Le espressioni regolari e la classe String


Nel capitolo 12 dedicato alle stringhe abbiamo già incontrato delle espressioni regolari che ora
possiamo comprendere meglio, rivediamole. Per accedere a delle sottostringhe si può scrivere
> str = "Ciao mondo"
=> "Ciao mondo"
> str[/\w+\s/]
=> "Ciao "

Sinonimo di [] è il metodo slice e il metodo slice!:


> str.slice(/\w+\s/)
=> "Ciao "

Allo stesso modo si utilizzano con index che restituisce l'indice della prima occorrenza della
sottostringa espressa dal pattern:
> str.index(/[aeiou]/)
=> 1

analogo il comportamento di rindex che, a differenza di index, restituisce l'ultima occorrenza


della sottostringa che verifica il pattern:
> str.rindex(/[aeiou]/)
=> 9

Possiamo usare le espressioni regolari anche come argomento dei metodi sub e gsub:
> str.gsub(/([aeiou])/, '(\1)' )
=> "C(i)(a)(o) m(o)nd(o)"
> str.gsub(/\s(\w+)/){|c| c.upcase}
=> "Ciao MONDO"

Anche split può essere utilizzato passando come argomento un'espressione regolare:
> str.split(/\s/)
=> ["Ciao", "mondo"]

Interessante è infine l'utilizzo delle regexp con scan che itera attraverso la stringa e inserisce
tutti i risultati che verificano il pattern all'interno di un array:
> str.scan(/\w+/)
=> ["Ciao", "mondo"]

Da questi esempi è lampante la notevole versatilità che le espressioni regolari aggiungono ai metodi
della classe String.
Le eccezioni
Concludiamo con questo e con il successivo capitolo la prima parte della nostra panoramica del
Ruby trattando la gestione delle eccezioni, dal prossimo ci occuperemo di questioni accessorie al
linguaggio vero e proprio.
Come tutti i linguaggi moderni, anche il Ruby, possiede il meccanismo delle eccezioni,
fondamentale nella gestione degli errori. Un'eccezione va sollevata utilizzando il metodo raise
del modulo Kernel. La sintassi completa prevede tre argomenti:
raise(exception [, string [, array]])

Il primo argomento è il nome di una classe della gerarchia Exception (vedi lista più avanti) e
rappresenta il tipo di eccezione da sollevare. Il secondo argomento è un messaggio che verrà
associato all'eccezione, mentre il terzo è un array con il callback dell'eccezione. Chiamato senza
nessun argomento raise solleverà un eccezione di tipo RuntimeError, oppure se definita
l'eccezione contenuta in $!. Ad esempio:
raise "Errore nell'apertura del file"
raise ArgumentError, "Tipo di argomento non gestito"
raise ArgumentError, "Tipo di argomento non gestito", caller

La gerarchia delle eccezioni in Ruby


Exception
NoMemoryError
ScriptError
LoadError
NotImplementedError
SyntaxError
SignalException
Interrupt
StandardError
ArgumentError
IOError
EOFError
IndexError
LocalJumpError
NameError
NoMethodError
RangeError
FloatDomainError
RegexpError
RuntimeError
SecurityError
SystemCallError
SystemStackError
ThreadError
TypeError
ZeroDivisionError
SystemExit
fatal

La gestione degli errori


Dopo aver visto come si sollevano le eccezioni diamo uno sguardo alla loro gestione attraverso
rescue ed ensure. Supponiamo di aver scritto una applicazione che si comporta in modo
diverso a seconda che esista o meno una determinata libreria. Vogliamo dunque intercettare
un'eventuale eccezione LoadError e comportarci di conseguenza. Per catturare le eccezioni
scriviamo il tutto in un blocco begin/end, ad esempio:
begin
require 'gmailer'
GMAIL_SUPPORTED = true
rescue LoadError
GMAIL_SUPPORTED = false
end

in questo modo impostiamo la costante GMAIL_SUPPORTED in base alla disponibilità della


libreria gmailer. È possibile anche indicare più clausole rescue all'interno dello stesso blocco,
mentre se non si indica nessun argomento rescue intercetterà tutti gli errori che discendono da
StandardError. Per recuperare il valore dell'eccezione va utilizzata questa sintassi:
begin
require 'gmailer'
GMAIL_SUPPORTED = true
rescue LoadError => error
puts "L'errore è: #{error}"
GMAIL_SUPPORTED = false
end

e in caso di eccezione LoadError vedremo sullo schermo la scritta "L'errore è: no such file to
load -- gmailer". All'interno del corpo di rescue si può inserire un'istruzione retry con la
funzione di rieseguire il blocco dall'inizio, ovviamente prima di retry vanno eseguite delle
istruzioni atte a risolvere il problema riscontrato. Infine c'è la clausola ensure utile quando si
devono eseguire delle operazioni all'uscita da un blocco begin/end. Un classico esempio è quello
della gestione dei file:
file = File.open("test.txt")
begin
# operazioni varie sul file
rescue
# gestione degli errori
ensure
file.close unless file.nil?
end

Nel blocco principale vengono eseguite le normali operazioni sul file aperto, poi c'è il blocco
rescue per la gestione degli errori e infine nel blocco ensure ci sono le istruzioni per chiudere
il file. In questo modo siamo sicuri che all'uscita dal blocco il file verrà chiuso.
Oltre che all'interno dei blocchi begin/end, è possibile gestire le eccezioni anche all'interno dei
normali metodi, il cui corpo è implicitamente un blocco begin/end:
def nome_metodo
# istruzioni varie
rescue
# gestione degli errori
end

Rdoc: introduzione e primo utilizzo


In questo e nei prossimi capitoli vedremo come rendere i nostri programmi pronti per essere
distribuiti al mondo intero. Ci occuperemo della documentazione, del packaging e del building.
Cominciamo a vedere uno degli aspetti, spesso snobbato, dell'intero ciclo di sviluppo: la
documentazione. I principali strumenti che Ruby mette a disposizione per l'integrazione della
documentazione all'interno del codice sono RDTool e RDoc. In questa guida faremo riferimento
solo a RDoc, che è diventato lo standard, mentre RDTool sta diventando lentamente obsoleto.
RDoc permette di introdurre, utilizzando un semplice linguaggio di markup, la documentazione
direttamente all'interno dei sorgenti. Vediamo innanzitutto quali elementi di formattazione sono
disponibili. Vediamo un esempio con incluse tutte le necessarie spiegazioni:
=begin rdoc

All'inizio va inserito l'apposito tag che segna l'inizio dei commenti utilizzati da RDoc.
Si possono creare vari effetti, ad esempio le parole in *grassetto* vanno inserite tra due asterischi,
quelle in _corsivo_ vanno tra due underscore e quelle +monospazio+ vanno scritte tra due più
(+).
Per creare frasi in grassetto, corsivo e con carattere monospace vanno utilizzati rispettivamente i tag
b, em e tt con le relative chiusure:
<b>Questa è una frase scritta in grassetto.</b>
<em>Quest'altra invece è in corsivo,</em> <tt>ed infine una scritta con
carattere monospazio.</tt>

Ci sono poi diversi tipi di liste, quelle con i classici punti


* primo elemento
* secondo elemento

ci sono poi le liste numerate e quelle alfabetiche


1. primo elemento
2. secondo elemento

a. primo elemento
b. secondo elemento

Infine ci sono le liste che utilizzano delle etichette


[nome] Lev
[cognome] Tolstoj

oppure
nome:: Lev
cognome:: Tolstoj

Per le intestazioni sono disponibili più livelli


= Livello 1
== Livello 2
=== Livello 3

e così via, aggiungendo degli "uguale" avremo livelli sempre inferiori.


Infine va messo il tag di chiusura
=end

Per generare la documentazione dall'esempio appena visto salviamolo in un file che chiamiamo
esempio_rdoc.rb ed eseguiamo RDoc:
$ rdoc esempio_rdoc.rb

esempio_rdoc.rb:
Generating HTML...

Files: 1
Classes: 0
Modules: 0
Methods: 0
Elapsed: 0.108s

Questo comando creerà una directory doc contenente la documentazione relativa ai nostri sorgenti.
In Figura 3 uno stralcio dell'output prodotto.
Figura 3: esempio di output di Rdoc

Rdoc: convertire i commenti in


documentazione
Oltre ai commenti scritti tra i tag visti prima, RDoc prende in considerazione anche i normali
commenti scritti facendo iniziare la riga con il carattere #. Ovviamente l'utilità di un simile
meccanismo sta nell'integrazione tra il codice e i commenti che con RDoc diventano anche
documentazione. Per illustrare questa caratteristica andiamo a ripescare le nostre classi e
aggiungiamoci i commenti. L'ultima versione era la seguente:
class Veicolo
attr_reader :carburante

def initialize (carburante)


@carburante = carburante
end

def rifornimento (quantita)


@carburante += quantita
end
end

class CarroArmato < Veicolo


attr_reader :colpi

def initialize (carburante, colpi)


super(carburante)
@colpi = colpi
end
end

class Camion < Veicolo


attr_reader :posti

def initialize (carburante, posti)


super(carburante)
@posti = posti
end
end

Commentiamole inserendo una descrizione generica per ogni classe, e poi descriviamo tutti gli
attributi e tutti i metodi; il risultato finale sarà:
# Implementazione di un veicolo generico utilizzato
# come superclasse per tutti gli altri veicoli.
class Veicolo

# quantità di carburante nel veicolo


attr_reader :carburante

# crea un nuovo veicolo con una determinata


# quantità di carburante
def initialize (carburante)
@carburante = carburante
end

# rifornisce il veicolo di carburante


def rifornimento (quantita)
@carburante += quantita
end
end

# Implementazione di un carro armato,


# ha come superclasse Veicolo.
class CarroArmato < Veicolo

# numero di colpi presenti nel carro armato


attr_reader :colpi

# crea un nuovo carro armato con una data quantità


# di carburante e di colpi
def initialize (carburante, colpi)
super(carburante)
@colpi = colpi
end
end

# Implementazione di un camion per il trasporto di soldati,


# ha come superclasse Veicolo.
class Camion < Veicolo

# numero di posti presenti nel camion


attr_reader :posti

# crea un nuovo camion con una data quantità


# di carburante e di posti
def initialize (carburante, posti)
super(carburante)
@posti = posti
end
end

Salviamo tutto ed eseguiamo RDoc che, se chiamato senza argomenti, genera la documentazione
per tutti i sorgenti Ruby e C (eventuali estensioni di Ruby) presenti nella directory corrente e nelle
sue sotto-directory:
$ rdoc veicolo.rb

veicolo.rb: c..c.c.
Generating HTML...

Files: 1
Classes: 3
Modules: 0
Methods: 4
Elapsed: 0.176s

In Figura 4 vediamo la pagina di documentazione della classe Veicolo con in evidenza il pop-up
del metodo rifornimento. I pop-up vengono chiamati attraverso i link collegati ai nomi dei
metodi e mostrano i relativi stralci di codice.
Figura 4: documentazione con Rdoc

In Figura 5 vediamo invece il codice come è generato da RDoc chiamato con le opzioni -NS che
vedremo dopo.
Figura 5: Documentazione con il comando Rdoc -NS
Rdoc: documentazione automatica
Oltre alle caratteristiche di formattazione viste prima RDoc mette a disposizione una gran quantità
di direttive molto avanzate. Rimando dunque alla documentazione ufficiale per maggiori dettagli e
approfondimenti.
Tra le tante cose, RDoc viene incontro anche a tutti quegli sviluppatori che odiano scrivere la
documentazione, fosse anche solo l'help in line. Infatti RDoc riesce a tirare fuori della
documentazione utile in HTML anche da sorgenti privi di qualsiasi commento. Facciamo una
prova, riprendiamo il nostro esempio nella versione senza commenti e vediamo RDoc cosa ci tira
fuori.
class Veicolo
attr_reader :carburante

def initialize (carburante)


@carburante = carburante
end

def rifornimento (quantita)


@carburante += quantita
end
end

class CarroArmato < Veicolo


attr_reader :colpi

def initialize (carburante, colpi)


super(carburante)
@colpi = colpi
end
end

class Camion < Veicolo


attr_reader :posti

def initialize (carburante, posti)


super(carburante)
@posti = posti
end
end

Salviamo il codice pubblicato sopra nel file veicolo.rb ed eseguiamo rdoc:


$ rdoc veicolo.rb

veicolo.rb: c..c.c.
Generating HTML...

Files: 1
Classes: 3
Modules: 0
Methods: 4
Elapsed: 0.123s

In Figura 6 una pagina generata da RDoc, è il massimo che si può ottenere senza scrivere una riga
di documentazione, ed è comunque utile per capire come sono strutturate le classi.
Figura 6: La documentazione automatica

Rdoc: le opzioni
Molti aspetti possono essere gestiti utilizzando le opzioni del tool rdoc, ecco le principali:
• --diagram, -d viene generato un semplice diagramma che mostra i moduli e le classi
• --inline-source, -S il codice sorgente dei moduli viene mostrato inline invece che attraverso
i pop-up
• --line-numbers, -N viene mostrato il numero di linea nei sorgenti
• --one-file, -1 tutto l'output viene inserito in un unico file
• --fmt, -f format name imposta il tipo di file generato, è possibile produrre file HTML, XML
e CHM
l'elenco completo delle opzioni è visualizzabile chiamando rdoc con l'opzione --help. RDoc è
capace di generare anche documentazione leggibile dal tool ri utilizzando le seguenti opzioni
• --ri, -r viene generata documentazione, leggibile da ri, che viene salvata nella directory
.rdoc nella directory home a meno che non venga indicato un altro percorso con l'opzione --
op oppure -o.
• --ri-site, -R i file generati vengono salvati in una directory globale, ad esempio
/usr/share/ri/1.8/site/ rendendoli accessibili a tutti gli utenti.
• --ri-system, -Y i file generati vengono salvati in una directory di sistema, ad esempio
/usr/share/ri/1.8/system/; opzione da utilizzare in fase di installazione.
Ad esempio:
$ rdoc --ri veicolo.rb

veicolo.rb: c..c.c.
Generating RI...

Files: 1
Classes: 3
Modules: 0
Methods: 4
Elapsed: 0.181s

$ ls .rdoc/
Camion/ CarroArmato/ created.rid Veicolo/

$ ri Veicolo#rifornimento

--------------------------------------------------- Veicolo#rifornimento
rifornimento(quantita)
------------------------------------------------------------------------
rifornisce il veicolo di carburante

Perfetto, tutto funziona come deve.

Introduzione a RubyGems
Dopo aver scritto la nostra applicazione, dopo averla opportunamente commentata e dopo aver
generato la documentazione non ci resta che rendere l'installazione il più agevole possibile per tutti i
probabili utenti. Gli strumenti che Ruby mette a disposizione per la gestione dei pacchetti sono
essenzialmente due, diversi per consistenza e funzionalità, ovvero il framework RubyGems e la
libreria setup. In questo capitolo ci occuperemo di RubyGems mentre nel prossimo daremo uno
sguardo al classico setup.rb.
RubyGems è un framework per la gestione automatica dei pacchetti che si occupa di tutti gli aspetti
riguardanti la pacchettizzazione: dall'installazione, all'aggiornamento e alla distribuzione. Di seguito
vedremo come va utilizzato per installare nuove librerie, e impareremo a creare i pacchetti gem per
le nostre applicazioni.
RubyGems non fa parte dei programmi distribuiti direttamente con Ruby ma va installato a parte. I
sorgenti sono disponibili sul sito ufficiale http://rubygems.rubyforge.org e va installato con il
classico:
$ ruby setup.rb

Cominciamo subito a capire come si utilizza vedendolo all'opera. Eseguiamolo senza nessuna
opzione per vedere una schermata di aiuto:
$ gem

RubyGems is a sophisticated package manager for Ruby. This is a


basic help message containing pointers to more information.

Usage:
gem -h/--help
gem -v/--version
gem command [arguments...] [options...]

Examples:
gem install rake
gem list --local
gem build package.gemspec
gem help install

Further help:
gem help commands list all 'gem' commands
gem help examples show some examples of usage
gem help <COMMAND> show help on COMMAND
(e.g. 'gem help install')
Further information:
http://rubygems.rubyforge.org

La lista completa dei comandi è visualizzabile con l'apposita opzione:


$ gem help commands

GEM commands are:

build Build a gem from a gemspec


cert Adjust RubyGems certificate settings
check Check installed gems
cleanup Cleanup old versions of installed gems in the local
repository
contents Display the contents of the installed gems
dependency Show the dependencies of an installed gem
environment Display RubyGems environmental information
help Provide help on the 'gem' command
install Install a gem into the local repository
list Display all gems whose name starts with STRING
query Query gem information in local or remote repositories
rdoc Generates RDoc for pre-installed gems
search Display all gems whose name contains STRING
specification Display gem specification (in yaml)
uninstall Uninstall a gem from the local repository
unpack Unpack an installed gem to the current directory
update Update the named gem (or all installed gems) in the local
repository

For help on a particular command, use 'gem help COMMAND'.

I comandi di RubyGems
Vediamo ora in dettaglio i principali comandi con degli esempi illustrativi. Cominciamo con quello
più utile e maggiormente utilizzato: install. Per installare una nuova gemma basta scrivere,
avendo i giusti permessi, semplicemente:
$ gem install <nome_pacchetto_da_installare>

ad esempio
$ gem install gmailer
Successfully installed gmailer-0.1.5

è possibile anche fornire delle utilissime opzioni, ad esempio:


• -v, --version seguito dal numero di versione del pacchetto da installare
• -r, --rdoc per generare la documentazione rdoc del pacchetto installato
• --ri per generare la documentazione ri del pacchetto installato
• -f, --force l'installazione viene forzata senza tenere conto delle dipendenze
• -t, --test prima dell'installazione vengono eseguiti i test unitari
L'elenco completo è visualizzabile con il comando
$ gem help install

Per poter utilizzare le librerie, installate con RubyGems, nei nostri sorgenti bisogna indicare
esplicitamente che è una gemma. Per fare questo occorre caricare prima rubygems e poi
chiamare gem e il normale require. Gem in pratica serve solo quando si vuole indicare a
require di caricare una particolare versione della libreria. Ad esempio:
require 'rubygems'
gem 'mysql', '=2.7'
require 'mysql'

oppure, dato che rubygems carica l'ultima versione disponibile, spesso può bastare un solo:
require 'rubygems'
require 'mysql'

In alternativa impostando la variabile d'ambiente RUBYOPT al valore 'rubygems'


export RUBYOPT=rubygems

ovvero eseguendo sempre ruby con l'opzione rubygems, possiamo richiamare le librerie all'interno
dei programmi senza dover specificare alcunché:
require 'mysql'

Per disinstallare un pacchetto installato con RubyGems va ovviamente utilizzato il comando


uninstall. Altro comando fondamentale è list che elenca tutte le gemme installate nel
sistema locale:
$ gem list

*** LOCAL GEMS ***

actionmailer (1.3.1)
Service layer for easy email delivery and testing.

actionpack (1.13.1)
Web-flow and rendering framework putting the VC in MVC.

actionwebservice (1.2.1)
Web service support for Action Pack.

Indicando l'opzione --remote è possibile elencare tutte le gemme presenti nel repository remoto,
che se non specificato diversamente è quello ufficiale su rubyforge.org. Possiamo inoltre specificare
come deve iniziare il nome del pacchetto passando una stringa come parametro e ottenendo l'elenco
parziale voluto:
$ gem list --remote gmail

*** REMOTE GEMS ***

gmailer (0.1.5, 0.1.4, 0.1.3, 0.1.2, 0.1.1, 0.1.0, 0.0.9, 0.0.8, 0.0.7, 0.0.6,
0.0.5)
An class interface of the Google's webmail service

Utilizzando search invece la ricerca avviene su tutto il nome del pacchetto. RubyGems permette,
come detto, anche di aggiornare i pacchetti installati con il comando update seguito dal nome
della gemma, se non viene indicato nessun parametro verranno aggiornati tutti i pacchetti installati
con gem. Le opzioni di update sono a grandi linee le stesse di install.

Creare un pacchetto gem


Dopo averne compreso le enormi potenzialità vediamo come creare un pacchetto gem contente una
nostra libreria. Innanzitutto i sorgenti e tutti i file accessori vanno disposti in una struttura coerente.
Ad esempio nella directory radice possiamo creare le seguenti directory:
lib/ contiene le applicazioni, e le librerie, Ruby vere e proprie
ext/ contiene eventuali estensioni Ruby scritte in C
bin/ contiene gli script necessari in fase di installazione
data/ contiene i file dati che accompagnano l'applicazione
conf/ contiene tutti i file di configurazione
man/ contiene le pagine del manuale
test/ contiene i test
Il primo passo consiste nel creare un file di specifica gemspec che conterrà una serie di metadati
utilizzati da gem per svolgere le sue operazioni. Un esempio è il seguente:
require 'rubygems'
SPEC = Gem::Specification.new do |spec|
spec.name = "ProvaGem"
spec.version = "1.0.0"
spec.author = "Foo Bar"
spec.email = "foo@bar.net"
spec.homepage = "http://qualchesito.net/ProvaGem"
spec.platform = Gem::Platform::RUBY
spec.summary = "Qui ci va una breve descrizione"
candidates = Dir.glob("{bin, lib, test}/**/*")
spec.files = candidates.delete_if do |item|
item.include?("CVS") || item.include?("rdoc")
end
spec.test_file = "test/ts_prova.rb"
spec.has_rdoc = true
spec.require_path = "lib"
spec.extra_rdoc_files = ["README", "ChangeLog"]
spec.add_dependency "QualcheLibreria", ">=1.0"
end

Commentiamo questo codice. All'inizio ci sono alcune informazioni relative al pacchetto e


all'autore. Segue poi platform che indica il tipo di piattaforma dove può essere installata la gemma,
ad esempio Gem::Platform::Ruby se si tratta di puro Ruby, Gem::Platform::Win32 per applicazioni
Windows e così via.
Scegliamo quindi i file da includere attraverso
candidates = Dir.glob("{bin, lib, test}/**/*")
spec.files = candidates.delete_if do |item|
item.include?("CVS") || item.include?("rdoc")
end

pescandoli nelle directory bin, lib e test ed escludendo i file relativi al repository e la
documentazione rdoc. In alternativa, in modo più semplice, avremmo potuto scrivere qualcosa tipo
spec.files = Dir['lib/**/*.rb'] + Dir['bin/*']

raccattando tutto quello presente nelle directory lib e bin. E per escludere alcuni tipi di file
l'alternativa è:
spec.files.reject! { |f| f.include? "RDoc" }

Seguono poi altre opzioni che riguardano i test, la documentazione e le dipendenze. L'elenco
completo è visualizzabile sull'homepage del progetto.
Fatto questo non ci resta che salvare il file, ad esempio come Provagem.gemspec, ed eseguire gem
col comando build:
$ gem build ProvaGem.gemspec
Successfully built RubyGem
Name: ProvaGem
Version: 1.0.0
File: ProvaGem-1.0.0.gem

Dopo questo comando nella directory corrente troveremo un pacchetto gem contenente tutto quello
che serve per l'installazione della nostra libreria.

Installazione con setup.rb


Dopo quanto visto nel capitolo precedente si direbbe che c'è bisogno di tutto tranne che di un altro
gestore dei pacchetti. RubyGems fa tutto quello che ci si può aspettare da uno strumento del genere
però, per diversi motivi, ha ancora senso la sopravvivenza del vecchio setup.rb.
Spesso le applicazione/librerie vengono distribuite sia nella forma gem sia come normale tarball
comprensivo di file setup.rb, o analogo file, in modo da permetterne sia l'installazione senza
RubyGems sia, ad esempio, la pacchettizzazione come rpm, deb e così via.
Quasi sempre nei tarball contenenti dei sorgenti Ruby troviamo un file di nome setup.rb, o
anche install.rb o qualcos'altro di inequivocabile, che va utilizzato alla maniera del classico
UNIX:
configure
make
make install

L'installazione avviene in genere anche qui in tre fasi:


ruby setup.rb config
ruby setup.rb setup
ruby setup.rb install

l'ultimo passo, che installa i file sul disco, va fatto con i giusti permessi.
Ogni aspetto del processo di installazione può essere gestito attraverso gli hook che altro non sono
che dei file con un determinato nome posizionati nel giusto posto. Per permettere a setub.rb di
operare nel migliore modo possibile occorre dunque sistemare i sorgenti seguendo uno schema di
directory preciso. Una possibile soluzione, simile a quella già vista nel capitolo precedente, è
riportata di seguito. Nella directory radice del progetto si creano le seguenti directory:
lib/ contiene le applicazioni, e le librerie, Ruby vere e proprie
ext/ contiene eventuali estensioni Ruby scritte in C
bin/ contiene le utility necessarie in fase di installazione
data/ contiene i file dati che accompagnano l'applicazione
conf/ contiene tutti i file di configurazione
man/ contiene le pagine del manuale
test/ contiene i test
e nella directory radice, oltre ai soliti file di istruzioni, di licenza e changelog, avremo anche
setup.rb da utilizzare per l'installazione. Utilizzare setup.rb come installer è molto semplice, basta
solo copiare il file setup.rb, reperibile sul sito http://i.loveruby.net/en/projects/setup/, nella directory
radice e la disposizione dei file nelle giuste directory farà il resto.

Gestire gli hook per setup.rb


Tornando agli hook va detto che il loro nome deve essere costruito facendo precedere il nome del
task dai prefissi pre e post. I task possibili sono:
• config: viene controllata la configurazione del sistema e eseguite altre operazioni per
l'installazione
• setup: vengono compilate le estensioni se presenti
• install: installa i file del pacchetto su disco
• test: vengono eseguiti i test se presenti
• clean: vengono rimossi i file temporanei creati durante l'installazione
• dist-clean: vengono rimossi tutti i file creati durante l'installazione
• show: mostra la configurazione attuale
Inoltre c'è il task all che viene eseguito quando setup.rb viene chiamato senza opzioni ed esegue
nell'ordine config, setup e install. Ad esempio avremo che pre-setup.rb viene eseguito prima
di ruby setup.rb setup e che post-test.rb viene eseguito dopo aver fatto i test con ruby
setup.rb test. Se necessario è possibile passare ai vari task anche delle opzioni oltre a quelle che
accetta setup.rb, ad esempio:
ruby setup.rb --quiet config --prefix=$HOME

dove --quiet è un opzione globale e --prefix è un opzione del task config. Tra le principali opzioni
globali troviamo:
• -q,--quiet non vengono mostrati i messaggi di output
• --verbose vengono mostrati i messaggi di output in modo esteso
• -h,--help viene mostrata una schermata di aiuto
• -v,--version viene mostrata la versione
• --copyright vengono mostrate le informazioni sul copyright
Ecco invece alcune delle opzioni accettate dai task config e all:
• --installdirs serve ad impostare la directory d'installazione
• --prefix serve ad impostare il prefisso per il percorso d'installazione
• --libruby serve ad impostare la directory contente le librerie
• --without-ext non vengono copilate e installate le estensioni
Per un elenco completo rimando alla documentazione presente sul sito ufficiale del progetto.

Dove distribuire le applicazioni Ruby


Una volta preparato il pacchetto, con setup.rb o con RubyGems, non ci resta che farlo conoscere a
tutti i probabili utenti. Una buona possibilità di visibilità è data da RubyForge e da RAA (Ruby
Application Archive). Il primo sito mette a disposizione, alla maniera di SourceForge, una gran
quantità di strumenti (repository, mailing list, wiki, etc.) a tutti gli sviluppatori Ruby. Il secondo sito
invece è solo una vetrina di applicazioni Ruby.

La struttura
Concludiamo la prima parte della guida a Ruby con l'implementazione, illustrata passo dopo passo,
di una applicazione completa. Nei prossimi tre capitoli scriveremo un programma da linea di
comando per la catalogazione dei CD. Doteremo la nostra applicazione solo delle principali
funzionalità: inserimento di un nuovo CD, cancellazione di un CD dalla lista, visualizzazione della
lista completa. Ecco come apparirà la nostra applicazione:
Ongaku menu:
(a) aggiungi CD (e) elimina CD
(l) visualizza lista (q) esci da 0ngaku
scegli:

La nostra applicazione, che chiameremo, per omaggiare Matz, Ongaku (musica in giapponese), sarà
composta sia da classi che rappresentano i dati veri e propri da gestire sia da classi che vanno a
costruire l'interfaccia. Strutturiamo dunque il tutto nel modo più chiaro possibile. Seguendo lo
schema visto prima nei capitoli sulla pacchettizzazione creiamo una directory base e all'interno di
questa creiamo tutto il necessario, nel nostro caso le directory doc, lib e man.
I file dell'applicazione vanno messi in lib/ e precisamente avremo uno schema simile (Lo stesso
albero di directory visualizzato all'interno di FreeRIDE è in Figura7):
~/Ongaku/lib$ tree
.
|-- ongaku
| |-- cd.rb
| |-- ui
| | |-- gui.rb
| | |-- text.rb
| | `-- web.rb
| `-- ui.rb
`-- ongaku.rb

Vediamo ogni file cosa conterrà:


• ongaku.rb conterrà la classe per la gestione delle opzioni passate da linea di comando.
Accetteremo solo due opzioni: --help che mostrerà un breve messaggio d'aiuto, e --ui che
permetterà di selezionare l'interfaccia (text, gui o web).
• ongaku/ui.rb conterrà la classe principale di Ongaku con l'implementazione delle principali
funzionalità e la selezione della UI.
• ongaku/cd.rb conterrà una semplice classe per la creazione di nuovi oggetti Cd.
• ongaku/ui/* conterrà i file che implementeranno le interfacce utente (testuale, grafica e
web). Per ora implementeremo solo una semplice interfaccia testuale.
Nel corso di questi capitoli d'esempio, per rendere il tutto il più semplice possibile, affronteremo
brevemente due nuovi argomenti che vedremo in maniera più dettagliata in futuro: GetoptLong e
YAML.
Un'altra necessaria premessa prima di cominciare: ho cercato di rendere il codice d'esempio il più
semplice e lineare possibile evitando inutili, in questa situazione, sottigliezze e puntando tutto
sull'aspetto didattico dell'applicazione. Quindi rendere il codice di Ongaku più "rubysh" può essere
un ottimo esercizio dopo aver appreso a dovere le basi del linguaggio e soprattutto dopo aver letto
del buon codice Ruby.

La classe Cd
Dopo queste premesse iniziamo a scrivere qualche riga di codice. Dato che Ongaku catalogherà CD
cominciamo con lo scrivere proprio la classe Cd. Nella nostra applicazione useremo il modulo
come namespace, quindi racchiuderemo tutte le nostre classi in
module Ongaku
...
end

Per ogni Cd inseriremo solo le informazioni riguardanti il titolo, l'autore e l'anno di pubblicazione.
Come abbiamo visto nel capitolo 8, creiamo gli attributi ai quali assegneremo i valori passati al
momento della creazione di un nuovo oggetto Cd:
attr_accessor :titolo, :autore, :anno

def initialize(titolo, autore, anno)


@titolo = titolo
@autore = autore
@anno = anno
end

Un oggetto di tipo Cd va creato quindi nel seguente modo:


cd = Cd.new ("Lepidoptera", "Fursaxa", 2006)

Poiché tra le funzionalità c'è anche la visualizzazione della nostra lista, che sarà un array ordinato di
oggetti Cd, dobbiamo creare il metodo to_s che viene chiamato automaticamente da puts e il
metodo <=> per permettere l'ordinamento dell'array. Il metodo per la stampa degli oggetti Cd è
banalmente:
def to_s
" #{self.autore} - #{self.titolo} (#{self.anno})"
end

Non facciamo altro che produrre una stringa contenente i campi del Cd con un minimo di
formattazione. In questo modo un semplice
puts cd

produrrà l'output
Fursaxa - Lepidoptera (2006)

Per quel che riguarda l'ordinamento dobbiamo solo includere il modulo Comparable (capitolo 11) e
scrivere il metodo <=>. In pratica Comparable ci evita di scrivere tutti i metodi di comparazione
basando il proprio funzionamento solo sull'operatore <=>. Avremo quindi:
include Comparable

def <=>(altro)
self.autore <=> altro.autore
end

In conclusione avremo, nel file cd.rb, la classe Cd:


module Ongaku
class Cd
include Comparable
attr_accessor :titolo, :autore, :anno

def initialize(titolo, autore, anno)


@titolo = titolo.capitalize
@autore = autore.capitalize
@anno = anno
end

def to_s
" #{self.autore} - #{self.titolo} (#{self.anno})"
end

def <=>(altro)
self.autore <=> altro.autore
end
end
end

La classe Applicazione
Vediamo ora il punto d'ingresso di Ongaku costituito dalla classe Applicazione contenuta in
ongaku.rb. Innanzitutto includiamo la libreria GetoptLong, che useremo per la gestione delle
opzioni, e il file ongaku/ui che contiene l'implementazione delle varie interfacce:
require 'getoptlong'
require 'ongaku/ui'

Impostiamo poi alcune costanti che useremo nel corso del programma:
NOME = "0ngaku"
VERSIONE = "0.0.1"
DESCRIZIONE = "Ongaku è un semplice catalogatore di CD"
NOMEFILE = 'cd.yaml'

Dove "cd.yaml" è il nome del file che conterrà la lista dei nostri CD, lo vedremo in dettaglio nel
prossimo capitolo. Svolte queste operazioni accessorie scriviamo la classe Applicazione che come
detto si occuperà di gestire le opzioni da linea di comando e di chiamare il metodo main del modulo
Ui passandogli il tipo scelto da riga di comando:
def initialize
opzioni
Ongaku::Ui.main(@ui)
end

Le opzioni le gestiremo attraverso i due metodi


def opzioni
opzioni = GetoptLong.new(
["--ui", "-i", GetoptLong::REQUIRED_ARGUMENT],
["--help", "-h", GetoptLong::NO_ARGUMENT]
)
opzioni.each{ |opt, val| gestione_opzioni(opt, val) }
end

def gestione_opzioni(opt, valore)


case opt
when '--help'
aiuto
when '--ui'
@ui = valore
end
end

Il primo metodo definisce quali opzioni sono consentite, utilizzando un oggetto GetoptLong, e
precisamente --ui (oppure analogamente -i) che prende un argomento e --help (oppure -h) che
non richiede argomenti. A GetoptLong vanno passati degli array contenenti le stringhe che
rappresentano le opzioni ed un flag per indicare se all'opzione va associato un argomento. Oltre ai
flag REQUIRED_ARGUMENT e NO_ARGUMENT è possibile usare anche OPTIONAL_ARGUMENT.
Dopodiché per ogni opzione passata viene invocato il metodo gestione_opzioni che con un
case sul tipo di opzione imposta alcuni valori oppure chiama semplicemente il metodo aiuto. Il
metodo aiuto visualizza solo alcune righe che mostrano le opzioni accettate ed esce:
def aiuto
puts DESCRIZIONE
puts "#{$0} -i [text|gui|web]"
exit
end

Alla fine il file ongaku.rb appare in questo modo:


require 'getoptlong'
require 'ongaku/ui'

module Ongaku
NOME = "0ngaku"
VERSIONE = "0.0.1"
DESCRIZIONE = "Ongaku è un semplice catalogatore di CD"
NOMEFILE = 'cd.yaml'

class Applicazione

def initialize
opzioni
Ongaku::Ui.main(@ui)
end

def opzioni
opzioni = GetoptLong.new(
["--ui", "-i", GetoptLong::OPTIONAL_ARGUMENT],
["--help", "-h", GetoptLong::NO_ARGUMENT]
)
opzioni.each { |opt, val| gestione_opzioni(opt, val) }
end

def gestione_opzioni(opt, valore)


case opt
when '--help'
Applicazione.aiuto
when '--ui'
@ui = valore
end
end
def self.aiuto
puts DESCRIZIONE
puts "#{$0} -i [text|gui|web]"
exit
end
end
end

Ongaku::Applicazione.new

Dove Ongaku::Applicazione.new crea un oggetto di tipo Applicazione eseguendo di fatto la nostra


applicazione.

Interfaccia e YAML
Nella classe Applicazione abbiamo messo Ongaku::Ui.main(@ui) che non fa altro che
chiamare il metodo main del modulo Ui passando come parametro il tipo di interfaccia scelta. Per
salvare i dati su disco utilizzeremo il YAML (potete leggere una nostra breve introduzione a
YAML), ma avremmo potuto usare qualsiasi altro formato, quello che ci interessa è che i dati
presenti nel file cd.yaml saranno caricati all'avvio dell'applicazione in un Array di Cd; dopodiché
tutte le operazioni avverranno su tale array e solo alla chiusura dell'applicazione aggiorneremo,
eventualmente, il file yaml contenente la lista. Vediamo i dettagli.
Come al solito per prima cosa includiamo le librerie e i file necessari:
require 'yaml'
require 'ongaku/ui/text'
require 'ongaku/ui/gui'
require 'ongaku/ui/web'
require 'ongaku/cd'

Creiamo quindi il modulo Ui contenente i principali metodi per la manipolazione dei dati. Il
metodo main chiamato da Applicazione.new() si occupa di caricare il file contenente la
lista e di avviare l'interfaccia selezionata dall'utente in fase di avvio.
def self.main(ui)
@modifica = false
$archivio = Array.new()
Ui.carica_cd

case ui
when 'text'
interfaccia = Ongaku::Text.new
when 'gui'
interfaccia = Ongaku::Gui.new
when 'web'
interfaccia = Ongaku::Web.new
else
puts "Interfaccia non implementata"
exit
end

interfaccia.avvia
end

La lista viene caricata nell'array di Cd $archivio utilizzando il metodo YAML::load_documents,


lo stesso array viene poi ordinato con sort!:
def self.carica_cd
YAML::load_documents(File.open(NOMEFILE, File::CREAT)) do |cd|
$archivio << cd
end
$archivio.sort!
end

Analogamente in chiusura l'array $archivio viene riscritto, un oggetto per volta, sul file con
YAML::dump. La variabile @modifica tiene conto delle modifiche all'array, se ce ne sono state
vale true, in caso contrario false.
def self.salva_cd
if @modifica then
open(NOMEFILE, 'w') do |file|
$archivio.each{|cd| YAML::dump(cd, file)}
end
end
end

Le altre due funzionalità implementate nel modulo Ui sono aggiungi_cd e elimina_cd. Il


primo prende come argomenti i dati del cd da aggiungere, istanzia un nuovo oggetto Cd e lo
aggiunge all'array $archivio:
def self.aggiungi_cd(tit, aut, anno)
@modifica = true
nuovo_cd = Cd.new(tit, aut, anno)
$archivio << nuovo_cd
$archivio.sort!
end

Anche elimina_cd è molto banale, e non fa altro che eliminare l'elemento dell'array alla
posizione passatagli come argomento.
def self.elimina_cd(index)
@modifica = true
$archivio.slice!(index)
end

L'interfaccia testuale
Come detto prima, dato che in questa prima parte della guida non abbiamo trattato nessuna GUI,
implementeremo solo l'interfaccia testuale. La nostra classe si occuperà di mostrare a video un
menù dei comandi minimale e quindi in base al comando ricevuto raccogliere, o mostrare, i dati
necessari. La classe che implementa tutto questo è Ongaku::Text nel file ongaku/ui/text.rb. Nel
metodo Ui.main le interfacce vengono eseguite con l'istruzione avvia che in questo caso sarà una
cosa del genere:
def avvia
stampa_menu
comando = gets.chomp

case comando
when 'a'
aggiungi
when 'e'
elimina
when 'l'
elenca
when 'q'
Ongaku::Ui.salva_cd
puts "Grazie per aver utilizzato #{NOME}!"
exit
else
puts "Comando inesistente: #{comando}"
end

avvia
end

Innanzitutto viene stampato il menu e poi viene catturato il comando digitato dall'utente, ed in base
a quest'ultimo viene eseguito il metodo associato. Il metodo stampa_menu non fa altro che
stampare a video delle stringhe che mostrano quali comandi sono possibili:
def stampa_menu
puts SEPARATORE
puts "Ongaku menu:"
puts " (a) aggiungi CD (e) elimina CD "
puts " (l) visualizza lista (q) esci da #{NOME}"
print "scegli: "
end

dove SEPARATORE è una stringa costante che vale 45 caratteri '='. Il metodo aggiungi invece
colleziona i dati necessari alla creazione di un nuovo oggetto Cd e poi chiama
Ongaku::Ui.aggiungi_cd:
def aggiungi
puts SEPARATORE
puts "Aggiungi CD"

print " Titolo: "


tit = gets.chomp
print " Autore: "
aut = gets.chomp
print " Anno di pubblicazione: "
anno = gets.chomp

Ongaku::Ui.aggiungi_cd(tit, aut, anno)


end

Per rimuovere un cd visualizziamo la lista completa e poi chiediamo all'utente di digitare l'indice
del cd da eliminare. Tale valore è passato poi a Ongaku::Ui.elimina_cd:
def elimina
elenca
print "Scegli il CD da eliminare: "
index = gets.chomp
Ongaku::Ui.elimina_cd(index.to_i - 1)
end

Dove elenca è il metodo utilizzato per visualizzare la lista completa, non facciamo altro che
stampare tutti gli elementi dell'array preceduti dal numero d'ordine:
def elenca
puts SEPARATORE
puts "Elenco dei Cd in archivio"
$archivio.each_index {|i| puts "#{(i + 1).to_s.rjust(4)}: #{$archivio[i]}"}
end

Infine quando si sceglie di chiudere l'applicazione con il comando q viene scritto l'array sul file con
il metodo Ongaku::Ui.salva_cd e viene stampato un messaggio d'uscita.
Ongaku::Ui.salva_cd
puts "Ora la lista contiene #{$archivio.size} CD"
puts "Grazie per aver utilizzato #{NOME}!"
exit

Per quel che riguarda le altre due interfacce, per ora, gli facciamo solo stampare un messaggio
d'errore qualora siano invocate.

L'applicazione in azione
A questo punto non resta che provare quello che abbiamo fatto. Cominciamo dalle opzioni:
$ ruby ongaku.rb -h
Ongaku è un semplice catalogatore di CD
ongaku.rb -i [text|gui|web]
$ ruby ongaku.rb -i foo
Interfaccia non implementata

Ecco invece una breve sessione in cui aggiungiamo un solo CD:


$ ruby ongaku.rb -i text
=============================================
Ongaku menu:
(a) aggiungi CD (e) elimina CD
(l) visualizza lista (q) esci da 0ngaku
scegli: a
=============================================
Aggiungi CD
Titolo: Lepidoptera
Autore: Fursaxa
Anno di pubblicazione: 2006
=============================================
Ongaku menu:
(a) aggiungi CD (e) elimina CD
(l) visualizza lista (q) esci da 0ngaku
scegli: l
=============================================
Elenco dei Cd in archivio
1: Fursaxa - Lepidoptera (2006)
=============================================
Ongaku menu:
(a) aggiungi CD (e) elimina CD
(l) visualizza lista (q) esci da 0ngaku
scegli: q
Ora la lista contiene 1 CD
Grazie per aver utilizzato 0ngaku!

Proviamo ora ad inserire altri Cd e a eliminarne alcuni:


$ ruby ongaku.rb -i text
=============================================
Ongaku menu:
(a) aggiungi CD (e) elimina CD
(l) visualizza lista (q) esci da 0ngaku
scegli: a
=============================================
Aggiungi CD
Titolo: Just another diamond day
Autore: Vashti Bunyan
Anno di pubblicazione: 1970
=============================================
Ongaku menu:
(a) aggiungi CD (e) elimina CD
(l) visualizza lista (q) esci da 0ngaku
scegli: a
=============================================
Aggiungi CD
Titolo: Co.dex
Autore: Giovanni Lindo Ferretti
Anno di pubblicazione: 2000
=============================================
Ongaku menu:
(a) aggiungi CD (e) elimina CD
(l) visualizza lista (q) esci da 0ngaku
scegli: l
=============================================
Elenco dei Cd in archivio
1: Fursaxa - Lepidoptera (2006)
2: Giovanni Lindo Ferretti - Co.dex (2000)
3: Vashti Bunyan - Just another diamond day (1970)
=============================================
Ongaku menu:
(a) aggiungi CD (e) elimina CD
(l) visualizza lista (q) esci da 0ngaku
scegli: e
=============================================
Elenco dei Cd in archivio
1: Fursaxa - Lepidoptera (2006)
2: Giovanni Lindo Ferretti - Co.dex (2000)
3: Vashti Bunyan - Just another diamond day (1970)
Scegli il CD da eliminare: 3
=============================================
Ongaku menu:
(a) aggiungi CD (e) elimina CD
(l) visualizza lista (q) esci da 0ngaku
scegli: l
=============================================
Elenco dei Cd in archivio
1: Fursaxa - Lepidoptera (2006)
2: Giovanni Lindo Ferretti - Co.dex (2000)
=============================================
Ongaku menu:
(a) aggiungi CD (e) elimina CD
(l) visualizza lista (q) esci da 0ngaku
scegli: q
Ora la lista contiene 2 CD
Grazie per aver utilizzato 0ngaku!

A questo punto il file cd.yaml conterrà:


--- !ruby/object:Ongaku::Cd
anno: "2006"
autore: Fursaxa
titolo: Lepidoptera
--- !ruby/object:Ongaku::Cd
anno: "2000"
autore: Giovanni Lindo Ferretti
titolo: Co.dex
Distribuzione e conclusioni
Visto che tutto funziona a dovere creiamo i mancanti file accessori, ovvero almeno un file
README che illustra il funzionamento e le istruzioni di installazione, una pagina di manuale, il file
della licenza ed un eventuale TODO con la lista delle funzionalità ancora da implementare. Una
volta che tutto e pronto, e al proprio posto, dobbiamo scegliere in che modo distribuire Ongaku.

Utili esercizi
Anche l'occhio inesperto si sarà accorto che Ongaku è ampiamente migliorabile sotto molti aspetti.
Lascio dunque al lettore come utile esercizio l'implementazione delle funzionalità mancanti, ecco di
seguito un elenco incompleto:
• Vanno aggiunti i commenti al codice
• Va aggiunta una funzione "Modifica CD"
• Sarebbe utile poter esportare la lista di cd in diversi formati
• Va aggiunta la gestione delle eccezioni
• Sarebbe utile anche la creazione di un file di configurazione
• Si può migliorare ampiamente la resa grafica
Altri aspetti invece riguardano argomenti che tratteremo nella seconda parte di questa guida come
ad esempio l'implementazione delle interfacce Gui e Web, la creazione di test unitari,
l'internazionalizzazione e così via.

Note finali
Come già detto si è cercato con questo esempio di fornire un supporto didattico alla guida, quindi
molte soluzioni non sono delle migliori ma sono state adottate perché bene si adattano allo scopo
prefisso. Inoltre è utile ricordare che è buona norma, soprattutto in previsione di una distribuzione,
dare dei nomi inglesi ai moduli alle classi e ai metodi; l'italiano è usato nell'esempio per non
aggiungere ulteriori difficoltà ai neofiti.