Le blog de la CT2C

Les conditions en Ruby

Par Kulgar , publié le 21 Novembre 2012

Je pense que tous ceux qui me liront connaissent déjà parfaitement les conditions mais en Ruby, les syntaxes font partie des plus flexibles qu'il m'ait été donné de voir dans les langages que je connais. Ruby est un langage un peu étrange à première vue. Ce qu'il faut toujours garder en tête c'est que ce langage se veut aussi "proche" de la langue "parlée" que possible.

Exemple simple en Ruby On Rails: Vous souhaitez faire une recherche comme celle-ci : je veux trouver tous les utilisateurs par leur nom : "Dupont".

En anglais, ça se traduirait par "I want to find all the user by lastname "Smith"" (oui, traduisons aussi le nom, peu importe pour l'exemple ).

Vous avez alors deux possibilités pour trouver ces utilisateurs :

User.where("name = ?", "Smith") ou
User.find_all_by_lastname("Smith")

Remarquez comme la deuxième syntaxe ressemble étrangement à la phrase en anglais. C'est ce que je voulais vous montrer, très souvent je parviens même à "deviner" des fonctions que je ne connais pas et que je n'ai jamais vues, juste en traduisant ce que je souhaite obtenir.

Tout cela pour dire, qu'en Ruby, la syntaxe des conditions n'échappent pas à cette spécificité, en général, elles s'écrivent comme on les dirait en anglais.

Les classiques if, else, elsif et les ovnis unless et ?


Tout le monde connaît les conditions classiques "if", "else" et "elsif", même si je suis sûr, la syntaxe de elsif en surprend plus d'un. Bref, faisons tout de même un petit rappel :

En Ruby, on a la logique suivante :
if condition is true then do #Si la condition est vrai alors exécuter
this code... #ce code...
elsif other condition is true then do #sinon si cette autre condition est vraie alors exécuter
that code #cet autre code
else #Sinon exécuter
that default code #ce code par défaut
end #Fin !

Sachant qu'en Ruby seuls "false" et "nil" (nothing at all, rien du tout) sont "faux", tout le reste est vrai, y compris 0 !!

Les comparateurs de Ruby


Pour les conditions, chacun sait qu'il existe des comparateurs ainsi que des opérateurs logiques, dont voici la liste :

== teste si deux valeurs sont égales. Exemple: 5 == 5 renvoie "true"

!= , c'est le contraire. Il teste si deux valeurs sont différentes. Exemple 5 != 5 renvoie "false"

=== Je ne pense pas que vous le connaissez celui-ci, il est quasi identique au "==" mais utilisé implicitement dans les conditions de type "case / when". Il sert également pour les tests sur les séries (range en Ruby On Rails).
(1..5) == 5 renvoie false mais (1..5) === 5 renvoie true. Nous verrons pourquoi c'est utile, plus tard.

> et >= testent si la première valeur est supérieure strictement (>) ou égale (>=) à l'autre valeur, Exemple: 5 > 5 renvoie "false" mais 5 >= 5 renvoie "true"

<= et < , idem mais pour les valeurs inférieures.
Exemple: 5 < 5 renvoie "false" mais 5 <= 5 renvoie "true"

<=> est un comparateur que peu connaissent (je pense). Figurez-vous qu'il "combine" les précédents. En effet, il renvoie 0 si les deux valeurs sont égales, 1 si la première valeur est supérieure à la seconde et -1 si elle est inférieure.
Donc... 5 <=> 5 renvoie 0 ! A vous de me dire ce que renvoie 5<=>6! ;)

.eql? , pour l'expliquer simplement, teste à la fois la valeur et le type de vos deux objets.
Exemple: Avec lui, 5.eql? 5 qui renvoie "true".
Par contre, 5.eql? 5.0 qui renvoie "false".

L'explication réside dans le fait que 5 est un "integer" et "5.0" un "float". De fait, ce sont deux objets différents. :)

.equal? quant à lui teste si deux objets sont identiques.
On a ainsi: a.equal?(b) renvoie "true" si et seulement si a et b représentent le même objet.
Par exemple: 5.equal?5 renvoie "true". :)

Pour les friands d'explications plus complexes, en fait .eql? et "==" sont quasi identiques et c'est souvent le cas, sauf pour certains types d'objet comme les instances de la classe "Numeric". Le signe "==" est en réalité une méthode définie dans la class Object qui peut être écrasée dans ses sous-classes. Dans la classe Numeric cette méthode est écrasée pour ajouter une opération de conversion avant d'effectuer le test, donc dans 5 == 5.0, 5.0 sera d'abord converti en integer (en fait en Fixnum, mais tout le monde connaît mieux Integer...) pour ensuite être comparé à 5.

En revanche, ce n'est pas vrai pour la méthode .eql? qui elle n'est pas écrasée et donc trouve que 5 n'est pas égal à 5.0 car ces deux chiffres ne sont pas des instances de la même class.

Les opérateurs logiques de Ruby
Je parle bien ici des opérateurs logiques et pas des opérateurs binaires

Les opérateurs logiques sont très utiles pour éviter des répétitions de code ou affiner des conditions. Ce sont principalement les opérateurs suivants :

&& : La condition est vraie si les deux sous-conditions sont vraies.
Exemple: (5 == 5) and (5 >= 5) renvoie "true" mais (5 == 5) and (5 > 5) renvoie "false".

|| : la condition est vraie si au moins une des deux sous-conditions est vraie.
Exemple: (7 == 5) or (6 == 5) renvoie "false" mais (5 == 5) or (6 == 5) renvoie "true".

! : Renverse la logique de la condition.
Exemple: not(5 == 5) renvoie false ! Effectivement not(5 == 5) équivaut à 5 != 5.


Dernière petite note: faites très attention, les conditions sont sensibles à la casse. Donc "toto" == "toto" renvoie true mais "toto" == "Toto" renvoie false. ;)

L'exemple des animaux !

Maintenant que j'ai bien rafraîchi la mémoire à tout le monde, prenons l'exemple des animaux et de leur cri, on souhaite récupéré le cri d'un animal dans une fonction prenant en paramètre l'animal et retournant son cri :
# Ici animal vaut "chat" (car j'aime les chats)
def get_cris(animal)
if animal == "chat"
return "Miaouw" # Notez que le return est facultatif en Ruby On Rails
elsif animal == "chien"
return "Ouaf!"
else
return "Je ne connais pas le cri de cet animal !"
end
end

Et donc comme "animal" vaut "chat", cette fonction renverra "Miaouw" ! :) Avec tout ce que je viens de vous expliquer, je pense que vous l'aviez deviné, et puis cette méthode n'est pas compliquée. Mais voyons plutôt quelques autres syntaxes !

Exécute mon code si ce qui suit est vrai !

En Ruby, il en existe une très, très pratique : exécuter mon code si la condition est vraie. Prenons un autre exemple, cette fois, on veut récupérer "quadrupède" si l'animal possède 4 pattes et bipède sinon. Nous pourrions écrire ceci :
def get_pattes(nbr_pattes)
if nbr_pattes == 4
return "quadrupède"
else
return "bipède"
end
end

Oui, mais c'est lourd et peu élégant. En Ruby, il sera plus élégant d'écrire :
def get_pattes(nbr_pattes)
return "quadrupède" if nbr_pattes == 4
return "bipède"
end

Ce qui est équivalent à la méthode précédente. Et cela, me semble être le bon moment pour introduire l'opérateur ternaire "?".

L'opérateur ternaire ?

C'est une sorte de raccourci pour ce que nous venons de voir. Il fonctionne de la façon suivante :
(condition) ? if true do this : if false do that.

Donc la méthode ci-dessus s'écrirait :
def get_pattes(nbr_pattes)
(nbr_pattes == 4) ? "quadrupède" : "bipède"
end

Ce qui est encore plus économe ! Cette syntaxe est très souvent utilisée dans les fichiers .html.erb de Ruby On Rails où on intègre du Ruby dans les balises HTML car au lieu d'écrire (par exemple) :
<body <% if content_for?(:body_id) -%>
<%= yield :body_id -%>
<% else -%>
<%= "id=default_body" -%>
<% end -%>

Il sera ô combien plus lisible, élégant et compréhensible d'écrire :
<body <%= content_for?(:body_id) ? (yield :body_id) : "id=default_body" %>

Pour ceux qui ne connaissent pas, en Ruby On Rails, yield est utilisé dans les layouts (ici dans application.html.erb), lorsque vous mettez un symbole devant comme ":body_id", vous pouvez définir la valeur de ce symbole dans les pages .html.erb qui viendront se greffer dans ce layout à l'aide de la fonction "content_for".
Vous récupérez ensuite la valeur de ce symbole dans votre layout à l'aide de "yield :body_id". La méthode "content_for?" permet de vérifier que ce symbole a été défini ou non et renvoie "true" si c'est le cas, "false" sinon. Donc dans notre exemple : soit il est défini et on intègre cet id à la balise html body, soit il ne l'est pas et on colle un id par défaut à la balise body.

C'est très pratique si vous souhaitez changer le fond de votre site uniquement sur une ou deux pages spécifiques, par exemple.

Unless


Pour terminer ce petit chapitre, parlons un peu d'unless. Pour l'heure, je ne l'ai vu que dans Ruby. "Unless" c'est tout simplement équivalent à "if not" et il fonctionne tout comme "if". Je le trouve vraiment très très pratique car je comprends bien mieux :
return user.address unless user.address.nil?
Que :
return user.address if !user.address.nil?

Évidemment, c'est un exemple simple, mais vous comprenez que pour des cas plus complexes, la première syntaxe est tout à fait plus compréhensible.

Comme je vous le disais, Ruby est un langage qui se rapproche de la langue parlée, et on voit bien encore une fois que c'est effectivement le cas. Comme unless fonctionne tout comme "if", vous pourrez aussi avoir "unless" suivi de "else", par contre "elsunless" n'existe pas.

Encore une fois, pour ceux qui ne connaissent pas, la méthode ".nil?" renvoie true si ce qui est testé vaut "nil" et false sinon. C'est très pratique lorsque vous récupérez une liste de données de votre base de données et que vous n'êtes pas sûr ou ne voulez pas des données non encore définies.

La condition : Case ... when ... else ... end


Il nous reste à voir le "switch" Rubyien. Dans beaucoup de langages, un switch s'écrit comme ceci :

switch(valeur){
case "3":
break;
case "5":
break;
default:
break;
}

En Ruby, c'est encore une fois plus proche du langage parlé. On dira plutôt: je teste l'expression suivante, quand elle vaut cette valeur ou celle-ci, je veux faire ça, quand elle vaut cette autre valeur, je veux plutôt faire ça, sinon, je fais ceci. Si nous reprenons notre fonction avec les cris d'animaux, cela donne :

def get_cris(animal)
case animal
when "chat", "cat"
return "Miaouw!"
when "chien", "dog"
return "Ouaf!"
else
return "Je ne connais pas le cri de cet animal."
end
end

Voyez ? J'ai même rajouté le nom anglais de l'animal pour vous montrer combien c'est pratique. Si nous voulions mettre des if, elsif, else, le code équivalent serait :

def get_cris(animal)
if animal === "chat" || animal === "cat"
return "Miaouw!"
elsif animal === "chien" || animal === "dog"
return "Ouaf!"
else
return "Je ne connais pas le cri de cet animal."
end
end

Avouez que c'est beaucoup moins élégant. Maintenant, comment faire pour savoir si votre chat favori est un bébé chat, un chaton, un chat arrivé à maturité ou un chat très vieux ? Allez-vous faire une succession de if age <= 2, elsif age <= 5, etc ?

Vous pourriez, mais ce n'est pas le plus élégant. A la place, nous allons combiner les séries de chiffres et les tests "case when". Voyez plutôt :
def get_age_chat(age)
case age #On va dire que l'âge est en années.
when 0 ... 1
return "Bébé chat"
when 1 ... 3
return "Chaton"
when 3 ... 10
return "Chat adulte"
else
return "Vieux matou"
end

#Dans les séries de chiffre, le troisième "." est utilisé pour exclure la valeur maximale de l'énumération
end

C'est là où nous repensons au comparateur "===", utilisé implicitement lors des tests "case when". En effet, il permet donc, sans écritures supplémentaires, de confronter une valeur à toute une série de chiffres en quelques lignes de code.

[EDIT] Les opérateurs "and" et "or" et ||=
Il me reste à vous présenter deux opérateurs qui ne sont pas des opérateurs logiques mais qui peuvent s'avérer très utiles. And et or sont des opérateurs de contrôle tout comme if / else / unless.

A quoi peuvent-ils bien servir ?

And peut s'avérer très pratique pour enchaîner les méthodes. Il fonctionne très simplement : tant que l'une des méthodes ne retourne pas false / nil, l'exécution suit son cours. Par exemple :
user = User.find(user_id) and user.grant_right(admin)

Dans cette fonction, Ruby cherche d'abord si l'utilisateur est bien enregistré dans la base de données, une fois trouvé et s'il est trouvé (la fonction .find renvoie "nil" s'il aucun utilisateur est trouvé), Ruby applique sur l'utilisateur la fonction "grant_right", pour lui octroyer les droits d’administrateurs. Si vous aviez utilisé "&&" à la place, vous auriez eu une erreur indiquant que "user" n'est pas défini.

And permet donc de cloisonner l'exécution de plusieurs méthodes tout en les enchaînant. Or fonctionne sur le même principe : tant que les méthodes échouent l'exécution se poursuit. Par exemple :
user = User.find(user_id) or raise "Cet utilisateur n'existe pas!"
Ici, si l'utilisateur n'est pas trouvé, le message "Cet utilisateur n'existe pas!" sera affiché.

Enfin, je voulais dire deux mots sur ||= que vous pourriez retrouver en lisant le code d'autres personnes. ||= n'est pas compliqué à comprendre, il fonctionne de la même façon qu'un simple +=. Donc si vous avez :
@user ||= current_user

Cela équivaudra à :
@user = @user || current_user

A quoi ça sert ? C'est simple : si @user n'est pas défini (et donc vaut nil), @user recevra la valeur de "current_user". Si @user ne vaut pas "nil" (donc déjà défini), il ne sera pas modifié. Cette technique est souvent utilisée dans les mécanismes d'authentification avec un "remember me".

Je crois avoir fait le tour sur toutes les conditions en Ruby. Une dernière petite note : en Ruby comme en Ruby On Rails, toutes les fonctions - et je dis bien toutes ! - se terminant par le caractère "?" renvoient un booléen et effectuent des tests utilisant les méthodes que nous venons de voir. Donc désormais, si vous voyez une méthode se terminant par "?" comme : content_for? - equal? - nil? - empty? ... vous pouvez être certains de pouvoir les utiliser au sein d'une condition pour vos tests.

Ce mot termine cet article conséquent sur les conditions en Ruby, j'espère qu'il vous a plu, et j'attends vos commentaires sur d'éventuels points à éclaircir ou sur des suggestions pour une prochaine leçon sur des notions que je n'ai pas pu aborder ici ou sur des notions abordées que vous ne connaissiez pas.


Index -- --

  • Aucun commentaire - Soyez le premier !

Insérez votre commentaire
  1. Min: 50 caractères, Max: 800. Actuellement: 0 caractères

  2. ne pas remplir