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 :

 
Sélectionnez

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.

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 :

 
Sélectionnez

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
 
Sélectionnez

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.

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

 
Sélectionnez

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

Et voici la sortie :

 
Sélectionnez

Bonjour Pierre-Baptiste

Rappel : irb ne le comprend pas.

II-B. Tableaux

Nous allons maintenant créer un tableau :

  • mon_tableau = ["a", 12, "b"]
 
Sélectionnez

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]
 
Sélectionnez

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.

 
Sélectionnez

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"}
 
Sélectionnez

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]
 
Sélectionnez

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 :

 
Sélectionnez

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 :

 
Sélectionnez

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

 
Sélectionnez

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 ;)

 
Sélectionnez

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 :

 
Sélectionnez

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 :

 
Sélectionnez

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 :

 
Sélectionnez

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 :

 
Sélectionnez

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

Et modifions la fin de notre fichier comme suit :

 
Sélectionnez

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 :

 
Sélectionnez

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 :

 
Sélectionnez

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

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

 
Sélectionnez

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 :

 
Sélectionnez

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 :

 
Sélectionnez

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 :

 
Sélectionnez

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

Plutot que :

 
Sélectionnez

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 :

 
Sélectionnez

  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 :

 
Sélectionnez

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

 
Sélectionnez

  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 :

 
Sélectionnez

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 :

 
Sélectionnez

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

 
Sélectionnez

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 :

 
Sélectionnez

class Animal<EtreVivant

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

 
Sélectionnez

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 :

 
Sélectionnez

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 :

 
Sélectionnez

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.

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 :

 
Sélectionnez

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

 
Sélectionnez

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 :

 
Sélectionnez

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

 
Sélectionnez

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

 
Sélectionnez

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 :

 
Sélectionnez

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.

 
Sélectionnez

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 :

 
Sélectionnez

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

IV-E. while ... end

 
Sélectionnez

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 :

 
Sélectionnez

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 :

 
Sélectionnez

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

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 :

 
Sélectionnez

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

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 :

 
Sélectionnez

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 :

 
Sélectionnez

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 :

 
Sélectionnez

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 :

 
Sélectionnez

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

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 :

 
Sélectionnez

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 :

 
Sélectionnez

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 :

 
Sélectionnez

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.

 
Sélectionnez

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 :

 
Sélectionnez

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 :

 
Sélectionnez

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

 
Sélectionnez

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

 
Sélectionnez

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

 
Sélectionnez

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 :

 
Sélectionnez

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. Ecrire dans un fichier

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

 
Sélectionnez

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

 
Sélectionnez

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 tout ses sous-répertoires :

 
Sélectionnez

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". Si 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 :

 
Sélectionnez

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 chaîne 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 leur 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 RubyForum Ruby et Ruby on Rails (RoR) de developpez.com, ou à consullter la FAQ Ruby / Rails.

Vous pouvez également vous initier à Ruby on RailsInitiation à Ruby on Rails, par Yann Marec.