Ruby

Introduction au langage de programmation Ruby
Damien Gouteux, 2012. Version Alpha 05

Avant-propos

Cette page est un condensé de notes sur le langage de programmation Ruby, découvert en 2006 par le livre "Ruby in a nutschell", écrit par Yukihiro Matsumoto (Matz) le créateur du langage.

Ruby est un langage orienté objet de façon très poussé, agréable à utiliser et plus bien pourvu en bibliothèques. Venant du Japon, il a mis un peu de temps à percer dans le reste du monde avant le grand boom dû à sa bibliothèque phare pour développer rapidement des applications web : Ruby On Rails ou RoR. De nos jours, la fièvre sur RoR est un peu retombée, notamment car des bibliothèques pour d'autres langages ont copié ses apports. Il n'en reste pas moins que les qualités propres à Ruby sont toujours là, et si vous venez de C#, Java, Python ou PHP, pour votre culture informatique ou bien par simple curiosité, Ruby a de belles choses à offrir.

Vous pouvez me contacter à damien POINT gouteux AROBASE gmail.com, en remplaçant les mots en majuscule par le symbole correspondant. Bonne lecture et merci pour votre temps, la chose la plus précieuse de l'univers.

Damien Gouteux, Toulouse, le 7 avril 2012.

P.S. : cette page ne contient pas de pubs, surfez tranquille !

Cristal de Rubis

Sommaire

  1. Introduction
  2. Mise en route et rappels
    1. Installation
    2. Organisation d'un programme Ruby
      1. Commentaires
      2. Littéraux
      3. Variables
      4. Constantes
      5. Symboles
      6. Méthodes
  3. Le langage Ruby
    1. Types de base
      1. Tableau résumé des types principaux
      2. La valeur nulle
      3. Nombres et chaînes de caractères
      4. Booléens
      5. Listes
      6. Dictionnaires
    2. Opérateurs
      1. Priorités
      2. Affectation
      3. Conversion
    3. Contrôle de flux
      1. Séquence
      2. Sélection
      3. Itération
      4. Blocs
    4. Méthodes
      1. Appels de méthodes
      2. Définition de méthodes
      3. Paramètres de méthodes
    5. Classes et objets
      1. Premiers pas dans l'orienté objet
      2. Les méthodes dans l'OO
      3. L'héritage
      4. L’encapsulation
    6. Modularité
    7. Gestions des exceptions
  4. Les bibliothèques
    1. Bibliothèque du noyau
      1. Classe Object
      2. Classes Fixnum et Bignum
      3. Classe Float
      4. Classe String
      5. Classe Range
      6. Classe Regexp
    2. Bibliothèque standard
    3. Bibliothèques externes
  5. Annexes
    1. Astuces
    2. Bibliographie
    3. Liens
    4. Résumé
    5. Index
    6. Historique du document

Introduction

Ruby est un langage de script : interprété et de haut niveau. Il est orienté objet (TOUT est objet) et dispose d’une bibliothèque importante de classes prédéfinies. Il est disponible sous les termes de la licence GNU GPL et de la licence Ruby, et fonctionne sur la plupart des plates-formes du marché (notamment Mac OS X, Windows, Linux et Unix).

Ruby a été créé au Japon par Yukihiro Matsumoto, connu sous le pseudonyme "Matz". Il commença à travailler dessus en 1993, et la première version sortit en 1995. Par tradition, les nouvelles versions du langage sortent aux environs de Noël (peut être parce que Matz est d'obédience chrétienne ?). La dernier version stable a été publiée le 30 octobre 2011, il s'agit de la 1.9.3. Le nom Ruby vient en référence à Perl, un langage de script plus vieux dont Ruby se veut le "successeur". Le livre de référence (en anglais), de Dave Thomas et Andrew Hunt, Programming Ruby, est surnommé le Pickaxe en référence à sa couverture (une pioche).

En plus de Perl, Ruby est assez proche du langage Python. La motivation première pour la création de Ruby était d'apporter l'orienté objet aux langages de script, ce qu'aucun des deux langages ne faisaient à l'époque. Bien qu'au fil des ans, ces deux langages aient évolué pour permettre aux programmeurs l'accès à ce paradigme, Ruby est le seul à avoir été pensé dès le début dans cette optique. Cela lui donne une cohérence, une élégance et une puissance plus grande car il n'a pas à garder un encombrant héritage non-objet. La syntaxe quand à elle, a des parentés avec Eiffel et Ada, en en supprimant toutefois la complexité et la verbosité. Quand au paradigme objet en lui même, son implémentation se rapproche beaucoup du langage précurseur Smalltalk.

L'interpréteur officiel de Ruby est écrit en C. D'autres interpréteurs existent, à destination de la JVM (JRuby), de la CLR (IronRuby), et de Parrot (Cardinal). Il existe également Rubinius, une machine virtuelle elle-même implémentée en Ruby, semblable au projet PyPY pour Python. Depuis le 1er janvier 2007, l'interpréteur officiel est basé sur le projet YARV, apportant un gain notable de performance, longtemps le talon d'Achille de Ruby.

Ruby se présente sous la forme d'un interpréteur en ligne de commande, nommé irb, pour Interactive Ruby, permettant d'évaluer rapidement quelques lignes de code, ou bien d'un interpréteur nommé ruby, auquel on va passer le script, stocké dans un fichier avec l'extension .rb, à interpréter. Ruby est supporté par Netbeans et Eclipse, ainsi que par un IDE dédié: RubyMine. De nombreux éditeurs de texte, comme Notepad++ sous windows, offre la coloration syntaxique des fichiers en Ruby.

Première partie : Mise en route et rappels

Installation

(retour sommaire)

Pour l'installer allez sur le site de Ruby, rubrique Téléchargements. Il est préférable pour Windows de choisir le RubyInstaller, qui est un installateur facile d'usage et qui apporte, en sus de Ruby, de nombreuses bibliothèques intéressantes.

Si votre système Linux le supporte, vous pouvez faire sudo apt-get install ruby.

Sur Mac OS X, il vaut mieux passer par le site MacRuby qui offre une très bonne implémentation de Ruby.

Irb est une console Ruby où on peut taper directement du code Ruby qui est exécuté de manière directe et immédiate. Très pratique pour expérimenter rapidement, il suffit d'ouvrir une console (en appuyant simultanément sur les touches windows et R sous Windows, ou avec un terminal sous Linux et Mac OS) et de taper irb dans celle-ci pour que l'interpréteur interactif se lance. Si vous avez des problèmes pour taper les caractères { et } qui ne s’affichent pas, au lieu de irb, entrez irb --noreadline pour résoudre ce problème.

Vous pouvez également utiliser irb sur Internet, sans aucune installation sur votre machine à cette adresse.

Pour faire votre premier programme, ouvrez irb et faites :

puts "Hello World!" # Affiche Hello World!

Et voilà ! Vous êtes à présent un Nuby (un newbie en Ruby).

Organisation d'un programme Ruby

(retour sommaire)

En Ruby, le code d'un programme est une suite d'expressions. Une expression peut être un calcul simple comme 5 + 2, ou bien une instruction de sélection comme if ou while. Chaque expression est séparée par un retour à la ligne ou bien un point-virgule.

puts "hello"
puts "world!"
# Est équivalent à :
puts "hello"; puts "world!"

Il est important de bien voir qu'en Ruby, toute instruction est une expression, c'est à dire est évaluée et à une valeur, même si cette valeur est nulle.

Une autre forme d'expression possible est l'appel à une méthode avec des arguments. Le premier exemple montre cela : puts est la méthode du noyau de Ruby qui affiche un objet, passé en argument, sur la sortie standard et saute à la ligne suivante. On peut entourer le paramètre de parenthèse. print est une autre méthode qui fait la même chose mais sans passer à la ligne suivante. Toutes les expressions suivantes sont équivalentes, sachant que le caractère \n symbolise un saut de ligne.

print "Hello World!\n"
print ("Hello World!\n")
puts "Hello World!"
puts ("Hello World!")

Normalement, les expressions sont évaluées à les unes à la suite des autres, en partant de la première venant dans le fichier contenant le programme. Mais Ruby permet aussi de préciser un code à exécuter au début du programme avec les blocs BEGIN {... } et à la fin END { ...}.

Le code suivant affichera Hello World! sur deux lignes :

puts 'World!'
BEGIN {
  puts 'Hello'
}

Commentaires

Un commentaire est une information laissée dans le code source par un programmeur. Un commentaire apporte une information utile qui aide à comprendre ce que fait le code ou pourquoi il le fait ainsi. Un commentaire commence par un # et se poursuit jusqu'à la fin de la ligne.

# commentaire jusqu'à la fin de la ligne

Littéraux

Un littéral est une valeur écrite "en dur" dans le code. Dans l'expression 5 + 2, 5 et 2 sont des valeurs entières écrites en dur, donc des littéraux. On peut ainsi écrire des types simples : nombres entiers 5, réels 2.3 ; ou bien plus complexes : chaînes de caractères "hello", listes [ 1, 2, 3 ], etc.

Variables

Une variable est un concept qui se décompose de deux éléments : un identificateur et une valeur que l'identificateur désigne. Voici deux exemples de déclaration de variables, nom et chiffre.

nom = 'bonjour'
chiffre = 5

Lorsque l'on déclare une variable, on doit spécifier sa valeur, via l'opérateur d'affectation. La première reçoit une valeur qui est la chaîne de caractère 'bonjour' et la seconde l'entier 5. La valeur de la variable est toujours typée, on peut accéder à son type en utilisant la méthode class (on voit ici une nouvelle manière d'appeler une méthode, en l'associant à un objet via l'opérateur .).

puts nom.class     # Affiche String, c'est à dire une chaîne de caractère
puts chiffre.class # Affiche Fixnum, c'est à dire un entier

Au cours de l'exécution du programme, une variable peut changer de valeur, à nouveau en utilisant l'opérateur d'affectation. L'identificateur désignera alors la nouvelle valeur qui peut être d'un type différent que la précédente valeur.

nom = 5     # La variable nom contient un entier
nom = 'abc' # La variable nom contient à présent une chaîne

En Ruby, tous les éléments manipulés sont des objets. Les littéraux 5 et 'bonjour' sont des objets, ainsi que les variables nom et chiffre. Voici un exemple utilisant l'affectation entre deux variables (et non d'un plus l'affectation d'un littéral à une variable) :

a = 5
b = a
a = 7
puts a # Affiche 7
puts b # Affiche 5

Nous avons ici deux variables, a et b. a référence la valeur 5. Puis a et affecté à b. a et b désigne donc la même valeur à ce moment là, 5. Puis on assigne à la variable a une nouvelle valeur, 7. Que désignera b dans ce cas ? Et bien toujours 5 ! Il faut penser l'opérateur d'affectation comme la création d'un lien entre l'identificateur et la valeur, qui change donc le contenu de la variable, et bien voir que deux variables peuvent partager la même valeur. Voici un autre exemple :

a = "abc"
b = a
a.capitalize!
puts a # Affiche Abc
puts b # Affiche Abc

Ici, b = a effectue la copie de la référence, c'est à dire que b et a désigne le même objet, "abc". On modifie l'objet via l'appel de la méthode capitalize! en passant par l'identificateur a. Mais b désignant le même objet, donc quand on cherche à afficher la valeur de b, on a exactement la même que celle de a, celle qui a été modifiée par l'appel de la méthode.

On remarquera qui si une variable peut se trouver à gauche ou à droite de l'opérateur d'affectation =, un littéral ne peut être qu'à droite.

Pour savoir si une variable est définie, on utilisera la méthode defined? avec la variable en argument.

a = 5
defined? a # Retourne "local-variable".
defined? b # Retourne la valeur nulle, nil

Les variables ont une portée et une visibilité. La portée est jusqu'où dans le code la variable vivra avant d'être détruite, alors que la visibilité est sa capacité à être vue. Voici un exemple concret utilisant la définition d'une méthode :

b = 22 def hello
  Ici b n'est pas visible !
  a = 5
end
hello      # On exécute la méthode hello
defined? a # Retourne nil : a n'est pas défini ici

Le code de la méthode hello définit une variable a. Sa portée et sa visibilité sont limitées à la méthode elle-même. Une fois celle-ci exécutée, la variable a est détruite. On parle de contexte d'évaluation : c'est à dire dans le contexte de la fonction hello, a est définie et a pour valeur 5, alors qu'ailleurs, elle n'existe pas. Chaque fonction, chaque bloc et chaque boucle a un contexte spécifique en Ruby, c'est à dire que les variables définies à l'intérieur ne seront pas connues du reste du programme.

Il existe des variables, appelées variables globales, qui peuvent être vues depuis n'importe où dans le code et qui ne seront détruire qu'à la fin du programme. Les variables globales commencent par un $ et retournent nil si on essaye d'y accéder avant de les avoir initialiser (alors que pour les variables normales, cela soulève une erreur). Plusieurs sont prédéfinies et accessibles et on peut déclarer les nôtres mais il ne faut pas en abuser :

$0 # Retourne le nom du script lancé ou "irb" si on l'utilise

$b = 22
def hello
  Ici $b est visible car globale !
  a = $b
end
hello      # On exécute la méthode hello

Par convention, on écrit les noms de variables en minuscules, et si le nom est composé de plusieurs mots, on les sépare par des soulignés ou tirets bas (_).

Constantes

Une constante est une variable spéciale dont le lien entre son identificateur et la valeur ne peut changer. C'est à dire une fois le lien formé, il ne doit pas être défait. En Ruby, une constante est indiquée par le premier caractère de son identificateur en majuscule. L'identificateur peut être entièrement en majuscule pour faciliter son repérage dans le code. Tenter de changer une constante aboutira à un avertissement en Ruby, mais le changement sera tout de même effectué.

A = "abc"
A = "def" # Déclenche un avertissement.

Il y a trois conceptions de la constance possibles : la première, c'est la constance du lien identificateur-valeur, c'est cette conception que Ruby implémente. La seconde c'est la constance de la valeur elle-même : c'est l'impossibilité de changer la valeur. En Ruby, on peut changer la valeur d'une constante.

A = "abc"
A.capitalize! # La valeur de A est maintenant Abc.

Pour avoir cette constance de valeur, on utilisera la méthode freeze. Une fois gelée, la valeur ne pourra plus être modifiée.

A = "abc"
A.freeze
A.capitalize! # Impossible ! Cela déclenche une erreur.

Une dernière forme de constance est la constance de type. C'est à dire qu'un identificateur donné ne peut référencer que des valeurs de même type. En Ruby, c'est le lien identificateur-valeur qui est constant, donc la constance de type est achevée dans ce sens. Les variables, elles, peuvent successivement prendre n'importe quelle valeur de n'importe quelle type.

a = "abc"
a.class # Retourne String
a = 5
a.class # Retourne Fixnum

Pour savoir de quel type est une variable ou une constante, on peut passer par la méthode class que nous avons vu précédemment, qui retourne son type, ou bien la méthode instance_of? qui renvoit vrai si la variable est du type passé en paramètre.

a = "abc"
a.instance_of? String # Retourne true
a = 5
a.instance_of? Fixnum # Retourne Fixnum

Les constantes ont une visibilité globale par défaut, il ne faut donc pas mettre de $ au début d'une constante.

Symboles

Le symbole est un moyen de considérer l'identificateur seul, sans la valeur qui lui est attaché.

:a.class # Retourne Symbol

Méthodes

Une méthode (ou fonction, ou sous-programme) est une partie de code qui n'est pas exécuté de suite, mais peut être exécuté à n'importe quel moment en exécutant la méthode, on parle alors d'appel de la méthode. Pour utiliser une méthode, il y a donc deux phases : la définition de celle-ci, puis son appel. Reprenons l'exemple précédent :

def hello
  a = 5
end
hello # On exécute la méthode hello
defined? a # Retourne nil : a n'est pas défini ici.

Cette méthode s'appelle hello. Lorsque elle est appelée, son code est exécuté un résultat est toujours renvoyé. Ici, ce sera le résultat de la dernière expression évaluée, qui est a = 5, donc 5.

Une méthode est en fait, comme les constantes, un genre de variable particulier. On a bien un identificateur, hello, et sa valeur qui est le code.

On peut créer un alias pour une méthode, c'est à dire un deuxième identificateur qui renverra à la même valeur.

alias :bonjour :hello
bonjour # On exécute la méthode hello via l'identificateur bonjour

On a vu également un autre moyen d'appeler une méthode : la syntaxe objet.méthode(paramètres). On dit que le message méthode est passé l'objet. En fait, ce moyen est le seul. Lorsque nous appelons dans notre programme directement une méthode sans préciser l'objet, le message sera envoyé à un objet par défaut, auquel on peut accéder par la variable self.

self.class # Retourne Object

def hello
  puts "hello"
end

hello      # Affiche "hello"
self.hello # Affiche "hello"

Les deux dernières lignes sont donc équivalentes. Chaque objet peut donc répondre à un certains nombres de messages propre à lui-même ou à son type. Voici quelques exemples :

4.next                   # Retourne 5
"hello ".concat("world") # Retourne "hello world"

Par convention, le nom d'une méthode est toujours en minuscule. S'il comporte plusieurs mots, on les sépare par un souligné ou tiret bas (_). Si une méthode retourne un résultat de type booléen (vrai ou faux), on terminera son nom par un point d'intérrogation. Si la méthode modifie l'objet sur lequel elle est appelée, on terminera son nom par un point d'exclamation. Exemples : instance_of?, capitalize!.

Il est donc important de connaître les différents types de Ruby, et les différentes méthodes dont-ils disposent.

Types de base

(retour sommaire)

Tableau résumé des types principaux

TypeNom en RubyMode de copieExemples
EntierFixnum, BignumValeur1 010 (8 en octal) 0x10 (16 en hexa)
0b10 (2 en binaire) 124_345 (ignoré)
RéelFloatValeur1.2 1e10
BooléensTrueClass
FalseClass
Valeurtrue
false
Chaîne de caractèresStringRéférence"youpi"
'youpi'
%s(youpi)
ListeArrayRéférence[1, 2, 3]
%w(1 2 3)
DictionnaireHashRéférence{'a' => 65}
IntervalRangeRéférence1..2 (inclusif)
1...3 (exclusif)
SymboleSymbolRéférence:a
Valeur nulleNilClassRéférencenil

La valeur nulle

Il existe en Ruby une valeur nulle, nil. On peut tester si une variable a pour valeur nil avec la méthode nil?

a = nil
a.nil? # Retourne vrai

Nombres et chaînes de caractères

Voici un exemple d'utilisation dans le code, on va affecter à 4 variables les constantes littérales suivantes :

un_reel = 3.14
un_entier = 5
une_chaine = "bonjour monsieur O'Reilly\n"
une_autre_chaine = 'bonjour le "monde"'

On remarque que les chaînes de caractère peuvent s'écrire entre guillemet simple ou double, ou encore avec la construction %s(...). Cela permet d'utiliser le guillemet simple dans une chaîne délimitée par des guillements doubles et inversement. Seule une chaîne délimitée par des guillemets doubles interprétra les caractères d'échappement. Ainsi \n sera remplacé par un saut à la ligne.

Tout obéit au paradigme objet dans Ruby. Ainsi on peut directement appeler une méthode sur un littéral, c'est à dire utiliser la forme objet.méthode :

5.to_s                      # Retourne une chaîne représentant le nombre 5
"bonjour".upcase            # Retourne une chaîne avec toutes les lettres en majuscule
"bonjour".include?('bon')   # Renvoie vrai, car la chaîne bonjour contient bon
"hello world".length        # Renvoie 11
"hello world".index("h")    # Renvoie la position du caractère h : 0
-4.abs                      # Renvoie la valeur absolue de -4 : 4

true, false, nil, __LINE__, __FILE__ sont appelés pseudo variable et sont inassignables : c'est à dire que comme les constantes littérales, elles ne peuvent apparaître à gauche d'une affectation.

On fera bien attention que 99. n'est pas un réel valide en Ruby. En effet, avec la possibilité d'appeler une méthode sur un objet, Ruby ne sera pas interpréter cette notation. Il faut à chaque que l'on veut écrire un réel ainsi, mettre une décimale : 99.0.

Booléens

Le type booléen dispose de deux valeurs : vrai, true, et faux, false.

En Ruby, les valeurs false et nil sont fausses. Tout le reste est vrai, même la valeur 0.

On peut utiliser les opérateurs booléens suivants : and, or, not. Les deux premiers sont des opérateurs binaires, le dernier est unaire. Voici les tables de vérité de ces différents opérateurs :

valeur avaleur ba and ba or bnot a
falsefalsefalsefalsetrue
falsetruefalsetruetrue
truefalsefalsetruefalse
truetruetruetruefalse

Listes

Appelées aussi tableaux. L'expression %w(1 2 3) permet de créer un tableau sans [] mais attention, chaque élément sera une chaîne de caractère. Ainsi : %w(1 2 3) est équivalent à ["1", "2", "3"] et pas [1, 2, 3] !

On peut mixer les types de données au sein d'une même liste. Ainsi une liste peut contenir en même temps un entier, une chaîne, un booléen, etc. Chaque élément a une position, numérotée de 0 pour la première à la longueur de la liste-1 pour la dernière. Pour accéder à un élément donné, on utilise l'opérateur [] avec l'indice de sa position. Si l'indice donné ne correspond à aucun élément, la valeur nulle, nil est retournée.

vide = []                        # Liste vide
vide2 = Array.new                # Autre moyen pour avoir une liste vide
liste = [1, 2, 'bonjour']
liste[0]                         # Retourne 1
liste.last                       # Retourne 'bonjour'
liste[1] = 3                     # Le tableau contient à présent : 1, 3, 'bonjour'
liste.length                     # Retourne 3
liste.push('au revoir')          # Ajoute à la fin du tableau 'au revoir'
autre = %w{ chien chat serpent } # Crée la liste ["chient", "chat", "serpent"]

Dictionnaire

Appelées aussi tableaux associatifs ou table de hachage. Un dictionnaire associe une clé à une valeur. La clé et la valeur peuvent être de n'importe quel type.

vide = {}
dico = { 'France' => 'Paris', 'Allemagne' => 'Berlin', 'Italie' => 'Rome' }
puts dico['France']        # Affiche Paris
dico['Espagne'] = 'Madrid' # Ajoute une nouvelle paire clé/valeur.
dico.length                # Retourne 4
dico.values                # Retourne la liste des valeurs ['Paris', 'Berlin', 'Rome', 'Madrid']
dico.keys                  # De même pour les clés ['France', 'Allemagne', 'Italie', 'Espagne']
dico['Palombie']           # La valeur nulle (nil) est retournée si la clé n'existe pas
vide2 = Hash.new(0)        # Si la clé n'existe pas, 0 sera retourné (nil par défaut)

Opérateurs

(retour sommaire)

Il y a en tout 25 opérateurs. On peut les classifier ainsi :

  • Les 6 opérateurs arithmétiques : +, -, /, *, % (modulo), ** (puissance)
  • Les 5 opérateurs binaires : &, |, ^, <<, >>
  • Les 7 opérateurs d’affectation : =, +=, -=, /=, *=, %=, **=
  • Les 5 opérateurs de comparaison : <, <=, >, >=, ==, !=, <=>
  • Les 3 opérateurs booléens : and, or, not ou &&, ||, !

Les opérateurs booléens ne renvoient pas vrai ou faux mais la dernière valeur évaluée. On rappelera que tout ce qui n'est pas égal à nil ou false est considéré comme vrai.

L'opérateur <=> renvoie 0 si les deux valeurs sont égales, -1 si la valeur de gauche est inférieure à celle de droite, et 1 si la valeur de gauche est supérieure à celle de droite. L'opérateur == peut aussi s'effectuer à l'aide des méthodes eql? et equal? mais nous verrons plus loin qu'il est possible de différencier leur comportement.

Les opérateurs sont en fait des appels de méthodes déguisées. Lorsque l'on fait a + b, derrière Ruby exécute la méthode + de l'objet a en lui passant en argument b.

On peut combiner les opérateurs d'arithmétiques avec l'opérateur d'affectation =. On parlera alors d'opérateurs raccourcis :

i = i – 1 # Est équivalent à :
i -= 1
i = i * 2 # Est équivalent à :
i *= 2

Il n'y a pas d'opérateurs de pré et post décrémentation (++ et --) en Ruby !

L'utilisation d'un opérateur avec des arguments invalides peut générer une erreur : si l'on fait 5 / 0 on obtiendra alors une ZeroDivisionError.

Priorités

Les opérateurs ont chacun un ordre de priorité. En effet, dans l'expression 5 + 2.5 * 2, il est important que l'opérateur * de multiplication s'exécute avant celui d'addition. Le tableau suivant présente les opérateurs par ordre de priorité, de la plus élevée à la plus faible :

Opérateurs
::
[]
**
-(unaire) +(unaire) ! ~
* / %
+ -
<< >>
&
| ^
> >= < <=
&=& == === != =~ !~
&&
||
.. ...
= += -= *= /= %=
not
and or

On remarquera que si and et or ont la même priorité, ce n'est pas le cas de && et || qui ont chacun une priorité différente et qu'elles sont différentes de celle pour and et or!

a = 100
b = 200
a = 3 && b = 5
# a est égal à 5 et b aussi !!!
a = 100
b = 200
a = 3 and b = 5
# a est égal à 3 et b est égal à 5

L'opérateur or (ou ||) est paresseux : dès qu'il rencontre une variable à vrai, il arrête d'évaluer ses paramètres.

r = 2 or (a=3) # r égal à 2 et l'affectation de 3 à a n'est pas effectuée !

Affectation

L'opérateur d'affectation consiste à lier un identificateur à une valeur. Elle présente plusieurs formers :

a = 5
a, b = 5, 6

Dans la dernière ligne, a est associé à la valeur 5 et b à 6. Si il y a plus d'identificateurs à droite que de valeurs à gauche, les derniers recevront la valeur nulle, nil.

On peut utiliser cela aussi pour les tableaux :

a = [1, 2, 3]
a[0], a[1], a[2] = 'a', 'b', 'c'

Si le tableau comporte plus d'élément qu'il y a de variables à gauche de l'affectation, on peut mettre une étoile devant le dernier identificateur de la liste pour préciser qu'il prend tout le reste du tableau.

a, b = [1, 2, 3]
puts b # Affiche 2
a, *b = [1, 2, 3]
puts b # Affiche [2, 3]

On peut également faire des listes d'initialisation, en séparant chaque affectation par une virgule, mais attention, avec cette construction, la première variable recevra un tableau de toutes valeurs affectées suivantes :

a = 1, b = 2, c = 3, d = 4
# a vaut [1, 2, 3, 4] !

Conversion

L'opération de conversion, connue aussi sous le nom de transtypage, ou casting en anglais, consiste à transformer une valeur d'un type donné en une valeur correspondante dans un autre type. Ruby utilise des méthodes pour effectuer cela :

  • to_s : méthode pour obtenir une chaîne
  • to_i : méthode pour obtenir un entier d'un flottant ou d'une chaîne
  • to_f : méthode pour obtenir un flottant d'un entier ou d'une chaîne

"5".to_i + 5 # On obtient 10

Contrôle de flux

(retour sommaire)

Le flux est la suite d'instructions exécutées par l'interpréteur Ruby. Normalement, l'exécution est séquentielle, mais nous allons voir qu'elle peut être aussi sélectif et itératif.

Séquence

instruction-1 # nouvelle ligne
instruction-2
instruction-3 ; instruction-4

Au contraire de nombreux langages, le ';' n'est pas un opérateur de terminaison d'une instruction mais de séparation de deux instructions successives.

On peut réunir les instructions en un morceau de code entouré de begin et end.

begin
  actions
end

Sélection

If et unless

La sélection est l'acte d'exécuter une instruction si et seulement une condition est réunie. On distingue deux structures pour la sélection : le if qui exécute l'instruction ou les instructions suivantes si la condition est vraie, et le unless, que l'on peut traduire par à moins que, sauf si, qui exécutera le ou les instructions suivantes si la condition est fausse.

Les éléments entre crochets et en italique sont optionnels. L'étoile indique que l'élément est répétable. Les éléments entre accolades et en italique séparés par | indique un choix obligatoire entre plusieurs possibilités.

if condition { then | \n | ; }
  instructions
[ elsif condition { then | \n | ; }
  instructions
]*
[ else
  instructions
]
end

unless condition { then | \n | ; }
  instructions
[ else
  instructions
]
end

Une autre forme est de mettre la sélection après l'expression que l'on veut garder par la condition.

action if condition
action unless condition

Si la condtion de if est une expression régulière seule, alors elle sera évaluée sur la variable globale $_. On notera aussi qu'il n'y a pas besoin d'entourer les conditions par des parenthèses, comme dans de nombreux langages.

mon_nombre = 5
if mon_nombre == 5
  puts "mon nombre vaut 5"
elsif mon_nombre == 4
  puts "mon nombre vaut 4"
else
  puts "mon nombre ne vaut pas ni 5 ni 4"
end
# Affiche "mon_nombre vaut 5"

puts "hello" if mon_nombre == 5
# Affiche "hello"

Case

L'instruction case propose un choix entre plusieurs valeurs. Les instructions correspondantes à la valeur de l'expression testée seront exécutées (et contrairement à d'autres langages, seulement celles-ci).

case expression
  when expression [ , expression ]* { then | \n | ; }
    instructions
  [ when expression { then | \n | ; }
    instructions
]*
  [ else
    instructions
]
end

Si la valeur qui figure dans la clause when est une classe, Ruby fera automatiquement un appel à la fonction instance_of? sur l'expression testée en passant en paramètre la classe.

Si la valeur qui figure dans la clause when est une expression régulière, Ruby fera automatiquement appel à la fonction match avec en paramètre l'expression testée. On pourra accéder au résultat retourné avec la méthode Regexp.last_match.

Opérateur ternaire

Enfin, il existe une dernière façon de procéder à une sélection : c'est d'utiliser l'opérateur ternaire ?. Il prend trois arguments : une condition à évaluer, une valeur à retourner si la condition est vraie, et une valeur à retourner si elle est fausse.

a = true ? "vraie" : "fausse"
puts a # Affiche "vraie"

Itération

For et while

L'itération est le fait d'exécuter plusieurs fois la même instruction au sein d'une boucle. Généralement, la boucle est gardée par une condition qui est réévaluée à chaque nouvelle itération de la boucle. Il existe deux principales constructions pour faire des boucles en Ruby : for et while. La première parcours une itération en affectant une valeur, et la seconde est une simple boucle gardée par une condition. Il existe aussi until qui à la différence de while, qui exécute la boucle tant que la condition est vraie, exécutera la boucle tant que sa condition sera fausse.

for variable in liste_de_valeurs { do | \n | ; }
  instructions
end

while condition { do | \n | ; }
  instructions
[ else
  instructions ]

end

until condition { do | \n | ; }
  instructions
end

Comme pour la sélection, une autre possibilité est de mettre la construction itérative après l'expression que l'on veut exécuter plusieurs fois. Dans ce cas, l'instruction est évaluée une première fois, puis la condition de la boucle est vérifiée.

instructions while condition
instructions until condition

A l'intérieur d'une boucle, l'instruction break permet de casser la boucle et d'en sortir immédiatement. L'instruction next permet elle de passer à l'itération suivante, sans exécuter les instructions restantes de la boucle. Le mot-clé redo permet de recommencer l'itération actuelle, sans réévaluer la condition de la boucle. Enfin, le mot-clé retry permet de recommencer complètement une itération, à son initialisation.

Loop

La méthode loop permet d'itérer sur un bloc de code, sans paramètre.

a = 10
loop {
  puts a
  a = a -1
  break if a == 5
end

Blocs

D'autres constructions, à l'aide de la méthode times par exemple, permettent de faire des boucles. On utilisera alors les blocs. Un bloc est délimité par do et end ou bien { et }. Il peut avoir des paramètres, que l'on mettra au tout début du bloc, encadrés par | ... |. Un bloc fonctionne comme un morceau de code que l'on passe en paramètre d'une méthode.

do 
  |paramètres|
  instructions
end

{
  |paramètres|
  instructions
}

Exemples

2.times do
  puts 'bonjour !'
end
# Affiche deux fois "bonjour!"

for x in 1..3 do
  puts x
end
# Affiche 1, 2, et 3 sur trois lignes.

(1..3).each do
  |x|
  puts x
end
# Affiche 1, 2, et 3 sur trois lignes.

mon_dic = {'a' => 1, 'b' => 2}
for key, value in mon_dic
  puts key, value
end
# Affiche a 1, b 2, à chaque fois sur deux lignes.

b = 3.times {
  puts 'hello'
}
# Affiche 'hello' trois fois.

cpt = 0 puts (cpt+=1) while cpt < 5
# Affiche 1, 2, 3, 4, 5 sur cinq lignes.

Certaines méthodes, comme each, collect, detect sont des itérateurs : elles vont itérer parmi toutes les valeurs de l'objet sur lequel elles sont appelées.

['a', 'b', 'c'].each { |x| puts x }
# Affiche 'a', 'b', 'c' sur trois lignes.

['a', 'b', 'c'].collect { |word| word.capitalize }
# Retourne une nouvelle liste ['A', 'B', 'C']

['a', 'b', 'c'].detect { |element| element == 'a'}
# Retourne 'a'

Un dernier usage des blocs est la construction avec catch et throw. On passe un bloc à un catch étiquetté avec un identificateur qui pourra être quitté à n'importe quel moment via l'appel à throw avec l'étiquette correspondante.

catch :etiquette {
  a = 10
  while a > 0
    puts a
    a = a - 1
    throw :etiquette if a == 5
  end
}

Un bloc peut être stocké dans un objet Proc. Dans ce cas là, il agit comme une fonction anonyme.

p = Proc.new do |a, b|
  a + b
end
p.call(2,3) # Retourne 5

Méthodes

(retour sommaire)

Une méthode, appelée aussi fonction, sous-programme ou procédure, est un morceau de code que l'on peut exécuter plusieurs fois, via un appel. L'appel ce fait via l'intermédiaire d'un identifiant, le nom de la méthode. On peut spécifier des paramètres formels pour une méthode, qui seront remplacés, au moment de l'appel, par des paramètres réels (ou effectifs) qui peuvent être différent à chaque appel.

Appels de méthodes

On doit entourer les paramètres réels seulement si cela pose un problème de compréhension à l'interpréteur. Sinon on peut les omettre. S'il n'y a pas de paramètres, on peut se passer également de parenthèses. Les différents paramètres sont séparés par une virgule. On utilisera dans les exemples la méthode add qui réalise une simple addition de deux paramètres, et la méthode assert_equal vérifiant l'égalité de ses deux paramètres, que nous spécifierons dans le paragraphe suivant.

nom (paramètres réels)

puts 'bonjour'
puts ('bonjour')

number = 5
assert_equal (5, number)

add(2 ,3)        # Retourne 5
add 2, 3         # Retourne 5
add 2, add(3, 4) # Retourne 9

Le nombre de paramètres réels doit correspondre exactement au nombre de paramètres formels définis, sinon une erreur ArgumentError est générée, sauf si une valeur par défaut a été spécifiée pour le paramètre formel. Dans ce dernier cas, le paramètre prendra comme valeur sa valeur par défaut.

En Ruby, l'appel de fonction se fait par passage de messages : le nom de la méthode est un message et l'objet est le receveur de ce message. Si le receveur n'est pas spécifié, l'objet main est utilisé (main est de type Object).

En Ruby, on peut toujours passer un bloc à une méthode comme ultime paramètre. Ce bloc peut être appelé à l'intérieur de la fonction à l'aide du mot clé yield suivit du nombre de paramètres nécessaires à l'exécution du bloc, séparés par des virgules.

def say_hello(name)
  yield name
end

say_hello("john") do |s|
  puts "hello #{s}"
end
# Affiche "hello john"
say_hello("martin") { |s|
  puts "bonjour #{s}"
}
# Affiche "bonjour martin"
say_hello("aude")
# Erreur LocalJumpError car il n'y a pas de bloc donné !

Définition de méthodes

Syntaxe

def identifiant(paramètres formels)
  instructions
end

Exemple

def assert_equal(expected, actual)
  if expected != actual
    puts "failed"
  end
end

def add(a, b)
  return a+b
end

L'instruction return indique que la méthode se termine et renvoit la valeur suivant le mot-clé. Si aucune valeur n'est spécifiée, la valeur nulle est renvoyée. Les méthodes retournent toujours une valeur, et si aucune n'est spécifiée, c'est celle de la dernière expression exécutée dans la méthode qui sera retournée.

def five
  5 # C'est équivalent à return 5
end
puts five

Paramètres de méthodes

Les différents paramètres sont séparés par des virgules. Si il n’y pas de paramètres (), les parenthèses sont optionnelles aussi bien pour l'appel de la méthode que sa définition.

def pipo()
  puts 'pipo'
end
pipo() # Affiche "pipo"
# Equivaut à :
def pipo
  puts 'pipo'
end
pipo   # Affiche "pipo"

Néanmoins, mon conseil est de toujours mettre des parenthèes. En effet, si on déclare une fonction du même nom qu’une variable, et que on ne met pas de parenthèses, Ruby préférera croire que c’est la variable plutôt qu’un appel à une fonction !

def pipo()
  puts 'pipo'
end
pipo     # Affiche "pipo"
pipo = 3 # Déclaration d'une variable entière égale à 3
pipo     # Affiche 3 : Ruby choisit la variable !
pipo()   # Affiche "pipo" : L'identificateur pipo correspond toujours à une méthode aussi !

Il y a deux moyens de passer un paramètre : par position ou par nom. On peut mixer les deux, mais les paramètres par position doivent toujours venir en premier et ne peuvent plus être utilisés dès qu'il y a un paramètre par nom qui est passé.

def add3(a, b, c)
  return a+b+c
end

add3(2, c=3, b=5) # Retourne 10

On peut aussi définir des valeurs par défaut pour les paramètres.

def add3(a=1, b=2, c=2)
  return a+b+c
end

add3(0) # Retourne 4. a est mis à 0.

Il existe également des paramètres spéciaux, qui doivent être mis à la fin des paramètres formels dans la définition de la fonction. Il ne peut y en avoir qu'un de chaque type, et l'ordre doit être respecté. Le premier type est symbolisé par le préfixe * et contiendra tous les paramètres supplémentaires passés à la fonction dans une liste. Le second type est préfixé par ** et contidendra tous les paramètres supplémentaires passés par nom dans un dictionnaire où le nom sera utilisé comme clé. Enfin le dernier type est préfixé par & et permet de convertir le bloc passé à la méthode en object Proc. Ces trois paramètres sont toujours optionnels et seront égaux à nil s'ils ne sont pas renseignés.

def make_something_on_list(*liste, &action)
  for i in liste do
    yield i # ou : action.call(i)
  end
end

make_something_on_list(1, 2, 3) do |i| puts i end # Affiche 1, 2, 3 sur trois lignes.

Lorsque l'on exécute un appel d'une méthode, celle-ci doit être définie à ce moment de l'exécution. Mais on peut la mentionner avant dans le corps d'autres méthodes, tant que celles-ci ne sont pas appelées.

On peut annuler la définition d'une méthode avec le mot clé undef.

undef :make_something_on_list

On peut faire des méthodes enchâssées, c'est à dire des définitions de méthode dans une méthode.

Exemples

def sayHello(name)
 result = "Hello, " + name
 return result
end

puts sayHello("Elyane")
puts (sayHello("Marianne"))
puts (sayHello "Josianne") # Ruby conseille de mettre des parenthèses.
puts sayHello "Ariane"     # De même.

Classes et objets

(retour sommaire)

Nous avons vu que tout était objet dans Ruby. Et que chaque objet a un type. Le type est une définition abstraite, et les objets sont ses exemples concrets. Par exemple, vous pouvez avoir un type Voiture et ses objets : la voiture de Paul et la voiture du Charlotte. Au niveau du type, on a défini qu'une voiture avait une couleur. Et au niveau des objets, on précisera la valeur de cette couleur : verte pour Paul et bleue pour Charlotte. Ces objets particuliers sont appelés instances de la classe Voiture. Cette conception des choses, Classe/Instances, s'appelle l'orienté objet ou OO. On parlera de programmation orienté objet, ou POO.

Premiers pas dans l'orienté objet

L'orienté objet implique donc deux étapes : la définition de classes, et la création d'instances de ces classes. Commençons par définir une classe, nous allons voir dans un premier temps la syntaxe complète, puis différents exemples pour l'illustrer :

class Identifier < SuperClasse
  instructions
end

class Voiture
  def initialize(c)
    @couleur = c
  end
end

On a défini ici la classe Voiture par une structure qui commence par le mot clé class suivit du nom que l'on veut donner à la classe et se termine par end. Le nom de la classe doit commencer par une majuscule, indiquant ainsi la constance entre l'identificateur et la classe qu'il désigne. Entre, on définit la méthode initialize. Il faut bien voir que ce qu'on définit là, si cela appartient à la classe et à ses futures instances, ne pourra être exécuté qu'au niveau des instances de classe. On remarque aussi une variable préfixée par @. Ce préfixe indique que c'est une variable d'instance, qui a comme portée la durée de vie de l'instance à laquelle elle appartient et est visible dans toutes ses méthodes. Une classe peut se voir comme constituer de deux parties : une partie statique qui est un regroupement de variables (ici @couleur), appelées aussi attributs dans ce cas là, et d'une partie dynamique, les méthodes (ici initialize). A chaque fois, on définit cela au niveau de la classe, mais c'est au niveau de l'instance que cela prend son sens.

Pour créer une instance, opération que l'on appelle l'instanciation, on fera :

voiture_de_paul = Voiture.new("verte")
voiture_de_charlotte = Voiture.new("bleue")

On appelle la méthode new de la classe Voiture, qui va créer une instance, et appeler dessus la méthode initialize en lui passant ses paramètres, ici une chaîne représentant la couleur de la voiture. La méthode initialize est automatiquement appelée par new, et elle est appelée un constructeur. Elle permet d'initialiser l'instance.

On peut bien sûr avoir plus d'une méthode dans une classe. Ajoutons la méthode rouler à notre classe :

class Voiture
  def rouler()
    puts 'je roule'
    end
end

Pour ajouter une méthode à une classe, il suffit de réouvrir la structure class et d'y écrire au milieu la ou les méthodes à ajouter. La méthode rouler que l'on ajoute ne prend pas d’arguments comme l'indique les parenthèses qui suivent le nom de la méthode et sont donc optionnelles. Au contraire de la méthode initialize, qui est une méthode spéciale automatiquement appelée lors de la création d’une nouvelle instance, nous devons appeler explicitement rouler.

voiture_de_sam = Voiture.new("noire")
voiture_de_sam.rouler # Appel de la méthode rouler qui affiche "je roule"

Et maintenant, on veut ajouter un nouvel attribut à la classe Voiture. On veut que chaque voiture est une marque. Il nous faut donc redéfinir la méthode initialize.

class Voiture
  def initialize(c, m)
    @couleur = c
    @marque = m
  end
end

Les instances précédentes n'ont pas d'attribut @marque puisqu'elles ont été créées avant. Il faut donc les créer à nouveau avec les marques 'Lyon' et 'CNX' :

voiture_de_paul = Voiture.new("verte", 'Lyon')
voiture_de_charlotte = Voiture.new("bleue", 'CNX')

La méthode new de la classe Voiture va créer deux nouvelles instances et appelera la nouvelle fonction initialize pour ces deux objets en leur passant ses paramètres. Pour voir si tout c’est bien passé, vous pouvez faire un appel à la méthode inspect :

voiture_de_paul.inspect # Affiche @couleur="verte" @marque="Lyon"

Les méthodes dans l'OO

Une méthode en Ruby appartient à un objet et ce déclare avec la syntaxe suivante :

def nom_methode(paramètres)
  code
end

Nous avons vu jusqu'à présent seulement des méthodes d'instance définies à l'intérieur d'une classe. Mais on peut aussi définir une méthode pour la classe elle-même, ou bien pour une instance particulière.

Méthode d'instance

La définition de la méthode se fait dans la classe de l’objet. Ces méthodes sont appelées méthodes d’instances.

class Voiture
  def rouler()
    puts 'je roule en #{@marque}'
  end
end

v = Voiture.new("blanche", "CNX")
v.rouler # Affiche "je roule en CNX"

On peut remarquer au passage un moyen simple pour inclure une variable dans une chaîne : la syntaxe #{code} permet cela.

Méthode de classe

On peut définir une méthode pour la classe elle-même. Il suffit de faire précéder son nom par le nom de la classe suivit d'un point dans sa définition. Ces méthodes sont appelées méthodes de classe.

class Voiture
  def Voiture.affiche_nom_classe()
    puts 'Voiture'
  end
end

Voiture.affiche_nom_classe() # Affiche "Voiture"

Contrairement à d'autres langages, on ne peut appeler une méthode de classe que sur la classe elle-même et pas une instance de celle-ci.

Méthode singleton

Une méthode singleton est une méthode définie pour une seule instance. On fait précéder son nom par l'identificateur de l'objet concerné sous la forme identificateur.nom_methode. Une méthode définie pour un objet particulier s’appelle une méthode singleton.

v1 = Voiture.new('rouge', 'CNX')
def v1.rouler_vite()
  puts 'je roule vite'
end
v1.rouler_vite() # Affiche : je roule vite

v2 = Voiture.new('violette', 'Lyon')
v2.rouler_vite() # Erreur! rouler_vite n'est pas définie pour v2 mais seulement pour v1 !

On peut aussi définir une méthode singleton par la construction suivante :

aa = "hello"
class<< aa
  def to_s
    "The value is '#{self}'"
  end
end
puts aa.to_s

On remarquera que quand on définit une méthode hors d'une classe, celle-ci est définie comme méthode singleton de l'objet main. On a une équivalence ici entre def nom_methode et def self.nom_methode. Mais celui-ci a un comportement particulier, car ces méthodes, contrairement aux méthodes singletons classiques, seront rattachées à la classe Object et donc à toutes les instances de cette classe (même celles créées avant !), rendant ces méthodes appelables depuis le contexte de tous les objets.

L’héritage

Un des avantages de l'orienté objet est la capacité de réutiliser du code et de pouvoir écrire des traitements génériques pour des éléments spécialisés. Je m'explique : prenons un avion, une voiture et un bateau. On peut faire trois classes différentes, réciproquement Avion, Voiture et Bateau. Mais voilà, on peut considérer que ces trois classes, sont toutes les trois des véhicules et factoriser leurs méthodes et attributs communs dans une nouvelle classe, la classe Véhicule. Puis on va faire hériter les trois classes de Véhicule. C'est à dire qu'elles recevront les méthodes et attributs de cette classe-là, en plus de tous ceux qu'elles pourront définir dans leur propre définition.

class Véhicule
  def aller_a(lieu)
    puts "j'avance vers #{lieu}"
  end
end

class Avion < Véhicule
  def aller_a(lieu)
    puts "je vole vers #{lieu}"
  end
end

class Voiture < Véhicule
  def aller_a(lieu)
    puts "je roule vers #{lieu}"
  end
end

liste = [ Voiture.new, Avion.new ]
lieu = "Paris"
for v in liste do
  v.aller_a(lieu)
end

Vous avez l'exemple typique de l'avantage que confère l'orienté objet : la liste comporte des éléments de type Voiture et Avion mais nous savons que les deux héritent de Véhicule, donc répondrons à la méthode aller_a même si le code pour chacune est différente ! On a ainsi un processus générique qui permet d'avoir en même temps des traitements spécialisés ! Véhicule sera la super classe, ou classe-mère, et Voiture et Avion seront les deux sous-classes ou classes-filles. Le fait de rédéfinir une méthode dans une sous-classe s'appelle la redéfinition.

L’encapsulation

L'encapsulation est un principe important de l'orienté objet. Cela consiste à masquer des méthodes ou des attributs de nos objets pour limiter leur utilisation. Par exemple, quand leur utilisation ne regarde pas l’utilisateur de la classe mais seulement le concepteur, il peut être utile de protéger des variables internes des utilisateurs de la classe. Ainsi, on divise la classe en deux parties : une, publique, qui sera connue des utilisateurs et devra être maintenue au fil des versions, et une autre, privée, qui pourra être changée en profondeur si besoin est par son concepteur.

Encapsulation des méthodes

Lors de la définition d’une classe, vous pouvez définir le niveau d’encapsulation de chaque méthode. Ainsi, le niveau d’encapsulation par défaut est public. Cela définit qu’une méthode f défini pour un objet de la classe C pourra être appelée depuis n’importe où. Le niveau protected indique que la méthode ne pourra être appelée qu’à l’intérieur du code de la classe ou de une de ses classes filles. Le niveau private enfin, indique la méthode ne pourra être appelée qu’à l’intérieur du code de la classe, sans indiquer de récepteur. La syntaxe est la suivante :

public [:symbol]
protected [:symbol]
private [:symbol]

Si les symboles sont omis, toutes les méthodes suivantes seront déclarées à ce niveau d’encapsulation.

Encapsulation des attributs

En Ruby, tout attribut d’instance est par définition encapsulé au niveau privé. Pour déclarer que l’on peut y accéder en lecture il faut définir une méthode d’accès en lecture grâce à la syntaxe :

attr_reader :symbol[,:symbol]*

Pour déclarer qu’un attribut peut être aussi en écriture, il suffit de déclarer une méthode d’accès (ou accesseurs) en écriture :

attr_writer :symbol[,:symbol]*

Pour déclarer un accesseur dans les deux sens (lecture et écriture) on peut faire :

attr_accessor :symbol[,:symbol]*

Voici une définition rapide d'une classe Personne avec les méthodes auxquelles les instances doivent répondre et ses attributs. Les éléments privés sont marqués d'un - et les éléments publics d'un + :

Personne # Nom de la classe
  -age
  -nom
  +SePrésenter()
  +FêterAnniversaire()
  +getAge()

Et voici la définition de la classe correspondante en Ruby :

class Personne

  def initialize(n, a)
    @nom = n
    @âge = a
  end

  def SePrésenter()
    puts "je m'appelle #{@nom} et j'ai #{@âge} ans"
  end

  def FêterAnniversaire()
    @âge += 1
  end

  attr_reader :âge, :nom

end

L'avantage de cette conception, est que l'on protège notre variable nom et notre variable âge. N'importe qui peut les lire, mais pour les modifier, il faudra toujours passer par les méthodes définies ici.

Utilisation :

bob = Personne.new("Bob", 27)
puts bob.age      # Affiche 27
bob.FêterAnniversaire()
puts bob.age      # Affiche 28
bob.SePrésenter() # Affiche "je m'appelle Bob et j'ai 28 ans"

Et voilà !

Modularité

(retour sommaire)

 module Identifier
     instructions
 end

# When you include a module, Ruby creates an anonymous proxy class that references that module, and inserts that proxy as the direct superclass of the class that did the including. The proxy class contains references to the instance variables and methods of the module. This is important: the same module may be included in many different classes, and will appear in many different inheritance chains. However, thanks to the proxy class, there is still only one underlying module: change a method definition in that module, and it will change in all classes that include that module, both past and future.

 puts
 puts "Mix">in and modules"
 module HelloMod
    def hello
        "Hello!"
    end
 end
 class C
    include HelloMod
 end
 c = C.new
 puts c.hello
 module HelloMod
    def hello
       "zorbix"
    end
 end
 puts c.hello

Extending objects

 module Laetitia
def hello
"hey !" 
end
 end
 cc = "Grouchy"
 cc.extend Laetitia
 puts cc.hello
 class Lae 
include Laetitia # ajoute comme méthode d'instance
  extend Laetitia # appel à object#extend, donc self.extend avec self = Lae (la classe) donc méthode de classe
 end
 lae = Lae.new
 puts lae.hello
 puts Lae.hello

Gestion des exceptions

(retour sommaire)

 begin
     instructions
 rescue Exception
     instructions en cas d'erreur
 ensure
     instructions toujours exécutées à la fin
 end

retry

Astuces

(retour sommaire)

Intervertir la valeur de deux variables sans passer par une variable intermédiaire :

a, b = 1, 5
# Au lieu de faire :
c = a
a = b
b = c
# On fait plus simplement ça :
a, b = b, a

Pour obtenir un nombre au hasard :

i = rand(max)  # Obtient à un nombre de 0 à max-1
d6 = rand(6)+1 # Simule un lancé de dé

Appel de bibliothèque

 require 'ma_bibliothèque'

Ruby cherchera alors le fichier ma_bibliothèque.rb ou .so dans les dossiers spécifiés par loadpath ainsi que le répertoire courant. On peut changer le loadpath en faisant : $LOAD_PATH << 'c:/mon_dossier/'

 require 'gosu'

Va chercher la librairie gosu.dll. Toutes les éléments sont importées via l'espace de nom Gosu dans l'espace courant.

Gosu::xxx

Si on veut directement importer les éléments de Gosu dans l'espace de nom courant faire :

include Gosu

Expression régulière

 regexp =~ string

Compare une expression à une chaîne, retourne un nombre (l'emplacement du début de la partie associée) si il y a association. Sinon nil.

 text = "La lune est une dure maîtresse - Pour qui sonne le glas - Heart of Darkness"
 if /lune|glas/ =~ text then puts "glaslune !" end
 text.sub!(/lune/, 'moon of mars'1ère occurence
 text.gsub!(/of/, 'de'# toutes les occurences
 puts text

Substitution d'expression

 name = "Adrien"
 puts "Mon nom est #{name}"
 puts "4+4 = #{4+4}"

Bibliothèques

(retour sommaire)

Connaître le langage n'est que la moitié de l'affaire. En effet, il est important de connaître les bibliothèques offertes pour pouvoir utiliser dans sa pleine puissance Ruby, et surtout ne pas recoder quelque chose qui a déjà été codé, souvent en mieux ! Il y a trois niveaux de bibliothèque : la bibliothèque du noyau concerne les méthodes offertes par les types de base de Ruby. La bibliothèque standard est incluse dans chaque distribution de Ruby, mais nécessite d'employer des require pour faire appel aux parties souhaitées. Enfin, les bibliothèques externes doivent être au préalable téléchargées et installées sur la machine avant de pouvoir être utilisées depuis Ruby.

Bibliothèque du noyau

Classe Object

Ces méthodes sont communes à tous les objets Ruby.

Méthodes
nil?
Renvoie vrai si l'objet est égal à la valeur nulle.
Opérateurs
=~
Cette opérateur est défini au niveau d'objet mais sert surtout, une fois redéfini, aux expressions régulières.

Classes Fixnum et Bignum

Les deux classes héritent de Integer qui hérite de Numeric.

Littéraux

# 8 en base décimale, binaire, héxadécimale et octale :
8
0b1000
0x08
010

Méthodes
abs
Renvoie la valeur absolue du nombre.
between?(min, max)
Renvoie vrai si nombre est compris entre min et max inclus.
chr
Renvoie la chaîne du caractère correspond au nombre.
downto(min) bloc/1
Fait une itération du nombre à min en passant en argument la valeur courante au bloc en paramètre.
to_f
Transforme l'entier en flottant. Si on essaye de convertir un Bignum en flottant, le flottant peut être trop petit pour le contenir, et prendra comme valeur l'infini.
next
Renvoie nombre+1 (équivalent à succ).
size
Renvoie le nombre d'octets nécessaire pour stocker en mémoire ce nombre.
step(limite, step) bloc/1
Fait une itération du nombre à limite en ajoutant step à chaque fois et en passant la valeur courante au bloc.
succ
Renvoie nombre+1 (équivalent à next).
times bloc/0-1
Fait n itération de 0 à nombre-1 en appelant le bloc avec l'index.
to_s(base)
Convertit le nombre en une chaîne dans la base donnée, 10 par défaut.
upto(max) bloc/1
Fait une itération du nombre à max en passant en argument la valeur courante au bloc en paramètre.
zero?
Renvoie vrai si nombre est égal à 0.
Opérateurs
[index]
Renvoie le bit à l'emplacement d'index.
Arithmétiques : + - * / ** %
Opérateurs arithmétiques classiques. La division de deux entiers produit toujours un entier.
Binaires : & (et) | (ou) ^ (ou exclusif) ~ (inversion des bits)
Opérateurs binaires classiques.
De décalage : << (à gauche) >> (à droite)
Opérateurs de décalage (décale en ajoutant des 0).
De comparaison : < <= > >= == != <=>
Renvoie vrai ou faux, sauf <=> qui renvoie 0 (égaux), -1 (gauche < droite), +1 (gauche > droite).

Classe Float

Cette classe hérite de Numeric.

Littéraux

# 0.5 dans deux notations différentes :
0.5
5e-1

Méthodes
floor
Arrondit le nombre à l'inférieur.
ceil
Arrondit le nombre au supérieur.
round
Arrondit le nombre à l'inférieur si partie décimale < 0.5, à l'inférieur si >= 0.5.
infinite?
Renvoie 1 si le nombre a pour valeur infini positive, -1 si infini négatif et nil si le nombre est fini.
finite?
Renvoie vrai si le nombre a une valeur finie, faux sinon.
nan?
Renvoie vrai si le nombre n'en est pas un (suite à une opération).
floor
Arrondit le nombre à l'inférieur.
to_s
Transforme le nombre en chaîne de caractère après avoir fait un arrondit à l'inférieur dessus.
to_i
Transforme le nombre en entier en troncant sa partie décimale.
Opérateurs

Classe String

Ruby utilise, pour stocker en mémoire les chaînes, un octet par caractère.

Littéraux

"hello"
'hello'
%q{hello} # Est équivalent à des guillemets simples.
%Q{hello} # Est équivalent à des guillemets doubles.
"hello
world!"
  # Chaîne sur plusieurs lignes (possible avec guillemets doubles).

Les chaînes entourées de guillement simple ne sont pas interprétées : les caractères spéciaux sont considérés comme des caractères normaux.

Méthodes
concat(str)
Retourne une nouvelle chaîne issue de la concaténation des deux.
to_i
Essaye de convertir la chaîne en un entier. Renvoie 0 sinon.
to_f
Essaye de convertir la chaîne en un flottant. Renvoie 0.0 sinon.
Opérateurs
+
Retourne une nouvelle chaîne issue de la concaténation des deux (comme concat).
Spécial
?lettre
Permet d'obtenir la chaîne correspondante à la lettre.
"#{code}"
Exécute le code puis le convertit en chaîne et l'insert dans la chaîne.
\n
Dans une chaîne interprétée (guillement double) cela représente un retour à la ligne et un saut à la prochaine ligne.
\t
Dans une chaîne interprétée (guillement double) cela représente une tabulation.
\xCODE
Dans une chaîne interprétée (guillement double) cela représente un caractère unicode. CODE doit être en hexadécimal.
\\
Dans une chaîne interprétée (guillement double) cela permet d'avoir le caractère \.

Classe Range

Classe Regexp

Littéraux

Un littéral d'expression régulière peut être immédiatement suivi d'option. Un option permet d'altérer le comportement de l'expression régulière. L'option i permet d'ignorer la différence entre majuscules et minuscules. L'option m permet de parcourir du texte sur plusieurs lignes. L'option x ignore les espaces et autres caractères blancs dans la définition de l'expression. L'option o ne fait évaluer les blocs de Ruby à l'intérieur de la définition de l'expression qu'une fois.

# Expression régulière pour détecter une année (2005 ou 05)
/\d{4}|\d{2}/
%r{\d{4}|\d{2}
a = /\d{4}|\d{2}/
a.match("2005")
# Retourne un objet MatchData
a.match("2005")[0]
# Retourne "2005"
a.match("hello")
# Retourne nil

# Expression régulière pour décomposer le prénom et le nom (M. Martin Pons)
t = /(M|Mme|Mlle)\. (\w+) (\w+)/
r = t.match("M. Martin Pons")
puts t[0] # Affiche "M. Martin Pons"
puts t[1] # Affiche "M"
puts t[2] # Affiche "Martin"
puts t[3] # Affiche "Pons"

Dans la dernière expression, les parenthèses sont très importantes car elles forment des groupes. Si l'expression est détectée dans la chaîne que l'on passe en paramètre à la méthode match, le résultat sera à la fois l'intégralité de ce qui est reconnu mais aussi divisé en autant de groupes qu'il y a de groupes parenthésés, 3 ici. Le premier correspondra à M ou Mme ou Mlle, le second au prénom, et le troisième au nom. On peut accéder à chaque partie avec l'opérateur [ ] sur l'objet MatchData. Du fait que l'intégralité occupe toujours la position d'indice 0, les groupes seront accessibles de par les indices 1 à 3 ici. On peut également y accéder avec les variables globales $1, $2, etc.

Méthodes
Regexp.new(pattern , [options])
Construit une nouvelle expression régulière.
Regexp.last_match(indice)
Retourne le résultat du dernier appel de match sur une instance de la classe.
match(chaîne)
Essaye de détecter l'expression régulière dans la chaîne. Renvoie la valeur nulle si elle ne la trouve pas, sinon renvoie un objet MatchData.
Opérateurs
=~
Effectue un match sur la chaîne passée en paramètre. C'est équivalent à la méthode match
Spécial

Le langage des regex est un langage spécifique de domaine (DSL en anglais) : c'est à dire un langage propre au domain des expressions régulières. Il utilise plusieurs caractères avec un sens bien particulier :

^
Début de chaîne ou ligne (à gauche)
$
Fin de chaîne ou ligne (à droite)
[abc]
Un élément de ce groupe de caractères (a, b ou c ici)
[^abc]
Tout sauf un élément de ce groupe de caractères (a, b ou c ici)
[a-z]
Un élément de ce groupe de caractères (de a à z ici)
[^a-z]
Tout sauf un élément de ce groupe de caractères (de a à z ici)
|
Opérateur ou
( )
Permet de regrouper des éléments
+
Permet de spécifier que l'élément précédent doit apparaître 1 fois ou plus
*
Permet de spécifier que l'élément précédent est optionnel mais peut être répété plusieurs fois
?
Permet de spécifier que l'élément précédent est optionnel
{i}
Permet de spécifier que l'élément précédent doit apparaître i fois.
{i, j}
Permet de spécifier que l'élément précédent doit apparaître au moins i fois et peut apparaître jusqu'à i fois.
\s
Correspond aux caractères blancs : espace, tabulation, nouvelle ligne, etc.
\S
Correspond à tous sauf aux caractères blancs : espace, tabulation, nouvelle ligne, etc.
\d
Correspond à un chiffre, de 0 à 9
\D
Correspond à tout sauf à un chiffre, de 0 à 9
\h
Correspond à un chiffre hexadécimal, de 0 à 9 puis de A ou a à F ou f.
\H
Correspond à tout sauf à un chiffre hexadécimal.
\w
Correspond à n'importe quel caractère pouvant appraître dans un mot
\W
Correspond à tout sauf à n'importe quel caractère pouvant appraître dans un mot
.
Correspond à n'importe quel caractère sauf une nouvelle ligne (sauf si l'option m est activée)
\
Permet d'échapper un caractère spécial. Par exemple, si vous voulez détecter un point (.) vous devez faire : \. sinon il sera interprété comme le caractère spécial . qui signifie n'importe quel caractère.
#{code}
Permet d'intégrer du code Ruby dans l'expression d'une regex.

Bibliothèque standard

Bibliothèques externes

On peut trouver de nombreuses bibliothèques externes sur le site de la Ruby Application Archive ou RAA qui les liste. RAA est un peu comme CPAN, Pear et Pecl pour le langage Perl ou PyPI, the Python Package Index pour le langage Python. Il existe aussi Ruby Forge qui héberge de nombreux projets en Ruby dont des bibliothèques.

Watir

Watir est une bibliothèque créée pour tester les applications web. Elle se charge de dialoguer avec un navigateur internet, pour permettre d'automatiser son comportement en vue de tester une application web (simulation de clic sur un bouton, de remplissage de formulaire).Il fonctionne avec les principaux navigateurs. Des versions pour .Net (Watin) et Java (Watij) existent aussi.

require "watir"
test_site = "http://www.google.com"
ie = Watir::IE.new
ie.goto test_site
ie.text_field(:name, "q").set "pickaxe"
ie.button(:name, "btnG").click
ie.text.include? "Programming Ruby"

Gosu

Gosu est une bibliothèque multimédia (sons, images, périphériques d'entrée tels que la souris et le clavier) qui permet notamment de faire des jeux en 2D avec Ruby. La classe principale de notre application devra hériter de la classe Window de Gosu.

class MaFenetre < Gosu::Window

  def initialize
    super(largeur, hauteur, booléen)
    self.caption = "Titre"
    initialisation
  end

  def update
  end

  def draw
  end

  def button_down(id)
  end

end

f = MaFenetre.new
f.show

Gosu::Button::xxx

Enumération de tous les boutons possibles.

Bibliographie

(retour sommaire)
  • Alexandre Brillant, Ruby, les fondamentaux du langage, ENI éditions, collection Ressources Informatiques, 2008, France.
  • Yukihiro Matsumoto, Eric Jacoboni (trad.), Ruby In A Nutshell, O'Reilly éditions, 2002, France.

Liens

(retour sommaire)

Résumé

(retour sommaire)

Index

(retour sommaire)
affectation par copie de la valeur
aff_copie_valeur
affectation par copie de la référence
aff_copie_ref
caractères d'échappement
car_echap
pseudo variable
pseudo_var

Historique du document

(retour sommaire)
  • Vieilles versions et notes
    • Découverte de Ruby par la lecture de Ruby in a nutshell.
    • 5 feuilles papiers non transposées sur machine du 12 octobre 2005.
    • 1 feuille papier transposée sur machine du 1er mai 2006.
    • Petit fichier intégré du vendredi 26 mars 2010.
    • Fichier de transposition et d'intégration de Gdoc du 29 avril 2011.
  • Samedi 7 Avril
    • Version Alpha 01.
    • Mise en ligne sur ran.
  • Dimanche 8 Avril
    • Version Alpha 02.
    • première partie OK.
  • Mercredi 11 Avril
    • Version Alpha 03.
    • division en grandes parties.
    • seconde partie - les opérateurs OK.
  • Jeudi 12 Avril
    • Version Alpha 04.
    • ajout référence Ruby in a nutshell.
    • seconde partie - contrôle du flux OK.
    • seconde partie - méthodes OK.
  • Lundi 16 Avril
    • Version Alpha 05 (corrections de Vendredi, Dimanche, Lundi).
    • seconde partie - classes et objets OK.
    • Fixnum, Bignum, Float et Regex dans la bibliothèque noyau OK.