Ruby de Newby à Ready
Qu'est-ce que Ruby?
- Langage de programmation créé par Yukihiro "Matz" Matsumoto en 1995
- Orienté objet, dynamique et interprété
- Philosophie: "Optimisé pour le bonheur du programmeur" et basé sur le principe de moindre surprise
- Syntaxe élégante et naturelle
- Fait pour créer vite.
Pourquoi apprendre Ruby?
- Productivité élevée
- Communauté active et bienveillante
- Écosystème riche (Rails, Sinatra, Jekyll)
- Utilisé par: GitHub, Airbnb, Shopify, etc.
Installation de Ruby
# Sur macOS avec Homebrew
brew install ruby
# Sur Ubuntu/Debian
sudo apt-get install ruby-full
# Avec un gestionnaire de versions (recommandé)
# RVM
\curl -sSL https://get.rvm.io | bash -s stable --ruby
# ou rbenv
brew install rbenv ruby-build
rbenv init
rbenv install 3.2.2
Vérification de l'installation
ruby -v
# Ruby 3.2.2p53 (2023-03-30 revision 957bb7) [x86_64-darwin22]
irb
# Interactive Ruby Shell
Premier programme Ruby
# hello.rb
puts "Hello, World!"
# Exécution
# ruby hello.rb
Deuxième programme en Ruby
firstname = gets.chomp
puts "Hello, #{firstname}"
IRB
Définition et rôle
IRB signifie Interactive Ruby Shell. C'est un environnement de ligne de commande qui vous permet d'écrire et d'exécuter du code Ruby de manière interactive, instruction par instruction. Il est souvent appelé un REPL (Read-Eval-Print Loop), ce qui décrit son fonctionnement cyclique :
- Read (Lecture) : Il lit le code que vous tapez.
- Eval (Évaluation) : Il exécute ce code.
- Print (Affichage) : Il affiche le résultat de l'exécution.
- Loop (Boucle) : Il revient au début, attendant la prochaine instruction.
Pourquoi l'utiliser ?
IRB est un outil incroyablement puissant et polyvalent pour tout développeur Ruby, qu'il soit débutant ou expérimenté :
- Apprentissage et Expérimentation : C'est le "bac à sable" parfait pour tester de nouvelles idées, comprendre le comportement de Ruby, ou essayer des méthodes sans avoir à créer un fichier
.rb
complet. - Débogage Rapide : Vous pouvez rapidement vérifier la valeur d'une variable, tester une expression complexe, ou isoler un problème dans votre code.
- Prototypage : Pour tester de petits extraits de code ou des algorithmes avant de les intégrer dans un projet plus vaste.
- Exploration de la Bibliothèque Standard : Vous pouvez facilement explorer les méthodes disponibles sur les objets Ruby.
- Calculatrice Avancée : Pour des calculs rapides et complexes.
Démarrage d'IRB
Comment lancer IRB
Pour démarrer IRB, ouvrez simplement votre terminal (ou invite de commande sur Windows) et tapez la commande suivante :
irb
Appuyez sur Entrée
. Vous devriez voir un message de bienvenue et le prompt d'IRB.
Comprendre le prompt
Le prompt par défaut d'IRB ressemble généralement à ceci :
irb(main):001:0>
Décortiquons-le :
irb
: Indique que vous êtes dans la session IRB.(main)
: Représente le contexte actuel. Au début, vous êtes dans le contexte global (main
). Si vous entrez dans une classe ou un module, ce contexte peut changer.001
: C'est le numéro de la ligne de code que vous êtes sur le point de taper. Il s'incrémente à chaque nouvelle instruction.:0
: Indique le niveau d'imbrication de l'instruction.0
signifie que vous êtes au niveau le plus externe. Si vous commencez une instruction multi-lignes (comme unif
ou une définition de méthode), ce nombre augmentera.>
: Le curseur, indiquant qu'IRB attend votre saisie.
Le Cycle REPL (Read-Eval-Print-Loop)
C'est le cœur du fonctionnement d'IRB. Chaque fois que vous tapez une instruction et appuyez sur Entrée
, IRB suit ce cycle :
Read (Lecture)
IRB lit l'instruction que vous avez tapée.
irb(main):001:0> 1 + 1 # Vous tapez ceci
Eval (Évaluation)
IRB exécute le code Ruby que vous avez fourni.
Print (Affichage)
IRB affiche le résultat de l'évaluation. Le résultat est précédé de =>
.
irb(main):001:0> 1 + 1
=> 2 # IRB affiche ceci
Si une instruction ne retourne pas de valeur explicite (par exemple, une affectation de variable ou un appel à puts
qui affiche sur la console mais retourne nil
), IRB affichera => nil
.
irb(main):002:0> x = 10
=> 10 # La valeur de l'affectation est la valeur affectée
irb(main):003:0> puts "Bonjour"
Bonjour # Ceci est affiché sur la console par puts
=> nil # La méthode puts elle-même retourne nil
Loop (Boucle)
Après avoir affiché le résultat, IRB revient au prompt, prêt pour votre prochaine instruction.
Opérations Fondamentales dans IRB
Explorons quelques opérations de base que vous pouvez effectuer.
Calculs arithmétiques
irb(main):001:0> 5 * 3
=> 15
irb(main):002:0> 10 / 3 # Division entière
=> 3
irb(main):003:0> 10.0 / 3 # Division flottante
=> 3.3333333333333335
irb(main):004:0> (2 + 3) * 4
=> 20
Variables
Vous pouvez déclarer et utiliser des variables.
irb(main):005:0> nom = "Alice"
=> "Alice"
irb(main):006:0> age = 30
=> 30
irb(main):007:0> puts "Bonjour, je m'appelle #{nom} et j'ai #{age} ans."
Bonjour, je m'appelle Alice et j'ai 30 ans.
=> nil
irb(main):008:0> nom.upcase # Les variables conservent leur valeur entre les lignes
=> "ALICE"
Chaînes de caractères
irb(main):009:0> "Hello" + " World"
=> "Hello World"
irb(main):010:0> "Ruby " * 3
=> "Ruby Ruby Ruby "
irb(main):011:0> "Ceci est une chaîne".length
=> 19
Tableaux (Arrays) et Hachages (Hashes)
irb(main):012:0> fruits = ["pomme", "banane", "cerise"]
=> ["pomme", "banane", "cerise"]
irb(main):013:0> fruits[0]
=> "pomme"
irb(main):014:0> fruits << "orange" # Ajouter un élément
=> ["pomme", "banane", "cerise", "orange"]
irb(main):015:0> personne = { nom: "Bob", age: 25, ville: "Paris" }
=> {:nom=>"Bob", :age=>25, :ville=>"Paris"}
irb(main):016:0> personne[:ville]
=> "Paris"
Appel de méthodes
Vous pouvez appeler des méthodes sur n'importe quel objet.
irb(main):017:0> 10.even?
=> true
irb(main):018:0> "Bonjour".reverse
=> "ruojnoB"
irb(main):019:0> Time.now
=> 2023-10-27 10:30:45.123456 +0200 # L'heure actuelle
Conditions et boucles (aperçu)
IRB gère très bien les structures de contrôle multi-lignes.
irb(main):020:0> if 5 > 2
irb(main):021:1> puts "5 est plus grand que 2"
irb(main):022:1> else
irb(main):023:1> puts "5 n'est pas plus grand que 2"
irb(main):024:1> end
5 est plus grand que 2
=> nil # Le résultat de l'expression if/else est nil ici
irb(main):025:0> 3.times do |i|
irb(main):026:1> puts "Répétition #{i + 1}"
irb(main):027:1> end
Répétition 1
Répétition 2
Répétition 3
=> 3 # La méthode times retourne le nombre de répétitions
Notez comment le prompt change (:1>
) pour indiquer que vous êtes dans une instruction multi-lignes. IRB attendra le mot-clé end
pour évaluer le bloc.
Fonctionnalités Avancées (Introductives)
Saisie multi-lignes
Comme vu précédemment, IRB est intelligent et sait quand une instruction n'est pas complète. Il change le prompt pour vous indiquer que vous êtes dans un bloc.
irb(main):028:0> def saluer(nom) # Début de la définition d'une méthode
irb(main):029:1> "Bonjour, #{nom} !"
irb(main):030:1> end # Fin de la définition
=> :saluer # IRB retourne le nom du symbole de la méthode définie
irb(main):031:0> saluer("Monde")
=> "Bonjour, Monde !"
Historique des commandes
Vous pouvez naviguer dans l'historique de vos commandes avec les flèches Haut et Bas de votre clavier. C'est extrêmement utile pour réexécuter une commande, la modifier, ou simplement se souvenir de ce que vous avez fait.
Autocomplétion (Tab)
IRB offre une autocomplétion très pratique. Tapez le début d'un nom de variable, de méthode ou de classe, puis appuyez sur la touche Tab.
- Si une seule correspondance existe, IRB complétera automatiquement.
- Si plusieurs correspondances existent, IRB affichera une liste des options possibles.
Exemple :
irb(main):032:0> "hello".upc # Appuyez sur Tab ici
IRB complétera en "hello".upcase
irb(main):033:0> [1, 2, 3].s # Appuyez sur Tab ici
IRB affichera une liste de méthodes commençant par s
(comme size
, sort
, shuffle
, etc.).
Chargement de fichiers Ruby (load
et require
)
Vous pouvez charger des fichiers Ruby existants dans votre session IRB.
load 'chemin/vers/votre_fichier.rb'
:Exemple :- Charge le fichier spécifié.
- Important : Si vous modifiez le fichier et que vous le
load
à nouveau, IRB rechargera la nouvelle version. C'est utile pour le développement et le test itératif. - Le chemin peut être relatif ou absolu.
- Charge le fichier spécifié (ou une gemme).
- Important :
require
ne charge un fichier qu'une seule fois par session. Si vous essayez de lerequire
à nouveau, il ne fera rien (sauf si le fichier a été explicitement déchargé). C'est le comportement standard pour les bibliothèques et les dépendances. - Pour les fichiers locaux, vous devez souvent utiliser
./
pour indiquer le chemin relatif :require './mon_script'
. Notez que l'extension.rb
est souvent omise avecrequire
.
require 'chemin/vers/votre_fichier'
(ou require 'nom_gemme'
) :Exemple :
irb(main):037:0> require './mon_script' # Charge le fichier une première fois
Bonjour depuis mon_script.rb !
=> true
irb(main):038:0> require './mon_script' # Ne fait rien la deuxième fois
=> false # Indique que le fichier n'a pas été rechargé
Dans IRB :
irb(main):034:0> load 'mon_script.rb'
Bonjour depuis mon_script.rb ! # Le puts est exécuté lors du chargement
=> true
irb(main):035:0> dire_bonjour
Bonjour depuis mon_script.rb !
=> nil
irb(main):036:0> MA_CONSTANTE
=> 123
Créez un fichier nommé mon_script.rb
avec le contenu suivant :
# mon_script.rb
def dire_bonjour
puts "Bonjour depuis mon_script.rb !"
end
MA_CONSTANTE = 123
Sortir d'IRB
Pour quitter votre session IRB, vous avez plusieurs options :
- Tapez
exit
et appuyez surEntrée
. - Tapez
quit
et appuyez surEntrée
. - Appuyez sur
Ctrl + D
(sur la plupart des systèmes Unix/Linux/macOS).
irb(main):039:0> exit
Vous reviendrez à votre prompt de terminal habituel.
Conseils et Bonnes Pratiques
- N'ayez pas peur d'expérimenter ! IRB est un environnement sûr. Vous ne pouvez pas "casser" votre système en l'utilisant. C'est l'endroit idéal pour faire des erreurs et apprendre d'elles.
- Utilisez-le pour tester des extraits de code : Avant d'intégrer un algorithme complexe ou une nouvelle méthode dans votre application, testez-la dans IRB.
p
affiche la représentation de débogage de l'objet.pp
(pretty print) est encore plus lisible pour les structures complexes. Vous devrez peut-êtrerequire 'pp'
au début de votre session IRB pour l'utiliser.
- Explorez la documentation avec
ri
: Bien queri
ne soit pas dans IRB, c'est un compagnon essentiel. Ouvrez un nouveau terminal et tapezri String#reverse
pour obtenir la documentation de la méthodereverse
sur les chaînes de caractères.
Utilisez p
ou pp
pour un affichage lisible : Pour des objets complexes (comme des tableaux imbriqués ou des hachages), puts
peut ne pas être suffisant.
irb(main):042:0> data = { user: { name: "John Doe", email: "john@example.com", address: { street: "123 Main St", city: "Anytown" } } }
=> {:user=>{:name=>"John Doe", :email=>"john@example.com", :address=>{:street=>"123 Main St", :city=>"Anytown"}}}
irb(main):043:0> p data
{:user=>{:name=>"John Doe", :email=>"john@example.com", :address=>{:street=>"123 Main St", :city=>"Anytown"}}}
=> {:user=>{:name=>"John Doe", :email=>"john@example.com", :address=>{:street=>"123 Main St", :city=>"Anytown"}}}
irb(main):044:0> require 'pp'
=> true
irb(main):045:0> pp data
{:user=>
{:name=>"John Doe",
:email=>"john@example.com",
:address=>{:street=>"123 Main St", :city=>"Anytown"}}}
=> {:user=>{:name=>"John Doe", :email=>"john@example.com", :address=>{:street=>"123 Main St", :city=>"Anytown"}}}
Inspectez les objets : Si vous avez une variable et que vous voulez savoir ce qu'elle contient ou quelles méthodes elle a, tapez simplement le nom de la variable.
irb(main):040:0> ma_chaine = "Bonjour"
=> "Bonjour"
irb(main):041:0> ma_chaine.methods.sort # Liste toutes les méthodes disponibles sur l'objet
=> [:!, :!=, :!~, :%, :*, :+, :+@, :-, :-@, :<, :<<, :<=, :<=>, :==, :===, :=~, :>, :>=, :[], :[]=, :__id__, :__send__, :_dump, :_id2ref, :ascii_only?, :b, :between?, :bytes, :bytesize, :byteslice, :capitalize, :capitalize!, :casecmp, :casecmp?, :center, :chars, :chomp, :chomp!, :chop, :chop!, :chr, :clear, :clone, :codepoints, :concat, :count, :crypt, :delete, :delete!, :downcase, :downcase!, :dump, :each_byte, :each_char, :each_codepoint, :each_line, :empty?, :encode, :encode!, :encoding, :end_with?, :eql?, :extend, :freeze, :frozen?, :getbyte, :gsub, :gsub!, :hash, :hex, :include?, :index, :initialize_copy, :insert, :inspect, :instance_eval, :instance_exec, :instance_of?, :instance_variable_defined?, :instance_variable_get, :instance_variable_set, :instance_variables, :intern, :is_a?, :itself, :kconv, :ljust, :lines, :lstrip, :lstrip!, :match, :match?, :method, :methods, :module_eval, :next, :next!, :nil?, :oct, :ord, :partition, :prepend, :private_methods, :protected_methods, :public_method, :public_methods, :public_send, :q, :rindex, :rjust, :reverse, :reverse!, :rpartition, :rstrip, :rstrip!, :scrub, :scrub!, :setbyte, :shellescape, :singleton_class, :slice, :slice!, :split, :squeeze, :squeeze!, :start_with?, :strip, :strip!, :sub, :sub!, :succ, :succ!, :sum, :swapcase, :swapcase!, :taguri, :taguri=, :taint, :tainted?, :tap, :to_c, :to_d, :to_f, :to_i, :to_json, :to_r, :to_s, :to_sym, :tr, :tr!, :tr_s, :tr_s!, :trust, :trusted?, :type, :unicode_normalize, :unicode_normalize!, :unicode_normalized?, :untaint, :untrust, :untrusted?, :upcase, :upcase!, :using_utf8?, :valid_encoding?, :yield_self, :zip]
Conclusion sur IRB
IRB est bien plus qu'une simple calculatrice pour Ruby ; c'est un environnement de développement interactif complet qui accélérera votre apprentissage et votre productivité. En maîtrisant ses bases et en l'intégrant à votre flux de travail, vous découvrirez à quel point il est indispensable pour explorer, tester et déboguer votre code Ruby.
N'hésitez pas à l'ouvrir régulièrement, à taper du code, à tester des idées, et à vous familiariser avec son fonctionnement. C'est en pratiquant que vous en tirerez le meilleur parti !
Variables et types de données
Après ce premier aperçu avec IRB, revenons en détail sur les aspects du langage
Introduction
Les variables en Ruby sont des conteneurs qui permettent de stocker des données. Elles sont essentielles pour manipuler et gérer l'information dans un programme.
Aspect technique
- Typage dynamique : Ruby est un langage à typage dynamique, ce qui signifie que vous n'avez pas besoin de déclarer explicitement le type d'une variable. Le type est déterminé automatiquement lors de l'affectation.
- Passage par référence : Les variables en Ruby pointent vers des objets en mémoire. Lorsqu'on affecte une variable à une autre, elles peuvent référencer le même objet ou des objets différents selon le contexte.
Types de variables
Ruby possède plusieurs types de variables, classés selon leur portée et leur usage :
a. Variables locales
- Syntaxe :
nom_variable
- Portée : limitée à la méthode ou au bloc où elles sont déclarées.
- Exemple :
x = 10
b. Variables d'instance
- Syntaxe :
@nom_variable
- Portée : accessible dans toutes les méthodes de l'objet.
- Exemple :
@nom
c. Variables de classe
- Syntaxe :
@@nom_variable
- Portée : partagée entre toutes les instances de la classe.
- Exemple :
@@count
d. Variables globales
- Syntaxe :
$nom_variable
- Portée : accessible partout dans le programme.
- Exemple :
$global_var
Affectation et utilisation
- Affectation :
variable = valeur
- Ruby est sensible à la casse.
- La variable prend la référence de l'objet affecté.
- Exemple :
name = "Alice"
Recommandations
- Utilisez des variables locales autant que possible pour limiter la portée.
- Nommez vos variables de manière descriptive pour améliorer la lisibilité.
- Évitez d'utiliser des variables globales sauf si nécessaire.
- Respectez la convention de nommage : snake_case (ex :
mon_variable
).
Bonnes pratiques
- Initialisez toujours vos variables avant de les utiliser.
- Évitez de réaffecter des variables de manière imprévisible.
- Utilisez des constantes (
CONSTANTE = valeur
) pour des valeurs fixes.
Exemple complet
class Person
def initialize(name)
@name = name # variable d'instance
end
def greet
greeting = "Hello, #{@name}!" # variable locale
puts greeting
end
end
person = Person.new("Alice")
person.greet # Affiche : Hello, Alice!
# Variables - pas besoin de déclaration de type
name = "Ruby"
age = 28
is_oop = true
pi = 3.14159
# Types de base
puts name.class # String
puts age.class # Integer
puts is_oop.class # TrueClass
puts pi.class # Float
Les multi-affectations en Ruby permettent d'assigner plusieurs valeurs à plusieurs variables en une seule ligne, ce qui rend le code plus concis et lisible.
Multi-affectation en Ruby
Syntaxe de base
a, b, c = 1, 2, 3
Dans cet exemple :
a
reçoit la valeur1
b
reçoit la valeur2
c
reçoit la valeur3
Fonctionnement
- Ruby associe chaque variable à la valeur correspondante dans la liste.
- Si le nombre de variables est inférieur au nombre de valeurs, les valeurs restantes sont ignorées.
- Si le nombre de variables est supérieur au nombre de valeurs, les variables non assignées prennent la valeur
nil
.
Cas particuliers
Affectation avec une seule valeur
x, y = 10
x
reçoit10
y
reçoitnil
Affectation avec une seule variable
a, = 1
a
reçoit1
Échange de valeurs
Une utilisation courante est l’échange de valeurs entre deux variables :
x = 5
y = 10
x, y = y, x
# Maintenant, x vaut 10, y vaut 5
Utilisation avec des tableaux
Vous pouvez également faire de la décomposition de tableaux :
coordinates = [10, 20]
x, y = coordinates
# x vaut 10, y vaut 20
Exemple complet
# Affectation multiple
name, age, city = "Alice", 30, "Paris"
# Échange de valeurs
a, b = 1, 2
a, b = b, a
# Décomposition d'un tableau
point = [5, 10]
x, y = point
puts "Nom: #{name}, Age: #{age}, Ville: #{city}"
puts "a: #{a}, b: #{b}"
puts "x: #{x}, y: #{y}"
Résumé
Cas | Exemple | Résultat |
---|---|---|
Affectation simple | a, b = 1, 2 |
a=1 , b=2 |
Affectation avec moins de valeurs | a, b = 1 |
a=1 , b=nil |
Échange de valeurs | a, b = b, a |
échange des valeurs |
Décomposition tableau | x, y = [10, 20] |
x=10 , y=20 |
Si vous souhaitez des exemples plus avancés ou des cas spécifiques, je peux vous aider !
Types de données
Chaînes de caractères
# Création et manipulation
greeting = "Hello"
name = "Ruby"
# Concaténation
message = greeting + ", " + name + "!"
puts message # Hello, Ruby!
# Interpolation (préférable)
message = "#{greeting}, #{name}!"
puts message # Hello, Ruby!
# Méthodes utiles
puts name.upcase # RUBY
puts name.downcase # ruby
puts name.length # 4
puts "ruby".capitalize # Ruby
Nombres et opérations mathématiques
# Opérations de base
sum = 5 + 3 # 8
difference = 10 - 4 # 6
product = 4 * 3 # 12
quotient = 10 / 3 # 3 (division entière)
remainder = 10 % 3 # 1 (modulo)
# Division avec décimales
precise = 10.to_f / 3
precise = 10.0 / 3 # 3.3333333333333335
# Méthodes utiles
puts 5.even? # false
puts 6.even? # true
puts 7.odd? # true
puts 10.between?(5, 15) # true
Voici une explication détaillée des symboles en Ruby, en mettant en évidence leur différence avec les chaînes de caractères (strings), leur usage, leur relation avec les mots-clés, etc.
Les Symboles en Ruby
Qu'est-ce qu'un symbole ?
- Un symbole en Ruby est une identification immuable et unique, représentée par un nom précédé de deux points (
:
). - Exemple :
:nom
,:age
,:admin
Caractéristiques principales
- Immuables : une fois créés, ils ne changent pas.
- Uniqueness : chaque symbole est unique dans tout le programme. Si vous utilisez
:nom
plusieurs fois, Ruby ne crée qu'une seule instance en mémoire. - Rapides : leur utilisation est plus performante que celle des strings pour des identifiants ou des clés, car ils sont stockés une seule fois.
Différence avec les strings
Aspect | String ("texte" ) |
Symbole (:nom ) |
---|---|---|
Mutabilité | Modifiable (peut changer) | Immuable |
Création | Peut être créé à chaque utilisation | Créé une seule fois, partagé |
Usage principal | Contenu textuel, affichage, manipulation | Identifiants, clés, métadonnées |
Comparaison | Comparé par valeur (== ) |
Comparé par identité (object_id ) |
Exemple :
str1 = "hello"
str2 = "hello"
sym1 = :hello
sym2 = :hello
puts str1 == str2 # true (valeur identique)
puts sym1 == sym2 # true (valeur identique)
puts str1.object_id == str2.object_id # false (différentes instances)
puts sym1.object_id == sym2.object_id # true (même instance)
Usage courant des symboles
1. Clés dans les Hashes
person = { name: "Alice", age: 30 }
# ou
person = { :name => "Alice", :age => 30 }
2. Méthodes et options
def greet(name:, age:)
puts "Bonjour #{name}, vous avez #{age} ans."
end
greet(name: "Bob", age: 25)
3. Méta-programmation et métadonnées
Les symboles sont souvent utilisés pour représenter des noms de méthodes, des attributs, ou des options.
Les symboles et les mots-clés
- Les mots-clés en Ruby (comme
if
,while
,def
, etc.) sont réservés par le langage. - Les symboles sont des identifiants que vous pouvez définir vous-même, mais ils ne sont pas des mots-clés réservés.
- Exemple :
:if
est un symbole, maisif
est un mot-clé.
Résumé
Aspect | Symbole (:nom ) |
String ("texte" ) |
---|---|---|
Immuable | Oui | Non |
Créé une seule fois | Oui | Non |
Usage principal | Identifiants, clés, métadonnées | Contenu textuel, affichage |
Comparaison | Par identité (object_id ) |
Par valeur (== ) |
Les Ranges (Intervalles)
Un Range en Ruby représente un intervalle entre deux valeurs. C'est un concept puissant qui permet de manipuler des séquences de valeurs de manière élégante.
Syntaxe des Ranges
# Range inclusif (inclut la dernière valeur)
1..5 # représente les nombres 1, 2, 3, 4, 5
# Range exclusif (exclut la dernière valeur)
1...5 # représente les nombres 1, 2, 3, 4
Contextes d'usage des Ranges
1.Itération
(1..5).each { |n| puts n } # Affiche les nombres de 1 à 5
# Avec un pas spécifique
(1..10).step(2) { |n| puts n } # Affiche 1, 3, 5, 7, 9
2. Sélection dans les collections
array = [10, 20, 30, 40, 50]
array[1..3] # => [20, 30, 40]
3. Tests d'appartenance
age = 25
if (18..65).include?(age)
puts "Âge actif"
end
4. Conditions case
case score
when 0..59
puts "Échec"
when 60..69
puts "Passable"
when 70..79
puts "Bien"
when 80..100
puts "Excellent"
end
voir : sujet sur case en Ruby
5. Génération de séquences
letters = ('a'..'e').to_a # => ["a", "b", "c", "d", "e"]
Les structures de données
Arrays
# Création
fruits = ["pomme", "banane", "orange"]
# Accès
puts fruits[0] # pomme
puts fruits[-1] # orange (dernier élément)
puts fruits[0..1] # ["pomme", "banane"] (slice)
# Modification
fruits << "fraise" # Ajout à la fin
fruits.push("kiwi") # Ajout à la fin
fruits.unshift("ananas") # Ajout au début
fruits.pop # Supprime et retourne le dernier
fruits.shift # Supprime et retourne le premier
# Itération
fruits.each { |fruit| puts fruit }
fruits.map { |fruit| fruit.upcase } # Retourne un nouveau tableau
fruits.map! { |fruit| fruit.upcase } # altère le tableau
fruits.map { |fruit| fruit.upcase! } # !! altère le tableau
Hashes
# Création
person = {
"name" => "Alice",
"age" => 30,
"city" => "Paris"
}
# Syntaxe avec symboles (préférée)
person = {
name: "Alice",
age: 30,
city: "Paris"
}
# Accès
puts person[:name] # Alice
# Modification
person[:job] = "Developer"
person[:age] = 31
# Itération
person.each do |key, value|
puts "#{key}: #{value}"
end
Struct
La classe Struct permet de créer rapidement des structures de données avec des attributs nommés.
# Définition
Person = Struct.new(:name, :age, :city)
# Utilisation
alice = Person.new("Alice", 30, "Paris")
puts alice.name # => "Alice"
alice.age = 31 # Modification
OpenStruct
Similaire à Struct mais plus flexible (nécessite require 'ostruct'
).
require 'ostruct'
person = OpenStruct.new
person.name = "Bob"
person.age = 42
puts person.name # => "Bob"
Les structures de contrôle
Les Structures Conditionnelles
Elles permettent d'exécuter différents blocs de code en fonction de la vérité ou de la fausseté d'une ou plusieurs conditions.
if / elsif / else
La structure conditionnelle la plus courante.
Exemple :
age = 20
if age >= 18
puts "Vous êtes majeur."
elsif age >= 13
puts "Vous êtes un adolescent."
else
puts "Vous êtes un enfant."
end
Syntaxe :
if condition_1
# Code à exécuter si condition_1 est vraie
elsif condition_2
# Code à exécuter si condition_1 est fausse ET condition_2 est vraie
else
# Code à exécuter si toutes les conditions précédentes sont fausses
end
unless / else
L'inverse de if
. Le bloc de code est exécuté si la condition est fausse. Souvent utilisé pour améliorer la lisibilité lorsque la condition est naturellement exprimée de manière négative.
Exemple :
est_connecte = false
unless est_connecte
puts "Veuillez vous connecter."
else
puts "Bienvenue !"
end
Syntaxe :
unless condition
# Code à exécuter si la condition est fausse
else
# Code à exécuter si la condition est vraie (le 'else' est optionnel)
end
case / when / else
Utilisé pour gérer plusieurs conditions basées sur la valeur d'une seule expression. Plus propre que de multiples elsif
imbriqués.
Exemple :
jour_semaine = "Mardi"
case jour_semaine
when "Lundi"
puts "Début de semaine, courage !"
when "Mardi", "Mercredi", "Jeudi"
puts "Milieu de semaine."
when "Vendredi"
puts "C'est le week-end bientôt !"
else
puts "C'est le week-end !"
end
Syntaxe :
case expression
when valeur_1
# Code si expression == valeur_1
when valeur_2, valeur_3
# Code si expression == valeur_2 OU expression == valeur_3
when (valeur_min..valeur_max)
# Code si expression est dans la plage
else
# Code si aucune des conditions 'when' n'est remplie
end
Formes Modificatrices (Modifier Forms)
Ruby permet une syntaxe plus concise pour les conditions simples sur une seule ligne.
Exemple :
puts "Accès autorisé." if age >= 18
puts "Accès refusé." unless age >= 18
Syntaxe :
instruction if condition
instruction unless condition
Les Structures de Boucle (Itération)
Elles permettent de répéter un bloc de code plusieurs fois.
while
Exécute un bloc de code tant qu'une condition est vraie.
Exemple :
compteur = 0
while compteur < 5
puts "Compteur : #{compteur}"
compteur += 1
end
Syntaxe :
until
Exécute un bloc de code tant qu'une condition est fausse (l'inverse de while
).
Exemple :
compteur = 0
until compteur == 5
puts "Compteur : #{compteur}"
compteur += 1
end
Syntaxe :
until condition
# Code à répéter
end
for
(et pourquoi each
est souvent préféré)
Itère sur les éléments d'une collection.
- Note Importante : En Ruby,
for
est moins idiomatique que les méthodes d'itération desEnumerable
(commeeach
). La principale raison est quefor
ne crée pas un nouveau scope pour la variable d'itération, ce qui peut entraîner des fuites de variables.
Exemple :
nombres = [1, 2, 3]
for n in nombres
puts n
end
Syntaxe :
for element in collection
# Code à exécuter pour chaque element
end
each
et autres méthodes d'énumération
La manière la plus courante et idiomatique d'itérer en Ruby. Ces méthodes sont disponibles sur les objets qui incluent le module Enumerable
(Arrays, Hashes, Ranges, etc.).
- Autres méthodes utiles :
map
(oucollect
) : Transforme chaque élément et retourne un nouveau tableau.select
(oufilter
) : Sélectionne les éléments qui satisfont une condition.reject
: Rejette les éléments qui satisfont une condition.reduce
(ouinject
) : Combine tous les éléments d'une collection en un seul résultat.
each
: Itère sur chaque élément.
nombres = [1, 2, 3]
nombres.each do |n|
puts n
end
# Avec un hash
personne = { nom: "Alice", age: 30 }
personne.each do |cle, valeur|
puts "#{cle}: #{valeur}"
end
loop do
Crée une boucle infinie qui doit être explicitement arrêtée avec break
. Utile pour des boucles d'événements ou des processus continus.
Exemple :
reponse = ""
loop do
puts "Entrez 'quitter' pour sortir :"
reponse = gets.chomp
break if reponse == "quitter"
puts "Vous avez entré : #{reponse}"
end
puts "Boucle terminée."
Syntaxe :
loop do
# Code à répéter indéfiniment
# ...
break if condition_de_sortie
end
Méthodes d'itération numérique
Des méthodes pratiques pour itérer un nombre spécifique de fois ou sur une plage de nombres.
step
: Itère avec un pas spécifique.
0.step(10, 2) do |n|
puts n # Affiche 0, 2, 4, 6, 8, 10
end
downto
: Itère d'un nombre de départ jusqu'à un nombre de fin (décroissant).
3.downto(1) do |n|
puts n # Affiche 3, 2, 1
end
upto
: Itère d'un nombre de départ jusqu'à un nombre de fin.
1.upto(3) do |n|
puts n # Affiche 1, 2, 3
end
times
: Exécute un bloc un nombre de fois donné.
5.times do |i|
puts "Itération #{i}" # i va de 0 à 4
end
Les Modificateurs de Boucle
Ces mots-clés permettent de contrôler le flux d'exécution à l'intérieur d'une boucle.
break
Sort immédiatement de la boucle en cours.
Exemple :
nombres = [1, 2, 3, 4, 5]
nombres.each do |n|
break if n == 3
puts n
end
# Affiche 1, 2
next
Passe à l'itération suivante de la boucle, en ignorant le reste du code dans l'itération actuelle.
Exemple :
nombres = [1, 2, 3, 4, 5]
nombres.each do |n|
next if n % 2 == 0 # Passe les nombres pairs
puts n
end
# Affiche 1, 3, 5
redo
Redémarre l'itération actuelle de la boucle sans réévaluer la condition de la boucle. Moins courant et souvent un signe de logique complexe.
Exemple :
i = 0
while i < 3
puts "i est #{i}"
i += 1
redo if i == 2 # Redémarre l'itération quand i est 2
end
# Affiche:
# i est 0
# i est 1
# i est 2
# i est 2 (à cause du redo)
# i est 3 (quitte la boucle)
retry
Utilisé principalement dans les blocs rescue
pour retenter l'exécution du bloc begin
depuis le début.
Exemple (voir section 5 pour le contexte begin/rescue
) :
tentatives = 0
begin
puts "Tentative ##{tentatives + 1}"
raise "Erreur simulée" if tentatives < 2
puts "Opération réussie !"
rescue
tentatives += 1
puts "Erreur détectée. Nouvelle tentative..."
retry if tentatives < 3
puts "Échec après plusieurs tentatives."
end
La Gestion des Erreurs (Exceptions)
Ruby utilise un mécanisme d'exceptions pour gérer les erreurs et les situations imprévues.
begin / rescue / else / ensure
begin
: Le bloc de code où une exception pourrait se produire.rescue
: Le bloc de code exécuté si une exception se produit dans le blocbegin
. Vous pouvez spécifier le type d'exception à intercepter.else
: Le bloc de code exécuté si aucune exception ne se produit dans le blocbegin
.ensure
: Le bloc de code qui est toujours exécuté, qu'une exception se produise ou non. Idéal pour le nettoyage (fermeture de fichiers, connexions, etc.).
Exemple :
def diviser(a, b)
begin
resultat = a / b
puts "Le résultat est : #{resultat}"
rescue ZeroDivisionError
puts "Erreur : Division par zéro impossible !"
rescue TypeError => e
puts "Erreur de type : #{e.message}. Veuillez utiliser des nombres."
else
puts "La division s'est déroulée sans problème."
ensure
puts "Fin de l'opération de division."
end
end
diviser(10, 2)
puts "---"
diviser(10, 0)
puts "---"
diviser(10, "deux")
Syntaxe :
begin
# Code qui pourrait lever une exception
# ...
rescue TypeErreur1 => e
# Gérer TypeErreur1
puts "Erreur de type 1 : #{e.message}"
rescue TypeErreur2
# Gérer TypeErreur2
puts "Erreur de type 2"
rescue => e # Intercepte toute autre exception (StandardError par défaut)
puts "Une erreur inattendue s'est produite : #{e.class} - #{e.message}"
else
# Exécuté si AUCUNE exception n'est levée dans le bloc 'begin'
puts "Opération réussie sans erreur."
ensure
# Exécuté TOUJOURS, que l'exception soit levée ou non
puts "Nettoyage ou finalisation."
end
Récapitulatif
Nous avons exploré les structures de contrôle fondamentales en Ruby :
- Conditionnelles :
if
,unless
,case
pour la prise de décision. - Boucles :
while
,until
,each
,loop do
,times
, etc., pour la répétition. - Modificateurs de Boucle :
break
,next
,redo
,retry
pour un contrôle fin du flux. - Gestion des Erreurs :
begin/rescue/else/ensure
pour la robustesse du programme.
Bonnes Pratiques
- Lisibilité : Choisissez la structure qui rend votre intention la plus claire.
unless
pour les négations,case
pour de multipleselsif
. - Idiomatique Ruby : Préférez
each
et les méthodesEnumerable
àfor
pour l'itération. - Gestion des Erreurs : Interceptez les exceptions spécifiques plutôt que toutes les exceptions génériques (
rescue => e
) pour une meilleure gestion. - Simplicité : Évitez les boucles et conditions trop imbriquées ; refactorisez si nécessaire.
Maîtriser ces structures est essentiel pour écrire des programmes Ruby efficaces, lisibles et robustes.
Focus sur le statement case
en Ruby
Le case
est une structure de contrôle puissante en Ruby qui permet d'évaluer une expression selon plusieurs conditions. C'est une alternative élégante aux structures if/elsif/else
multiples, particulièrement lorsque vous comparez une même variable à différentes valeurs.
Syntaxe de base
case expression
when valeur1
# code exécuté si expression == valeur1
when valeur2
# code exécuté si expression == valeur2
else
# code exécuté si aucune condition n'est satisfaite
end
Fonctionnement détaillé
Lorsque Ruby évalue un statement case
, il compare l'expression à chaque condition when
en utilisant la méthode ===
(triple égal). Cette méthode a un comportement spécial selon le type de l'objet qui l'appelle.
Exemples d'utilisation
1. Comparaison simple avec des valeurs
day = "Lundi"
case day
when "Lundi"
puts "Début de semaine"
when "Mercredi"
puts "Milieu de semaine"
when "Vendredi"
puts "Fin de semaine"
else
puts "Autre jour"
end
# Affiche: "Début de semaine"
2. Utilisation avec des Ranges
note = 85
case note
when 0..49
puts "Échec"
when 50..64
puts "Passable"
when 65..74
puts "Bien"
when 75..89
puts "Très bien"
when 90..100
puts "Excellent"
else
puts "Note invalide"
end
# Affiche: "Très bien"
3. Conditions multiples par clause when
jour = "Samedi"
case jour
when "Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi"
puts "Jour ouvrable"
when "Samedi", "Dimanche"
puts "Week-end"
end
# Affiche: "Week-end"
4. Utilisation avec des Regexp (expressions régulières)
fruit = "pomme"
case fruit
when /^p/
puts "Commence par p"
when /^b/
puts "Commence par b"
else
puts "Commence par une autre lettre"
end
# Affiche: "Commence par p"
5. Utilisation avec des Classes
objet = [1, 2, 3]
case objet
when String
puts "C'est une chaîne"
when Array
puts "C'est un tableau"
when Hash
puts "C'est un hash"
else
puts "C'est autre chose"
end
# Affiche: "C'est un tableau"
6. Case sans expression (pattern matching implicite)
case
when Time.now.hour < 12
puts "Bon matin"
when Time.now.hour < 18
puts "Bon après-midi"
else
puts "Bonsoir"
end
# Affiche selon l'heure actuelle
7. Pattern matching (Ruby 2.7+)
resultat = [200, "OK", {id: 123}]
case resultat
when [200, _, hash] if hash[:id]
puts "Succès avec ID: #{hash[:id]}"
when [404, *]
puts "Non trouvé"
when [500..599, *]
puts "Erreur serveur"
else
puts "Autre réponse"
end
# Affiche: "Succès avec ID: 123"
Particularités importantes
- Retour de valeur : Comme la plupart des structures en Ruby,
case
retourne la valeur de la dernière expression évaluée.
statut = case niveau
when 1 then "Débutant"
when 2 then "Intermédiaire"
when 3 then "Avancé"
else "Inconnu"
end
- Opérateur
===
: Lecase
utilise l'opérateur===
pour les comparaisons, ce qui permet des comportements spéciaux:Range === valeur
vérifie si la valeur est dans l'intervalleRegexp === string
vérifie si la chaîne correspond à l'expression régulièreClass === objet
vérifie si l'objet est une instance de la classe
- Évaluation séquentielle : Ruby évalue les conditions
when
dans l'ordre et s'arrête à la première correspondance.
Avantages du case
par rapport aux if/elsif/else
- Lisibilité : Plus clair quand on compare une même variable à plusieurs valeurs
- Expressivité : Exploite la puissance de l'opérateur
===
pour des comparaisons sophistiquées - Concision : Permet d'écrire du code plus compact, surtout avec la syntaxe
then
Le statement case
est l'une des structures qui illustre parfaitement la philosophie de Ruby : offrir aux développeurs des outils élégants et expressifs pour écrire du code lisible et maintenable.
Les méthodes et blocs
Définition de méthodes
# Méthode simple
def greet
puts "Bonjour!"
end
# Avec paramètres
def greet_person(name)
puts "Bonjour, #{name}!"
end
# Avec valeur par défaut
def greet_with_language(name, language = :fr)
greetings = { fr: "Bonjour", en: "Hello" }
puts "#{greeting[language]}, #{name}!"
end
end
# Avec paramètres et keywords
def display(data, upcase: false)
data.up
puts (upcase)? data : data.upcase
end
# Avec retour explicite
def add(a, b)
return a + b
end
# Le retour est implicite (dernière expression)
def multiply(a, b)
a * b
end
Appel de méthodes
greet # Bonjour!
greet_person("Alice") # Bonjour, Alice!
greet_with_language("Alice") # Bonjour, Alice!
greet_with_language("Bob", :en) # Hello, Bob!
sum = add(5, 3) # 8
product = multiply(4, 2) # 8
display("Hello") # Hello
display("Bonjour", upcase: true) # BONJOUR
Blocs
# bloc type en ruby avec les contrôle d'exceptions
begin
# Ouverture du fichier en mode lecture
File.open('mon_fichier.txt', 'r') do |fichier|
# Lecture et affichage du contenu
puts "Contenu du fichier :"
fichier.each_line do |ligne|
puts ligne
end
end
rescue Errno::ENOENT
# Gestion de l'erreur si le fichier n'existe pas
puts "Erreur : Le fichier n'existe pas."
rescue Errno::EACCES
# Gestion de l'erreur si le fichier n'est pas accessible en lecture
puts "Erreur : Impossible d'accéder au fichier (problème de permissions)."
rescue IOError
# Gestion des erreurs d'entrée/sortie
puts "Erreur : Problème lors de la lecture du fichier."
rescue StandardError => e
# Capture toute autre erreur standard
puts "Erreur inattendue : #{e.message}"
puts e.backtrace
ensure
# Ce bloc sera toujours exécuté, que des erreurs se produisent ou non
puts "Opération terminée."
end
# Bloc avec do...end en closure
[1, 2, 3].each do |num|
puts num * 2
end
# Bloc avec accolades
[1, 2, 3].each { |num| puts num * 2 }
# Méthode avec bloc
def execute_block
puts "Avant le bloc"
yield if block_given?
puts "Après le bloc"
end
execute_block { puts "Dans le bloc" }
Proc et Lambda
# Proc
square = Proc.new { |x| x * x }
puts square.call(4) # 16
# Lambda
cube = ->(x) { x * x * x }
puts cube.call(3) # 27
# Différences entre Proc et Lambda
# 1. Vérification du nombre d'arguments
# 2. Comportement du return
Différence entre Proc et Lambda en Ruby
Les Proc et Lambda sont deux types de closures en Ruby, mais ils présentent plusieurs différences importantes :
Vérification du nombre d'arguments
# Lambda - strict sur le nombre d'arguments
my_lambda = lambda { |x, y| puts "#{x} et #{y}" }
# my_lambda.call(1) # Erreur: ArgumentError (wrong number of arguments)
# my_lambda.call(1, 2, 3) # Erreur: ArgumentError (wrong number of arguments)
my_lambda.call(1, 2) # Fonctionne: "1 et 2"
# Proc - flexible sur le nombre d'arguments
my_proc = Proc.new { |x, y| puts "#{x} et #{y}" }
my_proc.call(1) # Fonctionne: "1 et nil"
my_proc.call(1, 2, 3) # Fonctionne: "1 et 2" (ignore le 3ème argument)
Comportement du return
def test_lambda
my_lambda = -> { return "Retour du lambda" }
result = my_lambda.call
return "Retour de la méthode: #{result}"
end
def test_proc
my_proc = Proc.new { return "Retour du proc" }
result = my_proc.call
return "Cette ligne ne sera jamais exécutée"
end
puts test_lambda # Affiche: "Retour de la méthode: Retour du lambda"
puts test_proc # Affiche: "Retour du proc"
Syntaxe de création
# Création d'un Lambda
lambda1 = lambda { |x| x * 2 }
lambda2 = -> (x) { x * 2 } # Syntaxe alternative (Ruby 1.9+)
# Création d'un Proc
proc1 = Proc.new { |x| x * 2 }
proc2 = proc { |x| x * 2 } # Syntaxe alternative
Méthode lambda?
my_lambda = -> { puts "Lambda" }
my_proc = Proc.new { puts "Proc" }
puts my_lambda.lambda? # true
puts my_proc.lambda? # false
Résumé des différences
Caractéristique | Lambda | Proc |
---|---|---|
Vérification des arguments | Stricte (comme les méthodes) | Flexible (arguments manquants = nil, excédentaires ignorés) |
Comportement du return |
Retourne au contexte appelant | Retourne de la méthode englobante |
Syntaxe | lambda { } ou -> { } |
Proc.new { } ou proc { } |
Identité | lambda? == true |
lambda? == false |
En général, les lambdas sont plus proches des méthodes normales en termes de comportement, tandis que les Procs offrent plus de flexibilité mais peuvent avoir des effets de bord inattendus avec return
.
Comprendre les Appels de Méthodes en Ruby
L'Envoi de Messages en Ruby
Ruby est un langage orienté objet pur où tout est objet et les interactions se font par envoi de messages.
# Syntaxe standard
objet.methode(arguments)
# En coulisses, c'est transformé en:
objet.send(:methode, arguments)
L'appel de méthode est en réalité un message envoyé à un objet.
En Ruby même ce qui ressemble à des opérateurs ne l'est pas !
# Ceci...
p 1 + 2
# est équivalent à...
p 1.+(2)
# qui est la même chose que :
p 1.send "+", 2
La Méthode call
La méthode call
permet d'exécuter des objets appelables (Proc, lambda, méthodes).
# Avec un Proc
addition = proc { |a, b| a + b }
addition.call(3, 4) # => 7
# Avec une lambda
multiplication = ->(a, b) { a * b }
multiplication.call(3, 4) # => 12
# Avec une méthode
methode = "hello".method(:upcase)
methode.call # => "HELLO"
Arité (Arity)
L'arité définit le nombre d'arguments qu'une méthode attend.
# Vérifier l'arité
addition = proc { |a, b| a + b }
addition.arity # => 2
# Arité négative (arguments variables)
variadic = proc { |*args| args.sum }
variadic.arity # => -1
Différence clé:
- Proc: souple avec les arguments
- Lambda: strict avec les arguments
Types de Paramètres - Arguments Positionnels
# Arguments positionnels basiques
def saluer(nom, titre)
"Bonjour #{titre} #{nom}!"
end
saluer("Dupont", "M.") # => "Bonjour M. Dupont!"
# Arguments avec valeurs par défaut
def accueillir(nom, message = "Bienvenue")
"#{message}, #{nom}!"
end
accueillir("Marie") # => "Bienvenue, Marie!"
Arguments Variables
# Arguments variables avec splat (*)
def somme(*nombres)
nombres.sum
end
somme(1, 2, 3, 4) # => 10
# Combinaison d'arguments fixes et variables
def presenter(titre, *noms)
"#{titre}: #{noms.join(', ')}"
end
presenter("Participants", "Alice", "Bob", "Charlie")
# => "Participants: Alice, Bob, Charlie"
Arguments Nommés (Keywords)
# Arguments nommés
def configurer(taille:, couleur:, style: "normal")
"Configuration: taille=#{taille}, couleur=#{couleur}, style=#{style}"
end
configurer(taille: 12, couleur: "bleu")
# => "Configuration: taille=12, couleur=bleu, style=normal"
# Arguments nommés variables
def options(obligatoire:, **autres)
"Obligatoire: #{obligatoire}, Autres: #{autres}"
end
options(obligatoire: "oui", a: 1, b: 2)
# => "Obligatoire: oui, Autres: {:a=>1, :b=>2}"
Blocs comme Arguments
# Méthode acceptant un bloc
def executer
puts "Avant"
yield if block_given?
puts "Après"
end
executer { puts "Pendant" }
# Affiche:
# Avant
# Pendant
# Après
# Bloc explicite avec &
def transformer(tableau, &bloc)
tableau.map(&bloc)
end
transformer([1, 2, 3]) { |n| n * 2 } # => [2, 4, 6]
Exemple Complet
def traiter_donnees(id, *valeurs, format: "json", strict: false, &bloc)
resultat = {
id: id,
valeurs: valeurs,
format: format,
strict: strict
}
# Applique le bloc si fourni
resultat = bloc.call(resultat) if bloc
resultat
end
traiter_donnees(42, "a", "b", format: "xml") { |r| r.merge(traite: true) }
# => {:id=>42,
:valeurs=>["a", "b"],
:format=>"xml",
:strict=>false,
:traite=>true}
Méthodes comme Objets de Première Classe
# Obtenir un objet Method
class Calculateur
def additionner(a, b)
a + b
end
end
calc = Calculateur.new
methode_addition = calc.method(:additionner)
# Utiliser l'objet Method
methode_addition.call(5, 3) # => 8
methode_addition.arity # => 2
# Créer un Proc à partir d'une méthode
proc_addition = methode_addition.to_proc
[1, 2, 3].map(&proc_addition.curry[10]) # => [11, 12, 13]
Le Splat Operator en Ruby
Le splat operator (opérateur d'éclatement) est représenté par l'astérisque *
en Ruby. C'est un outil puissant qui permet de manipuler des collections d'éléments de différentes façons.
Splat simple (*
)
Conversion d'une collection en arguments séparés
def addition(a, b, c)
a + b + c
end
nombres = [1, 2, 3]
puts addition(*nombres) # Équivalent à addition(1, 2, 3)
Capture d'un nombre variable d'arguments
def afficher_tous(*elements)
elements.each { |e| puts e }
end
afficher_tous("pomme", "orange", "banane")
# Capture tous les arguments dans un tableau
Décomposition d'un tableau
premier, *milieu, dernier = [1, 2, 3, 4, 5]
puts "Premier: #{premier}" # 1
puts "Milieu: #{milieu}" # [2, 3, 4]
puts "Dernier: #{dernier}" # 5
Fusion de tableaux
array1 = [1, 2, 3]
array2 = [4, 5]
combined = [*array1, *array2] # [1, 2, 3, 4, 5]
Double splat (**
)
Le double splat s'applique aux hashes et permet de manipuler des paires clé-valeur.
Conversion d'un hash en arguments nommés
def profil(nom:, age:, ville:)
puts "#{nom}, #{age} ans, habite à #{ville}"
end
info = { nom: "Alice", age: 30, ville: "Paris" }
profil(**info) # Équivalent à profil(nom: "Alice", age: 30, ville: "Paris")
Capture d'arguments nommés variables
def options(obligatoire:, **autres)
puts "Option obligatoire: #{obligatoire}"
puts "Autres options: #{autres}"
end
options(obligatoire: "valeur", optionnel1: "test", optionnel2: 123)
# Autres options: {:optionnel1=>"test", :optionnel2=>123}
Fusion de hashes
hash1 = { a: 1, b: 2 }
hash2 = { c: 3, d: 4 }
combined = { **hash1, **hash2 } # { a: 1, b: 2, c: 3, d: 4 }
Cas d'utilisation avancés
Combinaison des deux types de splat
def methode(*args, **kwargs)
puts "Arguments positionnels: #{args}"
puts "Arguments nommés: #{kwargs}"
end
methode(1, 2, 3, a: "A", b: "B")
# Arguments positionnels: [1, 2, 3]
# Arguments nommés: {:a=>"A", :b=>"B"}
Transfert d'arguments à une autre méthode
def wrapper(*args, **kwargs)
puts "Avant l'appel"
autre_methode(*args, **kwargs)
puts "Après l'appel"
end
Utilisation avec des opérateurs de décomposition
range = (1..5)
puts [*range] # [1, 2, 3, 4, 5]
hash = { a: 1, b: 2 }
nouvelles_options = { c: 3, **hash } # { c: 3, a: 1, b: 2 }
Le splat operator est un outil essentiel en Ruby qui rend le code plus flexible et expressif, particulièrement utile pour créer des API élégantes et des méthodes à arguments variables.
Programmation full Objet
L'aspect "Full Object" de Ruby : Une Explication Détaillée
Qu'est-ce que "Full Object" en Ruby ?
En Ruby, "Full Object" (ou "Tout est objet") signifie que pratiquement tout élément du langage est un objet, y compris les types primitifs comme les nombres, les booléens, et même nil
(l'équivalent de null
dans d'autres langages). Chaque valeur peut recevoir des messages (appels de méthodes) et possède une identité propre.
Origines de cette approche
Influences historiques
Ruby a été créé par Yukihiro "Matz" Matsumoto en 1995, qui s'est inspiré de plusieurs langages :
- Smalltalk : Principal inspirateur pour le paradigme "tout est objet"
- Lisp : Pour certains aspects fonctionnels
- Perl : Pour la flexibilité et les expressions régulières
- Python : Pour la syntaxe claire
Matz voulait créer un langage qui mettrait l'accent sur la programmation orientée objet pure, tout en restant agréable à utiliser.
Philosophie de conception
La philosophie de Ruby est centrée sur le "Principe de moindre surprise" (Principle of Least Surprise). Matz a conçu Ruby pour que le langage fonctionne comme les programmeurs s'y attendraient intuitivement, en privilégiant la lisibilité et l'élégance du code.
Comment fonctionne l'approche "Full Object"
Tout est vraiment un objet
# Même les nombres sont des objets
42.class # => Integer
42.methods.count # => retourne un nombre important de méthodes
# Les chaînes de caractères aussi
"hello".upcase # => "HELLO"
# Même true, false et nil sont des objets
true.class # => TrueClass
false.class # => FalseClass
nil.class # => NilClass
Classes et métaclasses
Ruby implémente son système d'objets via une hiérarchie sophistiquée :
- Chaque objet est une instance d'une classe
- Chaque classe est elle-même un objet, instance de la classe
Class
- Chaque classe a une "métaclasse" (ou "singleton class") qui stocke ses méthodes de classe
# Définition d'une classe
class Person
def initialize(name)
@name = name
end
def greet
"Hello, I'm #{@name}"
end
end
# La classe est elle-même un objet
Person.class # => Class
Person.is_a?(Object) # => true
Rappel : Méthodes et messages
En Ruby, appeler une méthode est conceptuellement "envoyer un message" à un objet :
# Ces deux expressions sont équivalentes
"hello".upcase
"hello".send(:upcase)
Avantages de l'approche "Full Object"
- Cohérence : Pas d'exceptions à la règle "tout est objet"
- Expressivité : Permet d'écrire du code plus élégant et concis
- Métaprogrammation facilitée : Modification dynamique des classes et objets
- Extensibilité : Possibilité d'étendre même les types de base
Implications pratiques
Métaprogrammation
# Ajout d'une méthode à une classe existante
class String
def shout
self.upcase + "!"
end
end
"hello".shout # => "HELLO!"
Duck Typing
Ruby favorise le "duck typing" : si un objet répond aux méthodes attendues, son type exact importe peu.
def print_length(object)
puts object.length
end
print_length("hello") # => 5
print_length([1, 2, 3]) # => 3
print_length({a: 1, b: 2}) # => 2
L'approche "Full Object" de Ruby est fondamentale à son identité. Elle offre une cohérence conceptuelle et une grande flexibilité, permettant aux développeurs d'écrire du code expressif et élégant. Cette philosophie a influencé de nombreux langages modernes et reste l'un des aspects les plus appréciés de Ruby.
Classes et objets
# Définition d'une classe
class Person
def initialize(name, age)
@name = name
@age = age
end
def introduce
puts "Je m'appelle #{@name} et j'ai #{@age} ans."
end
end
# Création d'objets
alice = Person.new("Alice", 30)
bob = Person.new("Bob", 25)
# Appel de méthodes
alice.introduce # Je m'appelle Alice et j'ai 30 ans.
bob.introduce # Je m'appelle Bob et j'ai 25 ans.
Attributs et accesseurs
class Person
# Getters et setters automatiques
attr_accessor :name, :age
def initialize(name, age)
@name = name
@age = age
end
def sex
@sex
end
def sex=(sex)
@sex = sex
end
end
alice = Person.new("Alice", 30)
puts alice.name # Alice (getter)
alice.name = "Alicia" # (setter)
puts alice.name # Alicia
# Variantes
# attr_reader :name # Getter uniquement
# attr_writer :name # Setter uniquement
Méthodes d'instance et de classe
class MathHelper
# Méthode d'instance
def square(x)
x * x
end
# Méthode de classe
def self.cube(x)
x * x * x
end
end
helper = MathHelper.new
puts helper.square(4) # 16 (méthode d'instance)
puts MathHelper.cube(3) # 27 (méthode de classe)
Héritage
class Animal
attr_accessor :carnivorous, :coat
def initialize
@carnivorous = false
@coat = true
end
def speak
"Je suis un animal"
end
end
class Dog < Animal
def initialize
super
@carnivorous = true
end
def speak
"Woof !"
end
end
class Fish < Animal
def initialize
super
@coat = false
end
def speak
"Blup !"
end
end
puts Animal.new.speak # Je suis un animal
dingo = Dog.new ; nemo = Fish::new
puts dingo.speak # Woof!
p dingo.carnivorous # true
p dingo.coat # true
puts nemo.speak # Meow!
p nemo.carnivorous # false
p nemo.coat # false
Modules et mixins
# Module comme namespace
module Math
PI = 3.14159
def self.square(x)
x * x
end
end
puts Math::PI # 3.14159
puts Math.square(4) # 16
# Module comme mixin
module Swimmable
def swim
"Je nage!"
end
end
class Fish
include Swimmable
end
class Duck
include Swimmable
end
puts Fish.new.swim # Je nage!
puts Duck.new.swim # Je nage!
Visibilité des méthodes
En Ruby, les protections de méthode (ou niveaux de visibilité) permettent de contrôler l'accès aux méthodes d'une classe. Il existe trois niveaux de protection :
Public (par défaut)
Les méthodes publiques sont accessibles partout, sans restriction.
class Voiture
def demarrer # méthode publique par défaut
puts "Vroum vroum!"
end
end
ma_voiture = Voiture.new
ma_voiture.demarrer # Fonctionne sans problème
Protected
Les méthodes protégées sont accessibles :
- À l'intérieur de la classe qui les définit
- Dans les classes héritées
- Par d'autres instances de la même classe
class Personne
attr_reader :nom
def initialize(nom, age)
@nom = nom
@age = age
end
def plus_age_que?(autre_personne)
@age > autre_personne.age_actuel
end
protected
def age_actuel # méthode protégée
@age
end
end
alice = Personne.new("Alice", 30)
bob = Personne.new("Bob", 25)
puts alice.plus_age_que?(bob) # true
# puts alice.age_actuel # Erreur! Méthode protégée
Private
Les méthodes privées sont les plus restrictives :
- Accessibles uniquement à l'intérieur de la classe qui les définit
- Ne peuvent pas être appelées avec un receveur explicite (même
self
)
class CompteBancaire
def initialize(solde_initial)
@solde = solde_initial
end
def deposer(montant)
@solde += montant
actualiser_historique("Dépôt", montant)
afficher_solde
end
def retirer(montant)
if montant <= @solde
@solde -= montant
actualiser_historique("Retrait", montant)
afficher_solde
else
puts "Fonds insuffisants!"
end
end
private
def actualiser_historique(type, montant)
puts "#{type} de #{montant}€ effectué"
end
def afficher_solde
puts "Solde actuel: #{@solde}€"
end
end
compte = CompteBancaire.new(1000)
compte.deposer(500) # Fonctionne et appelle des méthodes privées en interne
# compte.afficher_solde # Erreur! Méthode privée
Ces protections aident à implémenter l'encapsulation, un principe fondamental de la programmation orientée objet, en cachant les détails d'implémentation et en exposant uniquement les interfaces nécessaires.
Exceptions
# Lever une exception
def divide(a, b)
raise ArgumentError, "Division par zéro!" if b == 0
a / b
end
# Gérer une exception
begin
result = divide(10, 0)
puts result
rescue ArgumentError => e
puts "Erreur: #{e.message}"
ensure
puts "Cette partie s'exécute toujours"
end
# Créer une exception personnalisée
class InsufficientFundsError < StandardError; end
def withdraw(amount)
raise InsufficientFundsError, "Fonds insuffisants" if amount > @balance
# ...
end
Gems et gestion de dépendances
Introduction aux gems
Qu'est-ce qu'une gem?
- Bibliothèque Ruby packagée
- Facilite la réutilisation du code
- Gérée par RubyGems (https://rubygems.org)
Installation d'une gem
$ gem install httparty
Librairie cliente HTTP pour faire des requêtes sur un web service, dans notre exemple l'API Github.
Utilisation dans un script
require 'httparty'
response = HTTParty.get('https://api.github.com')
puts response.code # 200
puts response.message # OK
puts response.headers.inspect
Bundler et Gemfile
Installation de Bundler
Installation de bundle pour gérer les dépendances
# gem install bundler
Initialisation d'un bundle
$ bundle init
=> Writing new Gemfile to /Users/romain/Gemfile
Cette commande créée un fichier Gemfile qui va lister les dépendances :
# frozen_string_literal: true
source "https://rubygems.org"
# gem "rails"
Ajout de la dépendances
$ bundle add httparty
=> Fetching gem metadata from https://rubygems.org/...........
Pour installer les dépendances on peut faire un :
$ bundle update
=> Resolving dependencies...
=> Bundle updated!
Exemple pratique avec HTTParty
require 'httparty'
require 'json'
class GithubClient
include HTTParty
base_uri 'https://api.github.com'
def initialize(token = nil)
@headers = {
'Accept' => 'application/vnd.github+json',
'X-GitHub-Api-Version' => '2022-11-28',
'User-Agent' => 'Ruby GitHub Client'
# 'Authorization' => "token #{token}"
}
end
def get_user(username)
self.class.get("/users/#{username}", headers: @headers)
end
def get_repos(username)
self.class.get("/users/#{username}/repos", headers: @headers)
end
end
client = GithubClient.new
user = client.get_user('lecid')
puts "Nom: #{user['name']}"
puts "Bio: #{user['bio']}"
pour executer le script standalone
$ bundle exec ruby test.rb
=> Nom: Romain GEORGES
=> Bio: Ruby enthousiast
=> Open source evangelist
YARD : Documentation pour Ruby
Introduction
YARD (Yet Another Ruby Documentation) est un outil de documentation pour le langage Ruby, conçu pour remplacer RDoc. Il offre une syntaxe plus riche et des fonctionnalités plus avancées pour documenter votre code Ruby.
Installation
gem install yard
ou l'ajouter dans les Gemspec ou dans le Gemfile
Remarque : Un fichier gemspec sert à "builder" un gem et dans un gemfile on peut faire une référence des dépendances depuis le fichier Gemspec, tel que dans le Gemfile, on a :
# frozen_string_literal: true
source 'https://rubygems.org'
gemspec
Syntaxe de base
YARD utilise des commentaires précédés par #
et des tags commençant par @
.
Structure d'un commentaire YARD
# Description générale sur une ou plusieurs lignes
#
# @param [Type] nom_paramètre description du paramètre
# @return [Type] description de la valeur de retour
# @example Titre de l'exemple
# code_exemple
Exemples pratiques
Documenter une méthode
# Calcule la somme de deux nombres
#
# @param [Integer] a Premier nombre à additionner
# @param [Integer] b Second nombre à additionner
# @return [Integer] La somme des deux nombres
# @example Addition de 2 et 3
# add(2, 3) #=> 5
def add(a, b)
a + b
end
Documenter une classe
# Une classe représentant un utilisateur dans le système
#
# @attr [String] name Le nom complet de l'utilisateur
# @attr [String] email L'adresse email de l'utilisateur
# @attr_reader [Time] created_at Date de création du compte
class User
attr_accessor :name, :email
attr_reader :created_at
# Initialise un nouvel utilisateur
#
# @param [String] name Le nom de l'utilisateur
# @param [String] email L'email de l'utilisateur
# @raise [ArgumentError] Si l'email est invalide
def initialize(name, email)
@name = name
@email = email
@created_at = Time.now
raise ArgumentError, "Email invalide" unless email.include?('@')
end
end
Tags YARD courants
Tag | Description |
---|---|
@param |
Documente un paramètre de méthode |
@return |
Documente la valeur de retour |
@example |
Fournit un exemple d'utilisation |
@raise |
Documente les exceptions possibles |
@see |
Référence à d'autres éléments |
@author |
Auteur du code |
@since |
Version d'introduction |
@deprecated |
Marque comme déprécié |
@todo |
Indique une tâche à faire |
@note |
Ajoute une note importante |
Types complexes
YARD permet de spécifier des types complexes :
# @param [Array<String>] noms Liste de noms
# @param [Hash{Symbol => String}] options Options de configuration
# @return [String, nil] Un texte ou nil si non trouvé
Génération de la documentation
Générer la documentation
yard doc
Lancer un serveur de documentation
yard server
Puis visitez http://localhost:8808
Astuces avancées
Grouper des méthodes
# @!group Opérations mathématiques
# Addition de deux nombres
# @param [Numeric] a Premier nombre
# @param [Numeric] b Second nombre
# @return [Numeric] Résultat de l'addition
def add(a, b)
a + b
end
# @!endgroup
Documenter des API privées
# @private
def méthode_interne
# ...
end
Conclusion
YARD est un outil puissant qui permet de créer une documentation riche et détaillée pour vos projets Ruby. Sa syntaxe flexible et ses nombreuses fonctionnalités en font un choix excellent pour maintenir une documentation de qualité.
Pour plus d'informations, consultez la documentation officielle de YARD.
Rake - Automatisation des tâches
Qu'est-ce que Rake ?
Rake est l'acronyme de "Ruby Make". C'est un utilitaire de construction et un gestionnaire de tâches écrit en Ruby. Son objectif principal est d'automatiser les tâches répétitives et complexes dans un projet Ruby (ou même d'autres types de projets).
Contrairement à des outils comme Make
(souvent utilisé en C/C++), Rake utilise la syntaxe Ruby pour définir ses tâches, ce qui le rend très flexible et puissant, car vous pouvez utiliser toute la puissance du langage Ruby à l'intérieur de vos tâches.
Pourquoi utiliser Rake ?
- Automatisation : Exécuter des séquences d'actions complexes avec une seule commande.
- Cohérence : Assurer que les tâches sont toujours exécutées de la même manière, réduisant les erreurs humaines.
- Lisibilité : Les tâches sont définies en Ruby, ce qui les rend souvent plus lisibles et maintenables que des scripts shell complexes.
- Dépendances : Gérer facilement les dépendances entre les tâches, garantissant que les prérequis sont satisfaits avant l'exécution d'une tâche.
- Portabilité : Étant basé sur Ruby, Rake est multiplateforme.
Concepts Clés de Rake
Le Rakefile
Toutes les tâches Rake sont définies dans un fichier nommé Rakefile
(sans extension) à la racine de votre projet. C'est là que Rake va chercher les définitions de tâches.
Les Tâches (task
)
Une tâche est l'unité fondamentale de Rake. Elle représente une action spécifique à exécuter.
# Rakefile
# Définition d'une tâche simple
task :hello do
puts "Bonjour depuis Rake !"
end
Pour exécuter cette tâche, vous iriez dans votre terminal à la racine du projet et taperiez :
rake hello
Les Descriptions (desc
)
Il est fortement recommandé d'ajouter une description à chaque tâche. Cela rend votre Rakefile
auto-documenté et permet à Rake d'afficher une liste des tâches disponibles avec leurs descriptions.
# Rakefile
desc "Affiche un message de salutation"
task :hello do
puts "Bonjour depuis Rake !"
end
Pour voir la liste des tâches avec leurs descriptions :
rake -T
Les Dépendances
Les tâches peuvent dépendre d'autres tâches. Cela signifie qu'une tâche ne sera exécutée qu'après que toutes ses dépendances aient été exécutées avec succès.
# Rakefile
desc "Prépare l'environnement (ex: crée des dossiers)"
task :prepare do
puts "1. Préparation de l'environnement..."
# Simuler une action
sleep 0.5
end
desc "Compile le code source"
task :compile => :prepare do
puts "2. Compilation du code..."
# Simuler une action
sleep 0.5
end
desc "Exécute les tests"
task :test => :compile do
puts "3. Exécution des tests..."
# Simuler une action
sleep 0.5
end
desc "Déploie l'application"
task :deploy => :test do
puts "4. Déploiement de l'application..."
# Simuler une action
sleep 0.5
puts "Déploiement terminé !"
end
Si vous exécutez rake deploy
, Rake s'assurera que test
est exécuté, qui à son tour s'assurera que compile
est exécuté, qui lui-même s'assurera que prepare
est exécuté. L'ordre d'exécution sera donc : prepare
, compile
, test
, deploy
.
Les Espaces de Noms (namespace
)
Pour organiser les tâches et éviter les conflits de noms, vous pouvez les regrouper dans des espaces de noms. C'est particulièrement utile dans les grands projets (comme Rails).
# Rakefile
namespace :db do
desc "Migre la base de données"
task :migrate do
puts "Migration de la base de données..."
# Logique de migration ici
end
desc "Remet la base de données à zéro (drop, create, migrate)"
task :reset do
puts "Réinitialisation de la base de données..."
# Logique de réinitialisation ici
end
end
namespace :app do
desc "Démarre le serveur d'application"
task :start do
puts "Démarrage du serveur..."
end
end
Pour exécuter ces tâches :
rake db:migrate
rake db:reset
rake app:start
Tâches avec Arguments
Les tâches peuvent accepter des arguments, ce qui les rend plus flexibles.
# Rakefile
desc "Salue une personne spécifique. Usage: rake greet[Nom]"
task :greet, [:name] do |t, args|
name = args[:name] || "monde" # Valeur par défaut si l'argument n'est pas fourni
puts "Bonjour, #{name} !"
end
Exécution :
rake greet[Alice]
# Output: Bonjour, Alice !
rake greet
# Output: Bonjour, monde !
Tâche par Défaut (task :default
)
Vous pouvez définir une tâche par défaut qui sera exécutée si vous tapez simplement rake
sans spécifier de nom de tâche.
# Rakefile
desc "Exécute la tâche par défaut (souvent 'test' ou 'build')"
task :default => :test do
puts "Tâche par défaut terminée."
end
task :test do
puts "Exécution des tests..."
end
Exécution :
rake
# Output:
# Exécution des tests...
# Tâche par défaut terminée.
Anatomie d'un Rakefile
Un Rakefile
typique peut inclure :
require
statements : Pour charger des bibliothèques Ruby externes ou des fichiers de votre propre projet qui contiennent la logique métier que vos tâches Rake vont utiliser.- Définitions de tâches : En utilisant
task
,desc
,namespace
. - Variables et constantes : Pour configurer le comportement des tâches.
# Rakefile
# Charger des bibliothèques ou des fichiers de votre projet
require 'fileutils' # Utile pour les opérations sur les fichiers
require_relative 'lib/my_app_logic' # Si vous avez de la logique dans lib/my_app_logic.rb
# Configuration
APP_NAME = "MyAwesomeApp"
BUILD_DIR = "build"
desc "Nettoie le répertoire de build"
task :clean do
puts "Nettoyage du répertoire #{BUILD_DIR}..."
FileUtils.rm_rf(BUILD_DIR) if File.directory?(BUILD_DIR)
puts "Nettoyage terminé."
end
desc "Crée le répertoire de build"
task :create_build_dir do
puts "Création du répertoire #{BUILD_DIR}..."
FileUtils.mkdir_p(BUILD_DIR) unless File.directory?(BUILD_DIR)
puts "Répertoire créé."
end
desc "Construit l'application"
task :build => [:clean, :create_build_dir] do
puts "Construction de #{APP_NAME}..."
# Simuler la copie de fichiers ou la compilation
File.write("#{BUILD_DIR}/#{APP_NAME}.txt", "Contenu de l'application")
puts "Construction terminée."
end
desc "Exécute l'application construite"
task :run_app => :build do
puts "Exécution de l'application #{APP_NAME}..."
content = File.read("#{BUILD_DIR}/#{APP_NAME}.txt")
puts "Contenu de l'application : '#{content}'"
MyAppLogic.do_something_important # Appel à une méthode de votre logique métier
end
namespace :docs do
desc "Génère la documentation"
task :generate do
puts "Génération de la documentation..."
# Commande pour générer la doc (ex: yard doc)
end
end
desc "Tâche par défaut : construit et exécute"
task :default => :run_app
Commandes Rake Utiles
rake
: Exécute la tâche par défaut.rake <task_name>
: Exécute une tâche spécifique.rake -T
ourake --tasks
: Liste toutes les tâches disponibles avec leurs descriptions.rake -D <task_name>
ourake --describe <task_name>
: Affiche la description détaillée d'une tâche spécifique.rake -P
ourake --prereqs
: Affiche les dépendances de chaque tâche.rake -f <Rakefile>
: Spécifie unRakefile
différent à utiliser.
Cas d'Usages Courants de Rake
Rake est omniprésent dans les projets Ruby, en particulier avec Ruby on Rails.
- Développement Web (Rails) :
rake db:migrate
: Appliquer les migrations de base de données.rake db:seed
: Remplir la base de données avec des données initiales.rake test
: Exécuter les tests unitaires et d'intégration.rake assets:precompile
: Précompiler les assets pour la production.rake routes
: Afficher toutes les routes de l'application.rake log:clear
: Nettoyer les fichiers de log.
- Automatisation de Build : Compiler du code, copier des fichiers, créer des archives (ZIP/TAR).
- Déploiement : Préparer l'environnement, déployer le code, redémarrer les services.
- Maintenance : Nettoyer les fichiers temporaires, sauvegarder des données, générer des rapports.
- Génération de Code : Créer des squelettes de fichiers, des modules, etc.
- Tests : Exécuter différentes suites de tests, générer des rapports de couverture.
Bonnes Pratiques avec Rake
- Tâches Atomiques et Focalisées : Chaque tâche devrait faire une seule chose bien définie. Si une tâche devient trop complexe, divisez-la en sous-tâches et utilisez les dépendances.
- Utilisez des Descriptions (
desc
) : C'est crucial pour la maintenabilité et la découverte des tâches. UnRakefile
sans descriptions est unRakefile
difficile à utiliser. - Organisez avec des Espaces de Noms (
namespace
) : Pour les projets de taille moyenne à grande, les namespaces améliorent grandement l'organisation et la lisibilité. - Déléguez la Logique Complexe : Évitez de mettre des blocs de code Ruby très longs et complexes directement dans votre
Rakefile
. Si une tâche nécessite une logique métier significative, encapsulez cette logique dans des classes ou modules Ruby séparés (par exemple, dans le dossierlib/
de votre projet) et appelez ces classes depuis votre tâche Rake. Cela rend votre code plus testable et réutilisable. - Gérez les Erreurs : Utilisez des blocs
begin...rescue...end
ou des vérifications conditionnelles pour gérer les erreurs potentielles dans vos tâches. - Idempotence : Dans la mesure du possible, rendez vos tâches idempotentes. Cela signifie que l'exécution répétée d'une tâche devrait produire le même résultat que sa première exécution, sans effets secondaires indésirables. Par exemple, une tâche de création de dossier ne devrait pas échouer si le dossier existe déjà.
- Testez vos Tâches Rake : Pour les tâches Rake complexes, il peut être judicieux d'écrire des tests unitaires ou d'intégration pour s'assurer qu'elles fonctionnent comme prévu. Cela est facilité si vous déléguez la logique complexe à des classes Ruby séparées.
- Versionnez votre
Rakefile
: LeRakefile
fait partie intégrante de votre projet et doit être sous contrôle de version (Git, SVN, etc.). - Utilisez des Variables d'Environnement : Pour des configurations qui varient entre les environnements (développement, staging, production), utilisez des variables d'environnement plutôt que de coder en dur les valeurs dans le
Rakefile
.
En résumé, Rake est un outil incroyablement puissant et flexible pour automatiser presque toutes les tâches imaginables dans un projet Ruby. En maîtrisant ses concepts clés et en suivant les bonnes pratiques, vous pouvez améliorer considérablement l'efficacité, la cohérence et la maintenabilité de vos workflows de développement.
Tests avec RSpec
Qu'est-ce que RSpec ?
RSpec est un framework de test pour le langage de programmation Ruby. Il est conçu pour faciliter le développement piloté par le comportement (Behavior-Driven Development - BDD). Contrairement aux frameworks de test traditionnels qui se concentrent sur la vérification des fonctions, RSpec met l'accent sur la spécification du comportement attendu d'une application.
Les tests RSpec sont écrits sous forme de "spécifications exécutables", qui décrivent ce qu'une partie du code est censée faire, dans un langage proche du langage naturel.
Pourquoi utiliser RSpec ?
- Clarté et Expressivité : La syntaxe de RSpec est très lisible et ressemble à des phrases en anglais, ce qui rend les spécifications faciles à comprendre pour les développeurs, les chefs de projet et même les clients.
- BDD (Behavior-Driven Development) : RSpec encourage une approche BDD, où les tests sont écrits avant le code de production, servant de documentation vivante et de guide pour le développement.
- Collaboration : Les spécifications claires facilitent la communication et la collaboration au sein des équipes.
- Confiance et Sécurité : Des tests robustes donnent confiance lors des refactorisations et des ajouts de nouvelles fonctionnalités, en s'assurant que les changements n'introduisent pas de régressions.
- Détection Précoce des Bugs : En écrivant des tests avant le code, les problèmes de conception ou les bugs sont souvent identifiés plus tôt dans le cycle de développement.
Principes du BDD
Le BDD est une extension du Test-Driven Development (TDD) qui se concentre sur le comportement du système plutôt que sur les détails d'implémentation. Il utilise un langage plus accessible pour décrire les exigences et les tests.
Les spécifications BDD suivent souvent le format "Given-When-Then" :
- Given (Étant donné) : Un état initial ou un contexte.
- When (Quand) : Une action ou un événement se produit.
- Then (Alors) : Un résultat ou un comportement attendu est observé.
RSpec traduit ces principes en code avec describe
(contexte), context
(sous-contexte), et it
(l'action et le résultat attendu).
Installation et Configuration
Pour une application Ruby pure
- Exécutez
bundle install
.
Initialisez RSpec dans votre projet :
bundle exec rspec --init
Cela créera un dossier spec/
et un fichier spec/spec_helper.rb
.
Ajoutez RSpec à votre Gemfile
:
# Gemfile
group :development, :test do
gem 'rspec'
end
Pour une application Rails
- Exécutez
bundle install
.
Générez la configuration RSpec pour Rails :
rails generate rspec:install
Cela créera un dossier spec/
avec spec/spec_helper.rb
et spec/rails_helper.rb
.
Ajoutez rspec-rails
à votre Gemfile
:
# Gemfile
group :development, :test do
gem 'rspec-rails'
end
Structure des fichiers de test
Par convention, les fichiers de spécification RSpec sont placés dans le répertoire spec/
et se terminent par _spec.rb
.
Exemples :
spec/models/user_spec.rb
(pour le modèleUser
)spec/lib/calculator_spec.rb
(pour une classeCalculator
danslib/
)spec/requests/users_spec.rb
(pour les requêtes HTTP liées aux utilisateurs)
Concepts Fondamentaux de RSpec
describe
et context
: Groupement des exemples
context
: Utilisé pour regrouper des exemples sous une condition ou un état spécifique. Il est souvent imbriqué dans un describe
pour affiner le contexte du test.
describe Calculator do
describe '#add' do # Teste la méthode 'add'
context 'when given positive numbers' do
# ... exemples pour l'addition de nombres positifs
end
context 'when given negative numbers' do
# ... exemples pour l'addition de nombres négatifs
end
end
end
describe
: Utilisé pour regrouper des exemples (tests) liés à une entité spécifique (une classe, un module, une méthode). C'est le bloc de plus haut niveau dans un fichier de spécification.
# spec/calculator_spec.rb
describe Calculator do
# ... exemples pour la classe Calculator
end
it
: L'exemple (la spécification)
Le bloc it
contient un seul exemple ou une seule spécification. La chaîne de caractères passée à it
doit décrire le comportement attendu de manière claire et concise.
describe Calculator do
describe '#add' do
context 'when given positive numbers' do
it 'returns the sum of the numbers' do
calculator = Calculator.new
expect(calculator.add(2, 3)).to eq(5)
end
end
end
end
expect
et les Matchers : Les assertions
expect
est la base des assertions dans RSpec. Il prend une valeur ou un bloc de code en argument, et est suivi d'un "matcher" qui définit la condition attendue.
expect(valeur_actuelle).to matcher(valeur_attendue)
expect(valeur_actuelle).not_to matcher(valeur_attendue) # Pour l'inverse
Matchers courants :
- Égalité :
eq(expected)
: Vérifie l'égalité de valeur (==
).be(expected)
: Vérifie l'égalité d'objet (même instance).
- Vérification de type/classe :
be_an_instance_of(Class)
be_a_kind_of(Class)
- Valeurs booléennes/nil :
be_nil
be_truthy
(tout ce qui n'est pasfalse
ounil
)be_falsey
(false
ounil
)
- Collections :
be_empty
include(item1, item2, ...)
have_key(:key)
/have_value('value')
(pour les hashes)
- Comparaison numérique :
be > (value)
be >= (value)
be < (value)
be <= (value)
be_between(min, max).inclusive
/.exclusive
- Exceptions :
raise_error(ErrorClass)
raise_error(ErrorClass, 'message')
- Changement d'état :
change(object, :method).from(old_value).to(new_value)
change(object, :method).by(delta)
change { block_of_code }.from(old_value).to(new_value)
- Messages (pour les Test Doubles) :
receive(:method_name)
(utilisé avecallow
ouexpect
)have_received(:method_name)
(pour vérifier qu'un message a été reçu)
Exemples :
expect(5).to eq(5)
expect([1, 2, 3]).to include(2)
expect(nil).to be_nil
expect(true).to be_truthy
expect { raise 'Error!' }.to raise_error(RuntimeError)
user = User.new(name: 'Alice')
expect { user.save }.to change(User, :count).by(1)
Exécution des tests
Pour exécuter tous les tests :
bundle exec rspec
Pour exécuter un fichier spécifique :
bundle exec rspec spec/models/user_spec.rb
Pour exécuter un test spécifique (en utilisant le numéro de ligne) :
bundle exec rspec spec/models/user_spec.rb:10
Pour exécuter les tests qui ont échoué lors de la dernière exécution :
bundle exec rspec --only-failures
Concepts Avancés et Bonnes Pratiques
Hooks (before
, after
, around
)
Les hooks permettent d'exécuter du code avant ou après les exemples.
before(:each)
(oubefore
) : Exécuté avant chaque exemple (it
) dans le blocdescribe
/context
courant et ses enfants. C'est le plus couramment utilisé pour la configuration de l'état initial.before(:all)
: Exécuté une seule fois avant tous les exemples dans le blocdescribe
/context
courant. À utiliser avec prudence car il peut introduire des dépendances entre les tests si l'état n'est pas correctement nettoyé.after(:each)
(ouafter
) : Exécuté après chaque exemple. Utile pour le nettoyage.after(:all)
: Exécuté une seule fois après tous les exemples.around(:each)
: Prend un bloc qui encapsule l'exécution de l'exemple. Utile pour des configurations plus complexes comme la gestion de transactions de base de données.
describe User do
let(:user) { User.create(name: 'Test User') } # Sera créé avant chaque test
before(:each) do
# Ce code s'exécute avant chaque 'it'
# Par exemple, réinitialiser une variable
@admin_user = User.create(admin: true)
end
after(:each) do
# Ce code s'exécute après chaque 'it'
# Par exemple, nettoyer des fichiers temporaires
end
context 'when user is admin' do
before(:all) do
# Ce code s'exécute une seule fois avant tous les 'it' de ce context
# À utiliser avec précaution !
@global_resource = create_expensive_resource
end
it 'can manage other users' do
expect(@admin_user.can_manage?(user)).to be_truthy
end
end
end
let
et let!
: Variables paresseuses et immédiates
Ces méthodes sont essentielles pour rendre vos tests DRY (Don't Repeat Yourself) et lisibles.
let!(:name) { value }
: Similaire à let
, mais la valeur est évaluée immédiatement avant chaque exemple (it
), comme si elle était dans un before(:each)
. Utile lorsque la création de l'objet a des effets secondaires que vous voulez garantir avant chaque test.
describe User do
let!(:user) { User.create(email: 'test@example.com') } # 'user' est créé avant chaque 'it'
it 'increments user count' do
expect(User.count).to eq(1) # Le user est déjà là
end
end
let(:name) { value }
: Définit une méthode qui retourne value
. La valeur est mémoisée (calculée une seule fois) et évaluée paresseusement (seulement la première fois qu'elle est appelée dans un exemple).
describe Article do
let(:article) { Article.create(title: 'My Article') }
it 'has a title' do
expect(article.title).to eq('My Article') # 'article' est créé ici
end
it 'is not published by default' do
expect(article).not_to be_published # 'article' est créé ici
end
end
subject
: Le sujet implicite ou explicite
subject
permet de définir l'objet principal que vous testez dans un bloc describe
.
Sujet explicite : Vous pouvez définir votre propre subject
en utilisant subject { ... }
.
describe User do
subject { User.new(name: 'Alice') } # subject est cette instance spécifique
it 'has a name' do
expect(subject.name).to eq('Alice')
end
end
Vous pouvez également lui donner un nom : subject(:user) { User.new(...) }
, ce qui vous permet d'utiliser user
au lieu de subject
.
Sujet implicite : Si votre describe
est sur une classe, RSpec crée automatiquement un subject
qui est une nouvelle instance de cette classe.
describe Calculator do # subject est une instance de Calculator
it 'adds two numbers' do
expect(subject.add(2, 3)).to eq(5) # subject est Calculator.new
end
end
Test Doubles (Mocks et Stubs)
Les "test doubles" sont des objets qui imitent le comportement d'objets réels dans un test. Ils sont utilisés pour isoler l'unité de code testée des dépendances externes (base de données, API externes, etc.), rendant les tests plus rapides et plus fiables.
double
: Crée un objet générique qui peut être utilisé comme un test double. Vous pouvez lui donner un nom pour une meilleure lisibilité des messages d'erreur.
user_double = double('User', name: 'Alice', email: 'alice@example.com')
expect(user_double.name).to eq('Alice')
Mocks (expect(...).to receive(...)
) : Un mock est un stub qui inclut également une attente sur la façon dont il est censé être utilisé. Il vérifie si une méthode spécifique a été appelée avec des arguments spécifiques.
describe OrderProcessor do
it 'sends a confirmation email after processing' do
mailer = double('Mailer')
expect(mailer).to receive(:send_confirmation).with('user@example.com', 'Order 123') # Attente
processor = OrderProcessor.new(mailer: mailer)
processor.process_and_confirm('user@example.com', 'Order 123')
end
end
Stubs (allow(...).to receive(...)
) : Un stub est un objet qui fournit des réponses prédéfinies à des appels de méthodes spécifiques. Il ne vérifie pas si la méthode a été appelée.
describe OrderProcessor do
it 'processes the order using the payment gateway' do
gateway = double('PaymentGateway') # Crée un double générique
allow(gateway).to receive(:charge).and_return(true) # Stubbe la méthode charge
processor = OrderProcessor.new(gateway: gateway)
expect(processor.process(100)).to be_truthy
end
end
Focus et Skip
Utiles pendant le développement pour se concentrer sur certains tests ou en ignorer d'autres temporairement.
xdescribe
, xcontext
, xit
: Marque un bloc ou un exemple pour qu'il soit ignoré.
xdescribe 'My unfinished feature' do # Ce bloc sera ignoré
# ...
end
it 'should do something else' do
skip 'This test is not ready yet' # Ce test sera ignoré avec un message
# ...
end
fdescribe
, fcontext
, fit
: Marque un bloc ou un exemple pour qu'il soit le seul à être exécuté.
fdescribe 'My feature' do # Seuls les tests dans ce bloc seront exécutés
fit 'should do something specific' do # Seul ce test sera exécuté
# ...
end
end
Attention : N'oubliez pas de retirer les f
et x
avant de commiter votre code !
Configuration de RSpec
Le fichier spec/spec_helper.rb
(et spec/rails_helper.rb
pour Rails) est l'endroit où vous configurez RSpec.
Exemples de configuration courante :
# spec/spec_helper.rb
RSpec.configure do |config|
# Afficher la documentation des tests (chaînes de caractères des describe/it)
config.formatter = :documentation
# Ne pas autoriser les mocks/stubs non définis
config.mock_with :rspec do |mocks|
mocks.verify_partial_doubles = true
end
# Nettoyer la base de données entre les tests (souvent avec DatabaseCleaner)
# config.before(:suite) do
# DatabaseCleaner.clean_all
# end
# config.before(:each) do
# DatabaseCleaner.strategy = :transaction
# end
# config.before(:each, js: true) do
# DatabaseCleaner.strategy = :truncation
# end
# config.before(:each) do
# DatabaseCleaner.start
# end
# config.after(:each) do
# DatabaseCleaner.clean
# end
# Inclure des modules d'aide
# config.include FactoryBot::Syntax::Methods # Si vous utilisez FactoryBot
end
Cas d'Usage et Types de Tests
RSpec est polyvalent et peut être utilisé pour différents niveaux de tests :
Tests Unitaires (Modèles, Classes de service)
- Objectif : Tester la plus petite unité de code isolément (une méthode, une classe).
- Exemple : Tester la logique d'une méthode dans un modèle Rails, ou une classe de service qui effectue un calcul.
- Bonne pratique : Utiliser des test doubles pour isoler les dépendances (base de données, API externes).
# spec/models/product_spec.rb
describe Product do
describe '#price_with_tax' do
it 'calculates the price including tax' do
product = Product.new(price: 100, tax_rate: 0.20)
expect(product.price_with_tax).to eq(120)
end
end
end
Tests d'Intégration (Interactions entre composants)
- Objectif : Tester comment différentes unités de code interagissent entre elles.
- Exemple : Tester qu'un contrôleur appelle correctement une méthode de service, ou qu'un modèle interagit correctement avec un autre modèle.
- Bonne pratique : Moins de mocks que les tests unitaires, laisser les composants réels interagir.
# spec/services/order_creator_spec.rb
describe OrderCreator do
let(:user) { create(:user) } # Utilise FactoryBot pour créer un vrai utilisateur
let(:product) { create(:product, price: 50) }
it 'creates an order and associates it with the user and products' do
order = OrderCreator.new(user, [product]).call
expect(order).to be_persisted
expect(order.user).to eq(user)
expect(order.products).to include(product)
expect(order.total_amount).to eq(50)
end
end
Tests Fonctionnels / Requêtes (API, Contrôleurs)
- Objectif : Tester les points d'entrée de votre application (API REST, actions de contrôleur) en simulant des requêtes HTTP.
- Exemple : Vérifier qu'une requête
POST /users
crée un utilisateur et retourne le bon statut HTTP. - Bonne pratique : Utiliser les helpers de Rails (comme
get
,post
,json_response
) et vérifier les statuts HTTP, les en-têtes et le corps de la réponse.
# spec/requests/users_spec.rb (pour une API REST)
describe 'Users API' do
describe 'POST /users' do
context 'with valid parameters' do
it 'creates a new user' do
expect {
post '/users', params: { user: { email: 'test@example.com', password: 'password' } }
}.to change(User, :count).by(1)
expect(response).to have_http_status(:created)
expect(json_response['email']).to eq('test@example.com')
end
end
context 'with invalid parameters' do
it 'does not create a user' do
expect {
post '/users', params: { user: { email: '', password: '123' } }
}.not_to change(User, :count)
expect(response).to have_http_status(:unprocessable_entity)
expect(json_response['errors']['email']).to include("can't be blank")
end
end
end
end
Tests de Feature / Système (avec Capybara)
- Objectif : Tester l'application du point de vue de l'utilisateur final, en simulant les interactions du navigateur.
- Exemple : Tester le processus d'inscription, de connexion, ou le parcours d'achat complet.
- Bonne pratique : Utiliser Capybara pour simuler les clics, les saisies de texte, et vérifier le contenu de la page. Ces tests sont plus lents et doivent être moins nombreux que les tests unitaires.
# spec/features/user_signup_spec.rb
require 'rails_helper'
RSpec.feature 'User Signup', type: :feature do
scenario 'a user can sign up successfully' do
visit new_user_registration_path
fill_in 'Email', with: 'newuser@example.com'
fill_in 'Password', with: 'password123'
fill_in 'Password confirmation', with: 'password123'
click_button 'Sign up'
expect(page).to have_content('Welcome! You have signed up successfully.')
expect(User.last.email).to eq('newuser@example.com')
end
scenario 'a user cannot sign up with invalid email' do
visit new_user_registration_path
fill_in 'Email', with: 'invalid-email'
fill_in 'Password', with: 'password123'
fill_in 'Password confirmation', with: 'password123'
click_button 'Sign up'
expect(page).to have_content('Email is invalid')
expect(User.count).to eq(0)
end
end
Tests de Régression
- Objectif : S'assurer que les bugs corrigés ne réapparaissent pas et que les nouvelles fonctionnalités n'introduisent pas de problèmes dans le code existant.
- Bonne pratique : Chaque fois qu'un bug est découvert et corrigé, écrire un test qui reproduit ce bug. Ce test échouera avant la correction et passera après, garantissant que le bug ne réapparaîtra pas sans que le test ne le signale.
Bonnes Pratiques Générales
Lisibilité et Expressivité
- Messages
describe
/context
/it
clairs : Ils doivent former des phrases lisibles qui décrivent le comportement attendu.describe User
context 'when logged in'
it 'should display the dashboard'
- Utiliser
let
etsubject
: Pour réduire la duplication et rendre les tests plus concis.
Isolation des Tests
- Chaque test doit être indépendant : Un test ne doit pas dépendre de l'ordre d'exécution des autres tests ou de l'état laissé par un test précédent.
- Nettoyage de l'état : Utiliser
before(:each)
pour configurer l'état etafter(:each)
(ou des outils comme DatabaseCleaner) pour le nettoyer.
Vitesse des Tests
- Tests unitaires rapides : Ils devraient s'exécuter en millisecondes. Évitez les accès à la base de données ou aux systèmes de fichiers dans les tests unitaires. Utilisez des test doubles.
- Tests d'intégration et de feature plus lents : C'est normal, mais minimisez leur nombre et leur complexité.
Éviter l'Over-Mocking
- Ne moquez pas tout : Moquez uniquement les dépendances externes (API, base de données, services tiers) ou les objets complexes qui ralentiraient le test.
- Testez le comportement, pas l'implémentation : Ne moquez pas des méthodes internes à la classe que vous testez. Si vous le faites, cela peut indiquer que votre classe a trop de responsabilités ou que votre test est trop lié aux détails d'implémentation.
Un seul expect
par it
(règle générale)
Bien que ce ne soit pas une règle stricte, avoir une seule assertion par it
rend le test plus clair sur ce qu'il vérifie et facilite l'identification de la cause d'un échec. Si vous avez plusieurs expect
, cela peut indiquer que votre it
teste trop de choses.
Nommage des spécifications
- Les noms des fichiers :
nom_de_la_classe_spec.rb
- Les blocs
describe
:describe NomDeLaClasse
oudescribe 'Nom de la fonctionnalité'
- Les blocs
context
:context 'quand la condition X est vraie'
- Les blocs
it
:it 'devrait faire Y'
ouit 'ne devrait pas faire Z'
Refactorisation des tests
Les tests sont du code. Ils doivent être maintenus, refactorisés et être aussi propres que le code de production. La duplication dans les tests est un signe qu'une refactorisation est nécessaire (souvent avec let
, before
, ou des helpers).
Conclusion
RSpec est un outil formidable pour écrire des tests clairs, expressifs et maintenables en Ruby. En adoptant les principes du BDD et en suivant les bonnes pratiques, vous pouvez construire une suite de tests robuste qui vous donnera confiance dans votre code, facilitera la collaboration et accélérera le développement.
N'oubliez pas que l'apprentissage de RSpec est un processus continu. Pratiquez, lisez le code des autres, et explorez la documentation officielle de RSpec pour découvrir de nouvelles fonctionnalités et techniques.
Annexes & références
Convention syntaxiques
- 2 espaces pour indenter, pas de tabs, (mineur)
- préférer [] à Array::new ou Array.new (mineur)
- préférer {} à Hash.new (mineur)éviter les variables globales
- éviter les evals
- éviter le camelCase, utiliser les_formes_lisibles => snake_case
- écrire du code humainement lisible, ruby est fait pour ça
- éviter les notations hongroise
voir http://github.com/chneukirchen/styleguide/raw/master/RUBY-STYLE
Règles syntaxiques
- les commentaires commencent part # (sharp) jusqu'à EOL
- un programme ruby est une séquence d'expressions fini par un point virgule (;) ou un retour chariot sauf si elle est incomplète\ en fin de ligne si incomplête.
mots reservés
alias and BEGIN begin break case class def defined?do else elsif END end ensure false for ifin module next nil not or redo rescue retryreturn self super then true undef unless until whenwhile yield
Types
les types de base sont :
- numbers
- strings
- ranges
- regexp
- symbols
- arrays
- hashes
Numbers
123- 1_234
- 123.45
- 1.2e-3
- 0xffff # hex
- 0b01011 # binaire
- 0377 # octal
Strings & quoting
- 'pas d\'interpolatio n\' echape que le \\'
- "#{interpolation}, et backslashes\n"
- %q(pas d\'interpolation)
- %Q(interpolation et backslashes)
- %(interpolation et backslashes)
- `interpretation avec interpolation et backslashes`. => execute la commande
- %x(interpretation avec interpolation et backslashes) => execute la commande
Backslashes
\t (tab), \n (newline), \r (carriage return), \f (form feed), \b(backspace), \a (bell), \e (escape), \s (whitespace), \nnn (octal),\xnn (hexadecimal), \cx (control x), \C-x (control x), \M-x (meta x),\M-\C-x (meta control x)
Documentation sur place
- <<identifier - interpolation
- <<"identifier" - pareil
- <<'identifier' - pas d'interpolation
- <<-identifier - accepte l'indentation préfixé par "-"
finir par le un retour chariot + identifier
Symbols
- 1.8: les Symboles ne doivent pas contenir \0 ou être vide.
- :symbol == :symbol
- :'#{"sans"} interpolation' == :"#{"sans"} interpolation"
- :"#{"avec"} interpolation" == :"avec interpolation"
- %s(#{"sans"} interpolation) == :"#{"sans"} interpolation"
- Ranges
1..10 - 1...10
- 'a'..'z'
- 'a'...'z'
- (1..10) === 5 # true
- (1..10) === 10 # true
- (1...10) === 10 # false
- (1..10) === 15 # false