Des contrôleurs optionnels grâce à const_missing !
Ah les contrôleurs, mieux ils sont fait, plus nombreux, semblables et inutiles ils seront. Ne gagnerait-on donc pas à gommer leurs ressemblances et les rendre optionnels ?
Mais qu’est-ce qu’un bon contrôleur ?
Quand cette question est soulevée, l’adage skinny controllers, fat models surgit. Moi je dirais optionnal controllers, object-oriented models.
Avant tout, le bon controlleur doit respecter le cadre logiciel dans lequel il évolue. Ruby on Rails a fait le choix de populariser le style d’architecture REST, qui consiste à considérer ce qu’on délivre via son application comme des représentations de ressources avec lesquelles on peut interagir à l’aide de verbes définies par le protocole HTTP. Un controlleur ne doit donc exposer qu’une liste restreinte d’actions qui correspondent aux verbes qui permettent d’interagir avec ces ressources.
En ce sens, l’ensemble des contrôleurs constitue l’interface visible d’une application qui s’exprime en ressources.
Qu’est-ce qu’une ressource ?
Ces ressources tendent à se confondre avec les modèles, mais cela s’avère vite trompeur. Car si vous y restreignez votre application, vous finirez bien vite par ajouter des actions supplémentaires à vos contrôleurs, violant ainsi les préceptes de REST.
Prenons le classique modèle User ! Si vous n’y voyez qu’une seule ressource, vous allez devoir ajouter des actions register, confirm_email, change_password, reset_password, confirm_reset_password alors que si vous considérez le mot de passe comme une resource, et l’inscription (registration) comme une autre, le tout s’emboite joliment dans des contrôleurs séparés faciles à écrire. Ou à “abstracter” comme nous verrons dans le reste de l’article !
Réalisation
Voici ce à quoi peut ressembler un contrôleur type (une version réduite à l’essentiel d’un scaffold)
CODE
Nous pouvons assez facilement le rendre générique et en faire hériter d’autres contrôleurs.
CODE
Dans la méthode resource_class , nous détectons en fonction du nom du contrôleur en cours (celui qui hérite de resources_controller) le nom de la classe associée et nous l’utilisons dans les différentes actions
Nous pouvons dès lors écrire des contrôleurs vides, qui ne contiennent que la déclaration :
CODE
Mais ce travail a déjà été fait, avec brio, sur tout le gem inherited_resources que nous utiliserons plus tard s’il est utilisé par l’application.
N’y aurait-il pas un moyen de se débarrasser de cette déclaration vide ?
Si, il y a const_missing!
Vous connaissez certainement method_missing, la méthode qui s’exécute quand un objet reçoit un message auquel il ne sait répondre, ni lui ni ses ancêtres dans la hiérarchie de classes.
const_missing est son corolaire pour les constantes, mais joue un rôle important : celui du chargement de classes. Nous verrons cela en détail dans la dernière partie de l’article. Concentrons-nous sur const_missing.
Cette fonction peut être définie au niveau des modules mais vous voulons dans notre cas la rendre globale. C’est donc dans la class Object que nous allons la définir en tant que méthode de classe.
CODE
Nous limitons notre intervention aux seuls noms de classes qui finissent par Controller et même dans ce cas-là nous donnons une chance à l’autoload (voir ci-après) dans le cas où le contrôleur existe, mais n’a juste pas encore été chargé.
CODE
Nous pouvons alors définir notre contrôleur tout en nous assurant que le modèle correspondant existe. Faisons cela dans la classe MissingController proprement dite :
CODE
CODE
That’s it ! Il ne nous reste maintenant qu’à donner vie à ces contrôleurs via les routes. Vous pouvez limiter les actions exposées via l’attribute only:
CODE
const_missing et autoload
const_missing est la pierre angulaire du mécanisme d’autoload, qui rend les classes de notre code disponibles partout sans le moindre require. Ruby embarque autoload que Ruby on Rails améliore pour permettre le chargement de classes au runtime et de définir différents paths (emplacements) d’où pourront être chargées les classes.
Le mécanisme de recherche de constantes en Ruby suit des règles simples pour déterminer l’emplacement de la bonne constante à utiliser à un endroit donné de votre code. Ce mécanisme repose sur la méthode Module.nesting qui renvoie le niveau d’imbrication de modules où elle est appelée. Votre constante est recherchée au sein de ces modules, ou dans les ancêtres du premier module (Module.nesting.first.ancestors) ou dans les ancêtres de object (Object.ancestors)
Si aucun de ces emplacements ne contient la constate convoitée, le processus d’autoload (chargement automatique de constante) est déclenché.













