Ici et ailleurs

Le blog d’un joueur … mais pas que le blog de Sky.

Ruby on Rails : Ruby et sa syntaxe part2

Ce post me permet d’inaugurer ma nouvelle CSS (F5 si vous ne voyez rien de changé), j’espère qu’elle vous plaira et permettra de faciliter la lecture de ce qui va suivre.

Suite de la partie précédente, nous continuons notre voyage au sein de la syntaxe de Ruby et on se concentre cette fois sur la partie Objet du langage. Encore une fois, pour savoir ce qu’est la POO, je vous invite à faire un tour sur le net.

Tout est Objet

En Ruby, tout est considéré comme un objet et tout hérite implicitement de la classe Object. Dirigez-vous vers l’irb et taper :

irb> 1.class
=> Fixnum

On obtient la classe de notre nombre “1″. Faisons la même chose sur les chaînes de caractères :

irb> "Je suis une chaine".class
=> String

On peut d’ailleurs essayer d’autres méthodes propres à la classe String. Pour savoir quelles méthodes sont à notre disposition, on peut se rendre sur l’api officielle de Rails, celle de Ruby (pour les classes faisant partie du Ruby Core, attention cependant), sur railsapi.com ou dans le dossier doc de votre installation Ruby. Pour la classe String, on se retrouve sur la doc fournie dans ruby19.chm (Classe String dans API Core Reference).

irb> chaine = "je suis une chaine"
=> "je suis une chaine"
irb> chaine.upcase
=> "JE SUIS UNE CHAINE"
irb>chaine
=> "je suis une chaine"
irb> chaine.upcase!
=> "JE SUIS UNE CHAINE"
irb> chaine
=>  "JE SUIS UNE CHAINE"

On remarque que l’utilisation de upcase! modifie l’appelant, dans ce cas notre variable chaine. Ce qui n’est pas le cas de upcase qui retourne juste une copie en majuscule. C’est donc ici qu’on retrouve la convention de nommage de Ruby pour les méthodes modifiant l’appelant dont on avait parlé.

irb> chaine.end_with?('e')
=> true

La méthode end_with?(suffixe) retourne true si la chaîne se termine par le suffixe envoyé en paramètre. Ceci juste pour vous montrer l’utilisation d’une méthode retournant un boolean.

Une histoire de Classe

Créons notre première classe et notre première méthode d’instance. Rien de bien compliqué. Nous allons créer une classe User et une méthode say_hello qui ne prendra pas de paramètres.

A chaque fois que nous redéclarerons une classe ou méthode existante, il faudra au préalable fermer et rouvrir l’irb.

irb> class User
irb>   def say_hello
irb>     return "Hello from someone!"
irb>   end
irb> end
=> nil
irb> jean = User.new
=> #<User:0x2140050>
irb> jean.say_hello
=> "Hello from someone!"

Un mot sur la visibilité des méthodes

Avant de continuer, un petit mot sur la visibilité des méthodes. Par définition, les méthodes, en Ruby, sont publiques, c’est à dire qu’on peut y’accéder de l’extérieur. On le verra plus bas, les variables sont de leur côté privées. Les différentes visibilités sont définies par les mots-clés suivants :

  • public : Tout le monde peut y’accéder, valeur par défaut des méthodes,
  • private : Visible uniquement à l’intérieur de la classe dans laquelle elle est déclarée, ne sera pas accessible de l’extérieur ou par une classe fille,
  • protected : idem que private mais sera accessible par les classes filles.

Reprenons l’exemple de dessus, si j’avais écris :

class User
  private # Toutes les méthodes suivantes seront privées jusqu'à l'apparition d'un autre modificateur de portée
  def say_hello
    puts "Hello from someone!"
  end
end

L’appel à la méthode say_hello aurait levé une erreur.

jean = User.new
jean.say_hello #=> Erreur, la méthode n'est pas accessible !

On peut préciser la visibilité d’une seule méthode grâce à la syntaxe suivante :

class User
  def say_hello
    puts "Hello from someone!"
  end
  private :say_hello
end

Dans le cas où il s’agit d’une méthode de classe (vous pouvez traduire par méthode statique) dont on parlera plus tard, on utilise la syntaxe private_class_method :nom_methode. Si j’avais utilisé le mot clé protected, cela aurait signifié qu’une classe héritant de ma classe User aurait pu appeler la méthode say_hello.

Maintenant que nous sommes au point sur la visibilité des méthodes, nous allons agrémenter notre méthode say_hello parce qu’avoir un utilisateur c’est bien, mais ce serait plus sympa si il avait un nom et un prénom pour commencer. Ce qui me permet d’enchaîner sur les variables d’instance.

Variables et méthodes d’instance

Les variables d’instance, comme leur nom l’indique sont propres à une instance d’une classe en particulier. Ce qui signifie que chaque instance pourra avoir sa propre valeur. Elles sont précédées d’un arobase “@“. Les variables sont privées, on ne peut pas y’accéder de l’extérieur. Pour y’accéder, on a recours à ce qu’on appelle des accesseurs qui eux, sont publics. Il existe deux types d’accesseurs, les getters (pour récupérer la valeur d’une variable) et les setters (pour mettre à jour la valeur d’une variable); c’est ce qu’on appelle l’encapsulation. Commençons par créer une variable d’instance @name ainsi que ses accesseurs. Nous en profiterons pour mettre à jour notre méthode say_hello qui se servira de cette variable lors de l’affichage.

class User
  def say_hello
    puts "Hello from #{name}"
  end

  def name=(x) # Setter, met à jour la variable
    @name = x
  end

  def name # Getter, retourne la variable @name
    return @name
  end
end

Dans notre méthode say_hello, on fait appel au getter pour afficher la valeur de @name. On aurait aussi pu écrire #{@name}, dans ce cas, on ne serait pas passer par l’accesseur. On peut désormais faire ceci :

jean = User.new
jean.name = "bon"
jean.say_hello
=> "Hello from bon!"

Dans le cas où vous n’avez pas besoin d’effectuer de vérifications lors de la modification/lecture d’une variable d’instance, il existe une manière plus simple et plus rapide de déclarer des variables d’instance accessibles de l’extérieur : les attributs, grâce aux mots-clé :

  • attr_reader : permet de rendre une variable lisible à l’extérieur de la classe,
  • attr_writer : permet de rendre une variable éditable à l’extérieur de la classe,
  • attr_accessor : permet de rendre une variable lisible et éditable à l’extérieur de la classe.

Ainsi, notre exemple précédent ressemblerait à ceci (avec l’ajout d’une autre variable : last_name) :

class User
  attr_reader :name, :last_name
  attr_writer :name, :last_name
  # ou en une ligne
  # attr_accessor :name, :last_name

  def say_hello
    puts "Hello from #{@name} #{@last_name}!"
  end
end

L’utilisation reste quant à elle identique.

jean = User.new
jean.name = "Jean"
jean.last_name = "Bon"
jean.name # => Retournera "Jean"

Nous avons donc parlé des membres d’instance, mais si nous voulons agir sur une classe, nous aurons besoin des variables et méthodes de classe.

Variables et méthodes de classe

Les variables de classe appartiennent à une classe et ne nécessite pas l’instanciation d’un objet. Elles sont préfixées par un double arobase “@@” et sont elles aussi privées. Imaginons que nous voulons garder un compteur qui serait incrémenté à chaque instanciation d’un objet.

La méthode de classe initialize est automatiquement appelée par la méthode new pour construire l’objet demandé et ainsi effectuer les éventuelles initialisations.

class User
  @@number_of_users = 0
  def initialize
    @@number_of_users += 1
  end

  def self.count
    return @@number_of_users
  end
end

Ce bout de code introduit aussi la notion de méthodes de classe (qu’on peut aussi nommer méthodes statiques) déclarées grâce à la syntaxe suivante : self.nom_methode, le mot-clé self faisant référence à la classe actuelle pouvant être remplacé par le nom de la classe en question. Et d’un point de vue utilisation, voilà ce que cela nous donne :

jean = User.new
bob = User.new
User.count
=> 2
bob.count # Déclenchera une erreur, il n'existe pas de méthode d'instance "count"

Je pense que c’est suffisamment clair, nous pouvons donc continuer.

Un mot sur la construction

Lorsqu’on demande l’instanciation d’un objet à une classe grâce à la méthode new, nous faisons ici appel à une méthode de classe. Cette méthode va appeler la méthode initialize de notre classe. Si vous voulez que la méthode new prenne des paramètres, c’est donc dans la méthode initialize que vous devez vous en occuper. Reprenons notre exemple plus haut pour directement initialiser nos variables name et last_name lors de l’instanciation de notre objet.

class User
  attr_reader :name, :last_name
  attr_writer :name, :last_name

  def initialize(name, last_name)
    @name = name
    @last_name = last_name
  end

  def say_hello
    puts "Hello from #{@name} #{@last_name}!"
  end
end

Nous pouvons désormais instancier notre classe User de cette manière :

jean = User.new("Jean", "Bon") # /!\ "User.new" sans paramètres provoquera une erreur
jean.say_hello
=> "Hello from Jean Bon!"

Luke, je suis ton père

Nous allons à présent parler de l’héritage. Un des concepts les plus importants de la POO.

Le fait d’hériter permet de récupérer toutes les méthodes et toutes les variables de la classe parente. - 29minutesparjour

Pour mettre en forme cette notion, reprenons notre classe User telle qu’elle figure juste au dessus. Maintenant, nous allons créer une classe AdminUser qui va hériter de notre classe User et donc récupérer entre autre la méthode say_hello.

En Ruby, une classe ne peut posséder qu’une seule superclasse.

Si la méthode say_hello était déclarée private et qu’on souhaitait y’accéder depuis notre classe fille AdminUser, il aurait fallu remplacer private par protected (cf. visibilité des méthodes)

class AdminUser < User # Le symbole "<" signifie hérite
end

john = AdminUser.new("John", "Doe")
john.say_hello
=> "Hello from John Doe!"

Ainsi on dispose des méthodes et variables de la classe parente dans notre classe fille. Imaginons maintenant que nous souhaitons pouvoir créer notre super utilisateur avec des identifiants prédéfinis, vous l’aurez deviné, on va devoir définir la méthode initialize de notre toute nouvelle classe. Par défaut, la classe fille appelle la méthode initialize de sa classe mère, remédions à ça.

class AdminUser < User
  def initialize
    @name = "Super"
    @last_name = "User"
  end
end

jean = AdminUser.new
jean.say_hello
=> "Hello from Super User!"

C’est déjà mieux, mais on a répéter notre bout de code pour l’initialisation de nos variables deux fois, ce qui serait intéressant, c’est de pouvoir appeler la méthode initialize de notre classe mère avec nos arguments “Super” et “User”. Let’s do this!

class AdminUser < User
  def initialize
    super("Super", "User")
  end
end

bob = AdminUser.new
bob.say_hello
=> "Hello from Super User!"

Le mot-clé super fait référence à la méthode de la classe parente. On peut l’utiliser de plusieurs manières :

  • super : sans paramètre, appelle la méthode parente avec les paramètres envoyés à la méthode fille
  • super(params) : avec paramètre(s) ou parenthèses vide, appelle la méthode parente avec uniquement les arguments envoyés

On y’est presque, nous allons finir avec de la réimplémentation de méthodes.

“Overriding” de méthodes

En Ruby, on ne peut pas avoir deux méthodes ou plus qui portent le même nom. La dernière déclaration sera toujours considérée comme la seule existante. De ce fait, la surcharge des méthodes n’existe pas.

Pour contourner ce problème, on peut créer une méthode générale qui, en fonction du nombre d’arguments, décide quelle méthode exécuter mais on ne le fera pas ici. Voilà ce que ça donne en pratique :

class A
  def ma_methode(param)
    puts "ma methode #{param}"
  end
  def ma_methode
    puts "ma nouvelle methode"
  end
end

objet = A.new
objet.ma_methode
=> "ma nouvelle methode"
objet.ma_methode("un param") # => Erreur, la méthode "ma_methode" ne prends pas de paramètre

Overriding dans les classes dérivées

Imaginons que nous souhaitons réimplémenter la méthode say_hello dans notre classe AdminUser. Avec ce qui est dit juste au dessus, rien de plus simple, on la redéfinit et le tour est joué. Si on souhaite toutefois appeler la méthode de la classe parente avant, on peut utiliser encore une fois le mot-clé super.

class AdminUser < User
  def initialize
    super("Super", "User")
  end

  def say_hello(param)
    super()
    puts "And I am admin, #{param}"
  end
end

Ici je mets des parenthèses car la méthode say_hello de la classe User ne prend pas de paramètres, ainsi j’obtiens le résultat suivant :

chuck = AdminUser.new
chuck.say_hello # Erreur
chuck.say_hello("Hey")
=> "Hello from Super User"
   "And I am admin, Hey"

Les modules

Les modules permettent de regrouper des classes, des méthodes ou des variables au sein d’un même groupe. Ils s’apparentent aux namespaces de .Net. Ils permettent de mieux diviser votre code en regroupant par exemple les éléments destinés à exécuter des actions sur une même partie de votre programme. On peut aussi les comparer aux packages de Java.

module A
  def ma_fonction
    puts "Hello from ma_fonction"
  end
end

class MaClasse
  include A
end

Le include A permet d’inclure les fonctionnalités du module A au sein de la classe, ainsi, nous pourrons les utiliser comme ceci :

obj = MaClasse.new
obj.ma_fonction
=> "Hello from ma_fonction"

Même si il ne s’agit pas syntaxiquement d’héritage, les modules peuvent être utilisés pour gérer des cas d’héritage multiple, cependant, mieux vaut rester prudent.

Voilà pour aujourd’hui, je ne me rendais pas compte du boulot que c’était de faire ce genre de tuto, j’espère avoir été à la fois clair et concis, il y’avait énormément de choses à voir. J’espère que ça vous aura plu. Le prochain article portera sur Rails (enfin !?), nous verrons notamment comment fonctionne Rails au niveau du routage, le tout sans scaffolding (génération automatique de code) dans un premier temps.

Tags: , , , , , , ,

12 commentaires pour “Ruby on Rails : Ruby et sa syntaxe part2”

  1. zejulio dit :

    Intéressant
    Continue!

    Au passage: ta css est très propre et lisible = j’adore/bravo !

  2. skaven dit :

    Pareil que zejulio.

  3. D0h dit :

    Pareil, css impec.
    Mais il n’y a que moi qui trouve la syntaxe de ce langage affreuse ?

  4. romainbessuges dit :

    J’avoue que j’ai du mal avec la déclaration des class, mais le langage a l’air vraiment orienté plaisir de coding donc ça peut pas être mal.

    Ce qui me branche moins c’est le coup des gems, qui te créent ouate-mille fichiers tu sais pas trop où et qui doit être un enfer à debugger.

  5. El_Porico dit :

    D0h je trouve ce langage vraiment dégueulasse aussi. Ses “end” me font trop penser à Visual Basic; les appels à des methodes sans parenthèses (ie: on utilise indifféremment string.upcase que maclasse.monattribut alors que “upcase” est clairement une méthode) me choquent, on déclare des trucs à l’arrache…

    C’est sans doute assez puissant et performant mais ça me rebute totalement.

    En fait je vois pas comment on peut passer de langages à base de curly brackets et de syntaxe “universelle” type C/C++ (je compte PHP/C#/Java avec) à ça si on a pas commencé par Ruby.

  6. Sky dit :

    @El_Porico, @D0h : J’ai commencé comme beaucoup par du C, je fais régulièrement du Java, C# et du PHP, c’est un coup à prendre. Pour les appels des méthodes/attributs, on peut préciser les parenthèses en théorie, c’est au choix du développeur.

    Au final, je suis moi aussi rebuter par le VB, l’absence de points-virgule entre autre et apprendre Ruby reste sympa. C’est surtout que Rails a l’air vraiment excellent, du coup, j’essaie de me dire que ça vaut le coup de souffrir un peu :)

  7. Radical dit :

    Bon la syntaxe on doit pouvoir s’y faire, surtout avec un bon IDE (plugin Eclipse ?).

    J’ai envie de dire, c’est pas ça qui compte. En revanche moi ce qui m’intéresse c’est de savoir dans quel contexte il est adapté. Pour quel projet Ruby va m’apporter un plus ?

  8. Sky dit :

    @Radical : Très bon point, à vrai dire dans mon cas, c’est surtout pour un apport technique mais je pense que tu as raison, il va falloir que je crée un article pour expliquer “Why Ruby?”.

  9. El_Porico dit :

    La syntaxe y est pour beaucoup dans le succès d’une plateforme.

    Si on reprend le tout début de .NET, Microsoft axait sa communication autour de Visual Basic. Or, ce qui l’a rendu vraiment populaire, c’est C# car une horde de développeur expérimentés C/Java ont pu s’y mettre sans peine et découvrir le potentiel du produit. Aujourd’hui VB.NET est relayé au rang de relique maintenue en vie pour les projets déjà développés dessus.

    En toute honnêteté, quel est l’intérêt de vouloir mettre:
    jean = User.new
    au lieu de
    jean = new User ?

    Pourquoi ont ils décidé de faire
    class AdminUser < User
    au lieu de
    class AdminUser : User ?

    On dirait qu’ils essayent volontairement de s’écarter des canons du genre. C’est idiot et contreproductif à mon avis.

  10. Lork dit :

    Je suis du même avis que El_Porico, la syntaxe me rebute complètement.
    Et comme dit au dessus, je ne comprends pas trop les avantages de Ruby face au langages déjà existants.
    RoR a l’air super puissant, mais le seul soft Ruby que j’hérberge (Redmine) bouffe une ressource pas possible et manque clairement de réactivité à mon goût.

    Bref, quitte à vouloir du neuf, le SSJS me semble plus attrayant (Node.js par exemple), car j’y vois vraiment un intérêt face aux langages existants.

    Il n’en reste pas moins que ces articles sont très intéressants pour aller plus loin que ce qui nous fait fuir au premier coup d’oeil.

  11. Sky dit :

    De toute manière, je continuerais ma série RoR. On le verra bientôt, ce qui est énorme avec Rails, c’est l’architecture et la conception des apps. Tout est bien découpé et ça donne quelque chose de relativement propre.

  12. romainbessuges dit :

    Ca a l’air bien sympa Node.js, mais j’ai un doute concernant l’utilisation d’un langage à typage faible et orienté prototype pour du code serveur. C’eût été 1000 fois plus bandant avec un code moderne type Java, C# ou AS3

Laisser un commentaire

Si vous avez un compte sur WeFrag, connectez-vous pour publier un commentaire.

Vous pouvez, entre autres, utiliser les tags XHTML suivant :
<a href="" title="">...</a>,<b>...</b>,<blockquote cite="">...</blockquote>,<code>...</code>,<i>...</i>