source: Wikiversity

lien a voir :

Ruby est un langage de programmation libre. Il est interprété, orienté objet et multi-paradigme.

Wikipedia

Sommaire


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.

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.

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.

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 :

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

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.

sources

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.

sources

Ruby un langage objet

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

Il est possible de passer une classe héritant de RuntimeError.

Les chaines de caractères

afficher une variable

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

le retour nil veut dire que print / puts ne renvoie pas d’expression. nil fait partie de la classe NilClass

Ici on voit bien que la chaine "coucou" fait partie de la classe String.

les methodes des objets

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

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.

convertion d’un type à un autre

Il est possible de convertir un objet à un autre type :

1.to_s      #=> convertie 1 en string
'1'.to_i    #=> convertie en int

la concatenation de caractere

Les opérateurs

opérateur de puissance et modulo

opérateur de comparaison

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

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 .shufflepermet 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]]

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)

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 :

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

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

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

On entre ici dans une boucle infinie.

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

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

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

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 :

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

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 :

le monkey patching défini le fait de réécrire le comportement d’une classe qui n’est pas la votre.

Ex:

avantages:

inconveniants:

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