Accueil
Rechercher:
sur developpez.com sur les forums
Forums | Tutoriels | F.A.Q's | Participez | Hébergement | Contacts
Accueil Conception Java DotNET Visual Basic  C  C++ Delphi MS-Office SQL & SGBD Oracle  4D  Business Intelligence
Club Emploi Blogs   TV   Dév. Web PHP XML Python Autres 2D-3D-Jeux Sécurité Windows Linux PC Mac
ACCUEIL RUBY & RAILS FORUM RUBY & RAILS FAQS RUBY & RAILS TUTORIELS RUBY & RAILS LIVRES  RUBY & RAILS BLOG  RUBY & RAILS

Premiers pas avec Ruby

Date de publication : 26 Mars 2007 , Date de mise à jour : 26 Mars 2007

Par Pierre-Baptiste Naigeon (Tutos, tests et articles web)
 

Ruby est un langage interprété entièrement orienté objet, et multi plate-forme.
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.

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 chaînes, 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
IV-A. Opérateurs de comparaison
IV-B. if ... else ... end
IV-C. case ... end
IV-D. unless ... end
IV-E. while ... end
IV-F. until ... end
IV-G. for ... end
IV-H. each { ... } et autres itérateurs
IV-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. Ecrire 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 chaîne 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 chaîne (la partie entre guillemets), que nous concaténons à une autre chaîne à l'aide de l'opérateur +.
Cette deuxième chaîne 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 chaîne à 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.
A 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.

idea 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 chaîne :

  • to_i : convertit une chaîne 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 chaîne ou la longueur d'un tableau
  • upcase : convertit une chaîne en majuscules
  • downcase : convertit une chaîne en minuscules
  • swapcase : intervertit les majuscules et les minuscules dans une chaîne
  • capitalize : met en majuscule le premier caractère de la chaîne
  • reverse : inverse la chaîne ou les éléments d'un tableau
Jouons encore un peu, et découvrons les chaînes.
Une chaîne peut être définie soit entre apostrophes, soit entre guillemets. Une chaîne 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 chaîne 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 chaîne !
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 chaîne (le \n). Pour l'enlever, rien de plus simple, il suffit d'utiliser la méthode chomp, qui renvoie notre chaîne sans le caractère de retour à la ligne.

info Lorsque vous souhaitez effectuer le remplacement de variables dans une chaîne en Ruby, il faudra utiliser la syntaxe suivante (notez bien la présence de guillemets pour la chaîne 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 chaînes, de tableaux et de hash

Il est possible d'accéder aux différentes parties de la chaîne 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 inclues). 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 chaîne 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 chaîne 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 chaîne (dans le cas présent), alors que mon_tabl[0..0] retourne cette même chaîne, 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 chaîne dans un tableau en fonction d'un séparateur donné, et join vous permet de regrouper les éléments d'un tableau dans une chaîne, toujours avec un séparateur.

A vous maintenant la manipulation de chaînes :)


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 chaîne à 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 chaîne, 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 commencant 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ée 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 chaîne. Les balises #{...} permettent d'évaluer du code Ruby au sein d'une chaîne.


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 si 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ée 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 connaître à 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).

A chaque instanciation d'un objet, nous incrémentons cette variable dans la méthode "initialize".

Nous avons également crée 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. Si 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 "coeur_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 "coeur_bat" ne renvoie pas la même chose entre les deux appels. Elle a donc bien été redéfinie.

idea 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 re-dé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 coeur_bat pour la classe Animal.
Ainsi, la méthode coeur_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 chaînes :

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 vaut 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 vaut 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 {...}).


IV-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

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

warning 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 chaîne ne correspond pas, il nous retourne 0.


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


IV-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

IV-E. while ... end


puts "Je vais compter de 1 a 10"
i = 0
while (i < 10)
  i += 1
  puts i
end
A noter la méthode d'incrémentation... inutile d'essayer i++, qui ne ferait que vous générer une erreur.


IV-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

IV-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

IV-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 chaîne à 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

IV-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 : re-passe l'itération courante de la boucle
warning 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à ou ç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 soucis, 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 ou 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 tout 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 chose compliquées
ensure
  # Un code qui s'exécutera à la fin, quel que soit le cas dans le quel 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.