Fort heureusement, ce processus de recherche est plutôt simple à comprendre.

Le plus simple pour expliquer la démarche suivie par l'interpréteur Ruby pour faire ses choix est tout simplement d'écrire du code simple avec quelques méthodes qui ne feront qu'afficher un message pour qu'on sache où l'on se trouve dans le code lors de son exécution.

Nous allons donc écrire un morceau de code qui regroupe tous les cas possibles :

module M
def affichage
puts "méthode d'affichage dans le module M"
end
end
class C
include M
end
class D < C
end
objet = D.new
objet.affichage

On crée donc un objet de la classe D puis on appel sa méthode affichage. L'interpréteur Ruby voit que la méthode est inexistante dans la classe D, il regarde donc si cette classe mixe des modules, ce qui n'est pas le cas. L'interpréteur va donc regarder si la superclasse de D (C), implémente une telle méthode ce qui n'est toujours pas le cas. Il regarde donc si la classe C mixe un module, c'est le cas (M). L'interpréteur vérifie donc si ce module définit une méthode affichage, c'est le cas donc l'interpréteur l'utilise.

Nous venons de voir un exemple typique de cheminement de l'interpréteur dans le lookup path pour trouver une méthode qui correspond au message envoyé.

Jusqu'où peut aller la recherche ?

Eh bien, sachant que tout objet en Ruby est une instance ou un descendant de la classe Object, l'interpréteur pourra toujours remonter jusqu'à ce niveau, et même plus loin. En effet, la classe Object mixe le module Kernel qui est toujours vérifié en dernier. C'est d'ailleurs ce module qui nous donne accès à des méthodes comme respond_to? ou send depuis tous nos objets.

Si vous arrivez jusqu'au module Kernel et que votre méthode n'a toujours pas été trouvé, l'interpréteur abandonnera la recherche (puisqu'il n'a plus aucun endroit où chercher) et vous renverra une exception NoMethodError.

Schéma du chemin de recherche des méthodes


Ce schéma vous donne le chemin le plus complet qui peut être parcouru. Si une méthode portant le même nom est définie plusieurs fois à différents niveaux, alors la définition rencontrée en premier sera utilisée.

Oui mais si je mixe plusieurs modules qui ont les mêmes méthodes ?

C'est toujours aussi simple, le principe de moindre surprise est là pour nous. Imaginons que l'on fasse :

module M
def affichage
puts "Nous sommes dans M"
end
end
module N
def affichage
puts "Nous sommes dans N"
end
end
Class C
include M
include N
end
obj = C.new
obj.affichage
# => "Nous sommes dans N"

C'est tout simplement la dernière définition de méthode qui prend le dessus et donc celle définie par le dernier module inclus, dans notre cas le module N.

Et si je veux forcer la remontée dans le lookup path ?

C'est possible ! Il suffit de passer par l'utilisation du mot-clef super qui indique à l'interpréteur de poursuivre sa recherche même s'il a trouvé une méthode correspondante. Cette astuce est particulièrement pratique lorsque vous avez une méthode de la classe parent qui fait quasiment tout le nécessaire mais ne traite pas quelques détails. Vous pouvez donc utiliser cette astuce pour faire les choses spécifiques puis appeler la méthode parent pour achever le travail comme dans l'exemple suivant :

class Velo
attr_reader :roues, :places
  def initialize()
    @roues = 2
    @places = 1
end
end
class Tricycle
def initialize()
super
@roues = 3
end
end

super nous permet donc d'initialiser notre tricycle comme un vélo (2 roues et 1 place) puis de faire ensuite les modifications nécessaires pour qu'il ait les caractéristiques d'un tricycle (3 roues). Cette technique nous évite des répétitions inutiles.

Conclusion

Il est donc important de bien comprendre comment l'interpréteur recherche une méthode donnée. Comprendre ces principes vous permet de mettre en place des structures de code plus puissantes et avec moins de redondances. Une fois ces principes intégrés, vous aurez la possibilité d'utiliser l'héritage et les modules de façon optimale puisque vous aurez toutes les clefs en main pour comprendre comment et pourquoi une méthode est sélectionnée plutôt qu'une autre.

Et maintenant, il ne me reste plus qu'une chose à faire, c'est vous souhaiter une merveilleux Noël !