Ruby
source: Wikiversity
lien a voir :
Ruby est un langage de programmation libre. Il est interprété, orienté objet et multi-paradigme.
Sommaire
- Presentation des differents outils
- Ruby un langage objet
- Les variables
- La gestion des erreurs et des exceptions
- Les chaines de caractères
- Les opérateurs
- les tri
- Les tableaux
- La rangée dit Range
- Le tableau de hachage (dictionnaire) dit Hache
- Les conditions
- Les boucles
- Traitement des chaines de caracteres
- Les regex
- Les fonctions et les modules de Ruby
- Les classes
- Modularisation en fichiers et utilisation des Gems
- Syntaxe pour acceder aux attribut d’un module ou classe
- INPUT STDIN (entree clavier)
- Appel et réponse
- La méthode
.collect
- Yield
- Yield avec paramètres
- Les Procs (des “blocs sauvegardés”)
- Les Lambdas (les vrais méthodes sans noms)
- La reflexion du langage
- Les DSL sont des structure de code (populaire en ruby)
- Le monkey patching ou la réouverture de classe
- L’heritage
- Les exceptions
Presentation des differents outils
La console Ruby
Pour lancer la console ruby, il suffit de taper irb
dans le terminal.
Dans irb
faire $:
permet d’avoir les chemins de recherche des differents bibliotheques.
l’extention des scripts ruby
L’extension des scripts ruby est .rb
pour charger un script dans irb
load 'dog.rb'
Rails Installer :
Permet l’installation de Ruby, Rails et toutes ses dependances facilement pour Windows et MacOS.
RVM :
RVM est un outil en ligne de commande qui permet facilement d’installer, de gérer et de travailler avec differentes versions de Ruby. Cela permet de travailler sur differents versions d’environnements sans qu’ils ne rentrent en conflit les uns les autres.
- pour executer rvm :
$ source /Users/<USER_HOME>/.rvm/scripts/rvm
- pour voir les versions de ruby disponibles :
$ rvm list
- pour voir la version de ruby utilisée :
$ ruby -v
- pour utiliser une version de ruby qui sera utilisé par défaut :
$ rvm --default use 2.3.0
- pour utiliser une version de ruby :
$ rvm use 2.3.0
Gems :
Les gems representent la bibliothèque ruby. RubyGems est le gestionnaire de paquets de Ruby permettant de télécharger et d’installer des gems.
- pour effectuer une recherche sur RubyGems :
$ gem search -r <recherche>
Ceci listera toutes les gems liées à la recherche, l’option --remote
/ -r
indique qu’il faut chercher dans le dépôt distant.
L’option --local
/ -l
permet de faire la même chose en listant que les gems disponibles sur la machine locale.
- installer un gem avec une version :
$ gem install rails --version 5.0
- lister toutes les gems disponibles :
$ gem list -r
- lister toutes les gems installées :
$ gem list
- aide :
$ gem help
Utilisation des Gemsets de RVM
Concept des Gemsets
Pour rappel, un Gemset est un environnement hermétique dédié à l’installation et l’utilisation de gems.
Chaque version de Ruby connue de RVM dispose d’au moins 2 Gemsets :
- default (sans nom, en fait)
- global
Par défaut on est dans le Gemset sans nom. Le Gemset global permet de rendre disponibles les gems qui y sont installées dans tous les autres Gemsets de la version courante de Ruby.
On peut ensuite créer autant de Gemsets que l’on souhaite, par exemple 1 pour chaque projet de dev.
Ruby EE | Ruby 1.9.2 | autre Ruby |
---|---|---|
@default | @default | @default |
@app1 | @ppX | @ppX |
@ppX | … | … |
Ruby On Rails
Ruby on Rails, également appelé RoR ou Rails, est un framework web libre écrit en Ruby. Il suit le motif de conception modèle-vue-contrôleur aussi nommé MVC. En tant que framework, il propose une structure au programmeur qui lui permet de développer plus vite et plus intuitivement. Il ajoute aussi un grand niveau d’abstraction dans la programmation de l’application par un ensemble de fonctions de haut niveau qui lui offre ainsi l’économie d’écrire lui-même la plupart des routines obligatoires d’une application web.
sources: Wikipedia
- créer un nouveau projet rails :
$ rails new <project_dir>
Bundler
Bundler est une Gem écrite en Ruby, permettant de gérer les dépendances d’un projet écrit en Ruby, par exemple une application Rails.
Beaucoup plus efficace que l’ancien procédé au niveau de la gestion de l’arbre des dépendances il est surtout excellent pour installer rapidement les gems nécessaires à un projet dans un espace limité à ce projet, sans que les éventuelles autres gems installées sur le système ne puisse le perturber.
Bundler (c’est comme npm), il permet de gerer la gestion des dependances.
puis dans son code il faut inclure Bundler:
#!/usr/bin/env ruby
require 'bundler'
Bundler.require!
dans le Gemfile on a les gems que l’on veut:
gem "json"
gem "colorize" '~> 0.7.5'
enfin dans le terminal faire la commande ci-dessous pour telecharger les gems:
$ bundle
Phusion Passenger
Phusion Passenger sert à connecter des applis Rack ou Rails avec Apache (ou Nginx), permettant d’héberger ces applis presque aussi facilement que des applis/scripts en PHP.
C’est un composant logiciel maintenant bien stable, mais son usage en conjonction avec RVM et Bundler n’est pas toujours évident, surtout si on veut qu’il utilise un Gemset (et les gems qui sont dedans) différent pour chaque applis.
Ruby un langage objet
- Dans ruby, tout est objet.
- Ruby est sensible à la casse.
- En Ruby tous les symboles commencent par
:
- La documentation ruby se trouve sur ruby-doc.com
- mettre l’extention dans les fichiers scripts, exemple
code.rb
- mettre une ligne en commentaire avec
#
- mettre plusieurs lignes en commentaire :
=begin var = 100 puts var =end
Les variables
a = 1
a #=> 1
a = a + 1 #=> 2
a += 1 #=> 2
la portée des variables
Les variabls sont local quand ils sont déclarés dans des méthodes ou dans une loop
les variables globales
les variables globales qui commencent obligatoirement par un $. Ces variables une fois définies sont accessibles à travers tout le programme, quelque soit le fichier dans lequel on se trouve et celui où elles sont définies. Ces variables sont à utiliser avec parcimonie car elles “polluent” l’ensemble du programme et dénotent très souvent un problème de conception.
$VAR_GLOBALE = 42
les variables d’instances
les variables d’instances qui sont un type de variable très utilisé. Elles sont utilisées au sein d’un objet et représentent des données qui lui sont directement liées. Seul l’objet y a accès.
Ces variables d’instances commencent toujours par un @ et suivent les mêmes conventions que les variables locales. Voici quelques exemples :
@foobar = "baz"
@some_var_123 = 123
les variables de classe
les variables de classe qui contiennent des données accessibles à l’ensemble d’une classe et des ses instances. Ces variables sont partagées entre tous les objets issus de la classe en question. Les variables de classe commencent par deux @. Voici quelques exemples :
@@counter = 10
@@file_path = "/some/dir/file.txt"
les constantes
les constantes qui doivent commencer par une lettre majuscule. Par convention, de nombreux rubyistes écrivent les constantes avec uniquement des lettres majuscules et séparent les éventuels mots par un underscore.
Les constantes sont destinées à stocker des informations qui ne sont pas appelées à être modifiées. Il faut tout de même être vigilant puisque pour des raisons techniques, il est possible en Ruby de modifier une constante existante. Ça n’empêchera pas le programme de fonctionner, l’interpréteur émettra simplement un message d’avertissement.
Voici quelques exemples de noms de constantes :
STATUSES = ["draft", "published", "pinned"]
API_URL = "http://something.com"
Dans la pratique, en Ruby, les développeurs aiment encapsuler les logiques métier dans des classes et c’est dans ce contexte qu’on travaille le plus et que nous utilisons ces différents types de variables.
class User
MIN_AGE = 18
MAX_AGE = 90
@@count = 0
def initialize(name)
@name = name
@@count += 1
end
def self.instances_count
@@count
end
end
Il faut également distinguer des pseudo-variables qui sont générées automatiquement par l’interpréteur. Il s’agit de self qui représente l’objet courant ou encore nil qui est une instance de la classe NilClass.
Il y a également __FILE__
et __dir__
qui utilisés dans un fichier Ruby permettent d’obtenir respectivement le chemin absolu vers ce fichier ainsi que le chemin absolu vers le répertoire qui contient ce fichier. Au sein d’IRB ces deux variables n’ont pas réellement de sens.
assignation parallele / affectation multiple
a, b, c = 1, 2, 3 # ICI ON FAIT UNE ASSIGNATION PARALLELE, donc on a Créer 3 variables
inverser 2 variables
a, b = b, a
les constantes
En ruby, les constantes doivent etre en majuscule :
A = 1
A = 2 # UNE CONSTANTE NE DOIT ETRE MODIFIE, IL Y A UN WARNING QUI S'AFFICHE. MAIS ON PEUT QUAND MEME CHANGER LA VALEUR.
La gestion des erreurs et des exceptions
- Le mot clé
rescue
catch l’erreur:begin #faire operation rescue => SpecialError => e #Attention toujours commencer par les erreurs special puts "Gerer cette erreur special" rescue => e puts "Erreur: #{e}" end
- Pour déclancher une erreur avec le mot clé
raise
:if x.nill? raise "Il y a une erreur" end
Il est possible de passer une classe héritant de RuntimeError.
- S’assurer que le code s’execute quoi qu’il arrive grâce au mot clé
ensure
:f = File.open("monfichier","wb") begin #operations ensure f.close end
Les chaines de caractères
afficher une variable
- on affiche une variable dans une chaine de caracteres avec
#{maVariable}
. - les simples cotes renvoient la chaine telle qu’elle, sans traiter les variables potentielement presentes.
- les doubles cotes permetent de traiter les variables presentes dans la chaine de caractere.
nom = "toto" puts "hello #{nom}" #=> hello toto puts 'hello #{nom}' #=> hello #{nom}
inspect
Returns a string containing a human-readable representation of obj. The default inspect shows the object’s class name, an encoding of the object id, and a list of the instance variables and their values.
[ 1, 2, 3..4, 'five' ].inspect #=> "[1, 2, 3..4, \"five\"]"
puts var.inspect #permet de voir la variable ou le tableau cool pr debug
son equivalent est p var // soit puts var.inspect
la sortie standard STDOUT
- une chaine de caractères :
"coucou"
- redirection de la chaine vers la sortie standard STDOUT :
print "coucou" puts "coucou" #=> coucou #=> nil
le retour nil
veut dire que print
/ puts
ne renvoie pas d’expression.
nil
fait partie de la classe NilClass
print
ne fait pas de retour a la ligne maisputs
fait un retour a la ligne.print "a", "a" puts "a", "a" #=> aa #=> a #=> a
- pour tester si
"coucou"
est un objet :"coucou".class #=> String
Ici on voit bien que la chaine "coucou"
fait partie de la classe String.
les methodes des objets
-
tous objets possède des méthodes. Il y a 2 types de méthodes:
- les methodes de classes
- les methodes d’objet
Exemple, pour afficher la liste des méthodes de la classe String :
String.methods
#=> Affiche la liste
Exemple, pour afficher la liste des méthodes de l’objet "coucou"
(en plus des méthodes de classe) :
"coucou".methods
#=> Affiche la liste, avec plus de méthodes
- pour convertir la 1ère lettre en majuscule :
"coucou".capitalize #=> Coucou
- la methode upercase dans un string permet de convertir toute une chaine en majuscule :
var1 = "coucou" var2 = "bonjour" puts var1.upercase #ici, nous avons le retour de la méthode upercase sans modifier la variable var1 var2.upercase! #ici, la variable var2 contiendra le resultat de l'appel de la méthode upercase
Le point d’exclamation !
modifie l’objet sur lequel on travail.
les méthodes boolean
Le point d’interogation ?
utilisé en tant que dernier caractère des noms des méthodes bouléennes est une convention.
- tester si
"coucou"
est une instance de type String :"coucou".instance_of? String #=> true
23.class #=> Fixnum 23.instance_of? String #=> false true.class #=> TrueClass (2 + 2).class #=> Fixnum
- tester si une variable existe. Ici, on affiche “x” si la variable existe :
puts x if defined? x
- tester si “i” existe et est égale à 1 :
if defined? i and i == 1 puts "oui" end
convertion d’un type à un autre
Il est possible de convertir un objet à un autre type :
- en Fixnum
var.to_i
- en String
var.to_s
- en Array
var.to_a
1.to_s #=> convertie 1 en string
'1'.to_i #=> convertie en int
la concatenation de caractere
- nous pouvons concatener avec
<<
:'a' << 'b' #=> "ab" 'a' << 33 #=> "a!" car en ASCII 33 est égale à '!'
- nous pouvons également concatener avec la méthode
concat()
:'a'.concat('b') #=> "ab" 'a'.concat('33') #=> "a!" car en ASCII 33 est égale à '!'
- concatenation avec
+
:"2" + "2" #=> "22" 2 + 2 #=> 4 ICI pas de concatenation '1' + 1 #=> TypeError: can't convert Fixnum into String 1 + '1' #=> TypeError: String can't be coerced into Fixnum
- multiplier plusieurs fois une chaine et la concatener :
'3' * 4 #=> "3333"
Les opérateurs
- l’addition :
a + b
- la soustraction :
a - b
- la multiplication :
a * b
- la division :
a / b
opérateur de puissance et modulo
- 2^4 s’écrit
2 ** 4
- le modulo 5 mod 2 s’écrit
5 % 2
opérateur de comparaison
== < <= > >=
sont les opérateurs de comparaison habituels.=~
teste une chaine à une expression régulière.!= !~
sont les négations de==
et de=~
.
5 == 3 #=> false
5 != 3 #=> true
opérateur permettant de tester si une valeur est égale et du même type
5.eql? 5.0 #=> false
5.0.class #=> Float
les opérateurs sont des methodes des objets
'coucou' >> 33 #=> renvoie NoMethodError
L’affectation conditionnelle
Nous avons vu que l’on peut utiliser l’opérateur =
pour affecter une valeur à une variable. Mais existe-il un moyen d’affecter une valeur à une variable dans le seul cas où aucune valeur ne lui a été affectée jusque là ? La réponse est oui : pour cela, on utilise ce qu’on appelle l’opérateur d’affectation conditionnelle ||=
. Il est composé de l’opérateur logique “ou” ||
et du classique opérateur d’affectation =
.
livre_favori = nil
puts livre_favori
livre_favori ||= "Demain les chiens"
puts livre_favori
livre_favori ||= "Ruby - Les fondamentaux"
puts livre_favori
livre_favori = "Ruby - Les fondamentaux"
puts livre_favori
# RESULTATS :
Demain les chiens
Demain les chiens
Ruby - Les fondamentaux
nil
les tri
l’operateur de comparaison combinée dit ‘combined comparison operator’ ou encore ‘spaceship’
Nous pouvons également utiliser un nouvel opérateur appelé l’opérateur combiné de comparaison pour comparer deux objets Ruby.
L’opérateur de comparaison combinée ressemble à ceci <=>
:
- Elle retourne 0 si le premier opérande (point à comparer) est égale à la seconde.
- 1 si le premier opérande est plus grand que la seconde.
- -1 si le premier opérande est inférieur à la seconde.
- nil si les opérandes ne sont pas compatibles.
Un bloc qui est passé dans la méthode de tri doit retourner 1, 0, -1. Il doit retourner -1 si le premier paramètre de bloc devrait venir avant la seconde, 1 si vice versa et 0 si elles sont de poids égal, ce qui signifie qu’on ne vient pas avant l’autre (à savoir si deux valeurs sont égales).
book_1 = "A Wrinkle in Time"
book_2 = "A Brief History of Time"
book_1 <=> book_2
# Resultat: 1
Trier un tableau de int:
mon_tableau = [3, 4, 8, 7, 1, 6, 5, 9, 2]
mon_tableau.sort!
Inserer une valeur en fin de tableau:
nombres = [1, 2, 3, 4, 5, 6]
pairs = []
nombres.each do |nombre|
if number % 2 == 0
pairs.push(nombre)
end
end
print pairs
# prints '[2, 4, 6]'
# ON PEUT L'ECRIRE SOUS CETTE FORME :
[1, 2, 3] << 4
# ==> [1, 2, 3, 4]
# IDEM POUR LES STRING :
"Yukihiro " << "Matsumoto"
# ==> "Yukihiro Matsumoto"
Créer un tableau de string sans espace rapidement:
tab = %w(pomme poire)
#equivaut à tab = ["pomme", "poire"]
Inserer une valeur en début de tableau:
tab.unshift 1
On peut ajouter des valeurs nil dans le tableau:
tab << nil << nil
On peut le compacter pour supprimer les valeurs nil:
tab.compact!
On peut applatir un tableau de 2 dimenssion en 1 et supprimer les doublons:
tab.flatten!.uniq!
On peut recuperer l’index d’une valeur dans un tableau:
tab.index("val")
la methode .shuffle
permet de melanger un tableau
on peut doubler la valeur d’un tableau en utilisant la methode .map
qui permet d’iterer dans un tableau:
tab_double = tab.map {|v| v*2}
Il est possible de soustraire un tableau avec un autre, de faire une jointure et de faire une intersection:
# tab = [1,2,5,10,25,50,"foo"]
# tab_double = [2,4,10,20,50,100,"foofoo"]
tab_double - table
#=> [4,20,100,"foofoo"]
tab_double | tab
#=> [2,4,10,20,50,100,"foo",1,5,25,"foo"]
tab_double & tab
#=> [2, 10, 50]
Trier un tableau de string:
livres = ["Utopia", "Charlie et la chocolaterie","Une brève histoire du temps", "Guerre et Paix", "Un raccourci dans le temps"]
livres.sort!
Trier un tableau de string en ordre croissant et décroissant:
livres = ["Utopia", "Charlie et la chocolaterie","Une brève histoire du temps", "Guerre et Paix", "Un raccourci dans le temps"]
# Pour trier les livres dans l'ordre alphabétique
livres.sort! { |premierLivre, secondLivre| premierLivre <=> secondLivre }
# Triez vos livres dans l'ordre alphabétique inverse ci-dessous
livres.sort! { |premierLivre, secondLivre| secondLivre <=> premierLivre }
Les tableaux
un tableau est représenté de cette façon [1, 2, "coucou", [5, 6]]
- recuperer les 3 valeurs d’un tableau dans 3 variables :
t=[1,2,3] a, b, c = t #=> a=1 b=2 c=3
Si nous affectons moins de variable que d’élément de tableau alors les éléments sont affectés selon l’orde du tab. Donc nous avons :
#=> a=1 et b=2
Si il y a moins d’element de tableau que de variables alors les variables qui sont en trop n’ont pas d’affectation.
splat arguments
En utilisant *
sur une variable de fin du bloc d’affectation nous indiquons que nous voulons affecter un tableau.
t=[1,2,3]
a, *b= t
#=> a=1 et b=[2,3]
Dans une methode, si on pense avoir plusieurs argument on peut mettre une *
devant un param pour dire a ruby "je ne sais pas combien y aura d’arg mais peut etre + de 1".
def what_up(greeting, *bros)
bros.each { |bro| puts "#{greeting}, #{bro}!" }
end
what_up("What up", "Justin", "Ben", "Kevin Sorbo")
#=> What up, Justin!
#=> What up, Ben!
#=> What up, Kevin Sorbo!
Il est possible de l’ecrire sous ces differentes formes :
def what_up(greeting, *bros)
bros.each { |bro|
puts "#{greeting}, #{bro}!"
}
end
# OU ENCORE
def what_up(greeting, *bros)
bros.each do |bro|
puts "#{greeting}, #{bro}!"
end
end
extraire un element d’un tableau
[1, 2, "coucou", [5, 6]] [1] #=> 2
([1, 2, "coucou", [5, 6]]) [1] #=> 2
[1, 2, "coucou", [5, 6]] [3] [1] #=> 6
[1, 2, "coucou", [5, 6]] [-1] #=> [5, 6] ICI -1 RETOURNE LA DERNIERE VALEUR DU TABLEAU
[1, 2, "coucou", [5, 6]] [-2] #=> "coucou"
[5+1, 4*6] #=> [6, 24]
[5+1, 4*6].class #=> Array
[5+1, 4*6].length #=> 2
"coucou"[3] #=> "c"
"coucou"[3,2] #=> "co" ICI à partir de la position 3 on prend 2 caractères
[1, 2, "coucou", [5, 6]] [2][3] #=> "c"
Convertir chaques elements d’un tableau en String
la méthode join(separator=$,)
permet de convertir chaques elements d’un tableau en String, avec un séparateur si l’option est utilisée.
[ "a", "b", "c" ].join #=> "abc"
[ "a", "b", "c" ].join("-") #=> "a-b-c"
les methodes next et succ
.next
renvoie le prochain énumerateur :
puts 1.next #=> 2
.succ
Renvoie le successeur de str. Le successeur est calculé par incrémentation de caractères à partir du code alphanumérique à droite (ou le caractère de droite s’il n’y a pas de caractères alphanumériques) dans la chaîne. l’incrémentation un chiffre se traduit toujours par un autre chiffre, et l’incrémentation d’une lettre par une autre lettre de la même maniere. l’Incrémentation nonalphanumerics utilise la séquence de classement du jeu de caractères sous-jacent.
"a".succ #=> "b"
"abcd".succ #=> "abce"
"THX1138".succ #=> "THX1139"
"<<koala>>".succ #=> "<<koalb>>"
"1999zzz".succ #=> "2000aaa"
"ZZZ9999".succ #=> "AAAA0000"
"***".succ #=> "**+"
La rangée dit Range
Une rangée represente un interval de valeurs comme ceci (1..9)
- 2 points inclut la derniere valeur [1,9]
- 3 points exclut la derniere valeur [1,9[
1..6 #=> 1..6
(1..6).class #=> Range
[1, 2, "coucou", [5, 6]] [0..2] #=> [1, 2, "coucou"]
(1..6).to_a #=> [1, 2, 3, 4, 5, 6]
(1...6).to_a #=> [1, 2, 3, 4, 5]
(6..1).to_a #=> [] vide, ne marche pas
(-5..-1).to_a #=> [-5, -4, -3, -2, -1]
('a'..'c').to_a #=> ['a', 'b', 'c']
'a'.succ #=> "b"
'a'.succ.succ #=> "c"
('a'..'c').to_a.join #=> "abc" Devient un String
('a'..'c').to_a.join.class #=> String
('a'..'c').cover?('b') #=> true Est ce que b est dans la rangée? il est possible d'utiliser ('a'..'c').include?('b') mais il est obselete depuis ruby 1.9
Le tableau de hachage (dictionnaire) dit Hache
Un Hash est une collection de type dictionnaire des paires clés uniques et valeurs. Aussi appelés tableaux associatifs, ils sont semblables à des tableaux, mais à l’égare d’un tableau utilisant des entiers comme son indice, un Hash vous permet d’utiliser tout type d’objet.
Un tableau de hachage énumère ses valeurs dans l’ordre d’insertion.
{"key" => "value"}
{:key => "value"} # c'est plus optimisé, cela crée des symbole!
{:nom => "dupont", :prenom => "toto", :score => 100 }
{"nom" => "dupont", "prenom" => "toto", "score" => 100 }["nom"] #=> "dupont"
{:nom => "dupont", :prenom => "toto", :score => 100 } [:nom] #=> "dupont"
Depuis ruby 1.9 on peut faire :
{nom: "dupont", prenom: "toto", score: 100 } [:nom] #=> "dupont"
{nom: "dupont", prenom: "toto", score: 100 }.key? :nom #=> true
{nom: "dupont", prenom: "toto", score: 100 }.delete :nom #supprime le nom et sa valeur
{nom: "dupont", prenom: "toto", score: 100 }.to_a #=> [[:nom, "dupont"], [:prenom, "toto"], [:score, 100]]
{nom: "dupont", prenom: "toto", score: 100 }.to_a[0] #=> [:nom, "prenom"]
A quoi servent les symboles ?
Les symboles sont bien souvent très utiles en Ruby, mais ils sont, avant tout, utilisés comme clés de hash ou pour référencer des noms de méthode.
Les symboles sont de très bonnes clés de hash, et ce pour plusieurs raisons :
- Ils sont immuables, c’est à dire qu’ils ne peuvent pas être modifiés une fois qu’ils ont été créés.
- Une seule et unique copie d’un symbole existe à un moment donné, donc ils prennent peu d’espace.
- En tant que clé de hash, les symboles sont plus rapides que les strings pour les raisons énoncées ci-dessus.
Définissez la valeur par défaut
Vous n’êtes pas obligé de vous contenter de nil comme valeur par défaut. Si vous créez votre hash en utilisant la syntaxe Hash.new, vous pouvez spécifier une valeur par défaut, comme ça :
mon_hash = Hash.new("val-par-defaut")
#ou
h={a:'E',b:2}
h.default="val-par-defaut"
Il est possible de merger un hash vers un autre:
# h{a:'a',b:42,c:{d:'f'}}
# h2{a:'foo',z:'baz'}
h.merge! h2
#=> {a:"foo",b:42,c:{d:"f"},z:"baz"}
Qu’est-ce qu’un symbole ?
Vous pouvez imaginer les symboles Ruby comme des sortes de noms. Il est important de se souvenir que les symboles ne sont pas des strings :
"string" == :string # renvoit false
En plus de la différence de syntaxe, les symboles ont un comportement particulier qui les différencient des strings : alors qu’il pourrait y avoir plusieurs strings qui auront la même valeur, il n’y a qu’une seule copie d’un symbole particulier à un moment donné.
puts "string".object_id
puts "string".object_id
puts :symbol.object_id
puts :symbol.object_id
# RESULTATS:
8127640
8127440
317608
317608
nil
Conversion strings <=> symboles
Convertir une string en symbole et vice-versa est très simple :
:yeti.to_s
# ==> "yeti"
"yeti".to_sym
# ==> :yeti
En plus d’utiliser .to_sym
, vous pouvez utiliser .intern
. Cettte méthode va “internaliser” la string en un symbole, exactement comme .to_sym
:
"hello".intern
# ==> :hello
Creer un hash :
pets = Hash.new
pets["Stevie"] = "cat"
pets["dog"] = "woof"
prices = {
"apple" => 0.52,
"banana" => 0.23,
"kiwi" => 1.42
}
# OU ENCORE MIEUX:
prices = {
apple: 0.52,
banana: 0.23,
kiwi: 1.42
}
creer un dictionnaire key value et parser une phrase en entree “the rain in Spain falls mainly on the plain” en les separant avec " "
puis compter leurs occurances et tri le resultat en ordre décroissant :
# Resultat:
the 2
falls 1
on 1
mainly 1
in 1
rain 1
plain 1
Spain 1
puts "Text please: "
text = gets.chomp
words = text.split(" ")
frequencies = Hash.new(0)
words.each { |word| frequencies[word] += 1 }
frequencies = frequencies.sort_by {|a, b| b }
frequencies.reverse!
frequencies.each { |word, frequency| puts word + " " + frequency.to_s }
initialiser le hash a zero pour pouvoir l’incrementer par la suite : frequencies=Hash.new(0)
Filtrer un Hash avec .select
Nous allons voir comment filtrer un hash pour récupérer des valeurs correspondant à un certain critère. Pour cela, nous pouvons utiliser .select
.
notes = { alice: 100,
bob: 92,
chris: 95,
dave: 97
}
notes.select {|nom, note| note < 97}
# ==> {:bob=>92, :chris=>95}
Iterer sur les key ou sur les values
n Ruby, les hashes possèdent deux méthodes, .each_key qui itère sur les clés et .each_value, qui itère sur les valeurs.
mon_hash = { un: 1, deux: 2, trois: 3 }
mon_hash.each_key { |c| print c, " " }
# ==> un deux trois
mon_hash.each_value { |v| print v, " " }
# ==> 1 2 3
tester si une key n’existe pas dans un Hash
Il suffit d’utiliser la methode .nil?
:
if films[titre.to_sym].nil?
Exemple d’un programme qui mémorise les notes que nous donnons aux films.
films = {
Memento: 3,
Primer: 4,
Ishtar: 1
}
puts "Que voulez-vous faire ?"
puts "-- Entrez 'ajout' pour ajouter un film."
puts "-- Entrez 'maj' pour mettre à jour un film."
puts "-- Entrez 'affiche' pour afficher tous les films."
puts "-- Entrez 'suppr' pour supprimer un film."
choix = gets.chomp.downcase
case choix
when 'ajout'
puts "Quel film voulez-vous ajouter ?"
titre = gets.chomp
if films[titre.to_sym].nil?
puts "Quelle note voulez-vous lui attribuer ? (entrer un chiffre entre 0 et 4.)"
note = gets.chomp
films[titre.to_sym] = note.to_i
puts "Vous avez donné une note de #{note} au film #{titre}."
else
puts "Ce film existe déjà ! Sa note est #{films[titre.to_sym]}."
end
when 'maj'
puts "Quel film voulez-vous mettre à jour ?"
titre = gets.chomp
if films[titre.to_sym].nil?
puts "Film introuvable !"
else
puts "Quelle nouvelle note voulez-vous lui attribuer ? (entrer un chiffre entre 0 et 4.)"
note = gets.chomp
films[titre.to_sym] = note.to_i
puts "#{titre} a bien été mis à jour, sa note est désormais #{note}."
end
when 'affiche'
films.each do |film, note|
puts "#{film}: #{note}"
end
when 'suppr'
puts "Quel film voulez-vous supprimer ?"
titre = gets.chomp
if films[titre.to_sym].nil?
puts "Film introuvable !"
else
films.delete(titre.to_sym)
puts "Le film #{titre} a été supprimé."
end
else
puts "Désolé, je ne vous ai pas compris."
end
Benchmark entre les Strings et les Symboles
les hashes sont plus rapides avec des symboles qu’avec des strings:
require 'benchmark'
string_AZ = Hash[("a".."z").to_a.zip((1..26).to_a)]
symbole_AZ = Hash[(:a..:z).to_a.zip((1..26).to_a)]
string_time = Benchmark.realtime do
100_000.times { string_AZ["r"] }
end
symbole_time = Benchmark.realtime do
100_000.times { symbole_AZ[:r] }
end
puts "Temps des strings : #{string_time} secondes."
puts "Temps des symboles : #{symbole_time} secondes."
Les conditions
- la condition
if - else
:if (a < b) # false # elsif (b < c) # true # else # end
- afficher oui si 1 est égale à 1 :
puts "oui" if 1 == 1
- la condition
unless
est la contraire deif
. Elle exécute une instruction sauf si la condition est vérifiée.unless(estMajeur)
effectue la même chose queif!(estMajeur)
:unless 1>3 # condition fausse donc exécutée puts "ok" end
- la condition
case
:case z when 1 # dans le cas où z = 1 print "z = 1" when "maison" # dans le cas ou z contient la chaîne "maison" print "z = 'maison'" when z = 4 print "z = 4" end
age = 12 case age when 0..2 puts "bébé" when 3..12 puts "enfant" when 13...18 puts "ado" else puts "adulte" end
Il est possible de factoriser l’ecriture:
case langue when "JS" then puts "Sites web !" when "Python" then puts "Science !" when "Ruby" then puts "Applications web !" else puts "Aucune idée !" end
- la condition ternaire
condition ? "si vrai" : "sinon"
: ```ruby a==1 ? “Ok” : “Ko”
puts 1 < 2 ? “Un est plus petit que deux !” : “Un n’est pas plus petit que deux.”
## Les boucles
### break, redo, next et retry
break, redo, next et retry modifient le cours de l'exécution d'une boucle ou d'un itérateur.
* `break` sort immédiatement de la boucle ;
* `redo` recommence en début de boucle sans exécuter la condition de la boucle ;
* `retry` recommence en début de boucle en exécutant la condition ;
* `next` saute en fin de boucle, ce qui conduit à exécuter la condition.
break peut être suivi d'un ou plusieurs arguments qui sont retournés comme valeur de retour, sous forme d'un tableau s'il y en a plusieurs.
[source](http://a-io.eu/fr/p/1/356/1739)
les boucles existant sont :
* la boucle `for` :
```ruby
for n in (0..7)
print n
end
next
est l’équivalent de “continue“, dans une boucle elle permet de sauter une itération :for i in 1..5 next if i % 2 == 0 print i end
redo
permet de réévaluer le corps de la boucle, mais sans retester la condition, et sans utiliser l’élément suivant (dans un itérateur) :for i in 1..4 print "#{i}" if i==2 i=0 redo end puts i end #=> 1 1 #=> 2 0 0 #=> 3 3 #=> 4 4
La variable i
étant définie localement (dans la boucle), cela ne change pas le déroulement par rapport à la liste globale, mais la variable est
bien vue comme ayant une valeur différente de 2 la seconde fois et le redo
est évité. Le puts
suivant le redo
dans le bloc de code de la boucle
n’est pas exécuté lorsque celui est exécuté.
retry
recommence l’itération à son début, dans son état premier :for i in 1..5 puts i retry if i == 2 end #=> 1 #=> 2 #=> 1 #=> 2 #=> ....
On entre ici dans une boucle infinie.
- la boucle
while
:while (compteur <= 4) print compteur compteur += 1 end
- la boucle
begin end while
est equivalent à “do while“, Cette boucle, délimitée parbegin
etend while
, est toujours exécutée au moins une fois, la condition n’est évaluée qu’après le premier tour de boucle :begin print compteur compteur += 1 end while (compteur <= 4)
- la boucle
until
est équivalent à “tant que faux“, La boucleuntil
est à la bouclewhile
ce queunless
est àif
. Cette boucle s’exécute jusqu’à ce qu’une condition soit vraie. Elle est donc totalement équivalente àwhile!(condition)
:until n>5 print n n += 2 end
- la boucle
begin end until
, les instructions placées entrebegin
etend until
seront exécutées au moins une fois car la condition n’est évaluée qu’après le premier tour de boucle :begin print n n += 2 end until n>5
- la boucle
loop
est infinie, et ne teste donc pas de condition, elle équivaut à une bouclewhile(true)
. Étant donné qu’elle est infinie, il faudra utiliser l’instructionbreak
pour en sortir. Les accolades{}
peuvent être remplacées par les mots-clésdo
etend
:loop { print n n += 2 break if (n > 6) }
les boucles avec range
Voici un exemple de boucle utilisant range :
for x in 1..5
puts "hello #{x}" , 'hello #{x}'
# LE 1ER TRAITE LES VARIABLES ET LE SECOND EST FIXE
end
- boucler avec un range, ici
|x|
est l’iterateur de la boucle, on n’est pas obligé d’utiliser l’iterateur si on en a pas besoin :(1..5).each do |x| puts "hello #{x}" end
- boucler avec un Fixnum, ici on dit “affiche 5 fois hello suivit de l’iterateur” :
5.time do |x| puts "hello #{x}" end
- boucler avec un Fixnum, ici on dit “affiche 5 fois coucou suivit de l’iterateur”, la boucle va de 5 à 10 inclus :
5.upto(10) do |x| puts "coucou #{x}" end
- boucler en 1 seule ligne : ```ruby 5.times { puts “coucou” } [1, 2, “coucou”, [5, 6]].each { |element| puts element ; puts “couocu” }
OU ENCORE, AVEC UNE VARIABLE:
n=5 n.times { puts “coucou” }
* boucler dans un tableau :
```ruby
[1, 2, "coucou", [5, 6]].each do |element|
puts element
end
#=> cela traite d'un seule coup chaques elements
#revoie chaque element puis saut de ligne même pour le tableau ayant 5 et 6
la methode “.cycle(int)”
Elle permet de créer une boucle. ATTENTION, si on appel la methode .cycle()
sans parametre alors la boucle équivaut à une boucle infini :
[1, 2, "coucou", [5, 6]].cycle(2) do |element|
puts element
end
#=> boucle sur l'ensemble du tableau 2 fois !
boucler un hash
- boucler l’envoie des valeurs :
{nom: "dupont", prenom: "toto", score: 100 }.each do |v| puts v end
- boucler l’envoie des clés + valeurs :
{nom: "dupont", prenom: "toto", score: 100 }.each do |k, v| puts "#{k} : #{v}" end
- renvoyer un tableau de clés :
{nom: "dupont", prenom: "toto", score: 100 }.keys
- renvoyer un tableau de valeurs :
{nom: "dupont", prenom: "toto", score: 100 }.values
- renvoyer un tableau de valeurs puis boucler dessus pour recuperer les valeurs :
{nom: "dupont", prenom: "toto", score: 100 }.values.each do |v| puts "ma valeur est : #{v}" end
Traitement des chaines de caracteres
les methodes “.split()” et “.each_char()”
La méthode .split()
divise la chaine en sous-chaine avec le délimiteur passé en parametre, retourne un tableau de ces chaînes.
"coucou".split('') #ici le separateur c'est une chaine vide
#=> on obtient un tableau avec chaque caractaire ["c","o","u","c","o","u"]
"coucou,salut".split(,) #=> ["coucou" , "salut"]
- boucler sur une chaine pour afficher chaques lettres :
"coucou".split('').each { |lettre| puts lettre }
Une façon plus simple pour boucler sur un caractere est d’utiliser la méthode .each_char()
:
"coucou".each_char { |l| puts l }
la méthode “.each_byte”
Renvoie le string avec les caracteres ASCII de chaques lettres :
"hello".each_byte {|c| print c, ' ' }
#=> 104 101 108 108 111
Il est possible de récuperer chaques caracteres du string comme .each_char
:
"abcdef".each_byte{ |caractere| printf "%c\n", caractere }
#=> affiche lettre par lettre avec saut de ligne
la méthode “.each_line”
Affiche ligne par ligne séparé par le retour chariot :
["première", "suite", "autre"].join("\n").each_line{|ligne| puts ligne}
remplacement d’une chaine par une autre
Exemple, remplacer ‘s’ par ‘th’ :
if user_input.include? "s"
user_input.gsub!(/s/, "th")
else
puts "Nothing to do here!"
end
string_to_change.gsub!(/s/, "th")
tester si une sous-chaine existe dans une chaine
tester si une sous-chaine existe dans une chaine :
if string_to_check.include? "substring"
faire des calculs dans une chaine
puts "What is 3 + 2? #{3 + 2}"
recuperer le 1er caractere d’un string
varString="bonjour"
var=varString[0]
recuperer les caracteres du string du 2eme jusqu’au dernier
varString="bonjour"
var=varString[1..-1]
Mettre la premiere lettre en majuscule :
def capitalize(string)
puts "#{string[0].upcase}#{string[1..-1]}"
end
Les regex
Les expressions rationnelles sont exprimé entre deux /
. Allez sur Rubular.com pour acceder à leur generateur de regex ruby.
L’operateur qui permet d’exprimer une expression rationnel et de faire la comparaison immediatement sans creer d’objet est =~
.
exemple :
x = "Le docteur"
puts "c'est lui !" if x =~/Docteur|Who/i
#=> on ajoute le 'i' pour etre insenssible a la casse
# on detecte soit le mot docteur soit le mot who
# /D(octeu)?r/ toute cette partie doit etre presente 0 ou 1 fois
Les fonctions et les modules de Ruby
fonctions
On défini une fonction comme ceci :
def cest_le_docteur?(c)
!!(c =~ / Docteur | Who( |$)/i)
end
puts "c'est lui !" if cest_le_docteur? "Monsieur Who se fait mordre"
Il n’est pas obligé de forcer le retour en type bool mais il est possible
de le faire en utilisant le double point d’éxclamation !!(ma_condition)
Definir une fonction avec en entrée 1 param obligatoire et 1 optionnel:
def mafonction param_obligatoire, param_optionnel={}
******
end
Definir une fonction acceptant des parametres multiples (un tableau quoi…):
def mafonction *param_multiples
*****
end
modules
Les modules dans Ruby permettent de créer des groupes de méthodes que l’on pourra ensuite inclure dans nos différentes classe. Contrairement aux classes vu précédemment il n’est pas possible d’instancier un module ni d’utiliser l’héritage.
module Cercle
PI = 3.141516
def self.perimetre(rayon)
2 * PI * rayon
end
end
Cercle::PI # 3.141516
Cercle.perimetre(5)
cela permet le ‘mixin’ c’est à dire étendre les compétences d’une classe. Pas d’héritage multiple en Ruby mais utilisation des ‘Mixin’. un Mixin étend les compétences d’une classe en lui ajoutant une série de méthodes
Ruby prends le nom du module et en fait une constante!
Dans le module on met une fonction. Les fonctions doivent etre préfixé par self.
cela veut dire qu’on crée une methode/fonction qui appartient au module/a la classe elle même…
Il y a une difference entre une methode de classe qui peut etre appelé sans creer une instance et les methodes d’instances.
module DrWho
def self.cest_le_doctuer?(c)
!!(c =~ / Docteur | Who( |$)/i)
end
end
Notez le mot clé self
, ici il represente le module DrWho
et dit “créer une fonction globale dans le module”. Sans l’usage du self, la fonction ne pourrait être appelée correctement.
puis on essaient, on peut ajouter les ()
sur les string Monsi…mordre :
puts "c'est lui !" if DrWho.cest_le_docteur? "Monsieur Who se fait mordre"
Les classes
Les données stockées dans des variables de classe sont disponibles dans tous les objets de cette classe. Les données stockées dans des variables d’instance seront disponibles uniquement dans l’objet qui les a définies.
Dans ruby il existe un garbage collector :
class DrWho
# c'est le constructeur, un initialiseur en ruby
def initialize(text)
@texte = texte # ici c'est un attribut
end
def cest_le_doctuer?(c)
!!(c =~ / Docteur | Who( |$)/i)
end
def cest_le_doctuer222?
!!(@texte =~ / Docteur | Who( |$)/i)
end
def texte # c'est le Getter
@texte
end
end
Créer une instance de classe :
# creation d'une instance DrWho
homme = DrWho.new()
# ou
homme = DrWho.new
puts "c'est lui !" if homme.cest_le_docteur? "docteur Who se fait mordre"
homme2 = DrWho.new "docteur who se fait mordre"
puts "c'est lui !" if homme2.cest_le_doctuer222
puts homme.texte # NE FONCTIONNE PAS SI IL N'Y A PAS DE GETTER/SETTER !!!
On peut utiliser une syntaxe qui permet de faire créer automatiquement plusieurs getter :
class DrWho
attr_reader :texte, :texte2 #ici on crée des getters
def initialize(texte)
@texte = texte #ici c'est un attribut
end
end
homme=DrWho.new "bonjour"
puts homme.text
mettre une valeur dans un parametre d’une classe dans sa definition
def alphabetize(arr, rev=false)
Modularisation en fichiers et utilisation des Gems
Mettre la classe dans un fichier à part puis pour l’inclure il faut faire :
require 'DrWho'
require_relative 'DrWho' #permet de charger qu'une seule fois le fichier
Autre exemple avec une classe Animal
permettant de gérer des animaux:
class Animal
WILD = true
@@counter = 0
def initialize(name, sex, age)
@@counter += 1
@name, @sex, @age = name, sex, age
end
def age
@age
end
def age=(age)
@age = age
end
def description
puts "#{@name} is #{@age} years old."
end
def self.instances_count
puts "We created #{@@counter} animals."
end
end
On a donc créé une classe dans laquelle on a définie une constante WILD, une variable de classe @@counter qui est initialisée à 0 directement dans la définition de la classe.
On a ensuite définie la méthode initialize qui sert de constructeur. Cette méthode est appelée automatiquement dès qu’une instance est allouée. On profite de cette méthode pour stocker nos différents paramètres dans des variables d’instance mais aussi pour incrémenter notre compteur. On aura donc un historique du nombre d’animaux qu’on a instancié.
On a également définie une méthode d’instance description qui aura pour but d’afficher des informations concernant l’animal, ici on ré-utilise nos variables d’instance @name et @age pour construire une phrase descriptive.
Finalement on a définie une méthode de classe instances_count qui est donc précédée par le mot-clé self
. Cette méthode a pour but de nous informer sur le nombre d’instances qui ont été créées.
On va maintenant utiliser cette classe pour tester son comportement. On commence par charger notre fichier dans IRB grâce à la méthode load
:
load 'animal.rb'
On a donc ajouter la méthode age qui fait office de “getter”, son but est simplement de retourner la valeur de la variable d’instance @age. On a également ajouté la méthode age= qui sert quant à elle de “setter”. Elle va permettre de modifier le contenu de la variable d’instance @age. On note la présence du = dans le nom de méthode. Ce n’est absolument pas obligatoire mais par convention et pour améliorer la lisibilité, les Rubyistes ont pour habitude d’ajouter un = au noms de méthodes servant de “setter”. Cette méthode prend un paramètre qui sera utilisé comme nouvelle valeur.
On peut utiliser notre classe:
a1 = Animal.new('Simba', 'male', 5)
a2 = Animal.new('Cheetah', 'male', 20)
a1.description
a2.description
#Vérifions maintenant le nombre d'instances créées :
Animal.instances_count
#On peut également accéder à la constante depuis l'extérieur de la classe grâce à la notation :: :
Animal::WILD
#C'est très souvent utilisé pour accéder à la valeur depuis une autre partie du code.
a = Animal.new('Simba', 'male', 5)
a.age
a.description
a.age = 10
a.age
a.description
Pour les getters/setters ça fonctionne donc comme voulu, c’est satisfaisant sur le plan fonctionnel mais ce n’est pas du tout représentatif du code idiomatique à utiliser en Ruby. Ce besoin est tellement courant en Ruby qu’il met à notre disposition des macros permettant de faire ce travail à notre place.
On a donc à disposition trois méthodes qui sont attr_reader
, attr_writer
et attr_accessor
qui permettent respectivement de créer un “getter”, un “setter” ou les deux à la fois. On peut donc modifier notre classe pour la simplifier :
class Animal
WILD = true
@@counter = 0
attr_accessor :age
def initialize(name, sex, age)
@@counter += 1
@name, @sex, @age = name, sex, age
end
def description
puts "#{@name} is #{@age} years old."
end
def self.instances_count
puts "We created #{@@counter} animals."
end
end
On a donc supprimer nos deux méthodes dédiées à la gestion de l’âge pour les remplacer par un appel à attr_accessor
suivi du nom de la variable d’instance pour laquelle les méthodes doivent être générées.
Les GEMS
il faut aller sur RubyGems.org qui references les bibliotheques Gems de ruby, ici dans l’exemple on recupere le gem de twitter, c’est une interface twitter API, on l’installe avec :
gem install twitter
On peut cliquer sur homepage pour avoir des infos sur le codage.
Si on télécharge le gem twitter, il faudra faire un require 'twitter'
pour l’inclure dans le code.
Syntaxe pour acceder aux attribut d’un module ou classe
MODULEouCLASSE::ATTRIBUTdeCLASSE
INPUT STDIN (entree clavier)
La méthode .gets
permet de récuperer l’entrée clavier.
La méthode .chomp
assoscé à .gets
de telle sorte .gets.chomp
permet de supprimer le saut à la ligne.
print "What's your first name?"
first_name = gets.chomp #ICI .chomp permet de supprimer le saut à la ligne
first_name.capitalize!
input un entier
On utilise la méthode Integer()
pour obliger que l’STDIN soit un entier user_num = Integer(gets.chomp)
.
print "Integer please: "
user_num = Integer(gets.chomp)
if user_num < 0
puts "You picked a negative integer!"
elsif user_num > 0
puts "You picked a positive integer!"
else
puts "You picked zero!"
end
le return implicite
En Ruby, les méthodes retournent la dernière expression évaluée.
Par exemple pour écrire une méthode comme celle-ci :
def ajouter(a,b)
return a + b
end
Il suffit d’écrire :
def ajouter(a,b)
a + b
end
Appel et réponse
.respond_to?
prend un symbole et retourne true
si un objet peut recevoir cette méthode, et false
sinon. Par exemple:
[1, 2, 3].respond_to?(:push)
retournerait true
, puisqu’on peut appeler .push
sur un tableau. Cependant:
[1, 2, 3].respondto?(:tosym)
retournerait false
, puisqu’on ne peut pas convertir un tableau en symbole.
Factorisation de code
$VERBOSE = nil # Ceci vous sera expliqué à la fin de la leçon
require 'prime' # Ceci est un module. On vous en dit plus très vite !
def n_premiers(n)
unless n.is_a? Integer
"n doit être un nombre entier."
end
if n <= 0
"n doit être supérieur à 0."
end
tableau_premiers = [] if tableau_premiers.nil?
premier = Prime.new
for nombre in (1..n)
tableau_premiers.push(premier.next)
end
tableau_premiers
end
La méthode .collect
La méthode .collect
prend un bloc et applique l’expression contenue dans le bloc à chaque élément présent dans un tableau. Voyez plutôt :
mon_tab = [1, 2, 3]
mon_tab.collect { |num| num ** 2 }
# ==> [1, 4, 9]
Si nous affichons les valeurs contenues dans mon_tab nous verrons que rien n’a changé :
mon_tab
# ==> [1, 2, 3]
En fait, c’est parce que .collect
retourne une copie de mon_tab mais ne modifie pas le tableau original mon_tab
. Si nous voulons effectivement le modifier, il faut rajouter un point d’exclamation devant le nom de la méthode : .collect!
:
mon_tab.collect! { |num| num ** 2 }
# ==> [1, 4, 9]
mon_tab
# ==> [1, 4, 9]
Yield
yield
permet d’éxécuter un module qui serait passer à la méthode que l’on souhaite éxécuter:
def demo(nombre)
yield(nombre)
end
puts demo(2) { |nombre| nombre + 10 }
La méthode définie renverra au block le nombre passé en paramètre. Dès qu’une méthode possède un yield
elle devra systématiquement être utilisé avec un block au risque de renvoyer une erreur.
yield
est un mot du langage ruby qui permet d’activer un bloc passé en parametre de fonction.
Pourquoi certaines méthodes acceptent un bloc et d’autres non ? En fait, les méthodes qui acceptent les blocs ont la faculté de transférer le contrôle de l’appel de la méthode au bloc et de le récupérer. Nous pouvons implémenter cela dans nos propres méthodes en utilisant yield
.
def bloc_test
puts "Nous sommes dans la méthode !"
puts "Appel de yield"
yield
puts "Nous sommes de retour dans la méthode !"
end
bloc_test { puts ">>> Nous sommes dans le bloc !" }
# On fait rentrer du code dans une methode via yield, grâce à l'ajout d'un bloc durant l'appel de la methode.
Yield avec paramètres
def yield_nom(nom)
puts "Dans la méthode. On utilise yield !."
yield("Kim") #utilisation du yield donc appel du bloc externe "{...}"
puts "Entre les yield !"
yield(nom) #idem
puts "Bloc terminé ! De retour dans la méthode."
end
yield_nom("fasca") { |n| puts "Mon nom est #{n}." }
#Resultat:
#Dans la méthode. On utilise yield !.
#Mon nom est Kim.
#Entre les yield !
#Mon nom est fasca.
#Bloc terminé ! De retour dans la méthode.
#nil
Autre exemple, la méthode double accepte un seul paramètre et utilise yield pour passer dans un bloc. Puis on l’appelle avec un bloc qui multiplie le nombre en paramètre par 2 :
def double(p)
puts "#{p}"
yield(p)
end
double(5) { |n| puts n*2 }
#Resultat:
#5
#10
#nil
Les Procs (des “blocs sauvegardés”)
Le problème des blocks, c’est qu’ils ne peuvent être stocké dans une variable. Aussi, si nous avons plusieurs méthodes auxquelles on souhaite passer une même logique, on est obligé de se répéter. Heureusement, les procs
permettent d’éviter se problème là. Un Procs
se définit de la manière suivante.
mon_proc = Proc.new do |number|
puts number * 2
end
[1, 2, 4].each(&mon_proc)
Pour créer un proc on utilisera la méthode Proc.new
avec un block. On pourra alors le stocker dans une variable pour le réutiliser plus tard. En revanche, il n’est pas possible de l’utiliser directement dans les méthodes qui attendent un block (comme la méthode each
par exemple. Il faudra alors convertir le Proc
en Block en utilisant le &
devant le nom de la variable.
Un Proc
est un type de variable particulier (objet) comme les entiers, les chaines de caractère ou autre. Il disposent donc de différentes méthodes, dont la méthode call
qui permet de l’appeller avec les paramètre souhaités. Ainsi, de multiples Proc
peuvent être passé en paramètre à une méthode, ce qui n’était pas possible avec les Blocks.
def ma_methode(nombre, proc_1, proc_2)
if nombre.even?
proc_1.call
else
proc_2.call
end
proca = { puts "Salut" }
procb = { puts "Aurevoir" }
ma_methode(2, proca, procb)
Enfin, il est aussi possible de faire la conversion vue tout à l’heure dans l’autre sens en converissant un block passé en paramètre en Proc
def ma_methode(&proc)
proc.call
end
ma_methode { puts "Salut" }
Le &
permet ici de convertir de manière automatique le block en Proc
.
Gardez votre code DRY
Vous vous souvenez quand nous vous avions dit qu’en Ruby tout est objet ? Et bien, nous avions un peu exagéré. Les blocs ne sont pas des objets, en fait il s’agit de l’une des rares exceptions à la règle “tout est objet” du Ruby.
A cause de cela, les blocs ne peuvent pas être sauvegardés dans des variables et ils n’ont pas tous les pouvoirs d’un vrai objet. C’est pour cela que nous allons avoir besoin des… procs !
Voyez les procs comme des “blocs sauvegardés” : de la même manière que vous pouvez donner un nom à un bout de code pour le transformer en méthode, vous pouvez donner un nom à un bloc pour le transformer en proc. Les procs sont parfaits pour garder votre code DRY, qui signifie “Ne se répète pas” (Don’t Repeat Yourself, en anglais). Avec les blocs, vous devez écrire le code à chaque fois que vous en avez besoin, avec un proc, vous écrivez une fois le code et vous pouvez l’utiliser plusieurs fois.
multiples_de_3 = Proc.new do |n|
n % 3 == 0
end
(1..100).to_a.select(&multiples_de_3)
# ==> [3, 6, 9, 12, 15, 18, 21, 24, 27, 30, 33, 36, 39, 42, 45, 48, 51, 54, 57, 60, 63, 66, 69, 72, 75, 78, 81, 84, 87, 90, 93, 96, 99]
La syntaxe des procs
Les procs sont très simples à définir ! Vous n’avez qu’à appeler Proc.new
et à lui passer le bloc que vous voulez qu’elle contienne. Voici comment créer une proc appelée cube
qui calcule le cube d’un nombre (qui l’élève à la puissance 3) :
cube = Proc.new { |x| x ** 3 }
Ensuite il est possible de passer la proc à une méthode qui demande un bloc, et nous n’aurons plus jamais à réécrire ce morceau de code.
[1, 2, 3].collect!(&cube)
# ==> [1, 8, 27]
[4, 5, 6].map!(&cube)
# ==> [64, 125, 216]
(Les méthodes .collect! et .map! font exactement la même chose.)
Le &
est utilisé pour convertir la proc cube
en un bloc (car .collect!
et .map!
attendent un bloc). Il faut l’écrire à chaque fois que l’on passe une proc à une méthode qui attend un bloc.
Pourquoi des procs ?
Pourquoi nous embêter à sauvegarder nos blocs dans des procs ? Il y a deux avantages à cela :
- Les procs sont des objets, donc elles ont les mêmes capacités que tous les objets. (Ce que les blocs n’ont pas.)
- Contrairement aux blocs, les procs peuvent être appelées autant de fois que vous en avez besoin sans avoir à les réécrire. Cela vous évite de taper à chaque fois le code pour l’exécuter.
# Vous êtes dans un parc d'attraction, vous devez mesurer
# 4 pieds ou plus pour monter dans les montagnes russes.
# Utilisez .select pour ne récuperer que les personnes assez grandes.
groupe_1 = [4.1, 5.5, 3.2, 3.3, 6.1, 3.9, 4.7]
groupe_2 = [7.0, 3.8, 6.2, 6.1, 4.4, 4.9, 3.0]
groupe_3 = [5.5, 5.1, 3.9, 4.3, 4.9, 3.2, 3.2]
# Complétez la ligne avec un proc
assez_grand = Proc.new do |hauteur|
hauteur >= 4
end
# Changez ceux la pour qu'ils utilisevotre proc assez_grand
peut_monter_1 = groupe_1.select(&assez_grand)
peut_monter_2 = groupe_2.select(&assez_grand)
peut_monter_3 = groupe_3.select(&assez_grand)
# ==> [5.5, 5.1, 4.3, 4.9]
Autre exemple, voici une methode qui appelle un proc :
def bonjour
yield
end
phrase = Proc.new { puts "Bonjour !"}
bonjour(&phrase)
# ==> Bonjour !
# nil
On peut aussi convertir en symbole:
strings = ["1", "2", "3"]
nums = strings.map(&:to_i)
# ==> [1, 2, 3]
Les Lambdas (les vrais méthodes sans noms)
Il existe un type de Proc
particulier, les lambdas, qui s’écrivent de la manière suivante:
# syntaxes courtes
ma_variable = lambda { |n| puts n }
ma_variable = ->(n) { |n| puts n }
# syntaxe longue
ma_variable = lambda do |n|
puts n
end
A première vu, un lambda
ressemble étrangement à un Proc
mais quelque différences subtiles existent justifiant leur utilisation.
Premièrement, un lambda
se comporte comme une méthode, le mot clef return
permet de quitter le block et rend le contrôle à la méthode qui l’a appellé
def ma_method_avec_lambda
lambda { return "a" }.call
return "b"
end
def ma_method_avec_proc
Proc.new { return "a" }.call
return "b"
end
puts ma_method_avec_lambda # b
puts ma_method_avec_lambda # a
Pour cette raison, on préfèrera souvent utiliser les lambda
pour éviter les surprise lors de certains retour.
La seconde différence est la vérification du nombre d’argument. Un Proc peut être appellé malgré un nombre d’argument différent et renverra alors la valeur nil
Proc.new { |a,b| b.inspect }.call(1)
# nil
lambda { |a,b| b.inspect }.call(1)
# ArgumentError: wrong number of arguments (1 for 2)
Voila pour les différences entre ces différentes options. Il faudra donc choisir suivant les cas d’utiliser l’une ou l’autre de ces options.
Le lambda Ruby
Comme les procs, les lambdas sont des objets. Les similitudes ne s’arrêtent pas là : si l’on enlève la différence de syntaxe et quelques différences de comportements, les lambdas sont identiques aux procs.
Observez le code. Vous voyez le lambda ? Ecrire :
lambda { puts "Bonjour !" }
# est exactement pareil qu'écrire :
Proc.new { puts "Bonjour!" }
Dans l’exemple ci-dessous, nous passons le lambda à la méthode lambda_demo, donc la méthode appelle le lambda et execute le code.
def lambda_demo(un_lambda)
puts "Je suis une méthode !"
un_lambda.call
end
lambda_demo(lambda { puts "Je suis le lambda !" })
#==> I'm the method!
# I'm the lambda!
We have an array of strings in the editor, but we want an array of symbols.
On line 4, create a new variable called symbolize. In symbolize, store a lambda that takes one parameter and calls .to_sym on that parameter.
We then use symbolize with the .collect method to convert the items in strings to symbols!
strings = ["leonardo", "donatello", "raphael", "michaelangelo"]
# Write your code below this line!
symbolize = lambda {|x| x.to_sym}
# Write your code above this line!
symbols = strings.collect(&symbolize)
print symbols
Les fonctions lambda sont des fonctions qui peuvent être stockées comme une variable:
fois2=lambda{|x| x*x}
fois2.call(4) #retourne 16
Vous pouvez passer une fonction lambda comme un bloc:
selecteurTri = lambda{ |a,b| a.prix <=> b.prix }
produits.sort(&selecteurTri)
Notez &
qui dit que le paramètre doit être traité comme un bloc de fin de fonction.
Vous pouvez vérifier qu’un block a bien été passé en utilisant block_given?
:
def fonction
yield if block_given?
end
Il existe d’autre manières de traiter un bloc passé qu’en utilisant yield
, la notation &block
voir : http://www.tutorialspoint.com/ruby/ruby_blocks.htm
La reflexion du langage
c’est la possibilité pour le langage de lister les variables, les methodes, nom des classes. Le langage peut agir sur lui-meme. On peut automatiser des fonctions/classes, appeler des methodes grace à un parametre (a la place du nom de la methode directement). (c’est utilisé dans ORM de ruby on rails, ActiveModel ex: si dans la bdd un champ mail existe alors ruby va créer un champ mail dans l’objet )
liste des fonctionnalités interessantes:
- ** methodes** : liste toute les méthodes de l’objet
- send : appel une méthode
- respond_to? : vérifie qu’une methode existe.
- is_a? : vérifie qu’un objet hérite bien d’une classe
- class : retourne la classe de l’objet en cours
- etc…
Ici nous allons passer une méthode en parametre, cela nous permet de passer du code dynamiquement via des variables:
str="BONJOUR MONDE"
maCommande = :downcase
str.send(#{maCommande})
#=> "bonjour monde"
str.send(:split, " ")
#=> ["bonjour", "monde"]
Les DSL sont des structure de code (populaire en ruby)
Elles permettent de rendre le code de definition claire et concis
Exemple sans DSL:
action = Action.new :list_done do
#faire action
end
action.description = "Lister les taches réalisées"
ActionManager.add action
Exemple avec DSL:
ActionManager.register do
description "Lister les taches réalisées"
action :list_done do
#faire action
end
end
class exemple
def initialize &block
instance_eval(&block)
end
le mot clé instance_eval
permet de lancer un bloc dans le contexte de l’objet.
les DSL nécessitent de créer une classe qui active les fonctionnalités du DSL.
Le monkey patching ou la réouverture de classe
En ruby, le redéfinition d’une classe existante ne l’écrase pas. Attributs et méthodes nouvelles sont ajoutés à la classe existante. Si les attributs et méthodes existent déja alors écrasés. Cela permet de :
- scinder le code d’une classe complexe dans plusieurs fichiers
- réécrire un comportement à posteriori
- améliorer/réadapter un code qui n’est pas de vous
le monkey patching défini le fait de réécrire le comportement d’une classe qui n’est pas la votre.
Ex:
- ajouter des methodes aux classes de base (string, array…)
- modifier le comportement d’une classe qui est utilisée par plusieurs gems
avantages:
- clarté du code écrit à l’aide de ces nouvelles méthodes
Date.yesterday => retourne la date d'hier
inconveniants:
- votre modification s’etend à toute les gems qui utilisent cette classe
- risque de collision avec les ressources utilisant cette méthode
Exemple de réécriture, on ajoute la méthode try pour la classe Objet. la concequence est que tous Objets aura cette methode :
class Objet
def try *args
if self.nil?
nil
else
send *args
end
end
end
L’heritage
Nous allons donc créer notre première classe qui hérite de la classe Animal
. Ce sera la classe Dog
. Dans cette classe, nous allons créer une méthode qui permet à l’animal d’émettre son cri. En Ruby, l’héritage se fait grâce à l’opérateur <
:
class Dog < Animal
WILD = false
def cry
puts "Woof!"
end
end
Redéfinition de méthode
L’héritage laisse aussi la liberté de ré-écrire entièrement ou en partie des méthodes de la classe parent. On va donc modifier la description pour qu’elle soit plus personnelle :
class Dog < Animal
WILD = false
def cry
puts "Woof!"
end
def description
puts "I'm #{@name} the dog and I'm #{@age} years old!"
end
end
On aurait pu vouloir garder le comportement par défaut mais simplement y ajouter du comportement additionnel. C’est possible grâce au mot-clé super qui permet d’appeler la méthode correspondante du parent. Modifions notre classe :
class Dog < Animal
WILD = false
def cry
puts "Woof!"
end
def description
super
puts "He's a dog."
end
end
Testons à nouveau:
load 'dog.rb'
d = Dog.new('Snoopy', 'male', 10)
d.description
On a donc la méthode description de la classe Animal qui génère la première ligne, puis le méthode description de notre classe Dog qui génère la deuxième ligne.
la visibilité des methodes
Lorsque vous écrivez une classe en Ruby, il est possible de limiter la visibilité de ses méthodes. Certaines méthodes sont conçues pour être utilisées directement par la classe, elle n’ont pas vocation à être utilisé à l’extérieur de celle ci. En limitant la visibilité, vous empêchez leur utilisation depuis l’extérieur.
Par défaut, les méthodes que vous ajoutez à une classe sont publiques, elle peuvent donc être appelées depuis l’extérieur. Les mots-clés protected et private permettent de modifier la visibilité des méthodes qui sont définies ensuite.
On a donc trois visibilités à disposition : public, protected et private.
class Visibility
def public_method
puts "public"
end
protected
def protected_method
puts "protected"
end
private
def private_method
puts "private"
end
def without_self
public_method
protected_method
private_method
end
def with_self
self.public_method
self.protected_method
self.private_method
end
end
Voyons maintenant comment ces trois méthodes se comportent au sein de la classe en ajoutant deux méthodes qui les utilisent :
withouth_self
: Les trois méthodes sont donc appelées sans le moindre problème avec un receveur implicite.
with_self
: Dans ce cas les méthodes publique et protégée sont bien appelées mais la méthode privée lève une exception NoMethodError.
Il est donc impossible d’appeler une méthode privé avec un receveur explicite, elle ne peut être appelée que sur l’objet courant.
Voyons maintenant comment ces méthodes se comportent depuis l’extérieur de la classe :
La méthode publique peut, sans surprise, être appelée de l’extérieur.
Les méthodes protégée et privée lèvent quant à elles une exception NoMethodError. Il est donc impossible d’y faire appelle depuis l’extérieur. Elles ont une visibilité limitée et ne font pas partie de l’interface publique de la classe.
Différence entre protectedet private:
Si l’objet qui envoie le message, autrement dit qui appelle la méthode, est du même type que l’objet qui le reçoit alors il peut appeler une méthode protégée. Il reste impossible d’appeler une méthode privée même dans ce cas.
Voyons un exemple :
class Extended < Visibility
def call_methods(other)
other.public_method
other.protected_method
other.private_method
end
end
class NotRelated
def public_method
puts "public"
end
protected
def protected_method
puts "protected"
end
private
def private_method
puts "private"
end
end
On ajoute une classe Extended qui hérite de Visibility. Cette classe implémente une méthode call_methods qui permet d’appeler nos trois méthodes mais sur un autre objet passé en paramètre.
Nous ajoutons également une classe NotRelated qui implémente nos trois méthodes mais qui n’a aucun lien avec nos deux classes précédentes.
Les méthodes publique et protégée de l’objet Visibility ont pu être appelées depuis l’objet Extended. C’est possible car ces deux objets proviennent de la même classe parent.
Seule la méthode privée ne peut être appelée.
Essayons maintenant avec un objet qui n’a aucun lien
Ici seule la méthode publique peut être appelée, l’appel à la méthode protégée lève une exception NoMethodError car les deux objets ne font pas partie de la même hiérarchie.
L’utilisation la plus fréquente pour les méthodes protégées est de permettre à deux objets du même type de coopérer. Par exemple pour écrire une méthode qui permet de comparer deux objets, disons des personnes et savoir qui est le plus âgé sans pour autant avoir accès publiquement à l’âge dans l’interface.
Les exceptions
Les exceptions sont un élément important dans la programmation car elles permettent de gérer les problèmes qui pourraient avoir lieu pendant l’éxécution de notre script. Capturer une exception
Lorsque l’on éxécute du code certaines erreurs peuvent arriver. Par exemple si vous essayez de diviser un chiffre par 0, ruby vous renverra une erreur ZeroDivisionError et arrètera l’éxécution du script. Dans certains cas nous allons chercher à capturer l’erreur pour effectuer une opération spécifique à la place (comme afficher un message d’erreur à l’utilisateur par exemple). Dans ce cas là on entourera notre code d’une instruction begin.
begin
# -
rescue ZeroDivisionError
# -
rescue UnAutreTypeDException
# -
rescue Exception
# Tous les autres types d'exceptions ici
end
Il est possible d’utiliser plusieurs rescue afin de gérer plusieurs types d’erreurs. Il est aussi possible de récupérer l’erreur dans une variable.
begin
rescue ZeroDivisionError => e
puts e.message
puts e.backtrace.inspect
rescue UnAutreTypeDException
# -
rescue Exception
# Tous les autres types d'exceptions ici
end
Renvoyer une Exception
Quand on va écrire notre code on va parfois se trouver dans des situations générant des erreurs. Dans ce cas là il est important d’être en mesutre de renvoyer une exception, qui pourra ensuite être récupérée. Pour cela on utilise la méthode raise.
def ajouterNote(note)
raise "Impossible d'ajouter une note qui n'est pas un chiffre" if !note.respond_to? :to_i
notes << note
end
Il est aussi possible de renvoyer un type d’erreur particulier afin de pouvoir la capturer plus simplement dans le cadre d’un begin.
class AjoutNoteError < RuntimeError
def initialize(msg = "Impossible d'ajouter la note pour la raison suivante : ...")
super
end
end
def ajouterNote(note)
raise AjoutNoteError if !note.respond_to? :to_i
notes << note
end
Comments