💎 Ruby – Le Guide Ultime
Deep Dive : Philosophie (Matz), Blocs (yield), POO (Mixins), Gems (Bundler) & Ruby on Rails.
1. Philosophie & Syntaxe
"Bonheur du développeur" (Matz). puts, @var. Fin de ligne (; optionnel).
2. Contrôle (if/unless/loops)
if/elsif/end. La magie de unless. while/until. Itérateurs (.each).
3. 🚀 Blocs, Procs & Lambdas
Le cœur de Ruby. do |x| ... end. yield. Proc vs lambda.
4. POO (Classes & Objets)
"Tout est objet". class, initialize, @var (instance), attr_accessor.
5. POO (Modules & Mixins)
Héritage multiple via Module. include (instance) vs extend (classe).
6. Structures (Array)
[], %w[] (array de mots). .push (<<), .pop, .shift. Slicing ([1..3]).
7. Structures (Hash & Symbols)
{ key: 'val' } vs { :key => 'val' }. L'importance des Symbols (:key).
8. Magie (Enumerables)
La puissance du module Enumerable. .map, .select, .reduce (.inject).
9. Gems & Bundler
gem install. Bundler (le "npm/pip" de Ruby). Gemfile & bundle install.
10. 🚂 Framework: Rails
Le "killer app". Convention over Configuration. ActiveRecord (ORM), MVC.
Rails ActiveRecord11. Gestion d'Erreurs
begin / rescue StandardError => e / ensure / end. raise "Erreur".
12. Outils & Liens
IRB (Console), Rake (Tâches), ERB (Templates). Liens (RubyGems).
Ruby est un langage de programmation interprété, dynamique, et purement orienté objet (une philosophie "tout est objet"). Il a été créé au milieu des années 90 par Yukihiro "Matz" Matsumoto.
La philosophie de Ruby est "le bonheur du développeur" (Developer Happiness). La syntaxe est conçue pour être élégante, lisible et "magique", en s'inspirant de Perl, Smalltalk, et Lisp.
Caractéristiques Clés
- Typage Dynamique Fort : Comme Python,
5 + "hello"est uneTypeError. - Tout est un Objet : Il n'y a pas de types primitifs.
5est une instance de la classeInteger."hello"est une instance deString. - Syntaxe Flexible : Les parenthèses
()sont souvent optionnelles. Les;sont inutiles. - Blocs : La "killer feature" de Ruby. C'est la base de ses itérateurs et de son élégance. (Voir 1.3).
Syntaxe de Base
# Commentaire
# Convention: snake_case pour les variables et méthodes
ma_variable = "Bonjour"
UN_CONSTANTE = 3.14 # Commence par une Majuscule
# Affichage (puts = put string + newline)
puts "Hello World"
p "Debug" # p est "inspect" (affiche les types) -> "Debug"
# Pas de parenthèses (optionnel)
puts "Hello"
# est égal à
puts("Hello")
# String interpolation (guillemets doubles)
nom = "Alice"
puts "Bonjour, #{nom.upcase}!" # "Bonjour, ALICE!"
Historique des Versions (10+ ans)
- Ruby 1.8 (Legacy) : La version qui a propulsé Ruby on Rails. Très lente.
- Ruby 1.9 (2007) : Réécriture majeure. Introduction de YARV (nouvelle VM), "hash rocket" (
=>) devient optionnel, gestion des encodings. - Ruby 2.x (2013-2020) : Améliorations de performance, "keyword arguments", refinements.
- Ruby 3.0 (2020) : Focus sur la concurrence (
Ractors), typage statique (RBS, Sorbet),Hashnouvelle syntaxe ({ x: 1 }) devient la norme.
Note : En Ruby, tous les blocs (if, def, while...) se terminent par le mot-clé end.
if / elsif / else
x = 10 if x > 20 puts "Grand" elsif x == 10 puts "Moyen" else puts "Petit" end # "If" inline (modificateur) puts "C'est 10 !" if x == 10
unless (La magie de Ruby)
unless est l'inverse de if. C'est du "sucre syntaxique" pour if !(...).
is_admin = false # Mauvais (difficile à lire) if !is_admin puts "Accès refusé" end # Bon (Pythonic... euh, Ruby-esque) unless is_admin puts "Accès refusé" end
Boucles (while & .each)
Le for (for i in 0..5) existe, mais n'est jamais utilisé. La "vraie" boucle Ruby est l'itérateur .each, qui prend un bloc.
while / until
i = 0 while i < 3 puts i i += 1 end
.each (La bonne façon)
fruits = ["pomme", "banane", "cerise"]
# Syntaxe multi-lignes (do...end)
fruits.each do |fruit|
puts fruit.capitalize
end
# Syntaxe une ligne ({...})
fruits.each { |fruit| puts fruit.capitalize }
Le "Bloc" est la fonctionnalité la plus puissante et la plus unique de Ruby. C'est un morceau de code anonyme que l'on peut "passer" à une méthode.
1. Le Bloc (Anonyme) & yield
Un bloc est *implicite*. Il ne peut pas être stocké dans une variable. On le "donne" à une méthode. La méthode l'exécute avec yield.
# 1. La méthode qui *accepte* un bloc def ma_methode puts "Début de la méthode" # 'yield' exécute le bloc qui a été passé yield yield puts "Fin de la méthode" end # 2. On appelle la méthode en lui *donnant* un bloc ma_methode do puts "-> Je suis le bloc !" end # Sortie: # Début de la méthode # -> Je suis le bloc ! # -> Je suis le bloc ! # Fin de la méthode
2. yield avec arguments
def ma_methode_args
# On "passe" une valeur au bloc
yield "Alice"
yield "Bob"
end
ma_methode_args do |nom|
puts "Bonjour, #{nom} !"
end
# Sortie:
# Bonjour, Alice !
# Bonjour, Bob !
3. Proc & lambda (Blocs "explicites")
Que faire si on veut *stocker* un bloc dans une variable ? On utilise un Proc (procédure) ou un lambda (une sorte de Proc plus strict).
# 1. Un Proc (stocke le bloc)
mon_proc = Proc.new { |x| puts "Proc dit: #{x}" }
# 2. Un Lambda (syntaxe "stabby arrow")
mon_lambda = ->(x) { puts "Lambda dit: #{x}" }
# 3. On peut les appeler
mon_proc.call(10) # "Proc dit: 10"
mon_lambda.call(20) # "Lambda dit: 20"
# 4. On peut les PASSER à une méthode (avec '&')
# '&' transforme le Proc en bloc implicite
["a", "b"].each(&mon_proc)
En Ruby, **tout est objet**. Il n'y a pas de types primitifs. 5 est un objet (instance de Integer). "hello" est un objet (instance de String).
Classes, initialize, et @
class NomDeClasse: Définit une classe (convention : CamelCase).def initialize: Le constructeur.@variable: Une variable d'instance (privée).@@variable: Une variable de classe (partagée, rare).$variable: Une variable globale (à éviter).
Getters, Setters & attr_accessor
En Ruby, @variable est privée. On utilise des "accessors" (méthodes) pour y accéder.
La "Longue" Façon (Manuelle)
class User
def initialize(name)
@name = name
end
# Getter
def name
@name
end
# Setter
def name=(new_name)
@name = new_name
end
end
u = User.new("Alice")
puts u.name # Appel du getter 'name'
u.name = "Bob" # Appel du setter 'name='
La "Bonne" Façon (attr_)
Ruby fournit des "macros" (des méthodes) pour écrire ces getters/setters à notre place.
class User
# Crée le getter 'def name'
attr_reader :name
# Crée le setter 'def status='
attr_writer :status
# Crée getter ET setter pour 'email'
attr_accessor :email
def initialize(name, email)
@name = name
@email = email
@status = "pending"
end
end
Ruby n'a pas d'héritage multiple (une classe ne peut hériter que d'UNE seule classe).
Le Problème : Comment partager des fonctionnalités communes (ex: marcher) entre Humain et Robot ?
La Solution : Les Mixins. Un Module est une collection de méthodes. Une Classe peut "inclure" un Module pour "mixer" (ajouter) ses méthodes.
include (Méthodes d'Instance)
include ajoute les méthodes du module en tant que méthodes d'instance (sur self).
# 1. Le Module (le "Mixin")
module Volant
def voler
puts "Je vole !"
end
end
# 2. Les Classes
class Oiseau
include Volant # "Mixe" les méthodes d'instance
end
class Avion
include Volant
end
# 3. Utilisation
oiseau = Oiseau.new
avion = Avion.new
oiseau.voler # "Je vole !"
avion.voler # "Je vole !"
extend (Méthodes de Classe)
extend ajoute les méthodes du module en tant que méthodes de classe (sur LaClasse.methode).
module Finder
def find_by_name(name)
# ... logique de recherche
puts "Recherche de #{name} dans #{self}"
end
end
class User
# On veut appeler User.find_by_name(...)
extend Finder
end
User.find_by_name("Alice")
# "Recherche de Alice dans User"
L'Array (liste) de Ruby est ordonné, indexé par 0, et mutable.
Création
# 1. Standard arr = [1, 2, 3] # 2. %w (Array de mots, pas de guillemets, pas d'interpolation) fruits = %w[pomme banane cerise] # (Identique à ["pomme", "banane", "cerise"]) # 3. %i (Array de symboles, idem) symboles = %i[un deux trois] # (Identique à [:un, :deux, :trois])
Méthodes Courantes (Modification)
arr = [1, 2, 3] # Ajouter (push) arr.push(4) # [1, 2, 3, 4] # "Shovel" (identique à push, très idiomatique) arr << 5 # [1, 2, 3, 4, 5] # Retirer (le dernier) arr.pop # 5 (renvoie l'élément) # Retirer (le premier) arr.shift # 1 (renvoie l'élément) # Ajouter (au début) arr.unshift(0) # [0, 2, 3, 4]
Accès & Slicing
arr = [10, 20, 30, 40, 50] # Accès arr[0] # 10 arr[-1] # 50 arr[99] # nil (Renvoie 'nil', ne plante PAS, != Python) arr.at(0) # 10 # Slicing (Range) arr[1..3] # [20, 30, 40] (Range INCLUSIF) arr[1...3] # [20, 30] (Range EXCLUSIF) # Slicing (Offset, Longueur) arr[1, 3] # [20, 30, 40] (Index 1, prend 3 éléments)
Le Hash est la structure clé/valeur de Ruby (équivalent du dict Python).
Syntaxe : "Hash Rocket" (Legacy) vs "JSON-style" (Moderne)
1. Syntaxe "Hash Rocket" (=>)
Obligatoire si la clé n'est pas un Symbole (ex: un String, un Int).
# Clés de type String
user = {
"nom" => "Alice",
"age" => 30
}
puts user["nom"]
2. Syntaxe "JSON" (:)
Depuis Ruby 1.9. Plus propre. Ne fonctionne que si la clé est un Symbole.
# Clés de type Symbol (voir ci-dessous)
user = {
nom: "Alice",
age: 30
}
# (Identique à { :nom => "Alice", :age => 30 })
puts user[:nom]
Le "Symbol" (:key)
C'est le concept le plus confus de Ruby. Un Symbole n'est pas un String.
- String (
"hello") : Mutable. Chaque"hello"est un *nouvel* objet en mémoire. - Symbol (
:hello) : Immutable. Chaque:helloest le *même* objet en mémoire. C'est un "label" ultra-rapide.
Règle : Toujours, *toujours* utiliser des Symbols comme clés de Hash (performance 10x).
Méthodes de Hash
user = { nom: "Alice", age: 30 }
# Accès
user[:nom] # "Alice"
user[:job] # nil (ne plante pas)
user.fetch(:job) # ERREUR (KeyError)
user.fetch(:job, "N/A") # "N/A" (défaut)
# Itération
user.each do |key, value|
puts "#{key} = #{value}"
end
C'est la *vraie* puissance de Ruby. Le module Enumerable est "mixé" (inclus) dans les Array, Hash, et Range. Il fournit des dizaines de méthodes d'itération incroyablement puissantes. (Similaire à LINQ ou Stream API).
Toutes ces méthodes prennent un bloc.
| Méthode | Description |
|---|---|
.each | Itération simple (le "for loop"). |
.map (ou .collect) | Transforme chaque élément et renvoie un *nouvel* Array. |
.select (ou .filter) | Renvoie un *nouvel* Array contenant uniquement les éléments où le bloc renvoie true. |
.reject | L'inverse de .select. |
.reduce (ou .inject) | "Réduit" la liste à une seule valeur (ex: une somme, un hash). |
.find (ou .detect) | Renvoie le *premier* élément où le bloc renvoie true. |
.any? / .all? | Renvoie true si *au moins un* / *tous les* éléments passent le test. |
Exemple "Pythonic" vs "Ruby-esque"
Objectif : Obtenir la somme des carrés des nombres pairs de 1 à 10.
# Syntaxe "enchaînée" (chaîning)
resultat = (1..10) # (1) Un Range
.select { |n| n.even? } # (2) Sélectionne les pairs [2, 4, 6, 8, 10]
.map { |n| n * n } # (3) Met au carré [4, 16, 36, 64, 100]
.reduce(0) { |sum, n| sum + n } # (4) Somme (avec 0 comme départ)
# 'reduce' est le plus puissant :
# (0, 4) -> 4
# (4, 16) -> 20
# (20, 36) -> 56
# ...
# Résultat : 220
L'écosystème de Ruby est géré par RubyGems (le dépôt de packages) et Bundler (le gestionnaire de dépendances).
| Outil | Équivalent Python | Description |
|---|---|---|
| Gem | Package PyPI | Une librairie/plugin (ex: rails, nokogiri). |
| RubyGems | PyPI (le site) | Le dépôt (repository) central des gems. |
gem install | pip install | La commande pour installer un gem globalement. |
Gemfile | requirements.txt | Un fichier qui *définit* les dépendances du projet. |
| Bundler | venv + pip | L'outil qui lit le Gemfile et installe les gems *localement* pour le projet. |
Workflow (Bundler)
Bundler est l'équivalent de venv + requirements.txt. Il gère l'isolation et les dépendances.
1. Gemfile (Nouveau fichier)
À la racine du projet, on définit les dépendances.
# 1. Source (où chercher les gems) source 'https://rubygems.org' # 2. Version de Ruby ruby '3.2.2' # 3. Dépendances gem 'rails', '~> 7.0.0' # '~>' = Version optimiste (>= 7.0, < 7.1) gem 'pg' # (Pour PostgreSQL) # 4. Groupes (dépendances de dev/test) group :development, :test do gem 'rspec-rails' # (Pour les tests) gem 'pry-rails' # (Pour le débug) end
2. Installation
# 1. Installer bundler $ gem install bundler # 2. Installer les gems du Gemfile (localement) $ bundle install # 3. Exécuter un script (en utilisant les gems du bundle) $ bundle exec rake db:migrate
Ruby on Rails (ou "Rails") est le "killer app" de Ruby. C'est un framework web "full-stack" (backend + frontend) créé par David Heinemeier Hansson (DHH) en 2004.
Les 2 Philosophies Clés
- Convention over Configuration (CoC) (Convention > Configuration)
Rails prend des milliers de décisions pour vous. (Ex: "Si ton modèle s'appelleUser, ta table en BDD *doit* s'appelerusers"). Vous n'avez pas à configurer cela. Si vous suivez les conventions, tout "marche magiquement". - Don't Repeat Yourself (DRY) (Ne vous répétez pas)
La logique doit être définie à un seul endroit. (Ex:ActiveRecordgère la validation, pas le contrôleur, ni la vue).
L'Architecture (MVC)
| Composant | Rôle (Description) |
|---|---|
| ActiveRecord (Model) | L'ORM (Object-Relational Mapper). La "magie" de Rails. Une classe User est *directement* liée à la table users. User.find(1) -> SELECT * FROM users WHERE id=1. |
| ActionController (Controller) | Le "cerveau". Reçoit la requête HTTP (via routes.rb), parle au Modèle (@user = User.find(1)), et prépare les données pour la Vue. |
| ActionView (View) | La "présentation". Fichiers .html.erb (Embedded Ruby). C'est du HTML avec du Ruby dedans (ex: <%= @user.name %>). |
Autres Frameworks
- Sinatra : L'équivalent de "Flask" (Python). Un micro-framework pour les petites APIs.
Ruby utilise une structure begin/rescue/ensure/end pour gérer les exceptions (similaire à try/catch/finally de Python/Java).
Attraper (begin/rescue)
# 1. 'begin' enveloppe le code à risque
begin
# Lève une ZeroDivisionError
resultat = 10 / 0
# 2. 'rescue' attrape une erreur spécifique
rescue ZeroDivisionError => e
puts "Erreur : Division par zéro ! (#{e.message})"
resultat = 0
# 3. 'rescue' (sans type) attrape 'StandardError' (tout, sauf les erreurs système)
rescue => e
puts "Erreur inconnue: #{e.class}"
# 4. 'else' s'exécute SI AUCUNE erreur n'a eu lieu
else
puts "Calcul réussi."
# 5. 'ensure' s'exécute TOUJOURS (erreur ou non)
ensure
puts "Fin du bloc begin/rescue."
end
Lever (raise)
On peut (et on doit) lever ses propres erreurs.
def set_age(age)
unless age.is_a?(Integer)
# 1. Lève (lance) une nouvelle exception
raise TypeError, "L'âge doit être un entier"
end
if age < 0
raise "L'âge ne peut pas être négatif." # (Crée une RuntimeError)
end
end
# 'rescue' peut être utilisé en "modificateur"
set_age("vingt") rescue puts("Erreur capturée")
Outils (Ligne de commande)
| Outil | Description |
|---|---|
irb | Interactive Ruby Bell. La console (REPL) de Ruby. (Équivalent python). |
Rake | Ruby "Make". Un outil de "tâches" (build, tests, déploiement). (rake db:migrate dans Rails). |
ERB | Embedded Ruby. Le système de templating (HTML) de la lib standard. (<%= ... %>). |
RSpec | Le framework de test BDD (Behavior-Driven) n°1. (describe/it/expect). |
RuboCop | Le "linter" et "formatter" n°1. (L'équivalent de Flake8 + Black de Python). |
Cheatsheet (Idiomes)
# 'nil' est "falsey" (tout le reste est "truthy")
a = nil
if a
# Non
else
# Oui
end
# Opérateur "Safe Navigation" (Ruby 2.3+)
# (Évite 'nil' error)
# Si @user est nil, renvoie nil (ne plante pas)
@user&.name
# Opérateur "||=" (Assignation conditionnelle)
# Si @nom est 'nil' ou 'false', l'assigner.
@nom ||= "Valeur par défaut"
# "Tap" (Pour débugger en chaînage)
(1..5).select { |n| n.even? }
.tap { |arr| puts "Pairs: #{arr.inspect}" }
.map { |n| n * 2 }
