Guida Ruby

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

description

Fonte html.it

Transcript of Guida Ruby

Page 1: Guida Ruby

Introduzione1. 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 strumenti1. 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 OO1. 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 dati1. 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

Page 2: Guida Ruby

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 directory1. 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 eccezioni1. 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: Rdoc1. 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

Page 3: Guida Ruby

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

Page 4: Guida Ruby

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

Page 5: Guida Ruby

Caratteristiche di RubyDiamo 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 RubyInnanzitutto 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

Page 6: Guida Ruby

-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 RubyL'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

Page 7: Guida Ruby

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

eRubyeRuby (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:

Page 8: Guida Ruby

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.

TestrbUn 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

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

IDEEsistono 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

Page 9: Guida Ruby

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

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

WindowsAnche 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

Page 10: Guida Ruby

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

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

AltroInfine è 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 RubyPrima 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.

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

Page 11: Guida Ruby

• Le variabili di classe con due segni @. • Le costanti con una lettera maiuscola.

Ecco degli esempi:

nome = "Matz" # nome è una variabile localeMesi = 12 # Mesi è una costanteMESI = 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 classdef defined? do else elsif end ensurefalse for if in module next nil notor redo rescue retry return self super thentrue undef unless until when while yield

Ne scopriremo il significato nel corso della guida.

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

=beginQuesto è 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.

DocumentazioneCome 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

Page 12: Guida Ruby

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 refineyour 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 oggettiPoiché 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.

Page 13: Guida Ruby

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 - IVediamo 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" endend

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

una volta definite le classi possiamo istanziarne degli oggetti. In irb, dopo aver creato le classi scriviamo:

> ca = CarroArmato.newSono un nuovo carro armato=> #<CarroArmato:0xb7d327fc>> c = Camion.newSono 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)

Page 14: Guida Ruby

@carburante = carburante @colpi = colpi endend

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 endend

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 endend

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 endend

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

> ca.colpi = 50

Page 15: Guida Ruby

=> 50> puts ca.colpi=> 50

Ruby e gli oggetti - IILa 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 endend

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

def initialize (posti, carburante) @posti = posti @carburante = carburante endend

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, trueattr :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

Page 16: Guida Ruby

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: numeriIniziamo 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 = 123f.class=> Fixnumb = 1234567890b.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

Page 17: Guida Ruby

> 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: stringheCome 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"

Page 18: Guida Ruby

> 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

Page 19: Guida Ruby

• "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 iteratoriIn 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 iteratoriIniziamo 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

Page 20: Guida Ruby

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}Ciao

Mondo

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}CiaoMondo

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" yieldend

chiama { puts "Sono nel blocco" }

eseguendo questo codice otterremo il seguente output:

Sono nel metodoSono 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" yieldend

e anche passare parametri al blocco tramite yield:

Page 21: Guida Ruby

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

In questo modo avremo in output

Sono nel metodoSono 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 - IUn 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

Page 22: Guida Ruby

=> [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 - IIUn 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]

Page 23: Guida Ruby

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"]

Page 24: Guida Ruby

=> ["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}rossobianconero=> ["rosso", "bianco", "nero"]> arr.reverse_each {|colore| puts colore}nerobiancorosso=> ["rosso", "bianco", "nero"]

each_index invece di passare gli elementi come parametri passa gli indici di tali elementi:

> arr.each_index {|indice| puts indice}012 => ["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]

Page 25: Guida Ruby

delete_if elimina dall'array tutti quegli elementi per il quale la valutazione nel blocco restituisce valore true. Similmente si comportano select e reject.

HashGli 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!"

Page 26: Guida Ruby

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: Tolstojnome: Levpatronimico: Nikolaevic> hsh.each_key {|k| puts k}cognomenomepatronimico> hsh.each_value {|v| puts v}TolstojLevNikolaevic

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

IfIniziamo 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 istruzioneend

è 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

Page 27: Guida Ruby

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

UnlessOpposto 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

Page 28: Guida Ruby

@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: 70Carburante 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

CaseIn 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 commandwhen "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:

Page 29: Guida Ruby

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

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

Page 30: Guida Ruby

Uscire dai cicliConcludiamo 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 = 0while 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à singolaIn 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 endend

class Camion

Page 31: Guida Ruby

attr_reader :posti, :carburante

def initialize (posti, carburante) @posti = posti @carburante = carburante end

def rifornimento (quantita) @carburante += quantita endend

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 endend

possiamo scrivere le nostre classi di conseguenza:

class CarroArmato < Veicolo attr_reader :colpi

def initialize (carburante, colpi) super(carburante) @colpi = colpi endend

class CarroArmato < Veicolo attr_reader :colpi

def initialize (carburante, colpi) super(carburante) @colpi = colpi endend

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

Page 32: Guida Ruby

superclasse.

Ereditarietà multiplaRuby, 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 endend

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}" endend

class Tokyo include Inutileend

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.

Page 33: Guida Ruby

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 fileUna 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 fileCome è 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

Page 34: Guida Ruby

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 scritturaDopo 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 lineaend

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 invernaleUn monaco scintoCammina 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 eU n m o n a c o s c i n t oC 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

Page 35: Guida Ruby

dove lineno è un metodo della classe IO che restituisce il numero di linea corrente. L'output sarà dunque:

1 Vento invernale2 Un monaco scinto3 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 directoryConcludiamo 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

Page 36: Guida Ruby

• 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 directoryPrima 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

Page 37: Guida Ruby

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

\s spazio (spazio vero e proprio, tabulazione e carattere di fine riga), come [\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 parolaOltre 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+)/

Page 38: Guida Ruby

> stringa = "Ciao mondo"=> "Ciao mondo"> stringa =~ er=> 4> $1=> "mondo"

Le espressioni regolari e la classe StringNel 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.

Page 39: Guida Ruby

Le eccezioniConcludiamo 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 erroriDopo 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

Page 40: Guida Ruby

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 = truerescue LoadError GMAIL_SUPPORTED = falseend

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 = truerescue LoadError => error puts "L'errore è: #{error}" GMAIL_SUPPORTED = falseend

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 varierescue # gestione degli erroriend

Rdoc: introduzione e primo utilizzoIn 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

Page 41: Guida Ruby

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 elemento2. secondo elemento

a. primo elemento b. secondo elemento

Infine ci sono le liste che utilizzano delle etichette

[nome] Lev [cognome] Tolstoj

oppure

nome:: Levcognome:: 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:

Page 42: Guida Ruby

Generating HTML...

Files: 1Classes: 0Modules: 0Methods: 0Elapsed: 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 endend

class CarroArmato < Veicolo

Page 43: Guida Ruby

attr_reader :colpi

def initialize (carburante, colpi) super(carburante) @colpi = colpi endend

class Camion < Veicolo attr_reader :posti

def initialize (carburante, posti) super(carburante) @posti = posti endend

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 endend

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

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

Page 44: Guida Ruby

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

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: 1Classes: 3Modules: 0Methods: 4Elapsed: 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

Page 45: Guida Ruby

Rdoc: documentazione automaticaOltre 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 endend

class CarroArmato < Veicolo attr_reader :colpi

def initialize (carburante, colpi) super(carburante) @colpi = colpi endend

class Camion < Veicolo attr_reader :posti

def initialize (carburante, posti)

Page 46: Guida Ruby

super(carburante) @posti = posti endend

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

$ rdoc veicolo.rb

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

Files: 1Classes: 3Modules: 0Methods: 4Elapsed: 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 opzioniMolti 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 è

Page 47: Guida Ruby

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: 1Classes: 3Modules: 0Methods: 4Elapsed: 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 RubyGemsDopo 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

Page 48: Guida Ruby

opzione per vedere una schermata di aiuto:

$ gem

RubyGems is a sophisticated package manager for Ruby. This is abasic 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 RubyGemsVediamo 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:

Page 49: Guida Ruby

$ gem install <nome_pacchetto_da_installare>

ad esempio

$ gem install gmailerSuccessfully 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,

Page 50: Guida Ruby

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 gemDopo 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 proprieext/ contiene eventuali estensioni Ruby scritte in Cbin/ contiene gli script necessari in fase di installazionedata/ contiene i file dati che accompagnano l'applicazioneconf/ contiene tutti i file di configurazioneman/ contiene le pagine del manualetest/ 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 = "[email protected]" 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.

Page 51: Guida Ruby

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

configuremakemake install

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

ruby setup.rb configruby setup.rb setupruby 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

Page 52: Guida Ruby

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 proprieext/ contiene eventuali estensioni Ruby scritte in Cbin/ contiene le utility necessarie in fase di installazionedata/ contiene i file dati che accompagnano l'applicazioneconf/ contiene tutti i file di configurazioneman/ contiene le pagine del manualetest/ 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.rbTornando 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

Page 53: Guida Ruby

Per un elenco completo rimando alla documentazione presente sul sito ufficiale del progetto.

Dove distribuire le applicazioni RubyUna 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 strutturaConcludiamo 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 0ngakuscegli:

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

Page 54: Guida Ruby

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 CdDopo 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 = annoend

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)

Page 55: Guida Ruby

self.autore <=> altro.autoreend

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 endend

La classe ApplicazioneVediamo 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) }

Page 56: Guida Ruby

end def gestione_opzioni(opt, valore) case opt when '--help' aiuto when '--ui' @ui = valore endend

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

Page 57: Guida Ruby

def self.aiuto puts DESCRIZIONE puts "#{$0} -i [text|gui|web]" exit end endend

Ongaku::Applicazione.new

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

Interfaccia e YAMLNella 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.avviaend

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

Page 58: Guida Ruby

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 endend

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 testualeCome 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'

Page 59: Guida Ruby

Ongaku::Ui.salva_cd puts "Grazie per aver utilizzato #{NOME}!" exit else puts "Comando inesistente: #{comando}" end avviaend

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.

Page 60: Guida Ruby

Ongaku::Ui.salva_cdputs "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 azioneA questo punto non resta che provare quello che abbiamo fatto. Cominciamo dalle opzioni:

$ ruby ongaku.rb -hOngaku è un semplice catalogatore di CDongaku.rb -i [text|gui|web]$ ruby ongaku.rb -i fooInterfaccia 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 0ngakuscegli: 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 0ngakuscegli: l=============================================Elenco dei Cd in archivio 1: Fursaxa - Lepidoptera (2006)=============================================Ongaku menu: (a) aggiungi CD (e) elimina CD (l) visualizza lista (q) esci da 0ngakuscegli: qOra la lista contiene 1 CDGrazie 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 0ngakuscegli: a=============================================Aggiungi CD Titolo: Just another diamond day Autore: Vashti Bunyan Anno di pubblicazione: 1970

Page 61: Guida Ruby

=============================================Ongaku menu: (a) aggiungi CD (e) elimina CD (l) visualizza lista (q) esci da 0ngakuscegli: 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 0ngakuscegli: 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 0ngakuscegli: 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 0ngakuscegli: 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 0ngakuscegli: qOra la lista contiene 2 CDGrazie per aver utilizzato 0ngaku!

A questo punto il file cd.yaml conterrà:

--- !ruby/object:Ongaku::Cd anno: "2006"autore: Fursaxatitolo: Lepidoptera--- !ruby/object:Ongaku::Cd anno: "2000"autore: Giovanni Lindo Ferrettititolo: Co.dex

Page 62: Guida Ruby

Distribuzione e conclusioniVisto 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 eserciziAnche 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 finaliCome 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.