params.expect là où ils généraient auparavant require et permit. La nouvelle forme est plus courte. Elle corrige aussi un problème d’ordre qui provoque des exceptions évitables dans les applications Rails depuis que les strong parameters existent.
Ce que couvre ce tutoriel
- L’exception que déclenchent
requirepuispermiten cas d’entrée altérée. - Pourquoi inverser l’ordre corrige le problème, et pourquoi
expectest préférable à le faire soi-même. - Comment déclarer un paramètre comme un tableau pour qu’il ne puisse pas arriver sous forme de hash.
Prérequis
Le problème avec require puis permit
La forme habituelle s’écrit :require(:post) renvoie ce qui se trouve à cette clé et vérifie seulement que c’est présent. permit est ensuite appelé sur le résultat.
Cela fonctionne tant que post est un hash. Cela ne fonctionne plus si quelqu’un envoie ?post=hello. require renvoie sans problème la chaîne "hello", permit est appelé sur un String, et la requête échoue avec :
NoMethodError n’est pas le bon résultat. Les paramètres étaient malformés, ce qui est une erreur côté client, et les strong parameters existent précisément pour rejeter les entrées malformées. C’est pourtant le code chargé de nettoyer l’entrée qui plante. Les bots qui sondent une application trouvent cela rapidement, ce qui explique pourquoi ces erreurs apparaissent en volume dans le suivi des erreurs.
Pourquoi inverser l’ordre corrige le problème
Appelezpermit en premier, et la forme est vérifiée avant même qu’on extraie quoi que ce soit :
permit évalue post par rapport à une forme déclarée : « un hash qui peut contenir title et body ». Une chaîne de caractères ne correspond pas, elle est donc écartée plutôt que transmise, et require déclenche alors ActionController::ParameterMissing, que Rails traite déjà comme un 400.
Ce que fait expect
params.expect intègre cet ordre directement :
ParameterMissing plutôt que NoMethodError, sans que vous ayez à vous souvenir dans quel ordre écrire les deux méthodes.
Déclarer des tableaux
Le second problème que corrigeexpect ne peut pas s’exprimer du tout avec permit.
Prenons des catégories imbriquées. Avec permit, envoyer post[categories][name]=foo produit un unique hash, alors que votre code attend un tableau de ceux-ci. Les paramètres sont autorisés, les types sont incorrects, et l’échec n’apparaît que plus tard, ailleurs.
expect vous permet d’exiger un tableau, à l’aide d’une seconde paire de crochets :
Migrer
expect est opt-in, et require et permit ne sont pas dépréciés. Presque toutes les applications Rails existantes les utilisent, elles resteront donc en usage encore longtemps.
- Sur Rails 8 : faites passer vos contrôleurs à
expect. Rails a lui-même migré ses propres contrôleurs internes, y compris Action Mailbox, dans le même changement. La modification est mécanique : remplacezrequireetpermitparexpect, et ajoutez une seconde paire de crochets autour de tout ce qui doit être un tableau. - Pas encore sur Rails 8 : écrivez
permitavantrequire. Vous obtenez le bénéfice de l’ordre sans passer par la mise à niveau.
Ce que cela change dans votre suivi des erreurs
C’est l’un des rares refactorings dont l’effet est directement visible sur votre liste d’incidents.NoMethodError: undefined method 'permit' for an instance of String est du bruit. Cela provient de qui que ce soit qui sonde votre application, pas de vos utilisateurs. Cela ne fournit plus aucune information après la première occurrence, et cela entre en concurrence pour l’attention avec de vraies régressions. Passer à expect transforme ces erreurs en ParameterMissing, à laquelle Rails répond par un 400 sans que cela remonte dans votre application.
Deux choses valent la peine d’être faites en parallèle de ce changement :
- Observez l’incident dans le suivi des erreurs pour confirmer qu’il s’arrête après le déploiement. Ajoutez un marqueur de déploiement pour que la comparaison avant/après soit visible sur le graphique. Les paramètres de requête de chaque incident montrent ce qui a été soumis, ce qui permet de distinguer une sonde d’un formulaire envoyé par votre propre application.
- S’il subsiste du volume, ignorez l’erreur plutôt que de la laisser traîner dans votre liste. Ignorez-la parce que vous avez établi qu’il s’agit de bruit côté client, pas parce qu’elle est fréquente.
Transcription
Transcrit depuis la vidéo et légèrement édité : les sous-titres automatiques ont mal compris plusieurs noms de produits et d’API, et ceux-ci ont été corrigés.Lire la transcription
Lire la transcription
0:02 Salut à tous, dans cet épisode on va parler du tout nouveau
params.expect dans Rails 8, que vous avez peut-être ou pas encore adopté. Vous verrez cela dans vos scaffolds, où vous obtenez params.expect pour l’ID, et aussi params.expect pour le nom de votre modèle, comme post avec title et body. Et cela résout plusieurs problèmes de l’ancienne façon de faire. Regardons donc certains de ces problèmes. L’ancienne façon de faire serait de dire require post, permit, les attributs title et body, d’accord ?0:35 Si on va dans notre application Rails, ici j’ai pris la nouvelle action et j’affiche simplement les paramètres qui passent. Là, on voit que params.expect déclenche une erreur disant qu’il n’y a pas de post. Utilisons maintenant params.require. Cela dira aussi que c’est invalide parce qu’il n’y a pas de post dans les paramètres. Ajoutons donc post, et normalement on fait quelque chose comme ceci, où un attribut comme title est entre crochets à côté de post, pour qu’il sache le placer à l’intérieur de post et en faire un hash.1:10 Cependant, on a souvent des utilisateurs malveillants ou des bots ou autre qui viennent fouiller et modifier les choses. Et si on dit post égale hello, alors que c’est une chaîne au lieu d’un hash, nos paramètres vont exploser avec cette erreur de méthode inexistante, au lieu de vraiment nettoyer ces paramètres comme prévu. C’est donc un problème où on a params.require post. Cela va nous donner, agrandissons un peu ça, cela va nous renvoyer la chaîne hello, et évidemment permit n’est pas une méthode sur cette chaîne.1:42 Cela doit être appliqué à un objet de paramètres. La façon de corriger cela, c’est en fait d’inverser l’ordre. On peut faire ça en disant params.permit post avec title et body. Et on lui dit aussi de faire ce require sur post. Pour essayer ça, on va voir si ce require échoue avec le bon message d’erreur, l’erreur de paramètre manquant.2:13 Et c’est parce que quand params.permit regarde cela, il va évaluer post et voir que, ah, cela doit être un hash où title est optionnel à l’intérieur. Et c’est une chaîne, pas un hash. On ne devrait donc pas autoriser ce paramètre. Et voilà, on obtient un élément vide ici, ce qui nous permet ensuite d’exiger post. Mais tous nos scaffolds et contrôleurs ont fait l’inverse depuis que les strong parameters existent : ils ont fait le require en premier.2:47 Et c’est l’un des problèmes que params.expect résout pour nous. Si on exécute ça, on obtient automatiquement cette méthode de paramètre manquant, ou paramètre post manquant avec l’erreur de paramètre manquant, exactement comme on le voit ici par défaut. Mais on n’a pas besoin de spécifier ce require en double. Il va simplement gérer toute cette partie pour nous, ce qui est très bien. Cela simplifie donc un peu les choses et les rend un peu plus sûres, si bien que les personnes qui manipulent les paramètres reçoivent les bonnes erreurs pour des requêtes invalides.3:29 Cela va donc nettoyer les choses un peu plus proprement. Mais que se passe-t-il quand on a quelque chose comme un élément imbriqué, par exemple categories, et categories pourrait avoir un name dedans ? Si on fait ça et qu’on dit post avec, disons, title et post categories comme foo, cela va constater que categories n’était pas un hash, mais juste une chaîne. Ce n’est donc pas autorisé dans ces paramètres.4:01 Mais si on disait categories name foo, cela va assigner categories à ces paramètres autorisés imbriqués. Mais c’est incorrect, parce qu’on veut un tableau plutôt qu’un élément unique. Et donc nos paramètres restent quelque chose qu’on peut manipuler un peu ici, parce qu’il faut ces crochets vides à l’intérieur pour indiquer à Rack et à Action Dispatch que cela doit être analysé comme un tableau plutôt que comme un objet, comme un hash. C’est donc quelque chose qui n’est pas vraiment possible avec params.permit, mais qu’on peut faire ici. Et la façon de le faire, c’est de dire categories et d’utiliser des doubles crochets, si bien que le premier crochet dit, voilà, ceci va être un tableau, puis les attributs autorisés à l’intérieur de ce tableau pour chacun des objets.4:59 Si on rafraîchit maintenant, on obtient la bonne chose, avec des crochets pour la catégorie. On obtient donc un tableau. Si on essayait de manipuler ça et d’assigner categories à un hash à la place, cela dira que ce n’est pas autorisé, et gérera cela complètement. Et vous verrez que si on réactive l’ancienne façon de faire, categories sera toujours défini, mais avec un seul élément au lieu d’un tableau, ce qui n’est pas ce qu’on veut. Ce n’est pas correctement validé ni nettoyé ici. params.expect est donc une belle amélioration pour rendre tout ça plus cohérent et moins manipulable.5:41 Il faut donc le faire correctement pour obtenir ces types. Cela doit être soit un hash, soit un tableau, soit une valeur individuelle, et on peut lui indiquer cela, et cela va l’imposer comme prévu. Une chose que je veux souligner avant de terminer : params.expect est opt-in, les contrôleurs de vos applications Rails existantes n’ont pas à l’utiliser. permit et require vont probablement rester en usage encore longtemps, parce que la quasi-totalité des applications Rails les utilisent actuellement, et ce serait un très grand changement d’obliger tout le monde à passer à expect. Et bien que permit et require continuent de fonctionner, params.expect est vraiment la façon recommandée de faire, pour garder vos applications Rails un peu plus sûres. Donc si vous pouvez passer à Rails 8, mettez définitivement vos contrôleurs à jour vers la nouvelle syntaxe, et si vous ne pouvez pas encore passer à Rails 8, utilisez permit puis require dans cet ordre, pour bénéficier un peu plus de cet avantage d’ordre d’exécution pour autoriser ces paramètres.6:46 Donc quand des personnes malveillantes font ce genre de post égale name ou foo ou autre, et soumettent une chaîne au lieu d’un hash ou d’un tableau, vous serez protégé par cela, et vous aurez moins d’erreurs dans votre suivi des erreurs, et cela sera géré automatiquement pour ces situations, ce qui est très bien. Je recommande donc vivement de faire au moins cela, mais si vous pouvez passer à Rails 8, alors occupez-vous de toutes ces méthodes de paramètres et assurez-vous d’utiliser params.expect partout où vous le pouvez. Si vous voulez voir certains détails internes de tout ça, Martin Emde, qui travaille sur RubyGems et Bundler, a soumis cette pull request à Rails en avril, finalement fusionnée en septembre, et il y a beaucoup de discussions intéressantes sur la façon dont cela devrait fonctionner, avec des allers-retours sur les différents points, parce que c’est une fonctionnalité assez centrale de Rails qu’on modifie là. C’est donc une pull request assez importante, qui met à jour toute la Action Mailbox et d’autres contrôleurs internes de Rails pour qu’ils utilisent aussi params.expect, et tout cela est en fait assez simple.7:59 Il s’agit essentiellement d’ajouter expect, de retirer require et permit, puis d’envelopper les tableaux avec une paire de crochets supplémentaire pour gérer cela. Mais allez voir cette pull request, c’est vraiment une excellente discussion si vous voulez voir ce qui se passe autour d’une fonctionnalité aussi centrale, du côté de l’équipe cœur de Rails. Voilà donc, je pense que params.expect va être une belle amélioration, qui protège juste un peu mieux notre code, et on va en profiter avec moins de ces problèmes de méthode permit indéfinie, chaque fois que quelqu’un vient fouiller là où il ne devrait pas. C’est tout pour cet épisode, j’espère que vous l’avez apprécié, et je vous retrouve dans le prochain. Peace !Tutoriels associés
À propos de ce tutoriel
Ce tutoriel résume un screencast GoRails surparams.expect dans Rails 8, par Chris Oliver. Le screencast est l’œuvre originale. GoRails le publie, ainsi que le reste de la série, sur gorails.com.