Imparando Ruby
Imparando Ruby
a cura di
Stefano Sasso
stefano(at)gnustile.net
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
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
~# irb
irb(main):001:0> _
~# ruby nomedelfile.rb
oppure, più semplicemente, inserire in testa al file contenente il codice ruby la riga
#!/usr/bin/env ruby
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
~# ruby prova1.rb
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 ( ).
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!")
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)
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
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
3.+(4)
Ad esempio, i metodi
3.next
3.+(2)
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
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
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
ruby+Classe
ad esempio
ruby+String
http://www.ruby-doc.org/core/classes/String.html
5.class
"ciao".class
3E2.class
7
2.7 Standard input + Stringhe di caratteri
Consideramo questo breve programmino:
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 “ “:
var1="bao"
var2="ciao #{var1}"
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
esempio:
• == uguale a
• < minore di
• > maggiore di
• != 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“
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:
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
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:
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
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
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
array << 8
array.push 8
array.sort!
primo_elemento=array[0]
array[0]=-1
Un’altro oggetto particolare, simile agli array, è l’oggetto Range, (intervallo) che si definisce in
questo modo:
intervallo=(1..10)
array=(1..10).to_a
array=("a".."d").to_a
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
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
5.times do |n|
puts "Numero: #{n.to_s}"
end
array.each { |n|
puts n
}
13
o meglio ancora
• join - simile a to s, ma permette di definire una stringa da inserire tra i vari oggetti in fase
di concatenazione
array=[1,2,3]
array.push 4 #=> [1,2,3,4]
array=[1,2,3]
ultimo=array.pop # (array vale ora [1,2] e ultimo vale 3)
[1,2,3].first #=> 1
[1,2,3].last #=> 3
[1,2,3].size #=> 3
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
http://www.ruby-doc.org/core/classes/Array.html
• split - si può considerare come l’inverso di join applicato ad un array, infatti divide la
stringa in elementi di un array
• [] - è 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)
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
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")
"ciao".size #=> 4
a = "CIAO"
b=a.downcase # (b vale "ciao", a vale ancora "CIAO")
a.downcase! # (a vale "ciao")
• index - ritorna la posizione della prima occorrenza di una stringa in quella di partenza
• reverse e reverse! - serve a invertire i caratteri di una stringa, 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")
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}!"
def saluta(nome)
puts "Ciao, #{nome}!"
end
saluta(’Stefano’)
saluta ’Mirko’
def <nome_del_metodo>(<variabili_in_ingresso>)
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
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 ora vale 1, stringa vale ’ciao’ e array vale [1, 2].
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.
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:
Vediamo ora alcune funzioni utili per poter lavorare con il filesystem:
files_txt_directory_corrente=Dir[’*.txt’]
files_immagine_ricorsivi=Dir[’**/*.{JPG,jpg}’]
Dir.chdir ’/home/stefano/prove_ruby’
• rinominare un file
File.chmod(0644, "testfile.txt")
Dir.mkdir ’dir_1’
Dir.mkdir ’dir_2’, 0644
• directory corrente
current_directory=Dir.pwd
• copia di un file
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
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
ovviamente un oggetto Time ha tutta una serie di metodi di utilità; vediamone brevemente alcuni
con un esempio:
http://ruby-doc.org/core/classes/Time.html
21
2.17.2 La classe ’Hash’
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’
http://ruby-doc.org/core/classes/Hash.html
22
Capitolo 3
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
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
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
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
class Camion
attr_accessor :posti
attr_accessor :carburante
è 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
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
attr_accessor :variabile
26
corrisponde esattamente a scrivere
attr_reader :variabile
attr_writer :variabile
class Camion
attr_reader :posti
attr_reader :carburante
end
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
e poi far “discendere” camion e carroarmato direttamente da veicolo, in modo che ne ereditino le
proprietà:
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
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
• 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
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
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:
test_array = [’Elemento 1’ ,
’Elemento 2’ ,
’Elemento 3’ ]
test_string = test_array.to_yaml
filename = ’output_yaml.txt’
require ’net/smtp’
user_from = "[email protected]"
user_to = "[email protected]"
email_body = "From: [email protected]\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
# libreria mysql
require ’mysql’
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
require ’sqlite3’
db = SQLite3::Database.new("book.db")
35
# se invece siamo interessati solo alla prima riga:
prima_riga = db.get_first_row( "SELECT * FROM libro" )
db.close
# 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
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
36
book.insert("Reduce","Giovanni Lindo Ferretti",2006)
#=> 1
book.insert("Il bosco degli urogalli","Mario Rigoni Stern",1962)
#=> 2
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_titolo = book.select(:titolo)
#=> [#<struct #<Class:0xb79ac8d0> titolo="Reduce">, #<struct #<Class:0xb79ac8d0> titolo="Il bosco
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)
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
lettura di un file
require ’rubygems’
require ’xmlsimple’
xmlfile = File.new("cd.xml")
doc = XmlSimple.xml_in xmlfile
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 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
lettura di un file
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’
# vediamo il risultato
puts doc.to_s #=> <doc><example data=’20081001’>esempio 1</example></doc>
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]
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
41
Server:
require "socket"
Client:
require "socket"
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.
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’
http://localhost:9090/
42
Un server Web un po’ più complesso
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
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
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
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
#!/usr/bin/env ruby
47
puts "</head>"
puts "<body>"
puts "<h2>ciao mondo</h2>"
puts "</body>"
puts "</html>"
Andiamo quindi a
http://localhost/cgi-bin/ciao.rb
#!/usr/bin/env ruby
require ’cgi’
cgi = CGI.new
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.
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’)
<html>
<head>
<title>Prova ERB</title>
</head>
<body>
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
<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.1 Esercizio 1
#!/usr/bin/env ruby
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
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.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
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
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
# Inizializzo l’array
ar=[1, 2, 5, 6, 8, 12, 34, 56, 87]
ar_s=ar.join(",")
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
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
h={}
57
h[’stefano’]=’nerd’
h[’mirko’]=’porseo’
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
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
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 BankAccount
attr_reader :balance
def initialize
@balance = 0
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 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