You are on page 1of 65

Imparando Ruby...

Introduzione alla programmazione con Ruby

a cura di
Stefano Sasso
stefano(at)gnustile.net

Versione 1.0 - 10 Febbraio 2009


Indice

I Le Basi 1

1 Introduzione a Ruby 2
1.1 Interprete interattivo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
1.2 Interprete non interattivo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
1.3 La prima prova . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3

2 Il linguaggio Ruby 4
2.1 Metodi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
2.2 Assegnazione di variabili . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
2.3 Tipi di dato numerici . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
2.4 I commenti . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
2.5 Le costanti . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
2.6 Tutti i metodi di un oggetto . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
2.7 Standard input + Stringhe di caratteri . . . . . . . . . . . . . . . . . . . . . . . . . 8
2.8 Conversione tra diversi tipi di dato . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
2.9 Dati booleani . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
2.10 Enunciati decisionali - IF/UNLESS . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
2.11 Altro tipo di dato: gli array . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
2.12 Cicli di istruzioni . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
2.12.1 while . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
2.12.2 for . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
2.12.3 blocco each - blocco times . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
2.13 Ancora array... . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
2.14 Ancora stringhe... . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
2.15 Definizione di metodi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
2.16 Semplici operazioni con file di testo . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
2.16.1 lettura di un file . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19

i
2.16.2 scrittura di un file . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
2.16.3 lavorare con il filesystem . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
2.17 Altri oggetti utili . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
2.17.1 La classe ’Time’ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
2.17.2 La classe ’Hash’ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22

3 Programmazione orientata agli oggetti 23


3.1 Definizione di classe e oggetto . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
3.2 Creazione di classi personalizzate . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
3.2.1 costruzione di una classe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
3.2.2 variabili di istanza . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
3.2.3 ereditarietà . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
3.3 Modifica di classi esistenti . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
3.4 Gestione degli errori - Eccezioni . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29

4 Uso di librerie particolari 32


4.1 Gemme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
4.2 Lavorare con indirizzi IP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
4.3 YAML . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
4.4 Inviare mail via SMTP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
4.5 Accesso a database MySQL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
4.6 Accesso a database SQLite . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
4.7 Accesso a database KirbyBase . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
4.8 ActiveRecord . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
4.9 File XML . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
4.9.1 xmlsimple . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
4.9.2 rexml . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
4.10 Networking a basso livello . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40

II Programmazione web di base 46

5 CGI 47
5.1 Introduzione . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
5.2 CGI in ruby . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
5.3 CGI + ERB . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
5.4 mod ruby . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50

ii
III Ruby on Rails 51

A Esercizi e soluzioni 53
A.1 Semplici operazioni con stringhe . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
A.1.1 Esercizio 1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
A.2 Metodi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
A.2.1 Esercizio 1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
A.2.2 Esercizio 2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
A.3 Operazioni con Array . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
A.3.1 Esercizio 1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
A.3.2 Esercizio 2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56
A.3.3 Esercizio 3 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56
A.4 Operazioni con Hash . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
A.4.1 Esercizio 1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
A.5 Operazioni con File . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58
A.5.1 Esercizio 1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58
A.6 Operazioni con Oggetti . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
A.6.1 Esercizio 1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
A.6.2 Esercizio 2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
A.6.3 Esercizio 3 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60
A.6.4 Esercizio 4 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60
A.6.5 Esercizio 5 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61

iii
Parte I

Le Basi

1
Capitolo 1

Introduzione a Ruby

Ruby è un linguaggio di programmazione interpretato, questo significa che passando un file di


testo contentente codice ruby all’interprete, questo viene eseguito.
È possibile scrivere spezzoni di codice ruby anche in un interprete interattivo, il che significa che
appena uno spezzone di codice viene inserito, lo stesso viene eseguito.

1.1 Interprete interattivo


È possibile lanciare l’interprete interattivo con

~# irb

ci comparià quindi un prompt simile al seguente:

irb(main):001:0> _

1.2 Interprete non interattivo


Per lanciare l’interprete non interattivo è necessario scrivere all’interno di un file di testo il codice
ruby che vogliamo venga eseguito.
Possiamo poi lanciare l’interprete con

~# ruby nomedelfile.rb

oppure, più semplicemente, inserire in testa al file contenente il codice ruby la riga

#!/usr/bin/env ruby

come qualsiasi altro script in ambiente UNIX.

2
1.3 La prima prova
Facciamo subito la prima prova: creiamo il file prova1.rb con un editor di testo, e al suo interno
scriviamo

puts "Ciao mondo!"

poi eseguiamolo con

~# ruby prova1.rb

allo stesso modo apriamo irb, e al prompt digitiamo

puts "Ciao mondo!"

il nostro interprete sarà quindi in questa situazione:

irb(main):001:0> puts "Ciao mondo!"


Ciao mondo!
=> nil
irb(main):002:0> _

Analizziamo ora questo semplicissimo programma:


puts è una funzione del linguaggio che ci consente di mandare qualche oggetto allo standard
output.
Ruby è un linguaggio orientato agli oggetti ; per il momento consideriamo gli oggetti come delle
“cose” che vengono manipolate da un programma.
In questo caso l’oggetto in questione, da stampare a schermo, è una stringa di caratteri, delimitata
da doppi apici (00 ). Equivalente sarebbe stato delimitarla da apici singoli (’).

3
Capitolo 2

Il linguaggio Ruby

2.1 Metodi
Abbiamo visto prima che in Ruby esistono delle funzioni, comunemente detti metodi che compiono
una qualche azione. Ad esempio puts serve per stampare a schermo un oggetto.
Esistono più modi per richiamare un metodo da un programma: con o senza parentesi tonde ( ).

metodo parametro1, parametro2


metodo(parametri1, parametro2)

parametro1 e parametro1 sono degli oggetti su cui il metodo specifico andrà ad operare. Il numero
di oggetti “passabili” a un metodo è deciso dal progettista del metodo stesso.
Nel contesto di prima, ad esempio,

puts "Ciao mondo!"

puts("Ciao mondo!")

sono del tutto equivalenti. Il metodo puts può lavorare, come già visto, su degli oggetti stringa
di caratteri, ma anche, ad esempio, su oggetti del tipo numero intero (ma anche altri):

puts 12
puts(12)

Ricordiamoci che in Ruby, tutto è un oggetto, anche i numeri.


Su ogni oggetto è possibile invocare dei metodi, che lavoreranno direttamente su quell’oggetto. È
possibile anche invocare dei metodi su dei metodi ; questo perchè un metodo ritorna al programma
sempre un oggetto, quindi in realtà è come invocare un metodo su un oggetto. vediamo un esempio
(lavorando da irb):

3.next

4
in questo caso invochiamo il metodo next sull’oggetto 3. Il metodo ritornerà come valore di uscita
4. Ora, invocando il metodo next sull’oggetto 4, otterremo 5.
Lo stesso risultato si sarebbe potuto ottenere con

3.next.next

È da ricordare che ogni tipo di oggetto (chiamato classe) ha i suoi metodi, anche se più oggetti
possono avere dei metodi che si chiamano nello stesso modo.
Ad esempio

"a".next

non è lo stesso metodo visto prima (infatti, la classe di partenza è diversa), ma si chiama nello
stesso modo.
Ad esempio,

"ciao".length

ha senso (lunghezza della stringa), mentre

2.length

no.
Ah, nel frattempo abbiamo visto che l’operatore per invocare dei metodi su degli oggetti è il .
(punto).
Nel caso di numeri, le operazioni matematiche sono anch’esse metodi.
Ad esempio

3+4

viene automaticamente convertito da ruby in

3.+(4)

ovvero viene invocato il metodo che si chiama + sull’oggetto 3.


Idem per -, *, /, **
I metodi che agiscono su un oggetto di norma ritornano un altro oggetto; per ritornare non si
intende ritornare qualcosa all’utente, bensı̀ ritornare qualcosa (un oggetto) al programma.

(Capiremo meglio il significato di ritornare andando avanti con le nozioni imparate.)

Ad esempio, i metodi

3.next
3.+(2)

ritornano, rispettivamente, oggetti numero 4 e 5.


A volte i metodi possono ritornare un oggetto particolare, l’oggetto nil, che sta per nulla. Ad
esempio, nel caso di puts, non ha senso che venga ritornato un qualcosa, quindi ritornerà nil.

5
2.2 Assegnazione di variabili
Un operatore particolare è l’operatore di assegnazione, =, che consente di salvare il valore (oggetto)
ritornato da un metodo all’interno di un’area della memoria, chiamata variabile.
Ad esempio

risultato=2+5

L’area della memoria da noi chiamata risultato conterrà ora l’oggetto 7, ritornato dal metodo
+ con parametro 5 invocato sull’oggetto 2.
Su un’area di memoria (variabile) è possibile invocare tutti i metodi propri dell’oggetto che vi è
memorizzato:

risultato2=risultato.next

la variabile risultato2 conterrà ora l’oggetto numerico 8.

2.3 Tipi di dato numerici


Visto che ne stavamo parlando, vediamo ora come sono esprimibili i tipi di dato numerici.
Fondamentalmente è possibile esprimere un numero in due notazioni:

• Numero intero (Classe: Fixnum)

• Numero a virgola mobile (Classe: Float)

vediamo qualche esempio per comprendere meglio la “divisione”:

3 (classe: Fixnum)
-5 (classe: Fixnum)
4.33564 (classe: Float)
-4.5 (classe: Float)
1.34E98 (classe: Float)
-1.43E12 (classe: Float)
1.2E-5 (classe: Float)
-3.6E-3 (classe: Float)

ad esempio, gli oggetti della classe Float non hanno il metodo next, se hai studiato matematica
capirai il perchè :)

2.4 I commenti
I commenti sono parti di testo all’interno del codice, non vengono eseguiti dall’interprete ma
possono essere utili per annotare cose che servono al programmatore.

6
# commento su una riga
=begin
commento
su
piu‘
righe
=end

può essere utile ad esempio, in questo caso

# la variabile a contiene l’anno corrente


a=MioOggetto.mio_metodo

2.5 Le costanti
Le costanti sono come delle variabili e si usano per contenere dei valori che devono venir ripetuti
più volte all’interno del programma.
Si consiglia di usare nomi simbolici e descrittivi per le costanti, per facilitare la leggibilità del
programma.
Una costante si rappresenta con il nome formato da sole lettere maiuscole.
esempio, conversione Euro:

FATTORE_DI_CONVERSIONE=1936.27
euro=lire/FATTORE_DI_CONVERSIONE

2.6 Tutti i metodi di un oggetto


Come detto prima, ogni oggetto ha i suoi metodi. Per sapere quali sono i metodi disponibili in un
oggetto basta cercare su google

ruby+Classe

ad esempio

ruby+String

il primo risultato sarà qualcosa del tipo

http://www.ruby-doc.org/core/classes/String.html

Proviamo a darci una rapida occhiata.

Se abbiamo un oggetto, ma non ne conosciamo il tipo, possiamo invocare il metodo class


sull’oggetto stesso per conoscerne la classe di “partenza”.

5.class
"ciao".class
3E2.class

7
2.7 Standard input + Stringhe di caratteri
Consideramo questo breve programmino:

puts "Come ti chiami?"


nome = gets
nome.chomp!
puts "Ciao, " + nome

puts l’abbiamo già visto, gets invece è una funzione che recupera una riga dallo standard input
e la ritorna in una variabile di tipo stringa.
Perchè poi invochiamo il metodo chomp! su quell’oggetto?
Perchè gets ritorna l’input inserito COMPRENSIVO di ritorno a capo (carattere speciale \n ),
quindi chomp! serve a rimuovere proprio questo carattere dalla fine della stringa.
Volendo, si sarebbe anche potuto scrivere

nome = gets.chomp

la differenza tra chomp e chomp! ? vedere tra i metodi nella documentazione ufficiale :)
comunque, per convenzione, i metodi! agiscono direttamente sull’oggetto, mentre i metodi ne
ritornano una copia (modificata). vediamo un esempio:

a="ciao\n"
b=a.chomp
# a vale "ciao\n"
# b vale "ciao"
a.chomp!
# a vale "ciao"

Nel programmino di prima vediamo anche come sia possibile concatenare 2 stringhe, ovvero il
“Ciao, “ e nome. (sempre l’operatore-metodo +)
Sarebbe anche stato possibile includere la stringa nome direttamente all’interno della stringa
definita con “ “:

puts "Ciao, #{nome}"

La serie di caratteri # permette di includere in una stringa il contenuto di una variabile.


esempio:

var1="bao"
var2="ciao #{var1}"

var2 conterrà quindi “ciao bao“.

8
2.8 Conversione tra diversi tipi di dato
Su alcuni oggetti -tipi di dato è possibile invocare dei metodi per la conversione in altri oggetti -tipi
di dato.
Questi metodi si chiamano to , ad esempio

• .to i - converte in numero intero

• .to f - converte in numero con virgola

• .to s - converte in stringa

esempio:

k="ccc".to_i #=> k vale 0


k="ccc".to_f #=> k vale 0.0
k="ccc".to_s #=> k vale "ccc"
k="c123d4".to_i #=> k vale 0 (la stringa non e‘ convertibile)
k="123".to_i #=> k vale 123
k=123.to_s #=> k vale "123"
k=123.to_f #=> k vale 123.0
k=1.23.to_i #=> k vale 1

2.9 Dati booleani


Oltre a quelli già visti, ovvero numeri e stringhe di caratteri, esiste un altro tipo di dato “fonda-
mentale”: il tipo booleano. I dati booleani rispecchiano l’algebra di Boole, per cui il valore può
essere solamente true o false (vero o falso, 1 o 0); e vengono usati soprattutto negli enunciati
decisionali (che vedremo in seguito).
I principali metodi che ritornano un oggetto booelano, e che si trovano in gran parte delle classi
esistenti, sono i metodi di comparazione:

• == uguale a

• < minore di

• > maggiore di

• <= minore o uguale a

• >= maggiore o uguale a

• != diverso da

ad esempio:

confronto1= 4==5
confronto2= 4<5
confronto3= "a">="c"

9
ovviamente, confronto1 sarà false, confronto2 true e confronto3 false.
Nell’algebra booleana sono presenti gli operatori e, o e “negazione“; dunque anche i tipi di dato
booleano dispongono di questi operatori:

• and - e

• or - o

• ! - “negazione“

vediamo qualche esempio:

e1= 3<4 and 4<5 #=> true


e2= !(3<4) #=> false
e3= 3<4 or 5>7 #=> true
e4= "a"<"d" and !(3>6) #=> true
e5= !("h"<"c" and "h"<"z") #=> true
e6= ((2>=5) or !("a"!="r")) and e4 #=> false

2.10 Enunciati decisionali - IF/UNLESS


L’enunciato if, o il suo negato unless viene adoperato nel caso si debbano eseguire delle istruzioni
solo in presenza di una determinata condizione (oggetto booleano)
Ad esempio:

if (2 < 3)
puts "minore!!!"
end

ne abbiamo anche aprofittato per vedere che ogni blocco di codice - in questo caso quello da eseguire
nel caso la condizione sia vera - deve terminare con la parola chiave end.
Se però, come in questo caso, dobbiamo eseguire una sola istruzione, possiamo compattare tutto
in un’unica riga, evitando perciò il blocco di codice:

puts "minore!!!" if (2 < 3)

Ora, nel caso volessimo eseguire il blocco di codice solo nel caso la condizione di controllo sia falsa,
abbiamo due modi di procedere: 1) usando la negazione propria dell’algebra booleana; 2) usando
la parola chiave unless. Vediamolo insieme:

if !(2 < 3)
puts "non minore"
end

è del tutto equivalente a

10
unless (2 < 3)
puts "non minore"
end

Come visto prima per l’if, se abbiamo una sola istruzione da eseguire, anche l’unless può essere
compattato su una sola riga:

puts "non minore" unless (2 < 3)

Se alla condizione si vuole realizzare un’alternativa, si può utilizzare la clausula else, che sta per
altrimenti :

if condizione_booleana
puts "condizione vera"
else
puts "condizione falsa"
end

in questo caso si chiude con l’end il blocco totale, comprensivo di else.


La stessa cosa vale per unless:

unless condizione_booleana
puts "condizione falsa"
else
puts "condizione vera"
end

Nel caso di if è anche possibile aggiungere degli “step” intermedi; ipotizziamo di voler confrontare
due numeri:

if n1 < n2
puts "n1 minore di n2"
elsif n1 == n2
puts "n1 uguale a n2"
else
puts "n1 maggiore di n2"
end

2.11 Altro tipo di dato: gli array


Prima di vedere i cicli iterativi ci conviene dare uno sguardo agli array:
un array è un insieme di oggetti, a livello di codice delimitati da [ e ]
ad esempio, per “creare” un oggetto di tipo array vuoto:

array=[]

11
mentre, per creare un array contentente gli oggetti-numeri 1, 2, 3 e 4

array=[1, 2, 3, 4]

Se ad un array esistente volessimo aggiungere in coda un altro elemento è sufficiente usare <<:

array << 6
array << 5

ora array sarà [1, 2, 3, 4, 6, 5].


In realtà << è un “sinonimo” del metodo push invocato su un array,
quindi

array << 8

avrà lo stesso effetto di

array.push 8

Se volessimo ordinare gli elementi al suo interno

array.sort!

array sarà ora [1, 2, 3, 4, 5, 6, 8]


È possibile accedere ad uno specifico elemento di un array sempre con [], ricordandoci che ogni og-
getto ha un “indice” che lo identifica, e che gli indici partono da 0 e arrivano a numero di elementi -1
in questo caso:

primo_elemento=array[0]

primo elemento conterrà quindi 1


in maniera simile, nel caso volessimo sovrascriverne il valore

array[0]=-1

Un’altro oggetto particolare, simile agli array, è l’oggetto Range, (intervallo) che si definisce in
questo modo:

intervallo=(1..10)

dove intervallo rappresenta ora tutti i numeri da 1 a 10.


Se ora volessimo avere un array contenente tutti questi numeri basterebbe invocare il metodo
.to a sul range:

array=(1..10).to_a

è possibile una cosa simile anche con caratteri:

array=("a".."d").to_a

array sarà quindi [’a’, ’b’, ’c’, ’d’]


Un array può contenere al suo interno qualsiasi tipo di oggetto (quindi anche un array)

12
2.12 Cicli di istruzioni

2.12.1 while

Il ciclo while serve a ripetere delle istruzioni (blocco di codice) finchè una condizione è vera

count = 0
while count < 10
puts "count = #{count.to_s}"
count = count+1
end

2.12.2 for

Il ciclo for, a differenza del while, esegue delle istruzioni per un determinato numero di oggetti
(array o range):

array=[1, 2, 3, 4, 5]
for n in array
puts "Numero: #{n.to_s}"
end

2.12.3 blocco each - blocco times

Tuttavia esiste un modo più efficace per ottenere il risultato di un ciclo for:
tutti gli oggetti di tipo array o range possiedono il metodo each.
Il metodo each è un metodo di tipo particolare, esso infatti non richiede parametri in ingresso,
ma richiede che dopo la sua invocazione sia presente un blocco di codice da eseguire:
eccone un esempio:

array=[1, 2, 3, 4, 5]
array.each do |n|
puts "Numero: #{n.to_s}"
end

simile al metodo each di un array/range, è il metodo times dei numeri interi:

5.times do |n|
puts "Numero: #{n.to_s}"
end

A volte nella documentazione si trova qualcosa di simile a

array.each { |n|
puts n
}

13
o meglio ancora

array.each {|n| puts n }

ma il significato è sempre lo stesso.


Personalmente trovo sia più leggibile delimitare il blocco con do e end.

2.13 Ancora array...


Sempre per restare in tema di array vediamo altri metodi utili, invocabili su array:

• to s - converte un array in stringa

[1, 2, 3].to_s #=> "123"

• join - simile a to s, ma permette di definire una stringa da inserire tra i vari oggetti in fase
di concatenazione

[’a’, ’b’, ’c’].join(", ") #=> "a, b, c"


[’a’, ’b’, ’c’].join("--") #=> "a--b--c"

• push - vista prima, inserisce un oggetto in coda ad un array

array=[1,2,3]
array.push 4 #=> [1,2,3,4]

• pop - estrae dall’array l’ultimo elemento

array=[1,2,3]
ultimo=array.pop # (array vale ora [1,2] e ultimo vale 3)

• first - ritorna il primo elemento dell’array, senza estrarlo

[1,2,3].first #=> 1

• last - ritorna l’ultimo elemento dell’array, senza estrarlo

[1,2,3].last #=> 3

• length o size - ritorna la dimensione di un array

[1,2,3].size #=> 3

• sort e sort! - ricordi il ! nelle funzioni?


la prima ritorna un nuovo array, contenente gli elementi dell’array di partenza ordinati; la
seconda ordina in-place

ar1=[3, 5, 1]
ar2=ar1.sort # (ar2 vale [1, 3, 5], mentre ar1 resta invariato)
ar1.sort! # (ar1 vale [1, 3, 5])

• include? - ritorna un valore booleano, vero se l’elemento cercato è incluso, altrimenti falso

14
ar1=[1, 2, 3]
ar1.include? 2 #=> true
ar1.include? 8 #=> false

altri metodi consultabili su

http://www.ruby-doc.org/core/classes/Array.html

2.14 Ancora stringhe...


Vediamo ora alcuni metodi utili invocabili su una stringa:

• split - si può considerare come l’inverso di join applicato ad un array, infatti divide la
stringa in elementi di un array

"a--b--c".split("--") #=> ["a", "b", "c"]

• [] - è una funzione particolare che permette di accedere a determinati caratteri della stringa.
Se invocata con un numero ritorna il byte relativo, mentre se invocata con un “range” ritorna
la serie di caratteri relativa:

a = "hello there"
a[1] #=> 101 (byte in posizione 1)
a[1].chr #=> "e" (byte in posizione 1 convertito in carattere)
a[1,3] #=> "ell" (3 caratteri partendo dalla posizione 1 compresa)
a[2,3] #=> "llo" (3 caratteri partendo dalla posizione 2 compresa)
a[1..3] #=> "ell" (caratteri dalla posizione 1 alla posizione 3)
a[-3,2] #=> "er" (2 caratteri dalla posizione 3 partendo da destra)
a[-4..-2] #=> "her" (dalla posizione -4 alla -2)

• []= - cambia un determinato carattere della stringa:

a = "hello there"
a[0]="H" #=> a vale "Hello there"

• <=> - serve a comparare una stringa con un’altra; ritorna -1 se la stringa viene prima
(alfabeticamente parlando), 0 se equivalenti, 1 se viene dopo

"abc"<=>"cba" #=> -1
"ciao"<=>"ciao" #=> 0
"ciao"<=>"bao" #=> 1

• + - l’abbiamo già visto, è l’operatore di concatenazione

"ci"+"ao" #=> "ciao"

• * - ripete la stringa per n volte

saluto="ciao " * 3 #=> "ciao ciao ciao "

15
• capitalize e capitalize! - fanno diventare maiuscola la prima lettera, la prima crea un
nuovo oggetto, la seconda modifica l’oggetto esistente

a = "ciao"
b=a.capitalize # (b vale "Ciao", a vale ancora "ciao")
a.capitalize! # (a vale "Ciao")

• chomp e chomp! - rimuove carattere di fine linea, la prima crea un nuovo oggetto, la secondo
modifica l’oggetto esistente

a = "ciao\n"
b=a.chomp # (b vale "ciao", a vale ancora "ciao\n")
a.chomp! # (a vale "ciao")

• size - determina la lunghezza della stringa

"ciao".size #=> 4

• downcase e downcase! - trasformano tutti i caratteri maiuscoli in minuscoli, la prima crea


un nuovo oggetto, la secondo modifica l’oggetto esistente

a = "CIAO"
b=a.downcase # (b vale "ciao", a vale ancora "CIAO")
a.downcase! # (a vale "ciao")

• empty? - ritorna true se la stringa è vuota

"".empty? #=> true


"c".empty? #=> false

• include? - ritorna true se la stringa include una sequenza di caratteri

"ciao".include? "ao" #=> true


"ciao".include? "bo" #=> false

• index - ritorna la posizione della prima occorrenza di una stringa in quella di partenza

"ciao".index "a" #=> 2


"ciao".index "ia" #=> 1

• reverse e reverse! - serve a invertire i caratteri di una stringa, la prima crea un nuovo
oggetto, la secondo modifica l’oggetto esistente

"stefano".reverse #=> "onafets"

• upcase e upcase! - trasformano tutti i caratteri minuscoli in maiuscoli, la prima crea un


nuovo oggetto, la secondo modifica l’oggetto esistente

a = "ciao"
b=a.upcase # (b vale "CIAO", a vale ancora "ciao")
a.upcase! # (a vale "CIAO")

altri metodi consultabili su

http://www.ruby-doc.org/core/classes/String.html

16
2.15 Definizione di metodi
Abbiamo visto che le strutture iterative sono molto utili per evitare di dover scrivere codice
ripetitivo, un altro modo per evitare di scrivere codice ripetitivo è definire dei propri metodi (o
funzioni).
partiamo per esempi:
ipotizziamo il seguente programma:

nome="Mirko"
puts "Ciao, #{nome}!"
nome="Stefano"
puts "Ciao, #{nome}!"

vediamo chiaramente che è presente una parte ripetitiva.


Possiamo semplificarci la vita creando il metodo saluta, che accetta come parametro in entrata
un oggetto da stampare con puts:

def saluta(nome)
puts "Ciao, #{nome}!"
end

saluta(’Stefano’)
saluta ’Mirko’

Partiamo con l’analisi:


un metodo è un blocco di codice, che comincia con

def <nome_del_metodo>(<variabili_in_ingresso>)

ed essendo un blocco deve terminare con end.


All’interno possono starci delle istruzioni standard o chiamate ad altri metodi.
Come vediamo, dall’istruzione puts interna al metodo è usata la variabile nome, che è appunto
quella definita in ingresso.
Ora, come possiamo ben vedere, quello che abbiamo scritto è un metodo che non restituisce alcun
valore in uscita.
Come fare quindi, se vogliamo far uscire qualcosa da quel metodo?
Vediamo questo metodo un po’ più complesso che, dato un array di numeri interi torna in uscita
la somma di tutti i valori presenti:

def somma_valori(array)
somma=0
array.each do |elemento|
somma=somma+elemento
end
return somma

17
end

somma=somma_valori([1, 2, 3, 4])
puts somma

notiamo subito prima dell’end l’istruzione return somma, questa è l’istruzione che serve per
ritornare un valore al programma chiamante.
A volte l’istruzione return può anche essere omessa, in quanto, se tale, ruby ritornerà per default
il valore dell’ultima assegnazione o chiamata a funzione.
Ad esempio, molto banalmente

def banale
a=1
end

puts banale

stamperà a video 1
Nel nostro caso quindi avemo potuto scrivere

def somma_valori(array)
somma=0
array.each do |elemento|
somma=somma+elemento
end
somma
end

attenzione: il somma prima di end corrisponde ad un’assegnazione a vuoto, proprio per dire
a ruby che il valore da tenere nel buffer da ritornare è il valore di somma, altrimenti avrebbe
ritornato l’array di partenza, perchè l’ultima istruzione sarebbe stata array.each (che lavora
quindi sull’array di partenza) [in questo caso il blocco viene visto come istruzione unica!!!]
Ma allora, se è possibile farlo implicitamente, perchè usare l’istruzione return?
Perchè il return forza l’uscita dal metodo. Ad esempio:

def prova123(numero)
if numero < 10
return "si"
end
"no"
end

puts prova123(12) => ’no’


puts prova123(9) => ’si’

18
omettendo il return invece avrebbe scritto ’no’ anche per il 9, perchè l’esecuzione del metodo
sarebbe continuata, ottenendo cosı̀ come ultimo valore ’no’ in ogni caso.
A un metodo è possibile passare più di un parametro in ingresso, ad esempio:

def confronto(a, b)
if a<b
puts "a < b"
elsif a==b
puts "a = b"
else
puts "a > b"
end
end

ma nonostante un metodo possa prevedere più di un parametro in ingresso può ritornare un solo
oggetto. Tuttavia, nel caso sentissimo la necessità di tornare più di un oggetto in uscita possiamo
applicare un piccolo trucco: ritornare un array di oggetti, e usare l’assegnazione multipla:

def ritorno_multiplo
[1, ’ciao’, [1, 2]]
end

numero, stringa, array = ritorno_multiplo

numero ora vale 1, stringa vale ’ciao’ e array vale [1, 2].

2.16 Semplici operazioni con file di testo

2.16.1 lettura di un file

In fase di lettura tutto il file viene caricato come stringa unica, sta poi all’utente recuperare le
varie righe.
Il separatore di riga è sempre ritorno a capo, carattere speciale “\n“, quindi basta uno split, o,
più semplicemente, each line.

contenuto = File.read ’prova.txt’


# ora leggo le righe con uno split: (righe sara‘ un array)
righe = contenuto.split("\n")
# oppure con each_line:
contenuto.each_line do |riga|
...
end

19
2.16.2 scrittura di un file

La scrittura di un file di testo è leggermente più complicata, in quanto richiede un blocco di codice:

File.open ’prova123.txt’, ’w’ do |f|


f.write "Ciao, sono la riga 1\n" # ricordiamoci il ritorno a capo!
f.write "Ciao, sono la riga 2"
end

2.16.3 lavorare con il filesystem

Vediamo ora alcune funzioni utili per poter lavorare con il filesystem:

• directory listing (con array)

files_txt_directory_corrente=Dir[’*.txt’]
files_immagine_ricorsivi=Dir[’**/*.{JPG,jpg}’]

• spostamento in una directory

Dir.chdir ’/home/stefano/prove_ruby’

• rinominare un file

File.rename ’vecchio_file.txt’, ’nuovo_file.txt’

• cambiare i permessi di un file (solo Unix)

File.chmod(0644, "testfile.txt")

• creazione di una directory

Dir.mkdir ’dir_1’
Dir.mkdir ’dir_2’, 0644

• directory corrente

current_directory=Dir.pwd

• copia di un file

File.copy ’file_sorgente.txt’, ’file_destinazione.txt’

per altri metodi vedere

http://ruby-doc.org/core/classes/File.html
http://ruby-doc.org/core/classes/Dir.html

20
2.17 Altri oggetti utili
Vediamo ora altri oggetti che possono essere utili:
come introduzione è necessario sapere che per avere una nuova istanza di un oggetto bisogna
chiamare la classe stessa con il metodo new.
Finora non è stato visto perchè ruby introduce delle semplificazioni nei tipi di dato nativi,
ad esempio

stringa=""
array=[]

è equivalente a

stringa=String.new
array=Array.new

2.17.1 La classe ’Time’

Rappresenta una data comprensiva di giorno, mese, anno e orario:

adesso=Time.new
adesso_piu_un_minuto=adesso+60
puts adesso #=> Tue Sep 09 11:54:45 CEST 2008
puts adesso_piu_un_minuto #=> Tue Sep 09 11:55:45 CEST 2008

praticamente con l’operatore + vengono sommati i secondi.


Se si vuole creare un oggetto Time per uno specifico momento si può usare mktime:

puts Time.mktime(2000, 1, 1) #=> Sat Jan 01 00:00:00 CET 2000


puts Time.mktime(1986, 11, 21, 18, 30) #=> Fri Nov 21 18:30:00 CET 1986

ovviamente un oggetto Time ha tutta una serie di metodi di utilità; vediamone brevemente alcuni
con un esempio:

adesso=Time.new #=> Tue Sep 09 11:54:45 CEST 2008


giorno=adesso.day #=> 9
mese=adesso.month #=> 9
anno=adesso.year #=> 2008

Come ogni classe, l’elenco completo dei metodi è disponibile a

http://ruby-doc.org/core/classes/Time.html

21
2.17.2 La classe ’Hash’

È disponibile anche qui, come array, string, ... un costrutture semplificato,

hash=Hash.new

è infatti equivalente a

hash={}

Un hash è molto simile ad un’array, con la differenza che gli indici possono essere degli oggetti.
Vediamola molto simile ad un dizionario, in cui ad una parola corrisponde una definizione.
ad esempio:

hash={}
hash[’mirko’]=’porseo’
hash[’stefano’]=’nerd’

Anche come metodi è molto simile ad un array:

http://ruby-doc.org/core/classes/Hash.html

22
Capitolo 3

Programmazione orientata agli


oggetti

3.1 Definizione di classe e oggetto


Un oggetto è una “scatola”, con le proprie caratteristiche e i propri comportamenti;
ad esempio, un router ha marca e modello, e fa’ determinate cose (connettersi, disconnettersi, ...).
Questo può essere visto come un “oggetto”
Informaticamente parlando, per poter creare un tipo di oggetto occorre definire una classe che ne
definisce i metodi e gli attributi.
Diminuendo il grado di astrazione e tornando alla programmazione vera e propria possiamo dire che
un oggetto può essere visto come un contenitore di dati (che a questo punto possiamo identificare
con delle semplici variabili) dotato di una serie di funzioni (i metodi) che definiscono un’interfaccia
all’oggetto stesso (azioni da compiersi sull’oggetto).

3.2 Creazione di classi personalizzate

3.2.1 costruzione di una classe

Vediamo subito come è implementata in Ruby la programmazione orientata agli oggetti. Scriviamo
un semplice esempio supponendo di dover programmare un videogame di guerra dove tra le altre
cose sono presenti dei carri armati e dei camion per il trasporto dei soldati. Rappresentiamo questi
oggetti in Ruby:

class CarroArmato
def initialize
puts "Sono un nuovo carro armato"
end
end

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

ca = CarroArmato.new #=> Sono un nuovo carro armato


c = Camion.new #=> Sono un nuovo camion

abbiamo quindi capito che la definizione di una classe inizia con la parola chiave class e termina
con end; initialize è il metodo che viene invocato in fase di creazione di un oggetto (viene
chiamato costruttore).
In questo caso, molto banalmente, il costruttore stampa a video l’avvenuta creazione dell’oggetto.
Vediamo ora invece come sia possibile passare dei dati al costruttore; supponiamo quindi di vo-
ler definire alcuni dati caratteristici dell’oggetto (carroarmato I carburante e colpi; camion I
carburante e posti passeggeri):

class CarroArmato
def initialize (colpi, carburante)
@carburante = carburante
@colpi = colpi
end
end

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

3.2.2 variabili di istanza

Ma cosa sono quella specie di variabili il cui nome comincia per @?


Vengono chiamate variabili di instanza, e una volta inizializzate esse sono visibili a tutti i metodi
della classe, e verranno conservate per tutta l’esistenza del singolo oggetto.
capiremo poi il motivo della loro importanza.
Ora quindi possiamo creare un’istanza di carroarmato e una di camion:

ca = CarroArmato.new(10, 100)
c = Camion.new(20, 100)

le variabili di istanza @carburante, @posti, @colpi, ... vengono anche detti attributi della classe,
e definiscono i dati fondamentali su cui si basa la stessa.

24
Per accedere dall’esterno della classe a questi attributi dobbiamo definire degli appositi metodi;
ad esempio, per fare in modo di poter capire quanti posti sono presenti nel camion

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

def posti
@posti
end
end

da notare che il metodo posti si sarebbe anche potuto scrivere (come abbiamo già visto)

def posti
return @posti
end

(ovviamente sempre all’interno del blocco di codice che definisce la classe)


quindi, potremo

c = Camion.new(20, 100)
puts c.posti #=> 20

Abbiamo visto ora come accedere agli attributi di una classe in lettura; anche nel caso volessimo
accederci in scrittura è necessario definire un metodo;
rivediamo sempre la classe Camion, con la possibilità di modificare il numero di posti a disposizione:

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

def posti
@posti
end

def posti=(nuovi_posti)
@posti=nuovi_posti
end
end

il metodo per accedere in scrittura non è nient’altro che un semplicissimo metodo con un parametro
in ingresso. Riproviamo quindi ad eseguire:

25
c = Camion.new(20, 100)
puts c.posti #=> 20
c.posti=25
# le parentesi tonde () sono facoltative,
# scrivere c.posti=(25) e‘ uguale a c.posti=25
puts c.posti #=> 25

E qui ruby ci viene incontro ancora una volta...


Ipotizziamo voler avere 50 variabili che devono essere accessibili dall’esterno della classe sia in scrit-
tura che in lettura... dovremmo quindi scrivere per 50 volte i 2 metodi, variabile, e variabile=...
ruby ci viene in aiuto con una determinata parola chiave che ci permette di definire automatica-
mente questi due metodi per ogni variabile che vogliamo venga trattata:
vediamolo sempre con la classe Camion:

class Camion

attr_accessor :posti
attr_accessor :carburante

def initialize (posti, carburante)


@carburante = carburante
@posti = posti
end
end

è proprio la “parola” attr accessor che permette di definire automaticamente questi due metodi
per ogni variabile specificata.
Da notare che per ridurre ancora di più il codice scritto, invece di

attr_accessor :posti
attr_accessor :carburante

avremmo potuto scrivere

attr_accessor :posti, :carburante

ma... se volessimo che una variabile, chiamata sola lettura fosse accessibile in sola lettura, e
un’altra viariabile chiamata sola scrittura fosse accessibile in sola scrittura? Anche in questo
caso ruby ci viene in aiuto con due costrutti:

attr_reader :sola_lettura
attr_writer :sola_scrittura

da notare che, praticamente, scrivere

attr_accessor :variabile

26
corrisponde esattamente a scrivere

attr_reader :variabile
attr_writer :variabile

(ovviamente il primo caso è molto più pratico)


Vediamone ora un uso pratico delle variabili di istanza, che non sia il semplice reperimento/impostazione;
sempre riguardo la classe Camion:

class Camion

attr_reader :posti
attr_reader :carburante

def initialize (posti, carburante)


@carburante = carburante
@posti = posti
end

def rifornimento (quantita)


@carburante = @carburante + quantita
end

end

Vediamo che il metodo rifornimento va a modificare la variabile di instanza @carburante

c = Camion.new(20, 100)
puts c.posti #=> 20
puts c.carburante #=> 100
c.rifornimento 50
puts c.carburante #=> 150

Ricapitolando un attimo sulle variabili, abbiamo ora capito che ci sono 2 ambiti di visibilità delle
stesse:
le variabili che iniziano per @ sono visibili a tutta la classe in cui sono definite,
mentre le variabili senza @ sono visibili solo all’interno dei metodi in cui sono definite.

3.2.3 ereditarietà

Ora però, pensandoci bene, entrambi i due mezzi (camion e carroarmato) sono dei veicoli, e come
tale hanno delle cose in comune (ad esempio il carburante);
è perciò possibile definire una “macro-classe” che li contiene entrambi, quindi possiamo scrivere

27
class Veicolo
attr_reader :carburante

def initialize (carburante)


@carburante = carburante
end

def rifornimento (quantita)


@carburante += quantita
end
end

e poi far “discendere” camion e carroarmato direttamente da veicolo, in modo che ne ereditino le
proprietà:

class CarroArmato < Veicolo


attr_reader :colpi

def initialize (carburante, colpi)


super(carburante)
@colpi = colpi
end
end

class Camion < Veicolo


attr_reader :posti

def initialize (carburante, posti)


super(carburante)
@posti = posti
end
end

Il simbolo < sta per discende da, o eredita da,


mentre il metodo super serve per chiamare il metodo con lo stesso nome ma della classe genitore.
In questo caso, super richiama initialize del genitore.
Ovviamente questo ci fa risparimare un bel po’ di codice scritto, e ci può semplificare di molto
la vita... Ipotizziamo di avere 100 classi, e tutte queste classi hanno un metodo uguale. Dovendo
modificare la logica di quel metodo, avremo bisogno di andare a modificare ognuna delle 100 classi
precedentemente definite. Utilizzando l’ereditarietà invece, la modifica necessaria si riduce ad una
sola classe.

3.3 Modifica di classi esistenti


A volte, una cosa utile può essere modificare una classe esistente.

28
Supponiamo di avere un array di numeri, di cui calcolare la somma, oppure la media.
Certo con un each si può fare tutto, ma se il numero di array aumenta bisognerebbe scrivere un
each per ogni array...
ci farebbe molto comodo se per ogni oggetto array esistessero i metodi sum e avg... in ruby però
di default non esistono.
Possiamo però crearli noi andando a modificare al volo la classe Array! Basta semplicemente:

class Array
def sum
s=0
self.each do |x|
s=s+x
end
s
end
def avg
s=sum.to_f
l=self.size.to_f
s/l
end
end

somma=[1, 2, 3, 4, 5].sum #=> 15


media=[1, 2, 3, 4, 5].avg #=> 3

3.4 Gestione degli errori - Eccezioni


Un programma in esecuzione può passare attraverso vari problemi inaspettati. Un file che vorrebbe
leggere potrebbe non esistere; il disco potrebbe essere pieno quando desidera salvare dei dati;
l’utente potrebbe fornirgli dei dati inaccettabili.
Un programma robusto gestirà tali situazioni sensibilmente e in maniera elegante.
In ruby, come in molti linguaggi moderni, possiamo gestire le eccezioni per blocchi di codice in
modo compartimentato, ottenendo un risultato sorprendentemente efficiente ma senza caricare in
modo eccessivo il programmatore o chiunque tenti di leggere il codice in seguito. Il blocco di
codice segnalato con begin viene eseguito finchè non c’è un’eccezione, che causa il trasferimento
del controllo ad un blocco di gestione dell’eccezione, che viene segnalato da rescue. Se non c’è un
eccezione, il codice rescue non viene usato. Il metodo seguente ad esempio restituisce la prima
linea di un file di testo, o nil se c’è un eccezione:

def prima_linea(filename)
begin
file = open("un_file")
info = file.gets
file.close

29
info
rescue
nil
end
end

È necessario sapere che anche un’eccezione di per se è un oggetto come un altro (discendente di
Exception), ed essendo tale è perciò possibile distinguere il tipo di errore sollevato, e agire di
conseguenza.
Potremo quindi voler scrivere

begin
...codice...
rescue TipoDiEccezione1
...azione1...
rescue TipoDiEccezione2
...azione2...
end

ed è anche possibile recuperare e stampare a video in maniera più gradevole il tipo di errore:

begin
...codice...
rescue Exception => e
puts e.message
end

Volendo potremmo sollevare un’eccezione da un punto qualsiasi del nostro programma, ad esempio:

def inverse(x)
raise ArgumentError, ’Argument is not numeric’ unless x.is_a? Numeric
1.0 / x
end

La gerarchia delle eccezioni è questa:

• Exception

– NoMemoryError
– ScriptError
∗ LoadError
∗ NotImplementedError
∗ SyntaxError
– SignalException
∗ Interrupt

30
– StandardError
∗ ArgumentError
∗ IOError
· EOFError
∗ IndexError
∗ LocalJumpError
∗ NameError
· NoMethodError
∗ RangeError
· FloatDomainError
∗ RegexpError
∗ RuntimeError
∗ SecurityError
∗ SystemCallError
∗ SystemStackError
∗ ThreadError
∗ TypeError
∗ ZeroDivisionError
– SystemExit

31
Capitolo 4

Uso di librerie particolari

4.1 Gemme
Le librerie di ruby come potrebbero chiamarsi se non, per l’appunto, gemme?
Alcune gemme (le più usate) sono già incluse nell’installazione standard di ruby, mentre per altre
è necessaria l’installazione manuale. Per questo ci viene in aiuto il comando gem.
Ad esempio

~# gem install mysql

installa la gemma per accedere a database mysql.

4.2 Lavorare con indirizzi IP


Esiste una classe molto comoda che ci permette di trattare indirizzi IP:

# Include la libreria ’ipaddr’


require ’ipaddr’

# 1- indirizzo in notazione decimale puntata


IPAddr.new(’192.168.1.1’)

# 2- indirizzo + netmask in notazione decimale puntata


IPAddr.new(’192.168.1.1/255.255.255.255’)

# 3- indirizzo in notazione decimale puntata + CIDR


IPAddr.new(’192.168.1.1/32’)

# 4- verifica se un indirizzo appartiene a una determinata sottorete


network=IPAddr.new(’192.168.0.0/23’) # <IPAddr: IPv4:192.168.0.0/255.255.254.0>

32
network.include?(IPAddr.new(’192.168.0.23’)) #=> true
network.include?(IPAddr.new(’192.168.1.1’)) #=> true
network.include?(IPAddr.new(’192.168.10.1’)) #=> false
network.include?(IPAddr.new(’172.16.100.2’)) #=> false

4.3 YAML
YAML1 è un formato che permette di salvare come stringhe/file di testo degli oggetti ruby, insom-
ma, una specie di XML.
Vediamo un esempio rapido:

# inclusione della libreria YAML


require ’yaml’

test_array = [’Elemento 1’ ,
’Elemento 2’ ,
’Elemento 3’ ]

test_string = test_array.to_yaml

filename = ’output_yaml.txt’

File.open filename, ’w’ do |f|


f.write test_string
end

read_string = File.read filename

read_array = YAML::load read_string

puts(read_string == test_string) #=> true


puts(read_array == test_array ) #=> true

4.4 Inviare mail via SMTP


Utilizzando la libreria net/smtp è molto facile inviare una e-mail:

require ’net/smtp’
user_from = "stefano@gnustile.local"
user_to = "utente@dominio.com"
email_body = "From: stefano@gnustile.local\n"
email_body = email_body + "Subject: Ciao\n\nEmail da Ruby.\n\n"
1 http://en.wikipedia.org/wiki/YAML

33
# gestione delle eccezioni
begin
Net::SMTP.start(’my.smtp.server’, 25,) do |smtpclient|
smtpclient.send_message(email_body, user_from, user_to)
end
rescue Exception => e # rescue e‘ utilizzato per gestire le eccezioni
print "Eccezione verificata: " + e
end

4.5 Accesso a database MySQL


È necessaria la gemma mysql
Vediamone l’uso per esempi:

# libreria mysql
require ’mysql’

# stabilisce connessione con il server


con=Mysql.new(’localhost’, ’username’, ’password’, ’database_name’)

# query senza risultati di ritorno:


con.query("CREATE TABLE prova (
name VARCHAR(40),
email VARCHAR(40)
)")

con.query("DROP TABLE IF EXISTS prova1234")

con.query("INSERT INTO prova (name, email) VALUES


(’tizio’, ’tizio@tizio.com’),
(’caio’, ’caio@caio.net’),
(’sempronio’, ’sempro@sempry.it’)
)" )
numero_righe_inserite=con.affected_rows # => 3

# certo che pero‘ effettuare inserimenti cosi‘


# o interpolando stringhe in una query
# puo‘ risultare pericoloso...

# meglio inserire i caratteri di escape...


email=con.escape_string("sempronio@sempry.it")
con.query("UPDATE prova SET email = ’#{email}’ WHERE name = ’sempronio’")
numero_righe_inserite=con.affected_rows # => 1

34
# query con record di ritorno (SELECT)
rs=con.query(’select * from prova’)
numero_di_righe=rs.num_rows
# ora abbiamo due modi per scorrere un insieme di record (recordset):

# 1) ciclo while
while row = rs.fetch_hash do
puts "Nome: " + row[’name’]
puts "E-Mail: " + row[’email’]
puts "------"
end

# 2) each
rs.each_hash do |row|
puts "Nome: " + row[’name’]
puts "E-Mail: " + row[’email’]
puts "------"
end

# 3) each, includento nel come del campo anche il nome della tabella
rs.each_hash(with_table=true) do |row|
puts "Nome: " + row[’prova.name’]
puts "E-Mail: " + row[’prova.email’]
puts "------"
end

# liberiamo la ram utilizzata dal recordset


rs.free

# e ora chiudiamo la connessione


con.close

4.6 Accesso a database SQLite


È necessaria la gemma sqlite3
Un database SQLite è molto più facile da manipolare che non MySQL:

require ’sqlite3’
db = SQLite3::Database.new("book.db")

db.execute("SELECT * FROM libro") do |riga|


puts "#{riga.titolo} - #{riga.autore}"
end

35
# se invece siamo interessati solo alla prima riga:
prima_riga = db.get_first_row( "SELECT * FROM libro" )

db.close

4.7 Accesso a database KirbyBase


KirbyBase è un DBMS molto leggero e interamente scritto in Ruby. I dati vengono salvati su
file di testo e possono essere eventualmente modificati a mano, è possibile usarlo sia in modalità
embedded sia in modalità client/server. Può essere visto come una via di mezzo tra i file di testo
semplice e i database di tipo SQL.
Partiamo con la creazione di un database e di una tabella:

# richiesta la libreria base delle gemme


require ’rubygems’

# libreria kirbybase
require ’kirbybase’

db = KirbyBase.new

book = db.create_table(:libro,
:titolo, :String,
:autore, :String,
:anno, :Integer
)

KirbyBase.new crea un database nella directory corrente, se però volessimo utilizzare un’altra
directory per immagazzinare i dati sarebbe sufficiente dare

db = KirbyBase.new(:local, nil, nil, ’./data’)

Il metodo create table prende come argomenti il nome della tabella e il nome e il tipo di ogni
colonna. I tipi disponibili sono: String, Integer, Float, Boolean, Time, Date, DateTime, Memo,
Blob, e YAML. Per ogni campo è possibile specificare anche alcune proprietà come il valore di
default, l’obbligatorietà del campo, l’indicizzazione e riferimenti ad altri record in altre tabella.
E’ anche possibile cifrare la tabella, utilizzando

book = db.create_table(:libro, ...) do |t|


t.encrypt = true
end

Per popolare la nostra tabella ora si usa il metodo insert:

36
book.insert("Reduce","Giovanni Lindo Ferretti",2006)
#=> 1
book.insert("Il bosco degli urogalli","Mario Rigoni Stern",1962)
#=> 2

(il numero ritornato è il valore della chiave primaria)


in alternativa è possibile usare dei blocchi di codice:

book.insert do |l|
l.titolo = "Memorie dal sottosuolo"
l.autore = "Fedor Dostoevskij"
l.anno = 1864
end
#=> 3

se ora volessimo ricavare tutto il contenuto della tabella basta utilizzare il metodo select senza
nessun parametro:

ris = book.select
#=> [#<struct #<Class:0xb7a98f14> recno=1, titolo="Reduce", autore="Giovanni Lindo Ferretti", ann

ris è un oggetto di tipo KBResultSet.


Se invece si vogliono visualizzare solo alcune colonne basta specificarle come parametro:

ris_titolo = book.select(:titolo)
#=> [#<struct #<Class:0xb79ac8d0> titolo="Reduce">, #<struct #<Class:0xb79ac8d0> titolo="Il bosco

Per meglio definire la ricerca è possibile usare dei blocchi di codice:

ris_duemila = book.select(:titolo) do |l|


l.anno > 2000
end
#=> [#<struct #<Class:0xb79a6638> titolo="Reduce">]

I primi passi che abbiamo visto sono necessari a creare la tabella; se invece la nostra tabella esiste
già, e vogliamo soltanto un riferimento che punta ad essa:

tabella = db.get_table(:libro)

Per i vari dettagli e altre operazioni rimando a

http://www.netpromi.com/files/kirbybase ruby manual.html

4.8 ActiveRecord
**** DA FARE ****

37
4.9 File XML
Vedremo ora come utilizzare due diverse librerie per trattare file XML, capiremo come leggerli e
come costruirli.
Le librerie si chiamano xmlsimple e rexml. La prima permette di convertire un file xml in un
hash e viceversa. La seconda invece va a lavorare direttamente con la struttura DOM2 del file.

4.9.1 xmlsimple

È richiesta la gemma xml-simple.

lettura di un file

require ’rubygems’
require ’xmlsimple’

xmlfile = File.new("cd.xml")
doc = XmlSimple.xml_in xmlfile

Come detto prima, doc sarà un semplice oggetto di tipo Hash.

creazione di un file

require ’rubygems’
require ’xmlsimple’

miohash={}
miohash[’root’]={}

miohash[’root’][’dir1’]="Prova 123"
miohash[’root’][’dir2’]="Prova 124"

miohash[’root’][’k’]=[]
miohash[’root’][’k’][0]={"ciao"=>"bao1"}
miohash[’root’][’k’][1]={"ciao"=>"bao2"}

doc = XmlSimple.xml_out miohash

doc sarà una stringa contenente il nostro codice xml, che in questo caso sarà:

<opt>
<root dir1="Prova 123" dir2="Prova 124">
<k ciao="bao1" />
<k ciao="bao2" />
2 DOM: Document Object Model

38
</root>
</opt>

4.9.2 rexml

Questa libreria offre un trattamento più avanzato rispetto a xmlsimple.

lettura di un file

Ipotizziamo un file di esempio, cd.xml cosı̀ formato:

<negozio nome="CD House">


<genere nome="Stoner">
<cd asin="B00000JBDE">
<titolo>Frequencies From Planet Ten</titolo>
<autore>Orange Goblin</autore>
<anno>1997</anno>
</cd>
<cd asin="B000WM72FC">
<titolo>Witchcult Today</titolo>
<autore>Electric Wizard</autore>
<anno>2007</anno>
</cd>
</genere>
<genere nome="Colonne sonore">
<cd asin="B00005O6PA">
<titolo>Amelie</titolo>
<autore>Yann Tiersen</autore>
<anno>2001</anno>
</cd>
</genere>
</negozio>

per leggerlo avremo bisogno di un programma simile al seguente:

require ’rexml/document’

xmlfile = File.new("cd.xml")
doc = REXML::Document.new(xmlfile)

doc.root.each_element do |genere|
puts genere.attributes["nome"]
genere.each_element do |cd|
cd.each_element do |val|
puts " #{val.name}: #{val.text}"

39
end
end
end

creazione di un file

require ’rexml/document’

# creiamo ora un documento xml, con tag radice <doc/>


doc=REXML::Document.new("<doc/>")

# creiamo ora un elemento example:


el = REXML::Element.new "example"
# con il testo "esempio 1"
el.text="esempio 1"
# e un attributo data
el.attributes["data"]="20081001"

# e lo aggiungiamo alla radice:


rt = doc.root
rt << el

# vediamo il risultato
puts doc.to_s #=> <doc><example data=’20081001’>esempio 1</example></doc>

*** DA SISTEMARE ***

4.10 Networking a basso livello


In Ruby il networking a basso livello è gestito dalla libreria standard socket che è strutturata
nel seguente modo: alla base c’è BasicSocket classe astratta, sottoclasse di IO, che contiene
alcuni metodi fondamentali, ereditati da tutte le sue sottoclassi, come ad esempio close read,
close write, getpeername, getsockname, recv e send.
Da BasicSocket discendono direttamente IPSocket, Socket e UNIXSocket. IPSocket è la classe
che implementa le socket che usano il protocollo di trasporto IP e ha due sottoclassi: TCPSocket
e UDPSocket che rispettivamente trattano connessioni da, e verso, socket TCP e UDP. La classe
Socket fornisce invece direttamente accesso all’implementazione delle socket del sistema operativo.
UnixSocket infine gestisce le comunicazioni IPC utilizzando lo UNIX domain protocol.
Tutte le classi di socket discendono indirettamente dalla classe IO, questo vuol dire che è possibile
usare i metodi di IO sui socket cosı̀ come accade ad esempio per i file.

40
socket UDP

Iniziamo con un semplice esempio client-server, in cui il server invierà al client che si connetterà
ora e data corrente.
Server:

require "socket"

server = UDPSocket.open
server.bind(nil, 12345)

loop do
data, sender = server.recvfrom(1)
chost = sender[3]
cport = sender[1]

retstring = "Data corrente: #{Time.new.to_s}\n"


puts "Request from #{chost}:#{cport}"

server.send(retstring, 0, chost, cport)


end

Innanzitutto creiamo una nuova istanza di UDPSocket, e al mettiamo in ascolto sulla porta 12345
(ovviamente UDP).
Il loop principale non fa altro che rimanere in attesa di una connessione, e quando avviene invia
il messaggio con la data
Client:

require "socket"

client = UDPSocket.open
client.connect(’localhost’, 12345)

client.send("", 0)

while msg=client.gets
puts msg
end

In questo caso creiamo una connessione verso localhost, e inviamo una stringa vuota al server
(avremo potuto inviare qualsiasi cosa, tanto al nostro server non importa nulla). Infine ci mettiamo
in ascolto per la risposta da parte del server e la stampiamo a video.

socket TCP

Analogamente, un server e un client basati su TCP saranno:

41
Server:

require "socket"

server = TCPServer.open(’localhost’, 12345)

while session = server.accept


msg = "Data corrente: #{Time.new.to_s}\n"
session.puts msg
session.close
end

Client:

require "socket"

client = TCPSocket.open(’localhost’, 12345)

while msg=client.gets
puts msg
end

client.close

Vediamo che, grazie alle proprietà del protocollo, server e client TCP sono più semplici di quelli
UDP.

Un piccolo server Web

I server web si basano sul protocollo TCP; vediamo ora come scrivere in pochissime riche di codice
un web-server minimale che ad ogni richiesta risponde con data e ora corrente; ovviamente la
risposta dovrà essere conforme alle specifiche del protocollo HTTP:

require ’socket’

server = TCPServer.new(’localhost’, 9090)

while (session = server.accept)


puts "Request: #{session.gets}"
session.print "HTTP/1.1 200/OK\r\nContent-type: text/html\r\n\r\n"
session.print "<html><body><p>#{Time.new.to_s}</p></body></html>\r\n"
session.close
end

Proviamo ora a puntare il nostro browser a

http://localhost:9090/

42
Un server Web un po’ più complesso

richiede la gemma mime-types


Questo server web ridotto all’osso server solo contenuti statici. Niente contenuti dinamici.

require ’rubygems’
require ’socket’
require ’mime/types’

class HttpServer
def initialize(session, request, basePath)
@session = session
@request = request
@basePath = basePath
end

def getFullPath()
fileName = nil
if @request =~ /GET .* HTTP.*/
fileName = @request.gsub(/GET /, ’’).gsub(/ HTTP.*/, ’’)
end
fileName = fileName.strip
unless fileName == nil
fileName = @basePath + fileName
fileName = File.expand_path(fileName, @defaultPath)
end
fileName << "/index.html" if File.directory?(fileName)
return fileName
end

def serve()
@fullPath = getFullPath()
src = nil
begin
if File.exist?(@fullPath) and File.file?(@fullPath)
if @fullPath.index(@basePath) == 0 #path should start with base path
contentType = getContentType(@fullPath)
@session.print "HTTP/1.1 200/OK\r\n"
@session.print "Server: MySecondRubyWebServer 1.0\r\n"
@session.print "Content-type: #{contentType}\r\n\r\n"
src = File.open(@fullPath, "rb")
while (not src.eof?)
buffer = src.read(256)
@session.write(buffer)
end

43
src.close
src = nil
else
# should have sent a 403 Forbidden access but
# then the attacker knows that such a file exists
@session.print "HTTP/1.1 404/Object Not Found\r\n"
@session.print "Server: MySecondRubyWebServer 1.0\r\n\r\n"
end
else
@session.print "HTTP/1.1 404/Object Not Found\r\n"
@session.print "Server: MySecondRubyWebServer 1.0\r\n\r\n"
end
ensure
src.close unless src == nil
@session.close
end
end

def getContentType(path)
types= MIME::Types.type_for(path)
if types.size == 0
return "text/html"
else
return types[0].content_type
end
end
end

basePath = "/home/stefano/Manoscritti/Imparando_Ruby/temp/"
server = TCPServer.new(’localhost’, 9090)

loop do
session = server.accept
request = session.gets

Thread.start(session, request) do |session, request|


HttpServer.new(session, request, basePath).serve()
end
end

Un server Web ancora più complesso

Perchè complicarsi la vita a scrivere ancora più codice? I web server di prima erano a scopo
puramente didattico, ma quando vogliamo qualcosa di più perchè non appoggiarsi a prodotti

44
stabili e testati?
Vediamo ora come usare webrick, un web server piuttosto complesso, nei nostri programmi ruby.
In ogni caso questo listato è solo a titolo di esempio, affronteremo la programmazione web nel
dettaglio nei capitoli successivi.
richiede la gemma webrick

require ’webrick’

s = WEBrick::HTTPServer.new(
:Port => 2000,
:DocumentRoot => Dir.pwd + "/htdocs"
)

## mount sotto-directory
s.mount("/d1", WEBrick::HTTPServlet::FileHandler, "/srv/dati/dir1/public_html")
s.mount("/~stefano",
WEBrick::HTTPServlet::FileHandler, "/home/stefano/public_html",
true) #<= permette directory index

## mount di una "servlet"


class HelloServlet < WEBrick::HTTPServlet::AbstractServlet
def do_GET(req, res)
res.body = "<HTML>hello, world.</HTML>"
res[’Content-Type’] = "text/html"
end
end
s.mount("/hello", HelloServlet)

trap("INT"){ s.shutdown }
s.start

Rimando qui per ulteriori dettagli su come estendere webrick con delle “servlet”:

http://segment7.net/projects/ruby/WEBrick/servlets.html
http://microjet.ath.cx/WebWiki/WEBrick.html

Volendo potremo anche usare Apache e mod ruby, ma lo vedremo meglio più avanti...

45
Parte II

Programmazione web di base

46
Capitolo 5

CGI

5.1 Introduzione
**** i metodi http, e il passaggio di parametri ****

http://openskill.info/infobox.php?ID=357
http://it.wikipedia.org/wiki/HTTP

5.2 CGI in ruby


L’approccio tradizionale alla programmazione Web è sicuramente costituito dai CGI. Un program-
ma CGI sostanzialmente è un programma che viene eseguito dal webserver, e l’output viene inviato
al client. In ruby i CGI sono gestiti dalla libreria cgi che fornisce tutti gli strumenti necessari.
Cominciamo con un primo CGI, ciao.rb, che deve essere posizionato nella directory cgi-bin del
nostro web server (e ovviamente deve essere reso eseguibile):

#!/usr/bin/env ruby

# un programma CGI deve sempre dire quale è il formato


# del contenuto che manda in output+una linea vuota

# contenuto html - tipo MIME text/html


puts "Content-type: text/html"
# linea vuota
puts ""

# stampa del codice html


puts "<html>"
puts "<head>"
puts "<title>ciao</title>"

47
puts "</head>"
puts "<body>"
puts "<h2>ciao mondo</h2>"
puts "</body>"
puts "</html>"

Andiamo quindi a

http://localhost/cgi-bin/ciao.rb

per vedere il risultato.


L’input di programmi cgi non è però come il classico standard-input...
i dati in ingresso vengono dati formattati dal browser in modo particolare; per questo per leggerli
ci viene in aiuto la libreria ’cgi’: (ciao2.rb)

#!/usr/bin/env ruby

require ’cgi’
cgi = CGI.new

#un aiuto al puts ce lo da’ sempre il modulo cgi


#invece di puts "Content-type: text/html"
puts cgi.header("type"=>"text/html")

# stampa del codice html


puts "<html>"
puts "<head>"
puts "<title>ciao</title>"
puts "</head>"
puts "<body>"
puts "<h2>ciao #{cgi[’nome’]}</h2>"
puts "</body>"
puts "</html>"

Andiamo quindi a

http://localhost/cgi-bin/ciao2.rb?nome=Stefano

per vedere il risultato. In questo caso nome è un parametro in ingresso con il metodo HTTP GET,
ma anche se fosse stato inviato con POST la sua lettura sarebbe stata uguale.

5.3 CGI + ERB


Certo che però è scomodo dover copiare/scrivere codice html direttamente immerso nel codice
ruby...
Un grande aiuto ce lo danno i sistemi di template, come ad esempio ERB (embedded-ruby)

48
proviamo cosı̀:
ciao erb.rb:

#!/usr/bin/env ruby

require ’erb’
require ’cgi’

cgi = CGI.new

puts cgi.header("type"=>"text/html")

input = File.read(’ciao_erb.erb’)

# carichiamo l’oggetto erb


eruby = ERB.new(input)

# qualche variabile tanto per provare


list = [’aaa’, ’bbb’, ’ccc’, ’ddd’]
name = ’Bla Bla Bla’

# Print the result of the two files.


puts eruby.result(binding())

e nella stessa cartella creiamo il file ciao erb.erb:

<html>
<head>
<title>Prova ERB</title>
</head>

<body>

Name: <%= name %>


<ul>
<% for item in list %>
<li><%= item %></li>
<% end %>
<%# questo è un commento %>
</ul>
</body>
</html>

Ecco, cosı̀ è decisamente molto più leggibile :-)


Come possiamo vedere, in un template erb il codice ruby è delimitato da <% e %>. Un po’ come
PHP e ASP, insomma.

49
5.4 mod ruby
Si può dire che ormai, in applicazioni web di una certa complessità, CGI è veramente poco
performate, perchè ad ogni richiesta il web server deve eseguire tutti il programma CGI.
Per questo ci vengono in aiuto alcuni componenti direttamente integrati nei web server, nel caso
del popolare Apache parleremo di mod ruby.
È sufficiente installare mod ruby e inserire nella configurazione di apache

LoadModule ruby_module lib/httpd/modules/mod_ruby.so

<IfModule mod_ruby.c>
RubyRequire apache/ruby-run

<Location /ruby>
SetHandler ruby-object
RubyHandler Apache::RubyRun.instance
</Location>

<Files *.rbx>
SetHandler ruby-object
RubyHandler Apache::RubyRun.instance
</Files>

</IfModule>

Per far funzionare i nostri script ruby (che devono avere estensione .rbx) con performance deci-
samente migliori.

50
Parte III

Ruby on Rails

51
**** DA FARE ****

52
Appendice A

Esercizi e soluzioni

A.1 Semplici operazioni con stringhe

A.1.1 Esercizio 1

Chiedere all’utente una stringa, verificare se è o meno palindroma.

#!/usr/bin/env ruby

puts "Inserisci una stringa, verificherò se è palindroma..."


# prendo una stringa dallo standard input, elimino il caporiga e
# la faccio diventare maiuscola (o minuscola)
# Il passo di farla diventare maiuscola/minuscola è fondamentale,
# altrimenti il programma non saprebbe riconoscere che la parola
# ’Anna’ è palindroma. (’Anna’ è diverso da ’annA’)
s1=gets.chomp.upcase
# ora creo una stringa invertendo i caratteri di quella di partenza
s2=s1.reverse
# e quindi faccio il confronto
if s1==s2 then
puts "La stringa è palindroma"
else
puts "La stringa NON è palindroma"
end

A.2 Metodi

A.2.1 Esercizio 1

Chiedere, per tre volte, due numeri all’utente, e per ogni richiesta dire se il primo numero è
divisibile per il secondo o meno. (definire il metodo divisibile che accetta due parametri numerici

53
e ritorna true nel caso il primo numero sia divisibile per il secondo)

#!/usr/bin/env ruby

def divisibile(a,b)
return true if a%b==0
false

#scrittura compatta (al posto di tutte le righe precedenti):


#a%b==0
end

3.times do
puts "Inserisci a:"
a=gets.chomp.to_i
puts "Inserisci b:"
b=gets.chomp.to_i
if divisibile(a,b)
puts "#{a} è divisibile per #{b}"
else
puts "#{a} NON è divisibile per #{b}"
end
end

A.2.2 Esercizio 2

Scrivere il metodo is prime(x) che restituisce true se x è primo, false altrimenti. Testare poi il
metodo chiedendo a un utente un numero, e verificandolo con la funzione.

#!/usr/bin/env ruby

def is_prime(x)
# 0 o negativi: non posso verificare se primo o meno, ritorno false...
return false if x < 1
# casi base: 1 e 2
return true if x==1 or x==2
# altri casi
pr=true
i=2
while i < x and pr
pr=false if x%i==0
i=i+1
end
pr
end

54
puts "Inserisci un numero, verificherò se è primo."
n=gets.chomp.to_i
if is_prime n
puts "#{n} è un numero primo"
else
puts "#{n} NON è un numero primo (oppure non posso verificare se è primo o meno)"
end

A.3 Operazioni con Array

A.3.1 Esercizio 1

Scrivere un programma che chiede all’utente quanti voti ha collezionato; chiedere i vari voti e poi
trovare: massimo, minimo, media.

#!/usr/bin/env ruby

# inizializza l’array di voti


voti=[]

puts "Inserisci numero di voti:"

num=gets.chomp.to_i

# riempie l’array
num.times do |k|
n=k+1
puts "Inserisci voto numero #{n}:"
voti << gets.chomp.to_f
end

# calcola
somma=0.0
voti.each do |x|
somma=somma+x
end
media=somma/num
min=voti.min
max=voti.max

puts "Voto minimo: #{min}"


puts "Voto massimo: #{max}"
puts "Media: #{media}"

55
A.3.2 Esercizio 2

Dato un array cercare un elemento al suo interno senza usare i metodi di ricerca interni ad Array.
Usare un metodo che restituisce l’indice della prima ricorrenza se l’elemento è presente, -1 in caso
contrario.

#!/usr/bin/env ruby

def cerca_in_array(array, numero)


# r è il contatore della posizione in cui sono
r=0
array.each do |e|
# ritorno la posizione in caso l’elemento venga trovato
return r if e==numero
r=r+1
end
# ritorna -1 in caso non venga trovato
-1
end

# Inizializzo l’array
ar=[1, 2, 5, 6, 8, 12, 34, 56, 87]
ar_s=ar.join(",")

puts "Ecco l’array: #{ar_s}"


puts "Inserisci il numero da cercare:"
n=gets.chomp.to_i

posizione=cerca_in_array(ar, n)
if posizione==-1
puts "Numero non trovato."
else
puts "Il numero che cerchi è in posizione #{posizione}"
end

A.3.3 Esercizio 3

Con il metodo del crivello di eratostene trovare tutti i numeri primi da 1 a 100. Consiglio: vedere
il crivello come un array in cui l’indice di un elemento corrisponde al numero, il valore in posizione
k vale false se il numero è stato eliminato.

#!/usr/bin/env ruby

56
# numeri primi da 1 a 100 (Z)
Z=100
# secondo l’algoritmo trovo la radice quadrata del numero a cui arrivare
META=Math.sqrt(Z).to_i

# inizializzo l’array con tutti i valori a true


criv=[]
for i in (1..Z) do
criv[i]=true
end

# crivello vero e proprio


for i in (2..META) do
b=i+1
for k in (b..Z) do
criv[k]=false if k%i==0
end
end

puts "Ecco i numeri primi:"


for i in (1..Z) do
puts i if criv[i]
end

A.4 Operazioni con Hash

A.4.1 Esercizio 1

Dato un hash cercare la chiave relativa ad un suo elemento senza usare i metodi di ricerca interni
ad Hash. Usare un metodo che restituisce la chiave della prima ricorrenza se l’elemento è presente,
nil in caso contrario.

#!/usr/bin/env ruby

def find_in_hash(hash, el)


# in questo caso tra || uso due variabili. Questo perchè è definito cosı̀
# dal metodo .each_pair di hash. Vedere la documentazione ufficiale
hash.each_pair do |key, value|
return key if el==value
end
nil
end

h={}

57
h[’stefano’]=’nerd’
h[’mirko’]=’porseo’

puts "Ecco i valori contenuti nell’hash:"


# Vedere la documentazione ufficiale per capire il significato dei metodi
# seguenti
puts h.values.join(",")

puts "Che elemento devo cercare?"


el=gets.chomp
k=find_in_hash(h, el)
if k.nil?
puts "Elemento non trovato nell’hash."
else
puts "L’elemento cercato ha chiave #{k}"
end

A.5 Operazioni con File

A.5.1 Esercizio 1

Aprire un file di testo, e salvarlo come nome originale.ordinato dopo averne ordinato le righe.

#!/usr/bin/env ruby

puts "Inserisci il nome del file da ordinare:"


f1=gets.chomp

# verifica che il file sia leggibile, in caso contrario stampa messaggio


# di errore
if File.readable? f1 then
contenuto = File.read f1
# ora leggo le righe con uno split: (righe sarà un array)
righe = contenuto.split("\n")
righe.sort!
File.open "#{f1}.sorted", ’w’ do |f|
righe.each do |row|
f.write "#{row}\n" # ricordiamoci il ritorno a capo!
end
end
puts "Fatto."
else
puts "Il file non esiste o non è leggibile"
end

58
A.6 Operazioni con Oggetti

A.6.1 Esercizio 1

Definire un oggetto di tipo Libro avente come attributi accessibili in sola lettura isbn, autore,
titolo, editore, anno pubblicazione e prezzo. Tutti questi attributi devono essere settati in
fase di inizializzazione dell’oggetto.

class Libro
attr_reader :isbn, :autore, :titolo
attr_reader :editore, :anno_pubblicazione, :prezzo
# scritte due righe solo per semplicità di lettura da parte dell’utente.
# avrei potuto scrivere una riga solo

def initialize(isbn, autore, titolo, editore, anno_pubblicazione, prezzo)


@isbn = isbn
@autore = autore
@titolo = titolo
@editore = editore
@anno_pubblicazione = anno_pubblicazione
@prezzo = prezzo
end
end

A.6.2 Esercizio 2

Progettare la classe conto bancario (BankAccount), scrivere quindi un programma che costruisca
un conto bancario, versi in esso 1000 euro, prelevi da esso 500 euro, prelevi altri 400 euro e stampi
a video il saldo rimanente. (La progettazione della classe è libera)

class NegativeBalanceError < ArgumentError


end

class BankAccount

attr_reader :balance

def initialize
@balance = 0
end

def deposit (amount)


@balance = @balance + amount
end

59
def withdraw (amount)
if @balance - amount < 0
raise NegativeBalanceError
end
@balance = @balance - amount
end

end

ba=BankAccount.new
ba.deposit 1000
ba.withdraw 500
ba.withdraw 400

puts ba.balance

A.6.3 Esercizio 3

Progettare una classe Circle (cerchio), con i metodi area, per conoscere l’area, e perimeter, per
conoscerne la circonferenza. Nel costruttore, indicare il raggio del cerchio. Usare π come costante.
(PI=3.1415927)

class Circle

PI=3.1415927

def initialize(radius)
@radius = radius
end

def area
PI*(@radius**2)
end

def perimeter
2*PI*@radius
end

end

A.6.4 Esercizio 4

Scrivere un programma che chieda all’utente un numero intero e che poi stampi tutti i suoi fattori.
Progettare e usare una classe FactorGenerator con i metodi next factor e has more factors?
.

60
class FactorGenerator

def initialize (n)


raise ArgumentError, ’n must be greater than zero’ if n<=1
@numero_da_scomporre = n
@attuale_scomposizione = n
end

def has_more_factors?
return false if @attuale_scomposizione==1
true
end

def next_factor
raise Exception, ’there are no more factors’ unless has_more_factors?
for i in (2..@attuale_scomposizione) do
if @attuale_scomposizione%i==0
@attuale_scomposizione = @attuale_scomposizione/i
# break serve per forzare l’uscita dal ciclo
break
end
end
i
end

end

numero = 150
f=FactorGenerator.new(numero)

while f.has_more_factors? do
puts f.next_factor
end

A.6.5 Esercizio 5

Aggiungere alla classe String il metodo num rows che conta le righe presenti nella stringa stessa.
Consiglio: usare \n come separatore di linea.

class String
def num_rows
split("\n").size
end
end

61

You might also like