Premiers pas avec Ruby

Pierre-Baptiste Naigeon

Ruby est un langage interprété entièrement orienté objet, et multiplateforme.
Outre le fait d'être très intuitif (vous verrez pourquoi dans la suite de ce cours), Ruby peut servir de langage de script au même titre que PERL ou d'autres, mais son essor récent est surtout dû à l'apparition de Ruby on Rails, un framework web extrêmement puissant basé sur Ruby.
Nous allons voir ici les bases de ce langage, ses atouts, etc. ♪

- Titre : Premiers pas avec Ruby

- Auteur : Pierre-Baptiste Naigeon

- Parution : 26 mars 2007

- Mise à jour : 26 mars 2007

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2006 Pierre-Baptiste Naigeon. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Installation et concepts de base

- II. Premiers pas

- II-A. Manipulation de variables simples

- II-B. Tableaux

- II-C. Hash / tableaux associatifs

- II-D. Manipulation de chaines, de tableaux et de hash

- II-E. Conventions de nommage

- II-F. Expressions régulières

- III. Création de notre première classe

- III-A. Définition de la classe et de ses méthodes, variables d'instance

- III-B. Constructeur : méthode initialize

- III-C. Variable de classe

- III-D. Simplification de la classe grâce à Ruby

- III-E. Et l'héritage alors ?

- III-F. Redéfinir une méthode

- IV. Et les modules, qu'est-ce que c'est ?

- V. Les structures de contrôle

- V-A. Opérateurs de comparaison

- V-B. if … else … end

- V-C. case … end

- V-D. unless … end

- V-E. while … end

- V-F. until … end

- V-G. for … end

- V-H. each { … } et autres itérateurs

- V-I. Sortie de boucles

- VI. Procédures

- VII. Gestion d'erreurs

- VII-A. begin … rescue … end

- VII-B. … retry …

- VII-C. … raise …

- VII-D. .. ensure …

- VIII. Manipulation de fichiers

- VIII-A. Création de fichiers et de dossiers

- VIII-B. Afficher le contenu d'un fichier ligne par ligne avec le numéro de ligne

- VIII-C. Écrire dans un fichier

- VIII-D. Renommer un fichier

- VIII-E. Lister le contenu d'un répertoire et de ses sous-répertoires trié par ordre alphabétique

- IX. Bases de données - MySQL

- IX-A. Installer MySQL pour Ruby

- IX-B. Exemple d'utilisation

- X. Conclusion et remerciements

I - Installation et concepts de base

Voyons comment installer Ruby sous Windows.
Rien de très compliqué, il vous suffit de vous rendre sur la page de Ruby, puis de cliquer sur « Téléchargements », et de télécharger le One-Click Installer pour Windows. Il ne vous reste plus qu'à choisir votre répertoire d'installation, et à valider en attendant la fin de la procédure d'installation.

Ruby est livré par défaut avec deux éditeurs : SciTe et FreeRIDE.

Pour lancer FreeRIDE (intégralement écrit en Ruby), rendez-vous dans le répertoire d'installation de Ruby, puis dans le répertoire « freeride », et exécutez « run.bat ».

Je préfère SciTe, mais cela ne dépend que de considérations personnelles. Pour le lancer, rendez-vous dans le répertoire d'installation de Ruby, puis dans le répertoire « scite », et exécutez « SciTE.exe ».

En Ruby, tout est objet. Absolument tout. Un entier, une chaine de caractères, et même les classes, aussi étonnant que cela puisse paraître.

II - Premiers pas

Nous allons apprendre par l'expérience. Pour cela, nous allons utiliser irb (interactive Ruby).

En utilisant irb, nous allons pouvoir tester la syntaxe de base de Ruby et en visualiser directement le résultat, sans avoir à créer un fichier contenant notre code (un .rb) et à le modifier à chaque fois. Il vous suffit de lancer une fenêtre DOS (démarrer -> exécuter -> cmd), puis de taper « irb » dans l'invite de commande, et de valider.

II-A - Manipulation de variables simples

L'invite de commande s'est modifiée. Vous pouvez désormais taper directement vos commandes. Essayez de taper les lignes suivantes :

- a = 2

- b = 3

- puts « a + b vaut » + (a+b).to_s

Voici ce que vous devriez obtenir à l'écran :

irb(main):001:0> a = 2
=> 2
irb(main):002:0> b = 3
=> 3
irb(main):003:0> puts "a + b vaut " + (a+b).to_s
a + b vaut 5
=> nil

Beaucoup de choses dans ce simple exemple. Nous venons de créer deux variables, a et b, et de leur affecter des valeurs. Ensuite, nous avons décidé d'afficher leur somme à l'aide de la méthode puts. Nous affichons donc une chaine (la partie entre guillemets), que nous concaténons à une autre chaine à l'aide de l'opérateur +.
Cette deuxième chaine est en fait le résultat de la somme de nos deux variables (encore cette fois à l'aide de l'opérateur +, qui n'a donc pas la même fonction suivant le type avec lequel il est utilisé), que nous convertissons en chaine à l'aide de la méthode to_s (pour rendre possible la concaténation, il nous faut deux types identiques).

Vous remarquerez que nulle part nous n'avons défini le type de a ou de b. Ruby se débrouille comme un grand : c'est le duck typing (ça ressemble à un canard, ça se comporte comme un canard, donc c'est un canard).

Les opérateurs sur les nombres restent très classiques : + pour l'addition, - pour la soustraction, * pour la multiplication, et / pour la division.
À noter tout de même ** pour la puissance : a**n signifie a à la puissance n. Ici, n peut être un réel, donc pour obtenir racine de 2, il est tout à fait possible de faire 2**0.5.
Reste la fonction modulo : %, qui renvoie le reste de la division entière.

Ici, nous avons écrit « a+b ». Or, en Ruby, tout étant objet, les opérateurs ne sont que des méthodes.

Nous aurions donc pu écrire « a.+(b) » : l'appel à la méthode + de l'objet a avec en paramètre l'objet b.

Peu pratique à écrire, vous en conviendrez, mieux vaut utiliser la première notation. Ruby est bourré de ces sucres syntaxiques qui nous facilitent la vie.

Dans la même veine que to_s, voici quelques fonctions utiles de manipulation de chaine :

- to_i : convertit une chaine en un entier, ou retourne la partie entière d'un réel ;

- to_f : idem, mais retourne un réel ;

- length : retourne la longueur d'une chaine ou la longueur d'un tableau ;

- upcase : convertit une chaine en majuscules ;

- downcase : convertit une chaine en minuscules ;

- swapcase : intervertit les majuscules et les minuscules dans une chaine ;

- capitalize : met en majuscule le premier caractère de la chaine ;

- reverse : inverse la chaine ou les éléments d'un tableau.

Jouons encore un peu, et découvrons les chaines.
Une chaine peut être définie soit entre apostrophes, soit entre guillemets. Une chaine définie entre apostrophes ne prendra pas en compte les caractères spéciaux (\n par exemple), et n'évaluera pas non plus les expressions entourées par #{…} (que nous verrons un peu plus tard, irb ne le comprend pas).
Créons donc une chaine simple :

- c = 'cou'

- puts c * 2

Et observons la sortie :

irb(main):004:0> c = 'cou'
=> "cou"
irb(main):005:0> puts c * 2
=> "coucou"

Merveilleux, la multiplication répète notre chaine !
Voyons maintenant comment demander à l'utilisateur une saisie clavier :

- saisie = gets

- puts saisie.chomp

- saisie2 = gets.chomp

irb(main):006:0> saisie = gets
coucou
=> "coucou\n"
irb(main):007:0> saisie.chomp
=> "coucou"
irb(main):008:0> saisie2 = gets.chomp
tralala
=> "tralala"

Ici, gets permet de demander à l'utilisateur de saisir une valeur. En observant bien, on se rend compte que la frappe sur la touche Entrée est incluse dans la chaine (le \n). Pour l'enlever, rien de plus simple, il suffit d'utiliser la méthode chomp, qui renvoie notre chaine sans le caractère de retour à la ligne.

Lorsque vous souhaitez effectuer le remplacement de variables dans une chaine en Ruby, il faudra utiliser la syntaxe suivante (notez bien la présence de guillemets pour la chaine où se produit la substitution) :

nom = 'Pierre-Baptiste'
puts "Bonjour #{nom}

Et voici la sortie :

Bonjour Pierre-Baptiste

Rappel : irb ne le comprend pas.

II-B - Tableaux

Nous allons maintenant créer un tableau :

- mon_tableau = [« a », 12, « b »]

irb(main):001:0> mon_tableau = ["a", 12, "b"]
=> ["a", 12, "b"]

Pour accéder aux différents éléments de notre tableau, il suffit de faire suivre le nom de notre variable de crochets, avec l'indice désiré entre les crochets. Le premier indice du tableau est 0 :

- puts mon_tableau[1]

- mon_tableau[1] = 42

- puts mon_tableau[1]

irb(main):002:0> puts mon_tableau[1]
12
=> nil
irb(main):003:0> mon_tableau[1] = 42
=> 42
irb(main):004:0> puts mon_tableau[1]
42
=> nil

Il existe de nombreuses autres méthodes qui s'appliquent aux tableaux : push, pop, last, etc.

irb(main):005:0> tablo = ['a', 'z', 'e', 'r', 't']
=> ["a", "z", "e", "r", "t"]
irb(main):006:0> tablo.push('y', 'u')
=> ["a", "z", "e", "r", "t", "y", "u"]
irb(main):007:0> tablo.pop
=> "u"
irb(main):008:0> tablo
=> ["a", "z", "e", "r", "t", "y"]
irb(main):009:0> tablo.last
=> "y"
irb(main):010:0> tablo
=> ["a", "z", "e", "r", "t", "y"]

Juste une petite méthode fort sympathique pour en finir avec les tableaux : la méthode « sort ». Faites juste un « mon_tableau.sort », et vous verrez alors votre tableau trié :)

II-C - Hash / tableaux associatifs

Ruby gère également les tableaux associatifs. Il faut juste noter qu'en Ruby, ils sont appelés « hash ».

- mon_hash = {:nom => « Naigeon », :prenom => « PB »}

irb(main):001:0> mon_hash = {:nom => "Naigeon", :prenom => "PB"}
=> {:nom => "Naigeon", :prenom => "PB"}

Accéder aux éléments n'est guère plus compliqué :

- puts mon_hash[:nom]

- puts mon_hash[:prenom]

- mon_hash[:prenom] = « Pierre-Baptiste »

- puts mon_hash[:prenom]

irb(main):002:0> puts mon_hash[:nom]
Naigeon
=> nil
irb(main):003:0> puts mon_hash[:prenom]
PB
=> nil
irb(main):004:0> mon_hash[:prenom] = "Pierre-Baptiste"
=>"Pierre-Baptiste"
irb(main):005:0> puts mon_hash[:prenom]
Pierre-Baptiste
=> nil

II-D - Manipulation de chaines, de tableaux et de hash

Il est possible d'accéder aux différentes parties de la chaine comme suit :

irb(main):001:0> ma_var = 'azerty'
=> "azerty"
irb(main):002:0> ma_var[0]
=> 97
irb(main):003:0> ma_var[0..0]
=> "a"
irb(main):004:0> ma_var[0..3]
=> "azer"
irb(main):005:0> ma_var[0,4]
=> "azer"
irb(main):006:0> ma_var[-2,2]
=> "ty"

Dans le premier cas, nous obtenons le code ASCII de la première lettre (l'indice 0).
Ensuite, nous lui demandons d'afficher les lettres comprises entre l'indice 0 et l'indice 0 (les bornes sont incluses). Il n'y en a qu'une, il nous renvoie donc « a ».

Puis nous lui demandons donc d'afficher les lettres comprises entre l'indice 0 et l'indice 3 (quatre lettres donc). Il nous retourne « azer ».
Nous lui demandons ensuite de nous afficher à partir de l'indice 0 une chaine de longueur 4. Il nous retourne donc encore « azer ».
Pour finir, nous lui passons un indice négatif. Les indices négatifs partent de la fin du tableau (et commencent à 1, eux, puisqu'il est dur de différencier 0 et -0). Nous lui demandons donc d'afficher à partir du deuxième caractère avant la fin une chaine de longueur 2 (les deux derniers caractères quoi !).

Il est possible de procéder de même avec les tableaux :

irb(main):007:0> mon_tabl = ['q', 'w', 'e', 'r', 't', 'y']
=> ["q", "w", "e", "r", "t", "y"]
irb(main):008:0> mon_tabl[0]
=> "q"
irb(main):009:0> mon_tabl[0..0]
=> ["q"]
irb(main):010:0> mon_tabl[0..3]
=> ["q", "w", "e", "r"]
irb(main):011:0> mon_tabl[0,4]
=> ["q", "w", "e", "r"]
irb(main):012:0> mon_tabl[-2,2]
=> ["t", "y"]

Cela fonctionne exactement comme précédemment. Faites juste attention au fait que mon_tabl[0] retourne une chaine (dans le cas présent), alors que mon_tabl[0..0] retourne cette même chaine, mais dans un tableau !

Encore deux petites fonctions extrêmement utiles avant de conclure cette section : split et join (équivalents de explode et implode en PHP).

irb(main):010:0> ma_date = '18/02/1981'
=> "18/02/1981"
irb(main):011:0> mon_tableau = ma_date.split('/')
=> ["18", "02", "1981"]
irb(main):012:0> ma_date2 = mon_tableau.join('-')
=> "18-02-1981"

Vous pouvez bien entendu spécifier le séparateur de votre choix.
Split vous permet donc de découper une chaine dans un tableau en fonction d'un séparateur donné, et join vous permet de regrouper les éléments d'un tableau dans une chaine, toujours avec un séparateur.

À vous maintenant la manipulation de chaines :)

II-E - Conventions de nommage

Variables et méthodes

- nom_var

- nom_methode

- nom_methode? (une méthode qui renvoie true ou false : is_a?(Fixnum))

- nom_methode! (une méthode « destructrice », souvent qui modifie la variable : sort!)

- @nom_var_instance

- @@nom_var_classe

- $nom_var_globale (Attention, peut aussi s'écrire en majuscules : $NOM_VARIABLE_GLOBALE)

Classes

- NomClasse

Constantes

- NOMCONSTANTE ou NomConstante

II-F - Expressions régulières

Ruby est tout à fait de taille à jouer avec les expressions régulières. En revanche, moi, je suis loin d'être de taille à jouer avec elles, c'est pourquoi vous ne disposerez ici que d'un petit exemple, à vous de faire la suite ;)

chaine = 'la couleur #FF024D est-elle une couleur HTML ?'

test = (chaine =~ /#[0-9A-Fa-f]{6}/)

if (test)
puts "la chaine #{chaine[test,7]} est un code couleur HTML valide !"
else
puts "Pas de code couleur HTML valide dans la chaine"
end

Nous nous contentons simplement de parcourir notre chaine à la recherche du motif, et nous renvoyons sa position si le motif est trouvé.

Notez simplement l'opérateur « =~ ». C'est l'opérateur réservé à l'évaluation d'expressions régulières. Il retourne la position du premier caractère qui correspond dans la chaine, nil si rien n'est trouvé.

III - Création de notre première classe

III-A - Définition de la classe et de ses méthodes, variables d'instance

Nous allons nous atteler à la création de classes. Désormais, nous coderons directement dans un fichier. Créez donc un nouveau fichier nommé par exemple animaux.rb.

Nous allons y placer le code suivant :

class Animal
end

cochon = Animal.new

Nous avons donc ici une définition de la classe « Animal », ainsi que la création d'une instance de cette classe, nommée « cochon ».

Une classe se définit en utilisant le mot-clé « class », suivi par le nom de la classe. La fin de la définition est marquée par le mot-clé « end ». Notre classe ici ne fait donc rien, elle se contente d'exister.
Il ne nous reste plus qu'à en créer une instance en utilisant la méthode « new ».

Nous allons définir certaines méthodes (simplistes) pour la classe « Animal ». Un animal pourra donc « parler » (à défaut d'un meilleur terme), et aura un âge donné.
Il nous va donc falloir écrire des méthodes permettant de définir des propriétés et de les lire.
Une méthode se définit en commençant par le mot-clé def, suivi par le nom de la méthode, puis le code de la méthode, et se termine par le mot-clé end.
Modifions donc notre fichier comme suit :

class Animal
def set_parle(parole)
@parole = parole
end

def get_parle
return @parole
end

def set_age(age)
@age = age
end

def get_age
return @age
end
end

cochon = Animal.new
cochon.set_parle("groin groin")
cochon.set_age(12)

puts "le cochon fait #{cochon.get_parle}"
puts "le cochon a #{cochon.get_age} ans"

Pour exécuter ce programme, ouvrez une fenêtre DOS, places-vous dans le répertoire où se situe votre fichier « animaux.rb », et tapez la ligne suivante :

- ruby animaux.rb

Et voici ce que vous devriez obtenir en sortie :

le cochon fait groin groin
le cochon a 12 ans

Beaucoup de modifications cette fois-ci.

Nous avons simplement défini quatre méthodes : définition de l'âge, définition de la « parole », récupération de l'âge et récupération de la « parole ».
Les méthodes sont définies à l'aide du mot-clé « def » suivi du nom de la méthode, et se terminent par le mot-clé « end ».

Mais pourquoi ces @ devant le nom des variables ? Tous simplement parce que ce sont des variables d'instance. Elles seront donc accessibles tout au long de la vie de notre objet, et de n'importe quel endroit de notre objet. Une fois que nous avons créé l'instance « cochon » de notre classe « Animal », nous définissons sa « parole » ainsi que son âge.

Ensuite, nous nous contentons d'afficher ces deux valeurs au sein d'une chaine. Les balises #{…} permettent d'évaluer du code Ruby au sein d'une chaine.

III-B - Constructeur : méthode initialize

Nous allons maintenant définir les comportements par défaut de notre objet lors de sa création. Placez le code suivant dans votre définition de classe :

def initialize
@parole = "..."
@age = 0
end

Et modifions la fin de notre fichier comme suit :

cochon = Animal.new

puts "le cochon fait #{cochon.get_parle}"
puts "le cochon a #{cochon.get_age} ans"

cochon.set_parle("groin groin")
cochon.set_age(12)

puts "le cochon fait #{cochon.get_parle}"
puts "le cochon a #{cochon.get_age} ans"

Il ne nous reste plus qu'à exécuter notre fichier, et à observer la sortie :

le cochon fait ...
le cochon a 0 ans
le cochon fait groin groin
le cochon a 12 ans

En fait, la méthode initialize est une méthode spécifique, qui s'exécute automatiquement lors de l'instanciation d'un objet.
Ici donc, lors de la création de notre objet « cochon », nous définissons qu'il ne sait pas parler (« … »), et qu'il est âgé de 0 ans.
Nous affichons ces propriétés, puis nous les modifions et les réaffichons. Rien de très compliqué ici.

C'est bien mignon, mais s’il était possible de définir des valeurs directement lors de la création de l'objet, ça nous arrangerait.
Et bien, c'est possible ! Il suffit de modifier la méthode initialize :

def initialize(parole="...", age=0)
@parole = parole
@age = age
end

Nous pouvons donc désormais créer un objet comme suit :

cochon = Animal.new("groin groin", 12)

Notre animal fraîchement créé saura donc immédiatement parler, et sera âgé de 12 ans.
Dans le cas où les paramètres auraient été oubliés, ils auraient pris la valeur par défaut définie dans la méthode initialize, à savoir « … » et 0.

III-C - Variable de classe

Nous désirons maintenant connaitre à tout instant le nombre total d'animaux créés. Modifiez donc votre fichier comme suit :

class Animal
@@total_cree = 0

def initialize(parole="...", age=0)
@@total_cree += 1
@parole = parole
@age = age
end

def self.total
return @@total_cree
end

def set_parle(parole)
@parole = parole
end

def get_parle
return @parole
end

def set_age(age)
@age = age
end

def get_age
return @age
end
end

puts "Nombre total d'animaux crees : #{Animal.total}"

cochon = Animal.new
puts "Nombre total d'animaux crees : #{Animal.total}"

chien = Animal.new
puts "Nombre total d'animaux crees : #{Animal.total}"

chat = Animal.new
puts "Nombre total d'animaux crees : #{Animal.total}"

Exécutons-le, et observons la sortie :

Nombre total d'animaux crees : 0
Nombre total d'animaux crees : 1
Nombre total d'animaux crees : 2
Nombre total d'animaux crees : 3

Mais qu'est-ce donc que ce double arobase ? Il s'agit tout simplement d'une variable de classe. Sitôt la classe définie, cette variable est accessible, puisque nous l'initialisons directement à 0 (directement dans la classe).

À chaque instanciation d'un objet, nous incrémentons cette variable dans la méthode « initialize ».

Nous avons également créé une méthode « total », précédée du mot-clé « self ». Ce mot-clé fait référence à la classe elle-même. Cette méthode se contente de renvoyer la valeur de la variable de classe.
Cette méthode étant une méthode de classe, il faut donc l'appeler depuis la classe, et non une instance de cette classe : « Animal.total ».

III-D - Simplification de la classe grâce à Ruby

Nous allons maintenant profiter des possibilités de Ruby pour rendre la définition de notre classe plus lisible et plus fonctionnelle.
En effet, ne serait-il pas plus facile pour nous d'écrire :

cochon.parle = "groin groin"
puts cochon.parle

Plutot que :

cochon.set_parle("groin groin")
puts cochon.get_parle

Je préfère nettement la première des deux versions ;)
Et ça tombe bien, Ruby nous permet de mettre en place de telles méthodes. Voyons comment, en remplaçant les méthodes set_parle et get_parle par les deux méthodes ci-dessous :

def parle=(parole)
@parole = parole
end

def parle
return @parole
end

Et appelons ces deux méthodes après l'instanciation de notre objet cochon :

cochon.parle = "groin groin"
puts cochon.parle

Magie ! Le résultat escompté est bien là, nous avons donc simplifié tant la lecture de notre définition de classe que l'utilisation des différentes méthodes.
Il s'agit simplement d'une preuve d'intelligence de Ruby. S’il rencontre la méthode parle, suivie d'une affectation (=), il sait tout seul quelle méthode appliquer.

Il ne nous reste plus qu'à faire la même chose avec nos méthodes « set_age » et « get_age ».

def age=(age)
@age = age
end

def age
return @age
end

Nous allons à présent simplifier le code ci-dessus.

Pour commencer, les « return » présents au sein de nos différentes méthodes sont inutiles, puisque Ruby renvoie automatiquement la dernière expression évaluée.
Ainsi, « return @parole » peut devenir « @parole » seul.

Nous remarquons maintenant que les méthodes créées pour avoir accès en lecture et en écriture aux variables @age et @parole sont étrangement similaires et redondantes.
Pas de panique ! Ruby est là pour vous. Notre énorme fichier plein de lignes va se transformer ainsi :

class Animal
@@total_cree = 0
attr_accessor :parle, :age

def initialize(parle="...", age=0)
@@total_cree += 1
@parle = parle
@age = age
end

def self.total
@@total_cree
end
end

cochon = Animal.new
cochon.age = 12
puts cochon.age
puts cochon.parle
cochon.parle = "groin groin"
puts cochon.parle

Exécutons notre code, et observons la sortie :

12
...
groin groin

Simplement en utilisant la méthode « attr_accessor », nous avons raccourci notre code de 12 lignes. Cette méthode permet de créer automatiquement des méthodes d'accès en lecture et en écriture aux variables d'instance passées en paramètre.

Je sais que ce point est un peu dur à saisir, mais Ruby va nous générer automatiquement les méthodes parle, parle=, age et age= qui vont nous permettre de lire ou de modifier les variables d'instance @age et @parle.
Nous avons donc au passage modifié notre méthode initialize pour remplacer @parole par @parle.

Si nous avions voulu accéder uniquement en lecture à nos variables, nous aurions pu utiliser « attr_reader », et « attr_writer » si nous avions voulu y accéder uniquement en écriture.

III-E - Et l'héritage alors ?

Nous allons nous essayer aux joies de l'héritage.
Pour cela, nous allons prendre un exemple parfaitement stupide : un animal est un être vivant, et un être vivant bat du cœur.
Nous allons donc définir une classe « EtreVivant », définir une méthode « cœur_bat », et en faire hériter notre classe « Animal ».

class EtreVivant
def coeur_bat
puts "boum boum, j'ai le coeur qui bat !"
end
end

Il ne nous reste plus qu'à gérer l'héritage proprement dit.
Modifiez donc la définition de la classe « Animal » comme suit :

class Animal<EtreVivant

Il ne vous reste plus qu'à instancier un objet, et à tester la méthode :

cochon = Animal.new
cochon.coeur_bat

Et merveilleux, vous obtenez bien le texte « boum boum, j'ai le cœur qui bat ! ». La classe « Animal » a donc bien hérité des méthodes de la classe « EtreVivant ».

III-F - Redéfinir une méthode

Nous allons à présent redéfinir une méthode existante. Prenez le code suivant :

class EtreVivant
def coeur_bat
puts "boum boum, j'ai le coeur qui bat !"
end
end

class Animal<EtreVivant
@@total_cree = 0
attr_accessor :parle, :age

def initialize(parle="...", age=0)
@@total_cree += 1
@parle = parle
@age = age
end

def self.total
@@total_cree
end
end

cochon = Animal.new
cochon.coeur_bat

class EtreVivant
def coeur_bat
puts "bim bim, j'ai le coeur patraque !"
end
end

cochon.coeur_bat

Et vous obtenez en sortie :

boum boum, j'ai le coeur qui bat !
bim bim, j'ai le coeur patraque !

La méthode « cœur_bat » ne renvoie pas la même chose entre les deux appels. Elle a donc bien été redéfinie.

Vous pouvez vous en servir pour contrôler les évolutions de version de vos applications, redéfinir une méthode existante dans un contexte particulier…

Vous pouvez également la redéfinir au sein d'une classe fille. La méthode aura donc un comportement global, mais sera spécifique dans une classe précise. Pour cela, il suffit de redéfinir la méthode dans la classe voulue. Dans l'exemple ci-dessus, il aurait fallu définir la méthode cœur_bat pour la classe Animal.

Ainsi, la méthode cœur_bat serait restée la même partout, sauf pour la classe Animal. C'est ce qu'on appelle la surcharge de méthodes.

De la même façon, vous pouvez parfaitement étendre une classe existante, par exemple définir une méthode « je_parle_pas_clair » pour la classe String qui sera valable pour toutes les chaines :

class String
def je_parle_pas_clair
temp = self.reverse
end
end

texte = "Coucou les filles, ca va ?"
puts texte.je_parle_pas_clair

Merveilleux non ? ;)

Il y a également moyen de redéfinir une méthode pour un objet spécifique. C'est ce qu'on appellera une méthode Singleton.
Prenons cet exemple (parfaitement débile, pardon petit frère, mais j'étais en manque d'idées) :

class BeauGosse
def la_classe
puts "j'ai la classe"
end
end

titou = BeauGosse.new
titou.la_classe

petit_frere = BeauGosse.new

def petit_frere.la_classe
puts "compare a mes grands freres, je vaux pas un clou !"
end

petit_frere.la_classe

grand_frere = BeauGosse.new
grand_frere.la_classe

Et observons la sortie :

j'ai la classe
compare a mon grand frere, je vaux pas un clou !
j'ai la classe

Nous avons ici redéfini la méthode « la_classe » uniquement pour l'objet « petit_frere ».

IV - Et les modules, qu'est-ce que c'est ?

Les modules sont de petites choses qui ressemblent énormément à des classes, mais qui ne peuvent pas être instanciés.
En revanche, ils peuvent tout à fait être intégrés dans une classe, pour faire bénéficier cette classe de l'ensemble des méthodes définies dans le module.

Voyons de suite un petit exemple (encore une fois ridicule et inutile, juste pour se rendre compte de la construction et des possibilités) :

module ModTest
def mon_id
puts self.object_id
end
end

class ClassTest
include ModTest
# plein d'autres choses
end

class ClassTest2
include ModTest
# plein d'autres choses différentes de la classe précédente
end

toto = ClassTest.new
toto.mon_id

tata = ClassTest2.new
tata.mon_id

Et là, miracle, nos deux classes héritent des méthodes de notre module.

Nous aurions pu envisager pour en arriver au même résultat d'ajouter la méthode à une classe « supérieure » dans la hiérarchie des classes, pourquoi pas la classe « Object » elle-même, mais toutes nos classes n'ont pas besoin d'avoir ces méthodes de définies.
Dans le cas présent, nos deux classes peuvent appartenir à deux branches très différentes, et bénéficier de la même méthode.

V - Les structures de contrôle

Pour les différentes structures de contrôle ci-dessous, je vous montrerai simplement un exemple d'utilisation, je ne pense pas qu'une explication soit nécessaire ;)
D'une manière générale, les structures de contrôle s'appliquent à tout ce qui est compris entre ne nom de l'instruction (if, while…) et le mot-clé end (parfois les instructions seront comprises entre accolades {…}).

V-A - Opérateurs de comparaison

En Ruby, les opérateurs classiques de comparaison s'appliquent :

- a == b : a égal b (attention, contrairement à PHP par exemple, '42' == 42 renverra false)

- a != b : a différent de b

- a < b : a strictement inférieur à b

- a > b : a strictement supérieur à b

- a <= b : a inférieur ou égal à b

- a >= b : a supérieur ou égal à b

V-B - if … else … end

puts "Merci de saisir un nombre : "
chiffre = gets.chomp.to_i
if (chiffre == 42)
puts "Bingo, LA réponse !"
else
puts "Essaie encore"
end

Notez simplement le to_i, qui transforme la valeur récupérée est un entier.

Dans le cas d'une suite de caractères alphanumériques, to_i a un comportement spécifique :

irb(main):001:0> "42".to_i
=> 42
irb(main):002:0> "42abc".to_i
=> 42
irb(main):003:0> "abc42abc".to_i
=> 0
irb(main):004:0> "abc".to_i
=> 0

Tant que les caractères sont des chiffres, Ruby les prend en compte. Dès qu'il rencontre autre chose, il s'arrête. Dans le cas où le premier caractère de la chaine ne correspond pas, il nous retourne 0.

V-C - case … end

Le case permet d'effectuer différentes actions en fonction de la valeur d'un paramètre sans avoir à imbriquer trop de if.

puts "Merci de saisir un nombre : "
chiffre = gets.chomp.to_i
case chiffre
when 0..9
puts "Vous aimez les tout petits chiffres."
when 42
puts "Vous être un as !"
else
puts "Vous auriez mieux pu choisir... ;)"
end

La seule chose importante à noter ici est l'intervalle spécifié dans le « when 0..9 ».

V-D - unless … end

Un unless est simplement un if à l'envers : sauf si :

j = rand(10)
unless (j <= 5)
puts "j vaut plus de 5"
end

V-E - while … end

puts "Je vais compter de 1 a 10"
i = 0
while (i < 10)
i += 1
puts i
end

À noter la méthode d'incrémentation… inutile d'essayer i++, qui ne ferait que vous générer une erreur.

V-F - until … end

Le until est juste un while qui marche dans l'autre sens. En reprenant l'exemple précédent :

puts "Je vais compter de 1 a 10"
i = 0
until (i >= 10)
puts i
i += 1
end

V-G - for … end

La boucle for est un peu particulière, puisqu'elle s'applique à des éléments d'une collection :

puts "compte de 1 a 10"
for elt1 in (1..10)
puts elt1
end

tableau = [1, 2, 3, 4]
for elt2 in tableau
puts elt2 + 1
end

V-H - each { … } et autres itérateurs

Il est également possible de coder les deux exemples ci-dessus comme suit :

puts "compte de 1 a 10"
(1..10).each { |elt1|
puts elt1
}

tableau = [1, 2, 3, 4]
tableau.each { |elt2|
puts elt2 + 1
}

Je préfère cette dernière syntaxe, mais ce n'est qu'une question de goût personnel. Choisissez donc celle qui vous convient le mieux.

Notez simplement que le dernier exemple pourrait s'écrire ainsi :

tableau = [1, 2, 3, 4]
tableau.each do |elt2|
puts elt2 + 1
end

Il est également possible de parser un à un les caractères d'une chaine à l'aide de each_byte :

ma_chaine = "ayu"
ma_chaine.each_byte { |carac|
ascii_sup = carac+1
puts ascii_sup.chr
}

Merveilleux, nous avons augmenté notre code ASCII de 1, puis nous avons affiché les lettres correspondantes.
Notez simplement la méthode « chr » appelée à l'affichage, pour afficher le caractère et non le code ASCII.

Il est également possible de parser ligne par ligne. Je suis sûr que vous avez déjà deviné le nom de l'itérateur… Bingo, each_line !
Affichons donc le numéro de la ligne avant la ligne elle-même :

ma_chaine = "toto\ntiti\ntata"
i = 1
ma_chaine.each_line { |ligne|
puts "#{i} : #{ligne}"
i += 1
}

Un petit moyen simple de répéter une opération X fois :

2000.times do
puts "Moi, j'adore K2000 !" # © Les Nuls
end

V-I - Sortie de boucles

Il existe différents moyens de sortir d'une boucle : « return », « break », « next » et « redo ». Voyons les spécificités de chacun :

- return : sort complètement de la boucle et de la méthode qui contient la boucle ;

- break : sort de la boucle, et continue le code juste après.;

- next : passe directement à la prochaine itération de la boucle ;

- redo : repasse l'itération courante de la boucle.

Il est facile à l'aide de ces commandes de réaliser des boucles infinies. Prenez donc le temps de bien regarder comment elles fonctionnent.

VI - Procédures

Il est possible en Ruby de stocker des blocs de code dans des objets, pour mieux les exécuter plus tard à la demande :

quel_temps = Proc.new { |mon_param|
puts "il fait #{mon_param} aujourd'hui"
}
quel_temps.call "gris"
quel_temps.call "beau"

Merveilleux, notre code est exécuté. Ici, il ne s'agit que d'un simple affichage, mais il est possible d'avoir le code que l'on veut dans une procédure.

Vous me direz, assez peu d'intérêt dans l'état. Mais là où ça devient sympathique, c'est que l'on peut passer une procédure en paramètre à une méthode :

class Personne
def initialize(age, sexe)
@age = age
@sexe = sexe
end

def infos affiche_age
puts "Je suis de sexe " + @sexe
affiche_age.call @age
end

end

monAgeH = Proc.new do |age|
puts "J'ai "+age.to_s+" ans"
end
monAgeF = Proc.new do |age|
puts "On ne demande pas son age a une dame !"
end

paul = Personne.new(42, 'masculin')
virginie = Personne.new(32, 'feminin')

puts paul.infos(monAgeH)
puts virginie.infos(monAgeF)

Ce qui va nous afficher :

Je suis de sexe masculin
J'ai 42 ans
nil
Je suis de sexe feminin
On ne demande pas son age a une dame !
nil

Nous avons donc passé un bloc de code en paramètre à une méthode. Ce bloc peut varier du tout au tout, il ne sera pas utile de modifier la méthode, ou de créer une deuxième classe similaire à la première.
Plutôt avantageux non ?

VII - Gestion d'erreurs

Indispensable au développeur, la gestion d'erreurs en Ruby est extrêmement simple à mettre en place.

VII-A - begin … rescue … end

Vous voulez ouvrir un fichier qui n'existe pas, créer une table qui existe déjà ? Vous haïssez cordialement les messages d'erreurs et les interruptions impromptues ?

Aucun souci, dorénavant, vous serez capable de gérer ce genre de cas.

def ouvre_fichier(nom_fichier)
begin
fichier = File.open(nom_fichier, "r")
rescue
puts "Le fichier n'a pas pu être ouvert"
end
end

ouvre_fichier("test.txt")

Ne vous intéressez pas pour le moment à la manipulation de fichiers, cela fait l'objet de la section suivante.

Dans l'exemple ci-dessus, dans le cas où le fichier « test.txt » ne peut être ouvert, Ruby lève une exception, qui arrête l'exécution du programme.
Mais le fait d'avoir mis l'instruction d'ouverture dans un bloc « begin … rescue … end » permet de capturer cette erreur, d'afficher un message (ou toute autre opération), puis de continuer l'exécution du programme. Nous pourrions par exemple, au sein du « rescue », lui demander d'ouvrir un fichier par défaut.

VII-B - … retry …

Grâce à l'instruction « retry », nous pouvons également relancer notre bloc, en modifiant les paramètres par exemple :

def ouvre_fichier(nom_fichier)
begin
fichier = File.open(nom_fichier, "r")
rescue
#demande à l'utilisateur de saisir le nom du fichier
puts "Le fichier n'a pas pu être ouvert, merci de saisir un nom correct"
nom_fichier = gets.chomp
retry
end
end

ouvre_fichier("test.txt")

Attention tout de même, dans ce cas précis, nous bouclerons tant que l'utilisateur n'aura pas saisi un nom de fichier que Ruby est capable d'ouvrir.
Pensez donc à gérer ce cas ;)

VII-C - … raise …

Utiliser raise vous permet de lever vos propres erreurs, et de les traiter à l'aide d'un rescue.
Voici un exemple parfaitement stupide, mais qui donne une idée de l'utilisation qui peut en être faite :

begin
puts "Saisissez LE nombre :"
nombre = gets.chomp.to_i
if (nombre != 42)
raise "L'utilisateur ne connait pas ses classiques"
end
rescue
puts "Erreur : #{$!}"
end

La seule chose à noter ici est l'utilisation de « $! », qui retourne le dernier message d'erreur. Il s'agit d'une variable globale spécifique (il y en a quelques autres dans le même genre).

VII-D - .. ensure …

Afin de s'assurer que dans tous les cas, une instruction s'exécutera, il suffit d'utiliser « ensure » :

begin
# Plein de code compliqué
rescue
# Une gestion de l'erreur qui fait plein de choses compliquées
ensure
# Un code qui s'exécutera à la fin, quel que soit le cas dans lequel on se trouve.
end

VIII - Manipulation de fichiers

Les codes suivants vont être moins détaillés, l'idée est plutôt de voir différentes manières d'accéder aux dossiers et aux fichiers.

VIII-A - Création de fichiers et de dossiers

Rien de très compliqué dans cet exemple. Nous allons nous contenter de créer un dossier nommé « test » (avec les droits complets pour tout le monde), et de créer dans ce dossier un fichier vide nommé « test.txt » :

Dir::mkdir("test", 0777)
File::new("test/test.txt", "w+")

Comme nous n'avons pas l'intention de travailler plus avant ni sur le dossier, ni sur le fichier, inutile de les instancier, nous allons directement faire appel à la classe.

VIII-B - Afficher le contenu d'un fichier ligne par ligne avec le numéro de ligne

fichier = File.open("test.txt", "r")
i = 1
fichier.each_line { |ligne|
puts "#{i} - #{ligne}"
i += 1
}
fichier.close

Notez simplement l'utilisation de « each_line », qui se tape tout le boulot à notre place, pour notre plus grand plaisir ;)
La classe « File » héritant de la classe « IO », nous aurions pu faire la même chose comme suit :

tableau = File.readlines("test.txt")
i = 1
tableau.each { |ligne|
puts "#{i} - #{ligne}"
i += 1
}

Pas de grande différence, mais uniquement pour bien se rendre compte qu'il existe différents moyens de faire une même chose (« IO.readlines » aurait également convenu).

VIII-C - Écrire dans un fichier

Il est très facile d'écrire dans un fichier en Ruby :

mon_tabl = ["Bonjour,", "Content de vous rencontrer.", "ça va bien ?"]

mon_fichier = File.open("test.txt", "w")
mon_tabl.each { |element|
mon_fichier.write element+"\n"
#mon_fichier << element+"\n"
}
mon_fichier.close

Bien entendu, l'effet produit va dépendre des différents modes d'ouverture du fichier utilisés.
La ligne commentée n'est là que pour montrer quelle solution aurait également pu être utilisée. Libre à vous de choisir celle qui vous convient le mieux.

VIII-D - Renommer un fichier

C'est vraiment pour dire, mais rien de bien compliqué. Renommons notre fichier « test.txt » en « toto.txt » :

File::rename("test.txt", "toto.txt")

Vous étiez prévenus, c'est vraiment pour faire joli ;) Mais vous pouvez également vous servir de cette commande pour déplacer un fichier.

VIII-E - Lister le contenu d'un répertoire et de ses sous-répertoires trié par ordre alphabétique

Un bout de code légèrement plus complexe, mais qui peut servir : nous allons ajouter à la classe « String » une méthode « liste_rep » qui lorsqu'elle recevra un path en paramètre listera celui-ci et tous ses sous-répertoires :

class String
def liste_rep(espacement = "")

liste_exclus = [".", ".."]
d = Dir.open(self)

liste_dir = d.sort - liste_exclus

liste_dir.each { |fichier|
case File.ftype(self+fichier)
when "directory"
puts "#{espacement} + #{fichier}/"
espacement += "    "
(self + fichier + "/").liste_rep(espacement)
espacement = espacement[0, espacement.length-4]
when "file"
puts "#{espacement} - #{fichier}"
end
}
end
end

"./".liste_rep

Plusieurs points importants ici.

L'utilisation de « self » permet de savoir à tout moment quel est l'objet traité, puisqu'il s'agit de l'objet appelant la méthode. Cela permet de plus d'éviter de passer un paramètre optionnel dans notre méthode.

L'opération de soustraction sur les tableaux est autorisée. Ici, nous nous en servons pour exclure de la liste des fichiers à parcourir les dossiers « . » et « .. », histoire d'éviter de nous retrouver dans des boucles bizarres ;)

Nous nous contentons ensuite de vérifier le type du fichier. Si c'est un dossier, on l'affiche, on augmente l'indentation, on fait un appel récursif sur la fonction… puis on décrémente l'indentation
Si c'est un fichier normal, on se contente de l'afficher avant de passer à l'élément suivant.

IX - Bases de données - MySQL

Dans cet exemple, nous allons nous pencher exclusivement sur MySQL, mais sachez que Ruby est capable d'attaquer tout type de bases de données.

IX-A - Installer MySQL pour Ruby

Commencez par télécharger et installer MySQL en vous rendant sur la page de téléchargement MySQL.

Une fois installé et configuré, il ne vous reste plus qu'à installer le module MySQL pour Ruby. Rien de plus simple, grâce à un système de packages très bien conçu : Gem.

Ouvrez une invite de commande, et tapez « gem install mysql ».

Après une mise à jour des packages disponibles, il vous proposera une liste vous permettant de choisir quelle version du module vous souhaitez installer. J'ai opté pour celle-ci : « 1. mysql 2.7.1 (mswin32) ». Il vous suffit de taper le numéro correspondant, et de valider avec la touche « Entrée ».

Il va se charger de tout installer, il ne vous reste plus qu'à vérifier que cela fonctionne, en lançant irb, et en tapant : require « mysql ». S’il vous renvoie « true », bingo c'est gagné, nous pouvons donc nous attaquer au code :)

IX-B - Exemple d'utilisation

Cette fois encore, le code sera plus parlant qu'un long discours :

require "mysql"

db_host = "localhost"
db_user = "root"
db_pass = "mon_pass"
db_name = "base_test"

# Connexion à MySQL
connex = Mysql.new(db_host, db_user, db_pass)

# Création d'une base de test
strSQL = "CREATE DATABASE #{db_name}"
begin
connex.query(strSQL)
rescue
puts "### Impossible de creer la base ###"
end

# Connexion à cette base
connex.select_db(db_name)

# Création d'une table
strSQL = "CREATE TABLE liste_livres
(
id_livre INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
titre VARCHAR(255) NOT NULL,
auteur VARCHAR(50) NOT NULL,
editeur VARCHAR(50) NOT NULL
)"
begin
connex.query(strSQL)
rescue
puts "### Impossible de creer la table ###"
end

# Remplissage de la base
strSQL = [ \
"INSERT INTO liste_livres (titre, auteur, editeur) \
VALUES('Le guide du voyageur galactique', 'Douglas Adams', 'Gallimard')", \
"INSERT INTO liste_livres (titre, auteur, editeur) \
VALUES('Dune', 'Frank Herbert', 'Robert Laffont')", \
"INSERT INTO liste_livres (titre, auteur, editeur) \
VALUES('Fondation', 'Isaac Asimov', 'Gallimard')", \
"INSERT INTO liste_livres (titre, auteur, editeur) \
VALUES('La pierre et le sabre', 'Eiji Yoshikawa', 'J\\'ai Lu')", \
"INSERT INTO liste_livres (titre, auteur, editeur) \
VALUES('La parfaite lumière', 'Eiji Yoshikawa', 'J\\'ai Lu')" \
]
begin
strSQL.each { |ligne|
connex.query(ligne)
}
rescue
puts "### Impossible d'inserer les donnees dans la base ###"
end

# Un petit exemple de requête...
strSQL = "SELECT * FROM liste_livres ORDER BY titre"
result = connex.query(strSQL)
result.each_hash do |ligne|
puts "Titre : #{ligne['titre']}"
puts "Auteur : #{ligne['auteur']}"
puts "Editeur : #{ligne['editeur']}"
puts "-------------------------------------------------"
end

# Ferme la connexion à la DB
connex.close

Voici les choses importantes à retenir ici :

- require « mysql » : charge le module MySQL, et permet d'utiliser les classes et méthodes liées.

- Mysql#new : crée la connexion à la base de données.

- Mysql#select_db : définit la base sur laquelle travailler.

- Mysql#query : envoie une chaine SQL à la base.

- Mysql#close : ferme la connexion à la base.

Notez simplement l'utilisation du backslash (« \ ») en fin de ligne lors du remplissage de la base, qui me permet d'éviter des sauts de ligne intempestifs (et puis, c'est nettement plus facile à lire que tout à la suite).

Je vous laisse vous amuser un peu avec les bases, mais rien de très compliqué ici.

X - Conclusion et remerciements

Vous avez maintenant acquis les bases de Ruby, il ne tient plus qu'à vous de continuer à travailler cet excellent langage.
Un gros merci à Zfred ainsi qu'à Ricky81 pour leurs conseils et leur relecture attentive.

J'espère que ce tutoriel vous aura été utile, et n'hésitez pas à venir poser vos questions sur le forum Ruby, ou à consulter la FAQ Ruby / Rails.

Vous pouvez également vous initier à Ruby on Rails.