Tutoriel Ruby on Rails : apprendre Rails par l'exemple de Michael Hartl
Upcoming SlideShare
Loading in...5
×
 

Tutoriel Ruby on Rails : apprendre Rails par l'exemple de Michael Hartl

on

  • 10,750 views

Mise en page avec pagination du tutoriel Ruby on Rails de Michael Hartl traduit en français

Mise en page avec pagination du tutoriel Ruby on Rails de Michael Hartl traduit en français

Statistics

Views

Total Views
10,750
Views on SlideShare
10,543
Embed Views
207

Actions

Likes
1
Downloads
614
Comments
0

3 Embeds 207

http://rubylive.fr 104
http://news.humancoders.com 102
http://www.slashdocs.com 1

Accessibility

Categories

Upload Details

Uploaded via as Adobe PDF

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

    Tutoriel Ruby on Rails : apprendre Rails par l'exemple de Michael Hartl Tutoriel Ruby on Rails : apprendre Rails par l'exemple de Michael Hartl Document Transcript

    • 1 Tutoriel Ruby on Rails Apprendre Rails par lexemple Michael HartlContenuchapitre 1 De zéro au déploiement...........................................................................................................13 1.1 Introduction ....................................................................................................................................14 1.1.1 Commentaires pour la grande variété de lecteurs ...................................................................15 1.1.2 « Mise à léchelle » (Scaling) de Rails ....................................................................................18 1.1.3 Conventions utilisées dans ce livre .........................................................................................18 1.2 Debout et au boulot ........................................................................................................................20 1.2.1 Les environnements de développement ..................................................................................21 IDEs .............................................................................................................................................21 Éditeurs de texte et ligne de commande.......................................................................................21 Navigateurs internet .....................................................................................................................22 Une note à propos des outils ........................................................................................................22 1.2.2 Ruby, RubyGems, Rails, et Git...............................................................................................23 Installeur Rails (Windows) ..........................................................................................................23 Installation de Git.........................................................................................................................23 Installer Ruby...............................................................................................................................23 Installation de RubyGems ............................................................................................................25 Installer Rails ...............................................................................................................................26 1.2.3 La première application ..........................................................................................................26
    • 2 1.2.4 Bundler ...................................................................................................................................................28 1.2.5 rails server....................................................................................................................30 1.2.6 Modèle-Vue-Contrôleur (MVC) .............................................................................................32 1.3 Contrôle de version avec Git..........................................................................................................33 1.3.1 Installation et réglages ............................................................................................................34 Initialisation des réglages du système ..........................................................................................34 Initialisation des réglages du repository.......................................................................................35 1.3.2 Ajout et mandat de dépôt ........................................................................................................36 1.3.3 Quest-ce que Git peut faire de bien pour vous ? ....................................................................37 1.3.4 GitHub.....................................................................................................................................39 1.3.5 Branch, edit, commit, merge ...................................................................................................41 Branch ..........................................................................................................................................41 Edit ...............................................................................................................................................42 Commit.........................................................................................................................................42 Merge ...........................................................................................................................................43 Push..............................................................................................................................................44 1.4 Déploiement ...................................................................................................................................45 1.4.1 Réglages Heroku .....................................................................................................................46 1.4.2 Déploiement Heroku, première étape .....................................................................................46 1.4.3 Déploiement Heroku, seconde étape ............................................................................47 1.4.4 Commandes Heroku................................................................................................................48 1.5 Conclusion .....................................................................................................................................49 Chapitre 2 Une application Démo............................................................................................................50 2.1 Planifier lapplication .....................................................................................................................50 2.1.1 Modéliser les utilisateurs ........................................................................................................52 2.1.2 Modéliser les micro-messages ................................................................................................52
    • 3 2.2 La ressouce Utilisateur (Users) ......................................................................................................53 2.2.1 Un tour de lutilisateur.............................................................................................................55 2.2.2 MVC en action ........................................................................................................................60 2.2.3 Faiblesse de cette ressource utilisateur ...................................................................................66 2.3 La ressource micro-messages.........................................................................................................66 2.3.1 Un petit tour du micro-message ..............................................................................................66 2.3.2 Appliquer le micro aux micro-messages.................................................................................71 2.3.3 Un utilisateur possède plusieurs micro-messages...................................................................72 2.3.4 Hiérarchie des héritages ..........................................................................................................73 2.3.5 Déployer lapplication Démo ..................................................................................................76 2.4 Conclusion .....................................................................................................................................76Chapitre 3 Pages statiques courantes .......................................................................................................78 3.1 Pages statiques ...............................................................................................................................81 3.1.1 Vraies pages statiques .............................................................................................................81 3.1.2 Les pages statiques avec Rails ................................................................................................84 3.2 Nos premiers tests ..........................................................................................................................89 3.2.1 Outils de test............................................................................................................................90 Autotest ........................................................................................................................................90 3.2.2 TDD : Rouge, Vert, Restructurer ............................................................................................92 Spork ............................................................................................................................................98 Rouge .........................................................................................................................................102 Vert.............................................................................................................................................105 3.3 Pages (un peu) dynamiques..........................................................................................................108 3.3.1 Tester un changement de titre ...............................................................................................108 3.3.2 Réussir les tests de titre.........................................................................................................111 3.3.3 Variables dinstance et Ruby embarqué ................................................................................114
    • 4 3.3.4 Éliminer les duplications avec les layouts............................................................................................117 3.4 Conclusion ...................................................................................................................................119 3.5 Exercises ......................................................................................................................................120 chapitre 4 Rails au goût Ruby ................................................................................................................123 4.1 Motivation ..............................................................................................................................123 4.1.1 Un « helper » pour le titre.............................................................................................123 4.1.2 Feuilles de styles en cascade (CCS — Cascading Style Sheets)................................126 4.2 Chaines de caractères et méthodes ....................................................................................128 4.2.1 Commentaires ................................................................................................................128 4.2.2 Chaines de caractères....................................................................................................129 4.2.3 Objets et passage de message ......................................................................................132 4.2.4 Définition des méthodes...............................................................................................135 4.2.5 Retour à lhelper titre ...............................................................................................136 4.3 Autres structures de données..............................................................................................137 4.3.1 Tableaux (Arrays) et rangs (ranges)............................................................................137 4.3.2 Blocs ................................................................................................................................140 4.3.3 Tables de hachage et symboles ....................................................................................143 4.3.4 CSS Revisité....................................................................................................................146 4.4 Classes Ruby ..........................................................................................................................147 4.4.1 Constructeurs .................................................................................................................147 4.4.2 Héritage de classe..........................................................................................................149 4.4.3 Modifier les classes dorigine .......................................................................................152 4.4.4 Une classe contrôleur....................................................................................................153 4.4.5 La classe utilisateur .......................................................................................................155 4.5 Exercises.................................................................................................................................157 Chapitre 5 Poursuivre la mise en page...................................................................................................159
    • 5 5.1 Ajout de structure..................................................................................................................159 5.1.1 Navigation........................................................................................................................160 5.1.2 CSS personnalisés ..........................................................................................................166 5.1.3 Partiels .............................................................................................................................173 5.2 Liens pour la mise en page ..................................................................................................177 5.2.1 Test dintégration ...........................................................................................................178 5.2.2 Routes Rails....................................................................................................................181 5.2.3 Routes nommées............................................................................................................184 5.3 Inscription de lutilisateur : une première étape ..............................................................186 5.3.1 Contrôleur des utilisateurs (Users ) ............................................................................186 5.3.2 URL de linscription ......................................................................................................189 5.4 Conclusion .............................................................................................................................191 5.5 Exercises.................................................................................................................................192Chapitre 6 Modéliser et afficher les utilisateurs, partie I.......................................................................194 6.1 Modèle Utilisateur.................................................................................................................196 6.1.1 Migrations de la base de données ................................................................................196 6.1.2 Le fichier modèle............................................................................................................200 6.1.3 Créer des objets utilisateur ...........................................................................................203 6.1.4 Recherche dans les objets Utilisateurs (Users) .........................................................206 6.1.5 Actualisation des objets Utilisateurs (Users) .............................................................208 6.2 Validations de lutilisateur...................................................................................................209 6.2.1 Valider la présence.........................................................................................................210 6.2.2 Validation de la longueur (length) ..............................................................................217 6.2.3 Validation de format .....................................................................................................218 6.2.4 Validation dunicité .......................................................................................................222 6.3 Affichage des utilisateurs.....................................................................................................226
    • 6 6.3.1 Débuggage et environnement Rails ................................................................................................227 6.3.2 Modèle User, Vue, Contrôleur .....................................................................................229 6.3.3 Une ressource Users......................................................................................................232 6.4 Conclusion ...................................................................................................................................235 6.5 Exercises ......................................................................................................................................235 chapitre 7 Modéliser et afficher les utilisateurs, partie II ......................................................................236 7.1 Mots de passe peu sécurisés.................................................................................................237 7.1.1 Validations du mot de passe..........................................................................................237 7.1.2 La migration du mot de passe ......................................................................................240 7.1.3 Fonction de rappel dans lActive Record ....................................................................243 7.2 Mots de passe sécurisés........................................................................................................247 7.2.1 Un test de mot de passe sécurisé..................................................................................247 7.2.2 Un peu de théorie sur la sécurisation des mots de passe.........................................249 7.2.3 Implémenter has_password?...................................................................................251 7.2.4 Une méthode dauthentification..................................................................................254 7.3 Meilleures vues dutilisateurs..............................................................................................259 7.3.1 Tester la page daffichage de lutilisateur (avec factories)........................................259 7.3.2 Un nom et un Gravatar .................................................................................................264 7.3.3 Une barre utilisateur latérale .......................................................................................271 7.4 Conclusion..............................................................................................................................274 7.4.1 Dépôt Git .........................................................................................................................274 7.4.2 déploiement Heroku......................................................................................................275 7.5 Exercices.................................................................................................................................275 chapitre 8 Inscription .............................................................................................................................277 8.1 Formulaire dinscription ......................................................................................................277 8.1.1 Utiliser form_for .........................................................................................................279
    • 7 8.1.2 Le formulaire HTML .....................................................................................................282 8.2 Échec de linscription ...........................................................................................................285 8.2.1 Tester léchec ..................................................................................................................286 8.2.2 Un formulaire fonctionnel ...........................................................................................288 8.2.3 Message derreur à linscription ..................................................................................291 8.2.4 Filtrer les paramètres didentification........................................................................296 8.3 Réussite de linscription.......................................................................................................298 8.3.1 Tester la réussite ............................................................................................................299 8.3.2 Le formulaire dinscription final .................................................................................300 8.3.3 Le message « flash »......................................................................................................301 8.3.4 La première inscription ................................................................................................305 8.4 Les tests dintégration RSpec ..............................................................................................306 8.4.1 Tests dintégration sur les styles ..................................................................................307 8.4.2 Un échec dinscription ne devrait pas créer un nouvel utilisateur.........................308 8.4.3 Le succès dune inscription devrait créer un nouvel utilisateur .............................311 8.5 Conclusion .............................................................................................................................313 8.6 Exercises ................................................................................................................................314Chapitre 9 Connexion, déconnexion ......................................................................................................316 9.1 Les sessions ............................................................................................................................316 9.1.1 Contrôleur Sessions........................................................................................................317 9.1.2 Formulaire didentification...........................................................................................319 9.2 Échec de lidentification.......................................................................................................322 9.2.1 Examen de la soumission du formulaire ....................................................................323 9.2.2 Échec de lidentification (test et code)........................................................................325 9.3 Réussite de lidentification ..................................................................................................328 9.3.1 Laction create achevée..............................................................................................329
    • 8 9.3.2 Se souvenir de moi............................................................................................................................331 9.3.3 Utilisateur courant ........................................................................................................335 9.4 Déconnexion..........................................................................................................................344 9.4.1 Détruire les sessions ......................................................................................................345 9.4.2 Connexion à linscription .............................................................................................347 9.4.3 Changement des liens de la mise en page ..................................................................348 9.4.4 Test dintégration pour lidentification et la déconnexion.......................................353 9.5 Conclusion .............................................................................................................................354 9.6 Exercises.................................................................................................................................354 Chapitre 10Actualiser, afficher et supprimer des utilisateurs ................................................................356 10.1 Actualisation ........................................................................................................................356 10.1.1 Formulaire dédition ....................................................................................................356 10.1.2 Permettre lédition .......................................................................................................363 10.2 Protection des pages...........................................................................................................366 10.2.1 Lidentification requise de lutilisateur .....................................................................367 10.2.2 Nécessité du bon utilisateur.......................................................................................370 10.2.3 Redirection conviviale.................................................................................................373 10.3 Affichage des utilisateurs...................................................................................................376 10.3.1 Liste des utilisateurs ....................................................................................................376 10.3.2 Utilisateurs fictifs ........................................................................................................381 10.3.3 Pagination.....................................................................................................................383 10.3.4 Restructuration des partiels.......................................................................................389 10.4 Destruction des utilisateurs...............................................................................................391 10.4.1 Utilisateurs administrateurs.......................................................................................391 10.4.2 Laction destroy (détruire) .....................................................................................395 10.5 Conclusion............................................................................................................................400
    • 9 10.6 Exercices...............................................................................................................................401chapitre 11 Micro-messages dutilisateur...............................................................................................403 11.1 Un modèle `Micropost` ......................................................................................................403 11.1.1 Le modèle de base .........................................................................................................403 11.1.2 Association Utilisateur/Micro-message (User/Micropost) ....................................406 11.1.3 Affinements du micro-message ..................................................................................411 11.1.4 Validation du micro-message......................................................................................415 11.2 Affichage des micro-messages ...........................................................................................417 11.2.1 Etoffement de la page de lutilisateur ........................................................................418 11.2.2 Exemples de micro-messages.....................................................................................425 11.3 Manipuler les micro-messages ..........................................................................................428 11.3.1 Contrôle daccès ............................................................................................................429 11.3.2 Création des micro-messages .....................................................................................431 11.3.3 Une proto-alimentation...............................................................................................438 11.3.4 Destruction des micro-messages ...............................................................................445 11.3.5 Test de la nouvelle page daccueil ..............................................................................449 11.4 Conclusion ............................................................................................................................450 11.5 Exercices ...............................................................................................................................451Chapitre 12 Suivi des utilisateurs...........................................................................................................453 12.1 Le modèle de relation..........................................................................................................456 12.1.1 Problème avec le modèle de données (et ses solutions)..........................................456 12.1.2 Association utilisateur/relations................................................................................460 12.1.3 Validations.....................................................................................................................464 12.1.4 Suivi................................................................................................................................465 12.1.5 Les Lecteurs...................................................................................................................471 12.2 Une interface web pour les auteurs et les lecteurs .........................................................473
    • 10 12.2.1 Données de simple lecteur..............................................................................................................474 12.2.2 Statistiques et formulaire de suivi.............................................................................476 12.2.3 Pages des auteurs suivis et des lecteurs....................................................................485 12.2.4 Un bouton de suivi standard ......................................................................................491 12.2.5 Un bouton de suivi fonctionnel avec Ajax ................................................................493 12.3 Létat de lalimentation.......................................................................................................498 12.3.1 Motivation et stratégie.................................................................................................499 12.3.2 Une première implémentation de lalimentation....................................................502 12.3.3 Champs dapplication, sous-sélections et lambda ..................................................504 12.3.4 Le nouvel état de lalimentation ................................................................................509 12.4 Conclusion............................................................................................................................510 12.4.1 Extensions de lApplication Exemple ........................................................................511 12.4.2 Guide vers dautres ressources...................................................................................513 12.5 Exercices...............................................................................................................................514 Notes ......................................................................................................................................................516 Avant-propos Ma précédente compagnie (CD Baby) fut une des premières à basculer intégralement vers Ruby on Rails, et à rebasculer aussi intégralement vers PHP (googlez-moi si vous voulez prendre la mesure du drame). On ma tellement recommandé ce livre Michael Hartl que je nai pu faire autrement que de le lire. Cest ainsi que le Tutoriel Ruby on Rails ma fait revenir à nouveau à Rails. Bien quayant parcouru de nombreux livres sur Rails, cest ce tutoriel-là qui ma véritablement « mis en possession » de Rails. Tout est fait ici « à la manière de Rails » — une manière qui ne mavait jamais semblé naturelle avant que je ne lise ce livre. Cest aussi le seul ouvrage sur Rails qui met en place, dun bout à lautre, un Développement Dirigé par les Tests (Test-Driven Development), une approche que je savais hautement recommandée par les experts mais dont je navais jamais compris aussi bien la pertinence que dans ce livre.
    • 11Enfin, en incluant Git, GitHub et Heroku dans les exemples de la démonstration, lauteur vous donnevraiment le goût de ce quest le développement dun projet dans la vie réelle. Et le exemples de code ne sont pasen reste.La narration linéaire adoptée par ce tutoriel est vraiment un bon format. Personnellement, jai étudié LeTutoriel Rails en trois longues journées, en faisant tous les exemples et les exercices proposés à la fin de chaquechapitre. Cest en lisant ce livre du début à la fin, sans sauter la moindre partie, quon en tire tout le bénéfice.Régalez-vous ! Derek Sivers (sivers.org) Précédemment : Fondateur de CD Baby Actuellement : Fondateur de Thoughts Ltd. RemerciementsCe Tutoriel Ruby on Rails doit beaucoup à mon livre précédent sur Rails, RailsSpace, et donc à mon co-auteurAurelius Prochazka. Jaimerais remercier Aure à la fois pour le travail quil a accompli sur ce précédent livre etpour son soutien pour le présent ouvrage. Jaimerais aussi remercier Debra Williams Cauley, mon éditeur pourles deux ouvrages ; aussi longtemps quelle jouera avec moi au baseball, je continuerai décrire des livres pourelle. Jaimerais remercier une longue liste de Rubyistes qui mont parlé et inspiré au cours des années : David Heinemeier Hansson, Yehuda Katz, Carl Lerche, Jeremy Kemper, Xavier Noria, Ryan Bates, Geoffrey Grosenbach, Peter Cooper, Matt Aimonetti, Gregg Pollack, Wayne E. Seguin, Amy Hoy, Dave Chelimsky, Pat Maddox, Tom Preston-Werner, Chris Wanstrath, Chad Fowler, Josh Susser, Obie Fernandez, Ian McFarland, Steven Bristol, Giles Bowkett, Evan Dorn, Long Nguyen, James Lindenbaum, Adam Wiggins, Tikhon Bernstam, Ron Evans, Wyatt Greene, Miles Forrest, les gens bien de Pivotal Labs, le gang Heroku, les mecs de thoughtbot et léquipe de GitHub. Enfin, tellement, tellement, tellement de lecteurs — beaucoup trop pour les citer tous — qui ont contribué par leur rapport de bogues et leurs suggestions durant lécriture de ce livre, et je tiens à saluer leur aide sans laquelle ce livre ne serait pas ce quil est. À propos de lauteur Michael Hartl est programmeur, éducateur et entrepreneur. Il est le co-auteur de RailsSpace, un tutoriel Rails publié en 2007, et a été co-fondateur et développeur en chef de Insoshi, une plateforme de réseau social populaire en Ruby on Rails. Précédement, il a enseigné la théorie et la physique informatique au California Institute of Technology (Caltech), où il a reçu le Lifetime Achievement Award for Excellence en enseignement. Michael est diplômé du Harvard College, a un Ph.D. en physique (un doctorat. NdT) de Caltech, et il est ancien élève du programme des entrepreneurs Y Combinator. Copyright et licenseLe Tutoriel Ruby on Rails : apprendre Rails par lexemple. Copyright © 2010 par Michael Hartl. Tout le codesource du Tutoriel Ruby on Rails est disponible sous la license MIT License et la licence Beerware License. Copyright (c) 2010 Michael Hartl
    • 12 Permission est accordée, à titre gratuit, à toute personne obtenant une copie de ce logiciel et la documentation associée, pour faire des modification dans le logiciel sans restriction et sans limitation des droits d’utiliser, copier, modifier, fusionner, publier, distribuer, concéder sous licence, et / ou de vendre les copies du Logiciel, et à autoriser les personnes auxquelles le Logiciel est meublé de le faire, sous réserve des conditions suivantes: L’avis de copyright ci-dessus et cette autorisation doit être inclus dans toutes les copies ou parties substantielles du Logiciel. LE LOGICIEL EST FOURNI «TEL QUEL», SANS GARANTIE D’AUCUNE SORTE, EXPLICITE OU IMPLICITE, Y COMPRIS, MAIS SANS S’Y LIMITER, LES GARANTIES DE QUALITÉ MARCHANDE, ADAPTATION À UN USAGE PARTICULIER ET D’ABSENCE DE CONTREFAÇON. EN AUCUN CAS LES AUTEURS OU TITULAIRES DU ETRE TENU RESPONSABLE DE TOUT DOMMAGE, RÉCLAMATION OU AUTRES RESPONSABILITÉ, SOIT DANS UNE ACTION DE CONTRAT, UN TORT OU AUTRE, PROVENANT DE, DE OU EN RELATION AVEC LE LOGICIEL OU L’UTILISATION OU DE TRANSACTIONS AUTRES LE LOGICIEL. /* * ------------------------------------------------------------ * "LA LICENCE BEERWARE" (Révision 42) : * Michael Hartl a écrit ce code. Aussi longtemps que vous * conservez cette note, vous pouvez faire ce que vous voulez * de ce travail. Si nous nous rencontrons un jour, et que vous * pensez que ce travail en vaut la peine, vous pourrez me * payer une bière en retour. * ------------------------------------------------------------ */
    • 13chapitre 1 De zéro au déploiementBienvenue dans Le Tutoriel Ruby on Rails : Apprendre Rails par lexemple. Le but de ce livre est dêtre lameilleure réponse à la question : « Si je veux apprendre le développement web avec Ruby on Rails, par où dois-je commencer ? » Quand vous aurez achevé Le Turoriel Ruby on Rails, vous aurez toutes les connaissancesnécessaires pour développer et déployer votre propre application web. Vous serez également en mesure de tirerprofit de livre plus avancés, des blogs et des screencasts qui peuplent le écosystème Rails, prospère. Enfin,puisque le Tutoriel Ruby on Rails utilise Rails 3.0, la connaissance que vous acquiererez sera pleinementdactualité avec la dernière et meilleure version de Rails.1Le Tutoriel Ruby on Rails suit essentiellement la même approche que mon livre précédent,2 enseignant ledéveloppement web avec Rails en construisant un exemple dapplication conséquente, depuis le tout départ.Comme Derek Sivers le souligne dans lavant-propos, ce livre est structuré en narration linéaire, destinée enconséquence à être lue du début à la fin. Si vous avez lhabitude de survoler les livres techniques, suivre cetteapproche linéaire peut vous demander quelques ajustements, mais je ne saurais trop vous recommanderdessayer. Vous pouvez penser au Tutoriel Ruby on Rails comme un jeu vidéo dont vous êtes le héros, et oùvous augmentez votre niveau de développeur Rails à chaque chapitre (les exercices sont comme des mini-boss).Dans le premier chapitre, nous commencerons avec Ruby on Rails en installant tous les softwares nécessaires eten préparant votre environnement de développement (section 1.2). Nous créerons alors notre premièreapplication Rails, appelée (de façon assez pertinente ma foi) first_app (première_application). LeTutoriel Rails met laccent sur les bonnes pratiques de développement, aussi, immédiatement après avoir créénotre tout nouveau projet Rails nous le déposerons sous un contrôle de version avec Git (section 1.3). Et,croyez-le ou non, dans ce chapitre nous déploierons même notre première application en mode « production »(Section 1.4).Au chapitre 2, nous créerons un second projet, dont le but sera dillustrer les fonctions élémentaires duneapplication Rails. De façon très rapide, nous construirons cette application démo (qui sappellerademo_app) en utilisant le système déchaffaudage (« scaffolding ») (Box 1.1) pour générerautomatiquement le code ; comme ce code est aussi laid que complexe, le chapitre 2 se concentrera plutôt surlinteraction avec lapplication démo par le biais des URLs3 en utilisant un navigateur internet.Au chapitre 3, nous créerons une application exemple (que nous appellerons sample_app), en écrivantcette fois tout le code à partir de (presque) rien. Nous développerons cette application exemple en utilisant leTest Dirigé par le Développement (« test-driven development », TDD), amorcé au chapitre 3 en créant despages statiques puis en ajoutant un peu de contenu dynamique. Nous ferons un rapide détour au chapitre 4pour en apprendre un peu plus sur le langage Ruby sous-jacent à Rails. Puis, du chapitre 5 au chapitre 10, nouscomplèterons les fondations de lapplication exemple en fabricant un layout (une mise en page) pour le site, un
    • 14 modèle de données utilisateur (« user data model ») et un système complet et fonctionnel denregistrement et dauthentification. Enfin, aux chapitre 11 et chapitre 12 nous incorporerons des outils de micro-blogging (un système de mini-messages) et de réseau social pour faire un exemple de site tout à fait fonctionnel. Lapplication exemple finale aura plus dune ressemblance avec une certaine application sociale de micro- blogging — un site qui, est-ce un hasard, est également écrit en Rails. Bien que nos efforts se concentrent nécessairement sur cette application exemple spécifique, laccent dans ce Tutoriel Rails sera mis sur des principes généraux, donc vous acquièrerez des notions fondamentales et solides qui vous permettrons de développer vos propres applications web, quels que soient leur genres. Box 1.1.Échaffaudage (Scaffolding) : Plus rapide, plus facile, plus séduisant Depuis le départ, Rails a bénéficié dun engouement évident, notamment gràce au fameux Un Weblog vidéo en 15 minutes par le créateur de Rails David Heinemeier Hansson, actualisé à présent dans le Un Weblog en 15 minutes en utilisant Rails 2 par Ryan Bates. Ces vidéos sont une excellent façon de goûter à la puissance de Rails, et je recommande vivement de les regarder. Mais soyez averti quand même : ils accomplissent leur incroyable coup dadresse de quinze minutes en utilisant loutil appelé scaffolding (Échaffaudage), bâti essentiellement sur un code automatiquement généré, créé comme par magie par la commande Rails generate (Générer). En écrivant un tutoriel sur Ruby on rails, il est tentant de sappuyer sur lapproche par échaffaudage — elle est plus rapide, plus facile, et plus séduisante. Mais la complexité et lénorme quantité de code dun échaffaudage peut être rédhibitoire pour un développeur Rails débutant ; vous pouvez peut-être lutiliser, mais vous serez incapable de le comprendre. Suivre lapproche par échaffaudage risque de vous tourner vers un script générateur de code virtuose au détriment dune véritable connaissance de Rails. Dans le Tutoriel Ruby on Rails, nous adopterons lapproche presque exactement opposée : bien que le chapitre 2 développera une petite application démo en utilisant le système déchaffaudage, le cœur de ce tutoriel est lApplication Exemple, que nous commencerons à concevoir au chapitre 3. À chaque étape du développement de cette application exemple, nous générerons le code bouchée après bouchée, petite pièce de code par petite pièce de code — assez simple pour être compris, mais assez nouveau pour représenter une difficulté à surmonter. Leffet de cette approche par couches résultera en une approche profonde, et une connaissance flexible de Rails, vous fournissant de bonnes connaissances fondamentales pour concevoir pratiquement nimporte quel type dapplication web. 1.1 Introduction Depuis ses début en 2004, Ruby on Rails est rapidement devenu un des frameworks les plus populaires et les plus puissants pour construire des applications web dynamiques. Les utilisations de Rails couvrent un large
    • 15éventail dapplications, depuis la start-up bagarreuse jusquaux énormes compagnies : Posterous,UserVoice, 37signals, Shopify, Scribd, Twitter, Hulu, the Yellow Pages (les Pages Jaunes) — la liste des sitesutilisant Rails sallongent de jour en jour. Il y a aussi de nombreux producteurs de développements web qui sespécialisent en Rails, tels que ENTP, thoughtbot, Pivotal Labs ou Hashrocket, sans compter les innombrablesconsultants indépendants, entraineurs et entrepreneurs.Quest-ce qui rend Rails si fantastique ? Tout dabord, Ruby on Rails est 100% en « open-source », accessiblesous la licence MIT License, e qui signifie quil ne coûte rien de le télécharger et de lutiliser. Rails doitégalement le plus gros de son succès à son design compact et élégant ; en exploitant la maléabilité de la sous-couche en langage Ruby, Rails crée de façon effective un langage de domaine spécifique pour écrire desapplications web. Il en résulte que de nombreuse tâches courantes de programmation web — telles que générerdu code HTML, faire des modèles de données ou rediriger les URLs — deviennent faciles avec Rails, et le codede lapplication produit est concis et lisible.Rails sadapte également avec rapidité aux développements les plus récents de la technologie internet et à laconception par framework. Rails a par exemple été lun des premiers frameworks à digérer et implémenter entotalité larchitecture de style REST pour structurer les applications web (ce que nous apprendrons à faire aucours de ce tutoriel). Et quand dautres framewords développent avec succès de nouvelles technologies, lecréateur de Rails, David Heinemeier Hansson ainsi que le cœur de léquipe Rails nhésite jamais à incorporer àRails leurs nouvelles idées. Peut-être lexemple le plus parlant est la fusion de Rails et de Merb, un frameworkconcurrent, de telle sorte que Rails bénéficie maintenant de la conception modulaire de Merb, API stable et auxperformances améliorées. Quiconque a assisté à la conférence donnée par le développeur de Merb et le noyaudur de léquipe Yehuda Katz na pu faire autrement que de noter à quel point ce fut une extrêmement bonneidée de monter léquipe de Merb à bord.Enfin, Rails bénéficie dune communauté exceptionnellement enthousiaste et diverse. Les résultats en sont descentaines de contributeurs open-source, des conférences largement suivies, un nombre considérable de pluginset des gems (solutions clé-en-main pour des problèmes spécifiques comme la pagination ou le téléchargementdimages), une riche variété de blogs dinformation, et une corne dabandonce débordant de forums dediscussions et de canaux de discussion IRC. Le grand nombre de développeurs Rails rend également plus facilela résolution des erreurs inévitables de toute lapplication : lalgorithme « Googlelisez votre message derreur »renvoie pratiquement toujours vers le message pertinent dun blog ou un sujet de discussion dans un forum.1.1.1 Commentaires pour la grande variété de lecteursLe Tutoriel Rails contient des « tutoriels intégrés » non seulement pour Rails, mais aussi pour le langage sous-jacent Ruby, aussi bien que pour HTML, CSS, un peu de JavaScript, et même un peu de SQL. Cela signifie que,quelles que soient vos connaissances en matière de développement web, en finissant ce tutoriel vous serez en
    • 16 mesure dutiliser des ressources Rails plus avancées, et prêts de la même manière à approfondir de façon plus systématique tous les autres sujets abordés. Rails titre une grande partie de son pouvoir de la « magic » — il en va ainsi des capacités du framework (telles que déduire automatiquement les attributs dun objet à partir du nom des colonnes de la base de données) qui accomplissent des miracles mais dont les mécanismes peuvent rester plutôt obscures. Le Tutorial Ruby on Rails nest pas destiné à expliquer cette magie — principalement parce que la plupart des développeurs dapplications Rails nont jamais besoin de savoir ce qui se passe en coulisses (après tout, le langage Ruby lui- même est écrit principalement en langage de programmation C, mais vous navez pas besoin de creuser dans le code source en C pour utiliser Ruby). Si vous êtes le genre de personne qui aime bien tirer les choses de derrière le rideau, je vous recommande la lecture de The Rails 3 Way par Obie Fernandez qui accompagnera avec profit ce Tutoriel Ruby on Rails. Bien que ce livre ne nécessite pas de requis spéciaux, vous devez bien entendu posséder au moins quelque expérience des ordinateurs. Si vous navez même jamais utilisé déditeur de texte, ce ne sera pas facile pour vous davancer, mais avec un peu de détermination vous devriez pouvoir passer à travers. Si, dun autre côté, votre fichier .emacs est tellement complexe quil pourrait faire pleurer un adulte, il vous restera toujours ici quelque défi à relever. Le Tutoriel Rails est destiné à enseigner le développement Rails quels que soient ses acquis propres, mais lexpérience que vous ferez de cette lectutre dépendra de vos aptitudes particulières. À tous les lecteurs : une question classique en étudiant Rails est de savoir sil faut étudier Ruby au préalable. La réponse dépend de la façon dont vous apprenez. Si vous avez une prédilection pour apprendre tout depuis la base, de façon systématique, alors apprendre le langage Ruby peut vous être utile ; plusieurs livres sont recommandés dans cette section pour vous faire débuter. Dun autre côté, de nombreux développeurs Rails débutants sont excités à lidée de créer des applications web, et préfèreront ne pas se coltiner un livre de 500 pages en pur Ruby avant décrire la moindre page sur une simple page web. Plus encore, le sous-ensemble de Ruby nécessaire aux développeurs Rails est différent de ce que vous pourriez trouver dans une introduction sur du pur Ruby, tandis que le Tutoriel Rails se concentre exclusivement sur ce sous-ensemble. Si votre intérêt premier est de construire des applications Rails, je vous recommande de commencer par ce Tutoriel Rails et de lire des livres de pur Ruby ensuite. Ce nest pas une proposition « tout ou rien », pour autant : si vous commencez à lire ce Tutoriel Rails et sentez votre manque de connaissance du Ruby vous ralentir, soyez libre de basculer vers un livre sur le langage Ruby avant de revenir au tutoriel lorsque vous vous sentirez plus à laise. Vous pouvez aussi vous contenter dun goût de ce quest Ruby en suivant un court tutoriel en ligne, tels que ceux quon peut trouver sur les sites http://www.ruby-lang.org/ ou http://rubylearning.com/. Une autre question classique concerne lutilisation des tests dès le départ. Comme cela est indiqué dans lintroduction, ce Tutoriel Rails fait appel au « Test Dirigé par le Développement » (test-driven development, appelé aussi test-first development), qui selon moi est la meilleure façon de développer des applications Rails, mais le fait est que cette approche introduit une quantité substantielle de difficulté et de complexité. Si vous sentez que vous vous enlisez à cause des tests, sentez-vous libre de les sauter dans une première lecture.4 Vraiment, certains lecteurs se sentent dans un premier temps écrasés par linclusion de nombreux concepts et de pratiques nouvelles — telles que les tests, les contrôles de versions, le déploiement. Aussi, si vous vous rendez compte que vous dépensez une énergie excessive sur toutes ces étapes, nhésitez pas à les passer. Bien que jai inclus seulement le matériel que je considère essentiel au développement dun
    • 17 niveau professionnel dune application Rails, seul le cœur du code de lapplication est strictement nécessaire dans une première lecture.Aux programmeurs non expérimentés (non-concepteurs) : ce Tutoriel Rails nexige aucun autrebackground quune connaissance générale de lordinateur, donc si vous avez une expérience limitée de laprogrammation ce livre est une bonne façon de commencer. Gardez à lesprit que cest seulement la premièreétape dun long voyage ; le développement web possède de nombreux aspects différents, parmi lesquels leHTML/CSS, JavaScript, les bases de données (et SQL), les contrôles de version et le déploiement. Ce livrecontient de courtes introductions à ces sujets, mais il y a bien plus encore à connaitre.Aux programmeurs non expérimentés (concepteurs) : vos aptitudes en matière de conception webvous donnent une longueur davance, puisque vous connaissez certainement déjà le HTML et les CSS. Aprèsavoir fini ce livre, vous serez dans une excellente position pour travailler sur des projets Rails existants etpourrez probablement en amorcer un de votre propre chef. Vous pouvez trouver difficile tout ce qui concerne laprogrammation, mais le langage Ruby est exceptionnellement accessible aux débutants, tout spécialement ceuxqui possèdent une fibre artistique.Après avoir achevé la lecture du Tutoriel Ruby on Rails, je recommande aux nouveaux programmeurs la lecturede Beginning Ruby de Peter Cooper, qui partage la même philosophie pédagogique que ce Tutoriel Rails. Jerecommande aussi The Ruby Way de Hal Fulton. Enfin, pour acquérir une compréhension plus profonde deRails je recommande The Rails 3 Way de Obie Fernandez. Les applications Web, même les relativement simples, sont de par nature assez complexes. Si vous êtes tout à fait nouveau dans la programmation web et trouvez trop complexe la lecture de ce Tutoriel Rails, cest peut-être que vous nêtes pas tout à fait prêts encore à développer des applications web. Dans ce cas, je vous suggère dapprendre les bases du HTML et des CSS et de recommencer ensuite seulement la lecture de ce tutoriel (malheureusement, je nai pas de recommandations particulières ici, mais Head First HTML semble prometteur, et un des lecteurs recommande CSS: The Missing Manual de David Sawyer McFarland). Vous pouvez aussi lire avec profit les premiers chapitres de Beginning Ruby, qui débute avec lexemple dune application plus petite quune application web au sens fort du terme.Aux programmeurs expérimentés découvrant le développement web : votre expérience signifie quevous comprenez probablement déjà les concepts de classes, de méthodes, de structures de données, etc., ce quiconstitue un sérieux avantage. Soyez tout de même avertis, dans le cas où votre background est le C/C++ ou leJava, que vous pourriez trouver que le langage Ruby ressemble un peu à un canard boiteux, et sa prise en mainpourrait vous prendre du temps ; familiarisez-vous juste avec lui, et tout devrait aller pour le mieux (Ruby vousautorise même à mettre des points-virgule à la fin des lignes sils vous manquent trop !). Ce Tutoriel Railscouvre toutes les idées spécifiques au développement web dont vous avez besoin, donc ne vous inquiétez pas sivous ne distinguez pas pour le moment une requête PUT dune requête POST.Aux développeurs web expérimentés découvrant Rails : vous avez une sérieuse longueur davance, toutspécialement si vous avez utilisé un langage dynamique tel que le PHP ou (encore mieux) le langage Python. Lesbases de ce que nous couvrons vous seront pour le moins familières, mais le développement par le test(Développement Dirigé par le Test, ou TDD) sera peut-être nouveau pour vous, comme peut lêtre aussi
    • 18 larchitecture REST préconisé par Rails. Ruby possède également sa propre idiosyncrasie, peut-être sera-t-elle aussi nouvelle pour vous. Aux programmeurs Ruby expérimentés : lensemble des programmeurs Ruby ne connaissant pas Rails est très restreint de nos jours, mais si vous faites partie de ce groupe délite, vous pouvez survoler ce livre et vous diriger ensuite sur le livre The Rails 3 Way de Obie Fernandez. Aux programmeurs Rails inexpérimentés : vous avez peut-être lu dautres tutoriels et créé quelques petites applications Rails. En me basant sur le retour des lecteurs, je suis confiant sur le fait que vous pourrez tirer beaucoup de ce livre. Parmi dautres choses, les techniques employées ici sont peut-être plus dactualité que celles que vous avez abordés quand vous avez appris Rails. Aux programmeurs Rails expérimentés : ce livre ne vous sera pas utile, et pourtant, de nombreux développeurs Rails expérimentés mont exprimé leur surprise quant à la quantité de choses quils avaient tirés de ce livre, et vous pourriez être ravis daborder Rails sous un autre jour, dans une autre perspective. Après avoir terminé ce Tutoriel Ruby on Rails, je recommande aux programmeurs (non Ruby) expérimentés la lecture de The Well-Grounded Rubyist de David A. Black, qui est une excellente et profonde discussion sur Ruby depuis la base, ou The Ruby Way de Hal Fulton, qui est tout aussi avancé mais adopte une approche plus actuelle. Passez ensuite à The Rails 3 Way pour approfondir encore votre expertise de Rails. À la fin de ce processus, quel que soit doù vous partiez, vous serez en mesure dappréhender les ressources Rails plus avancées. Voilà celle que je recommande particulièrement : • Railscasts : dexcellents screencats gratuits sur Rails ; • PeepCode, Pragmatic.tv, EnvyCasts : dexcellent screencasters payants ; • Rails Guides : bonnes références sur Rails, dactualité et actualisées. Ce Tutoriel Rails fait fréquemment référence au Rails Guides pour un traitement plus en profondeur de certains sujets ; • Rails blogs : trop de choses à énumérer, et quelles choses ! 1.1.2 « Mise à léchelle » (Scaling) de Rails Avant de poursuivre avec la suite de lintroduction, jaimerais marrêter un moment sur un problème qui collait au framework Rails à ses tout débuts : sa supposée inaptitude à se « mettre à léchelle » — pour, par exemple, absorber un trafic important. Une partie de cette question repose sur une idée tout à fait fausse ; vous mettez à léchelle un site, pas un framework, et Rails, aussi intelligent soit-il, nest quun framework. Donc la vraie question aurait dû être « Est-ce quun site construit en Rails peut se mettre à léchelle ? ». Dans ce cas, la réponse est résolument « oui » : quelques uns des sites les plus lourds en trafic dans le monde utilisent Rails. En fait, rendre la mise à léchelle possible va bien au-delà du champ dapplication de Rails, mais soyez assurés que si votre application connait un trafic similaire à celui de Hulu ou des « Pages jaunes », Rails ne sera pas un handicap pour vous étendre au travers du monde. 1.1.3 Conventions utilisées dans ce livre Les conventions de ce livre sexpliquent la plupart du temps delles-mêmes ; dans cette section, je mentionne celles qui ne le pourraient pas.
    • 19Les éditions HTML et PDF de ce livre sont riches en liens, vers des sections internes (telles que Section 1.2)aussi bien que des sites externes (tels que la page principale Télécharger Ruby on Rails).5De nombreux exemples de ce livre utilisent la « ligne de commande ». Pour la simplicité, tous les exemples enligne de commande utilisent linvite classique Unix (un dollar), comme suit : $ echo "bonjour le monde" bonjour le mondeLes utilisateurs Windows doivent savoir que leur système utilisera linvite analogue « > » : C:Sites>echo Salut le monde Salut le mondeSur les systèmes dexploitation Unix, certaines commandes devront être exécutées avec sudo, qui signifie« substitute user do » (Lutilisateur de substition fait…). Par défaut, une commande exécutée avec sudo estjouée comme par un utilisateur-administrateur, qui a accès à des fichiers et des dossiers que les utilisateursnormaux ne peuvent pas toucher. Pour indiquer que lutilisation de sudo dépendra de votre système, elle seprésentera entre crochets quand elle peut être (ou ne pas être) nécessaire sur votre propre système, commedans lexemple de la section 1.2.2: $ [sudo] ruby setup.rbLa plupart des systèmes dexploitation Unix/Linux/Os X requièrent la commande sudo par défaut, sauf si vousutilisez le Ruby Version Manager comme cela est suggéré dans la section 1.2.2.3. Notez que vous ne devez pasutiliser les crochets ; vous devez utiliser soit… $ sudo ruby setup.rbsoit… $ ruby setup.rb… en fonction de votre système dexploitation.Rails est fourni avec un grand nombre de commandes qui peuvent être jouées en ligne de commande. Parexemple, dans la section 1.2.5 nous lancerons un serveur web local en mode développement comme suit : $ rails server
    • 20 Comme pour linvite de la commande en ligne, ce Tutoriel Rails utilise la convention Unix pour les séparateurs de dossiers (cest-à-dire une balance /). Ma propre Application exemple développée dans ce tutoriel, par exemple, se trouve dans : /Users/mhartl/rails_projects/sample_app Sur Windows, ladresse du dossier analogue devrait être : C:Sitessample_app Le dossier racine pour toute application donnée est connue comme la racine Rails (Rails root), et désormais tous les chemins daccès seront relatifs à ce dossier (notez bien que la « racine Rails » nest pas le dossier racine du framework Rails lui-même). Par exemple, le dossier config de mon application exemple est : /Users/mhartl/rails_projects/sample_app/config La racine Rails est ici ce qui se trouve avant config, cest-à-dire : /Users/mhartl/rails_projects/sample_app Par souci de brièveté, quand nous ferons référence au fichier : /Users/mhartl/rails_projects/sample_app/config/routes.rb … nous omettrons la racine Rails et écrirons simplement config/routes.rb. Pour finir, ce Tutoriel Rails montre souvent les sorties (les résultats) de programmes variés (commandes shell, état de contrôle de version, programmes Ruby, etc.). À cause des innombrables petites différences entre les différents systèmes dexploitation, la sortie que vous obtiendrez ne coïncidera pas toujours avec ce qui sera montré dans le texte, mais il ny a aucune raison de sen pré-occuper. De surcroît, certaines commandes pourrons produire des erreurs en fonction de votre système ; plutôt que dessayer la tâche Sisyphéenne de documenter tous ces types derreurs dans ce tutoriel, je délèguerai cette tâche à lalgorithme « Google le message derreur », qui est, parmi dautres choses, une bonne pratique dans le développement grandeur nature de logiciel. 1.2 Debout et au boulot Le temps est venu daborder lenvironnement de développement Ruby on Rails et notre première application. Il y a quelques casse-têtes ici, spécialement si vous navez pas une expérience étendue de la programmation, donc ne vous découragez pas si cela vous demande un moment avant de pouvoir réellement démarrer. Ça ne vous
    • 21concerne pas en particulier ; tout développeur doit en passer par là (et souvent plus dune fois), mais soyezassuré que les efforts seront richement récompensés.1.2.1 Les environnements de développementEn prenant en compte la grande variété des personnalisations possibles, il existe au moins autantdenvironnements de développement que de programmeurs Rails, mais il se dégage tout de même deuxméthodes principales : les environnements à base déditeur de texte/ligne de commande, et les environnementsde développement intégrés (IDEs). Considérons dabord le second.IDEsLes IDEs Rails ne manquent pas, incluant RadRails, RubyMine, et 3rd Rail. Tous sont « cross-platform »(utilisables sur nimporte quel système. NdT), et jai entendu dire du bien de quelques uns dentre eux. Je vousencourage à les essayer sils fonctionnent pour vous, mais jai une confession à vous faire : je nai jamais trouvéun IDE qui me permette de satisfaire tous mes besoins en matière de développement Rails — et pour certainsprojets je nai pas même été capable de les faire fonctionner.Éditeurs de texte et ligne de commandeQue devons-nous utiliser alors pour remplacer ces IDE tout-en-un ? Je parierai que la majorité desdéveloppeurs Rails ont opté pour la même solution que moi : utiliser un éditeur de texte pour éditer le texte, etune ligne de commande pour exécuter des commandes (Illustration 1.1). La combinaison que vous utiliserezdépendra de vos goûts et de votre plate-forme : • Macintosh OS X : comme beaucoup de développeurs Rails, jai une préférence pour TextMate. Dautres options incluent Emacs et MacVim (chargé par la commande mvim), lexcellente version Macintosh de Vim.6 Jutilise iTerm comme terminal de ligne de commande ; dautres affectionnent lapplication native « Terminal ». • Linux : vos options dédition sont à la base les mêmes que pour les OS X, à lexception de TextMate. Je recommande « graphical Vim » (gVim), gedit (avec le plugin GMate), ou Kate. En terme de ligne de commande, vous devez déjà être totalement équipés : toute distribution Linux est fournie avec une application terminal de ligne de commande (et même souvent plusieurs). • Windows : Certains éditeurs prometteurs sur Windows sont Vim, le E Text Editor, Komodo Edit, et Sublime Text. Pour les lignes de commande, je recommande dutiliser linvite fournie avec Rails Installer (Section 1.2.2.1).Si vous avez une connaissance de Vim, assurez-vous de puiser dans la florissante communauté des Vim-usingRails hackers. Attardez-vous spécialement sur les améliorations de rails.vim et le projet à tiroir NERD tree.
    • 22 Illustration 1.1: Un environnement de développement éditeur de texte/ligne de commande (TextMate/iTerm). Navigateurs internet Bien quon puisse choisir dans une large panoplie de navigateurs, la majorité des développeurs Rails font appel à Firefox, Safari ou Chrome pour développer leurs applications. Les copies décran dans ce tutoriel Rails proviennent en général de Firefox. Si vous utilisez Firefox, je vous suggère dutiliser ladd-on Firebug, qui vous permet de faire tout un tas de choses magiques, comment inspecter dynamiquement (ou même modifier) la structure HTML et les règles CSS de nimporte quelle page. Pour ceux qui nutilisent pas Firefox, Firebug Lite fonctionne avec la plupart des autres navigateurs, et Safari comme Chrome ont de façon native un outil dinspection des éléments accessible en cliquant-droit sur nimporte quelle partie de la page. Quel que soit le navigateur utilisé, lexpérience démontre que le temps passé à apprendre à se servir dun outil dinspection est rapidement et richement récompensé. Une note à propos des outils En vous appliquant à rendre votre environnement de développement opérationnel, vous constaterez peut-être que vous dépensez beaucoup de temps à faire en sorte que les choses, simplement, fonctionnent. Le processus dapprentissage des éditeurs et des IDEs est particulièrement long ; vous pourrez passer des semaines sur des tutoriels TextMate ou Vim. Si ce jeu est tout nouveau pour vous, je tiens à vous assurer que passer du temps à apprendre à connaître ses outils est normal. Tout le monde y passe. Parfois cela est frustrant, et il est facile de perdre patience quand vous avez en tête le projet dune magnifique application web et que vous voulez juste apprendre Rails tout de suite, mais que vous avez à « perdre » une semaine sur un ancien et bizarre éditeur de texte Unix pour pouvoir ne faire que commencer. Mais un artisan se doit de connaître ses outils ; au bout du compte, soyez certain que le jeu en vaut la chandelle.
    • 231.2.2 Ruby, RubyGems, Rails, et GitMaintenant, il est temps dinstaller Ruby et Rails. La source canonique et actualisée est la page detéléchargement de Ruby on Rails. Vous pouvez y aller dès à présent ; les parties de ce livre peuvent être luesavec profit sans être connecté à par celle-ci. Car je vais injecter certains commentaires personnels sur cetteétape.Installeur Rails (Windows)Linstallation de Rails sur Windows était auparavant une calamité, mais grâce aux efforts de bonnes personnesà Engine Yard — spécialement Dr. Nic Williams et Wayne E. Seguin — linstallation de Rails et des logicielsafférents sur Windows est maintenant incroyablement facilitée. Si vous êtes sous Windows, allez au RailsInstaller (http://railsinstaller.org/) et téléchargez lexécutable Rails Installer. Double-cliquez lexécutable etsuivez les instructions dinstallation pour installer Git (vous pourrez donc passer la section 1.2.2.2), Ruby(passer la section 1.2.2.3), RubyGems (passez la section 1.2.2.4), et Rails lui-même (passez la section 1.2.2.5).Une fois linstallation terminée, vous pouvez directement passer à la création de la première application à lasection 1.2.3.Installation de GitLe plus gros de lécosystème Rails repose dune manière ou dune autre sur un système de contrôle de versionappelé Git (couvert plus en détail dans la section 1.3). Puisque son usage est omniprésent, vous devriez installerGit même à ce point de départ ; je vous suggère de suivre les instructions dinstallation propres à votre plate-forme que vous trouverez à cette adresse : Installing Git section of Pro Git.Installer RubyLétape suivante consiste à installer Ruby. Il est possible que votre système le possède déjà ; essayez dexécuterla commande… $ ruby -v ruby 1.9.2… pour voir le numéro de version de votre Ruby. Rails 3 requiert Ruby 1.8.7 ou plus récent et fonctionne mieuxavec Ruby 1.9.2. Ce tutoriel pré-suppose que vous utilisez la dernière version de développent de Ruby 1.9.2,connu sous le nom Ruby 1.9.2-head, mais Ruby 1.8.7 devrait fonctionner tout aussi bien.La branche Ruby 1.9 est en développement lourd, donc malheureusement installer la dernière version de Rubypeut relever du challenge. Vous devrez compter alors sur les instructions les plus actualisées quon trouve en
    • 24 ligne. Ce qui suit est une série détapes que jai exécutées pour le fonctionnement sur mon système (Macintosh OS X), mais vous aurez peut-être à chercher à droite et à gauche pour quelles fonctionnent sur le vôtre. Dans le cadre de linstallation de Ruby, si vous utilisez OS X ou Linux je recommande fortement linstallation de Ruby en utilisant le Ruby Version Manager (RVM), qui vous permet dinstaller et gérer plusieurs versions de Ruby sur la même machine (le projet Pik accomplit la même chose sous Windows). Cela est particulièrement important si vous voulez faire tourner Rails 3 et Rails 2.3 sur le même ordinateur. Si vous voulez emprunter cette voie, je vous suggère lutilisation de RVM pour installer deux combinaisons Ruby/Rails : Ruby 1.8.7/Rails 2.3.10 et Ruby 1.9.2/Rails 3.0.5. Si vous rencontrez le moindre problème avec RVM, vous pouvez souvent trouver son créateur, Wayne E. Seguin, sur le canal IRC de RVM (#rvm on freenode.net).7 Après linstallation de RVM, vous pouvez installer Ruby de cette manière :8 $ rvm get head $ rvm reload $ rvm install 1.8.7 $ rvm install 1.9.2 <attendez un moment> Les deux premières commandes actualisent et rechargent RVM lui-même, ce qui constitue une bonne pratique puisque RVM est fréquemment actualisé. Les deux commandes finales procèdent à linstallation de Ruby ; en fonction de votre système, cela peut prendre plus ou moins de temps pour télécharger et compiler les sources, donc ne vous inquiétez pas si ça vous semble une éternité (soyez également informé que beaucoup de peites choses peuvent aller de travers. Par exemple, sur mon système la dernière version de Ruby 1.8.7 ne voudra pas se compiler ; après bien des recherches et des lamentations, jai découvert que javais besoin du « patchlevel » numéro 174 : $ rvm install 1.8.7-p174 Quand de telles choses surviennent, cest toujours très frustrant (Oh que oui ! NdT), mais au moins, vous saurez que ça narrive pas quà vous. Typiquement, les programmes Ruby sont distribués via des gems, qui sont des packages autonomes de code Ruby. Puisque des gems avec des numéros de versions différents peuvent parfois entrer en conflit, il est souvent pratique de créer des gemsets séparés, qui sont des faisceaux (bundles) autonomes de gems. En particulier, Rails est distribué comme un gem, et il y a des conflits entre Rails 2 et Rails 3, donc si vous voulez lancer plusieurs versions de Rails sur le même système vous aurez besoin de créer une gemset différent pour chacune delle :
    • 25 $ rvm --create 1.8.7-p174@rails2tutorial $ rvm --create use 1.9.2@rails3tutorialLa première commande crée le gemset rails2tutorial associé à Ruby 1.8.7-p174, tandis que la secondecommande crée le gemset rails3tutorial associé à Ruby 1.9.2 et lutilise (via la commande use) en mêmetemps. RVM supporte une large variété de commandes pour manipuler les gemsets ; voyez la documentation àladresse http://rvm.beginrescueend.com/gemsets/.Dans ce tutoriel, nous voulons que notre système utilise Ruby 1.9.2 et Rails 3.0 par défaut, ce que nous pouvonsdéfinir comme suit : $ rvm --default use 1.9.2@rails3tutorialCela définit simultanéement la version de Ruby par défaut à la version 1.9.2 et le gemset par défaut àrails3tutorial.En passant, si vous vous retrouvez coincé avec RVM, exécuter des commandes telles que les suivantes devraientvous permettre de retrouver toutes vos facultés : $ rvm --help $ rvm gemset --helpInstallation de RubyGemsRubyGems est un gestionnaire de packages pour les projets Ruby, et il y a légion dexcellentes librairies (Railsinclus) accessibles comme packages Ruby, ou comme gems. Linstallation de RubyGems devrait être facile unefois Ruby installé. En fait, si vous avez installé RVM, vous avez déjà RubyGems, puisque RVM linclutautomatiquement : $ which gem /Users/mhartl/.rvm/rubies/ruby-head/bin/gemSi vous ne lavez pas encore, vous devez télécharger RubyGems, lextraire, puis aller dans le dossier rubygemspour lancer le programme dinstallation : $ [sudo] ruby setup.rbSi RubyGems est déjà installé, vous pouvez vouloir actualiser votre système avec la dernière version :
    • 26 $ [sudo] gem update --system Pour finir, si vous utilisez Ubuntu Linux, vous devriez jeter un coup dœil à the Ubuntu/Rails 3.0 blog post by Toran Billups pour des instructions complètes sur linstallation. Installer Rails Une fois RubyGems installé, linstallation de Rails 3.0 devrait être un jeu denfant : $ [sudo] gem install rails --version 3.0.5 Pour vérifier si tout a fonctionné, exécutez la commande suivante : $ rails -v Rails 3.0.5 1.2.3 La première application Grosso-modo, toutes les applications Rails commencent de la même manière, par une commande rails. Ce programme pratique crée un squelette de lapplication Rails dans le dossier de votre choix. Pour commencer, faite un dossier pour votre projet Rails et exécutez la commande rails pour créer la première application : Illustration 1.1. Exécution du script rails pour générer une nouvelle application web. $ mkdir rails_projects $ cd rails_projects $ rails new first_app create create README create .gitignore create Rakefile create config.ru create Gemfile create app create app/controllers/application_controller.rb create app/helpers/application_helper.rb create app/views/layouts/application.html.erb create app/models create config create config/routes.rb
    • 27 create config/application.rb create config/environment.rb . Remarquez le nombre de fichiers et de dossier que la commande rails crée. Cette structure stantard de dossiers et de fichiers (Illustration 1.2) est lun des nombreux avantages de Rails ; cela vous propulse directement de zéro à une application fonctionnelle (bien que minimale). Plus encore, puisque la structure est commune à toutes les applications Rails, vous pouvez immédiatement prendre vos repères en regardant le code de quelquun dautre. Un résumé des fichiers Rails par défaut apparait dans la table 1.1 ; nous en apprendrons plus sur la plupart de ces fichiers tout au long de ce livre. Illustration 1.2: La structure du dossier pour une toute nouvelle application Rails. Fichier/Dossier Objet Cœur du code de lapplication (app), incluant des modèles (models), des vues (views), des contrôleurs (controllers), etapp/ des « assistants » (helpers)
    • 28 config/ Configuration de lapplication db/ Fichiers pour manipuler la base de données doc/ Documentation pour lapplication lib/ Modules libraries log/ Journaux (logs) de lapplication public/ Données publiques (p.e., accessibles aux navigateurs web), telles que les images et les feuilles de styles (CSS) script/rails Un script fourni par Rails pour générer du code, ouvrir les sessions console, ou démarrer une serveur web local test/ Tests de lapplication (rendu obsolète par le dossier spec/ introduit à la section 3.1.2) tmp/ Fichiers temporaires vendor/ Codes Tierce-partie tels que les plugins et les gems README Une brève description de lapplication Rakefile Tâches utiles accessibles par le biais de la commande rake Gemfile Les Gems requis par lapplication config.ru Un fichier de configuration pour Rack middleware .gitignore Les patterns pour les fichiers qui doivent être ignorés par Git Table 1.1: Résumé de la structure par défaut dun dossier Rails. 1.2.4 Bundler Après avoir créé une nouvelle application Rails, on utilise Bundler pour installer et inclure les gems utiles à lapplication. Cela consiste à ouvrir le fichier Gemfile avec votre éditeur de texte favori : $ cd first_app/ $ mate Gemfile Le résultat devrait ressembler à lillustration 1.2. Illustration 1.2. Le fichier Gemfile par défaut dans le dossier first_app. source http://rubygems.org gem rails, 3.0.5
    • 29 # Bundle edge Rails instead: # gem rails, :git => git://github.com/rails/rails.git gem sqlite3-ruby, :require => sqlite3 # Utilser unicorn comme serveur web # gem unicorn # Déployer lapplication avec Capistrano # gem capistrano # Le déboggueur # gem ruby-debug # Bundle les gems supplémentaires : # gem bj # gem nokogiri, 1.4.1 # gem sqlite3-ruby, :require => sqlite3 # gem aws-s3, :require => aws/s3 # Bundle ces gems pour certains environements: # gem rspec, :group => :test # group :test do # gem webrat # endLa plupart de ces lignes sont commentés par le symbole dièse (#) ; elles sont là pour vous montrer certains desgems les plus communs et donner des exemples de la syntaxe Bundler. Pour le moment, nous naurons besoindaucun gem autres que ceux par défaut : Rails lui-même, et le gem pour linterface Ruby pour la base dedonnées SQLite.Même si vous spécifiez un numéro de version pour la commande gem, Bundler installera toujoursautomatiquement la dernière version. Malheureusement, les actualisations des gems causent souvent desruptures mineures mais potentiellement perturbantes, donc dans ce tutoriel nous ajouterons en général unnuméro de version explicite connu, comme le montre lillustration 1.3.9 Illustration 1.3. Un fichier Gemfile avec un numéro de version du gem sqlite3-ruby explicite.
    • 30 source http://rubygems.org gem rails, 3.0.5 gem sqlite3-ruby, 1.3.2, :require => sqlite3 Cela change la ligne : gem sqlite3-ruby, :require => sqlite3 … de lextrait 1.2 en : gem sqlite3-ruby, 1.3.2, :require => sqlite3 … qui force Bundler à installer la version 1.3.2 du gem sqlite3-ruby (notez que vous aurez besoin de la version 1.2.5 du gem sqlite3-ruby si vous tournez sous OS X Leopard, comme 1.3.2 a besoin de Snow Leopard pour se compiler proprement). Si vous êtes sous Ubuntu Linux, vous pouvez devoir installer une paire dautre packages à ce point du tutoriel :10 $ sudo apt-get install libxslt-dev libxml2-dev libsqlite3-dev # pour Linux Une fois que vous avec assemblé correctement votre Gemfile, installez les gems en utilisant la commande bundle install :11 $ bundle install Fetching source index for http://rubygems.org/ . . Cela peut prendre un certain temps, mais une fois fait, notre application sera prête à démarrer. 1.2.5 rails server Grâce à lexécution de rails new à la section 1.2.3 et bundle install à la section 1.2.4, nous avons déjà une application que nous pouvons lancer — mais comment ? Heureusement, Rails est fourni avec un programme de commande en ligne, ou script, qui lance un serveur web local,12 visible seulement depuis votre machine de développement :13
    • 31 $ rails server => Booting WEBrick => Rails 3.0.5 application starting on http://0.0.0.0:3000 => Call with -d to detach => Ctrl-C to shutdown serverCe code nous dit que lapplication est lancée sur le port 300014 à ladresse 0.0.0.0. Cette adresse dit àlordinateur découter toutes les adresses IP accessibles et configurées sur la machine spécifiques ; enparticulier, nous pouvons voir lapplication en utilisant ladresse spéciale 127.0.0.1, connue aussi sous laforme localhost. Nous pouvons voir le résultat en visitant ladresse http://localhost:3000/de lillustration 1.3.15 Illustration 1.3: La page Rails par défaut (http://localhost:3000/).Pour voir les informations sur notre première application, cliquez sur le lien « About your applicationsenvironment ». Le résultat est montré dans lillustration 1.4.16
    • 32 Illustration 1.4: La page par défaut (http://localhost:3000/) avec lenvironnement de lapplication. Bien sûr, nous navons pas besoin de la page par défaut de Rails très longtemps, mais il est sympa de voir quelle fonctionne pour le moment. Nous détruirons la page par défaut et la remplacerons par une page daccueil personnalisée à la section 5.2.2. 1.2.6 Modèle-Vue-Contrôleur (MVC) Même à cette étape précoce, il est utile davoir un aperçu de haut-niveau de comment fonctionne les applications Rails (Illustration 1.5). Vous pouvez avoir noté que la structure standard dune application Rails (Illustration 1.2) possède un dossier appelé app/ contenant trois sous-dossiers : models (Modèles), views (Vues), and controllers (Contrôleurs). Cest lindice que Rails suit le modèle architectural Modèle-Vue- Contrôleur (Model-View-Controls ou MVC), qui force une séparation entre la « logique domaine » (appelée aussi « logique métier ») et les entrées, et la logique de présentation associée à une interface utilisateur graphique (Graphical User Interface ou GUI). Dans le cas des applications web, la « logique domaine » consiste typiquement en modèles de données pour des choses telles que les utilisateurs, les articles et les produits, et le GUI est juste une page web dans un navigateur web. En interagissant avec une application Rails, un navigateur envoie une requête, qui est reçue par un serveur web et passée à un contrôleur Rails, qui lui-même prend en charge la suite des opérations. Dans certains cas, le contrôleur rendra immédiatement une vue, qui est un modèle converti en HTML et renvoyé au navigateur. De façon plus classique pour les sites dynamiques, le contrôleur interagit avec le modèle, qui est un objet Ruby qui
    • 33représente un élément du site (tel quun utilisateur) et est en charge de la communication avec la base dedonnées. Après linvocation du modèle, le contrôleur produit alors une vue et retourne la page web achevée aunavigateur sous forme de code HTML. Illustration 1.5: Une représentation schématique de larchitecture modèle-vue-contrôleur (MVC).Si cette discussion vous semble un peu trop abstraite pour le moment, ne vous inquiétez pas ; nous y feronsfréquemment référence. Addionnellement, la section 2.2.2 contient une discussion plus détaillée sur le MVCdans le contexte de lApplication démo. Pour finir, lApplication exemple utilisera tous les aspects du MVC ;nous couvrirons le problème des contrôleurs et des vues en abordant la section 3.1.2, les modèles en abordant lasection 6.1, et nous verrons comment les trois fonctionnent ensemble dans la section 6.3.2.1.3 Contrôle de version avec GitMaintenant que nous avons une application Rails fraiche et fonctionnelle, nous allons prendre le temps pourune étape qui, bien que techniquement optionnelle, est considérée par de nombreux développeurs Rails commepratiquement essentielle, à savoir placer le code source de votre application sous contrôle de version. Lessystèmes de contrôle de version nous permettent de procéder à des modifications du code de notre projet, decollaborer plus aisément avec dautres développeurs, et de revenir en arrière en cas derreur (comme lasuppression accidentelle de fichiers). Savoir utiliser un système de contrôle de version est une compétence quedoit acquérir tout développeur.
    • 34 Il y a beaucoup doptions pour le contrôle de version, mais la communauté Rails a largement opté pour Git, un système de contrôle de version originellement développé par Linus Torvalds pour accueillir le kernel Linux. Git est un vaste sujet, et nous ne ferons que leffleurer dans ce livre, mais vous trouverez de nombreuses bonnes ressources sur le web ; je recommande spécialement Pro Git de Scott Chacon (Apress, 2009). Mettre votre code source sous contrôle de version avec Git est chaudement recommandé, pas seulement parce que cest une pratique universelle dans le monde de Rails, mais aussi parce que cela vous permettra de partager votre code plus facilement (Section 1.3.4) et déployer votre application dès le premier chapitre (Section 1.4). 1.3.1 Installation et réglages La première chose à faire est dinstaller Git si vous navez pas suivi les étapes de la section 1.2.2.2 (comme indiqué dans la section, cela implique davoir suivi les instructions de la Section « Installation de Git » de Pro Git (Installing Git section of Pro Git).) Initialisation des réglages du système Après avoir installé Git, vous devez effectuer une initialisation des réglages. Ce sont des réglages système, ce qui signifie que vous navez à les faire quune fois par ordinateur : $ git config --global user.name "Votre Nom" $ git config --global user.email youremail@example.com Jaime aussi utiliser co au lieu de la commande verbeuse checkout, ce que nous pouvons arranger comme suit : $ git config --global alias.co checkout Ce tutoriel utilisera souvent la commande complète checkout qui fonctionne pour les systèmes qui nont pas configuré ce co, mais dans la vie réelle jutilise presque toujours git co pour contrôler la sortie dun projet. En dernier réglage, vous pouvez optionnellement définir léditeur que Git utilisera pour les messages commits. Si vous utilisez un éditeur graphique comme TextMate, gVim ou MacVim, vous aurez besoin dutiliser un drapeau (flag) pour vous assurer que léditeur reste attaché à la shell au lieu de se détacher automatiquement :17 $ git config --global core.editor "mate -w" Remplacez "mate -w" par "gvim -f" pour gVim ou "mvim -f" pour MacVim.
    • 35Initialisation des réglages du repositoryNous allons maintenant effectuer quelques opérations nécessaires chaque fois que vous créez un nouveaurepository — un nouveau « système de contrôle », ou un dossier, où vous allez déposer vos versions delapplication . NdT — (ce qui narrive quune fois dans ce livre, mais qui est susceptible de se reproduire unjour). Commencez par naviguer dans le dossier racine de la première application et initialisez un nouveaurepository : $ git init Initialized empty Git repository in /Users/mhartl/rails_projects/first_app/.git/La prochaine étape consiste à ajouter les fichiers du projet au repository. Il y a une complication mineure,cependant : par défaut, Git piste les changements de tous les fichiers du dossier, même ceux que nous nevoulons pas pister. Par exemple, Rails crée des journaux (logs) pour enregistrer le comportement delapplication ; ces fichiers changent fréquemment, et nous ne voulons pas que notre système de contrôle deversion les actualise en permanence. Git possède un mécanisme simple pour ignorer de tels fichiers : ajoutezsimplement dans le dossier racine de Rails un fichier appelé .gitignore contenant quelques règles précisantà Git les fichiers à ignorer.En consultant à nouveau la Table 1.1, nous voyons que la commande rails crée un fichier .gitignore pardéfaut dans le dossier racine de Rails, comme le montre lillustration 1.4. Illustration 1.4. Le fichier par défaut .gitignore créé par la commande rails. .bundle db/*.sqlite3 log/*.log tmp/**/*Lillustration 1.4 contraint Git à ignorer les fichiers tels que les journaux (log), les fichiers temporaires de Rails(tmp), et les bases de données SQLite (par exemple, pour ignorer les journaux, qui se trouvent dans le dossierlog/, nous utilisons le code log/*.log pour ignorer tous les fichiers dextension .log). La plupart de cesfichiers ignorés changent fréquemment et automatiquement, donc les inclure dans le contrôle de version estgênant ; plus encore, lors dune collaboration, ils peuvent entrainer des conflits frustrants et inutiles.Le fichier .gitignore de lextrait 1.4 est suffisant pour ce tutoriel, mais en fonction de votre système vouspourrez trouver le code de lextrait 1.5 plus adapté.18 Ce fichier .gitignore augmenté est défini pour ignorerles fichiers de documentation Rails, les fichiers swap Vim et Emacs, (et pour les utilisateurs OS X) les fichiers dedossier .DS_Store créés par le finder du Mac. Si vous voulez appliquer ce réglage plus large de fichiers
    • 36 ignorés, ouvrez votre fichier .gitignore dans votre éditeur de texte favori et remplissez-le avec le contenu de lextrait 1.5. Extrait 1.5. Un fichier .gitignore augmenté. .bundle db/*.sqlite3* log/*.log *.log /tmp/ doc/ *.swp *~ .DS_Store 1.3.2 Ajout et mandat de dépôt Enfin, nous allons ajouter à Git les fichiers de notre nouveau projet Rails et « déposer » (commit) le résultat. Vous pouvez ajouter tous les fichiers (sauf ceux ignorés par le fichier .gitignore) comme suit :19 $ git add . Ici, le point « . » représente le dossier courant, et Git est suffisamment intelligent pour ajouter les fichiers de façon récursive, donc il inclut automatiquement tous les sous-dossiers. Cette commande ajoute les fichiers du projet à une « aire de repos » (staging area), qui contient les changements provisoires à votre projet ; vous pouvez voir les fichiers placés dans l« aire de repos » grâce à la commande status :20 $ git status # On branch master # # Initial commit # # Changes to be committed: # (use "git rm --cached <file>..." to unstage) # # new file: README # new file: Rakefile .
    • 37(Les retours sont plus longs, jai donc utilisé des points verticaux pour indiquer le texte supprimé)Pour indiquer à Git que vous voulez conserver les changements, utilisez la commande commit : $ git commit -m "Initial commit" [master (root-commit) df0a62f] Initial commit 42 files changed, 8461 insertions(+), 0 deletions(-) create mode 100644 README create mode 100644 Rakefile .Le drapeau -m vous laisse ajouter un message à votre dépôt ; si vous ommettez -m, Git ouvrira léditeur quevous avez défini dans la section 1.3.1 et vous demandera alors dentrer le message.Il est important de noter que les dépôts Git sont locaux, enregistrés seulement sur la machine où se fait ledépôt. Ce comportement est différent du système de contrôle de version populaire appelé Subversion, danslequel le dépôt fait nécessairement des changements dans le repository à distance. Git, lui, divise le dépôt destyle Subversion en ces deux parties logiques : un enregistrement local des changements (git commit) et un« push » des changements sur le repository à distance (git push). Nous verrons un exemple de létape« push » à la section 1.3.5.En passant, vous pouvez voir une liste des messages des mandats de dépôt en utilisant la commande log : $ git log commit df0a62f3f091e53ffa799309b3e32c27b0b38eb4 Author: Michael Hartl <michael@michaelhartl.com> Date: Thu Oct 15 11:36:21 2009 -0700 Dépôt initialPour quitter le git log, vous avez peut-être à taper q.1.3.3 Quest-ce que Git peut faire de bien pour vous ?Au point où nous en sommes, lutilité de mettre votre code source sous contrôle de version nest peut-être pasencore clair, donc laissez-moi vous donner juste un exemple (nous en verrons de nombreux autres au cours deschapitres suivants). Supposons que vous ayez effectué des changements accidentels, tels que (« oh noooon ! »)détruire le dossier critique app/controllers/ :
    • 38 $ ls app/controllers/ application_controller.rb $ rm -rf app/controllers/ $ ls app/controllers/ ls: app/controllers/: No such file or directory Nous utilisons ici la commande Unix ls pour lister le contenu du dossier app/controllers/ et la commande rm pour le supprimer. Le drapeau -rf force la récursivité, ce qui a pour effet de détruire tous les fichiers, dossiers, sous-dossiers, sans demander la confirmation de la suppression de ces éléments. Consultons létat (status) de Git pour voir où lon en est : $ git status # On branch master # Changed but not updated: # (use "git add/rm <file>..." to update what will be committed) # (use "git checkout -- <file>..." to discard changes in working directory) # # deleted: app/controllers/application_controller.rb # no changes added to commit (use "git add" and/or "git commit -a") Nous voyons quun certain nombre de fichiers ont été détruits, mais les changements se trouvent seulement dans l« arbre courant » (working tree) ; ils nont pas encore été « déposés ». Cela signifie que nous pouvons encore annuler les changements facilement en reconnectant Git sur le précédent dépôt avec la commande checkout (et un drapeau -f pour forcer le remplacement des changements courants) : $ git checkout -f $ git status # On branch master nothing to commit (working directory clean) $ ls app/controllers/ application_controller.rb Le dossier et les fichiers manquants sont de retour. Quel soulagement !
    • 391.3.4 GitHubMaintenant que vous avez déposé votre projet sous version de contrôle avec Git, il est temps de « pusher » votrecode vers GitHub, un site social de code optimisé pour accueillir et partager les repositories Git. Déposer unecopie de votre repository Git sur GitHub vise deux buts : cest une sauvegarde complète de votre code (quiinclus lhistorique complet de vos dépôts), et cela facilite toute collaboration future. Cette étape est optionnelle,mais être un membre GitHub vous ouvrira la porte dune large variété de projets Ruby on Rails (GitHubpossède un taux dadoption élevé dans la communauté Ruby et Rails, et en fait, il est écrit lui-même en Rails). Figure 1.6: La première page GitHub après la création du compte.
    • 40 Illustration 1.7: Création du repository de la première application sur GitHub. GitHub possède une large variété de plans payants, mais pour du code open-source les services sont gratuits, donc inscrivez-vous pour un free GitHub account (compte GitHub gratuit) si vous nen possédez pas encore un (vous pouvez lire dabord les instrutions sur les clés SSH). Après vous être inscrit, vous verrez une page similaire à celle de lillustration 1.6. Cliquez le lien create a repository (Créer un repository) et remplissez le formulaire comme dans lillustration 1.7. Après avoir soumis le formulaire, « pushez » votre première application (cest-à-dire envoyer votre code sur le repository distant) comme suit : $ git remote add origin git@github.com:<username>/first_app.git $ git push origin master Ces commandes indique à Git que vous voulez ajouter GitHub comme origine de votre branche principale (master) et « pusher » votre repository vers GitHub. Bien sûr, vous devrez remplacer <username> par votre nom dutilisateur réel. Par exemple, la commande que jai jouée pour lutilisateur railstutorial était : $ git remote add origin git@github.com:railstutorial/first_app.git Le résultat consiste en une page GitHub pour le repository de la première application, avec un système de navigation de fichiers, un historique complet des dépôts, et beaucoup dautres petites choses utiles (Illustration 1.8).
    • 41 Illustration 1.8: Une page repository GitHub.1.3.5 Branch, edit, commit, mergeSi vous avez suivi les étapes de la section 1.3.4, vous avez peut-être noté que GitHub affiche automatiquement lecontenu du fichier README sur la page principale du repository. Dans notre cas, puisque le projet est uneapplication Rails générée en utilisant la commande rails, le fichier README est celui qui vient avec Rails(Illustration 1.9). Il nest pas très utile, donc dans cette section nous créerons notre première édition enmodifiant ce fichier README pour décrire notre projet plutôt que le frameword Rails. Chemin faisant, nousverrons un premier exemple des commandes de flux de travail branch (branche), edit (éditer), commit(mandat de dépôt) et merge (fusionner) que je recommande dutiliser avec Git. Illustration 1.9: Le fichier README initial de notre projet sur GitHub.BranchGit est incroyablement bon pour faire des branches, qui sont en fait des copies du repository sur lesquelles nouspouvons effectuer des changements (peut-être expérimentaux) sans modifier les fichiers parents. Dans laplupart des cas, le repository parent est la branche master, et nous pouvons créer une nouvelle branche sujet enutilisant la commande checkout avec le drapeau -b : $ git checkout -b modify-README Switched to a new branch modify-README $ git branch master * modify-READMEIci, la seconde commande, git branch, liste simplement toutes les branches locales, et lastérisque « * »identifie la branche sur laquelle nous nous trouvons actuellement. Notez que git checkout -b modify-README dune part crée une nouvelle branche et dautre part bascule vers cette branche, comme lindique
    • 42 lastérisque devant la branche modify-README (si vous avez défini lalias co à la section 1.3, vous pouvez utiliser git co -b modify-README). La pertinence des branchements ne devient claire que lorsque vous travaillez sur un projet avec plusieurs développeurs,21 mais les branches sont utiles même pour un tutoriel à simple développeur comme celui-ci. En particulier, la branche maitresse est isolé de tout changement que nous faisons sur la branche sujet, donc même si nous vissons réellement des choses, nous pouvons toujours abandonner les changements en rejoignant la branche maitresse et en détruisant la branche sujet. Nous verrons comment faire cela à la fin de la section. En passant, pour un changement aussi mineur que celui-ci, je ne membêterais pas avec une nouvelle branche, mais il nest jamais trop tard pour prendre de bonnes habitudes. Edit Après avoir créé la branche sujet, nous léditerons pour la décrire un peu mieux. Jaime utiliser le langage Markdown pour ça, et si vous utilisez lextension de fichier .markdown alors GitHub mettra automatiquement en forme laffichage du fichier pour vous. Donc, nous allons utiliser la version Git de la commande Unix mv (« move », « déplacer ») pour changer le nom, et le remplir avec le contenu de lextrait 1.6: $ git mv README README.markdown $ mate README.markdown Extrait 1.6. Le nouveau fichier README, README.markdown # Tutoriel Ruby on Rails : première application Cest la première application pour le [*Tutoriel Ruby on Rails : Apprendre Rails par lexemple*](http://railstutorial.org/) de [Michael Hartl](http://michaelhartl.com/). Commit Les changements ainsi effectués, nous pouvons jeter un œil à létat de notre branche : $ git status # On branch modify-README # Changes to be committed: # (use "git reset HEAD <file>..." to unstage) # # renamed: README -> README.markdown
    • 43 # # Changed but not updated: # (use "git add <file>..." to update what will be committed) # (use "git checkout -- <file>..." to discard changes in working directory) # # modified: README.markdown #À ce point, nous pourrions utiliser git add . comme à la section 1.3.2, mais Git fournit le drapeau -a commeraccourci pour le (très commun) cas dun dépôt de toutes les modifications des fichiers existants (ou des fichierscréés avec git mv, qui ne comptent pas comme nouveau fichier pour Git) : $ git commit -a -m "Amelioration du fichier README" 2 files changed, 5 insertions(+), 243 deletions(-) delete mode 100644 README create mode 100644 README.markdownSoyez prudent en utilisant de façon inappropriée le drapeau -a ; si vous avez ajouté de nouveaux fichiers auprojet depuis le dernier dépôt, vous devez toujours le préciser à Git en utilisant dabord la commande git add.MergeMaintenant que nous avons achevé nos changements, nous sommes prêts à « merger » (fusionner) le résultatdans la branche maitresse :22 $ git checkout master Switched to branch master $ git merge modify-README Updating 34f06b7..2c92bef Fast forward README | 243 ----------------------------------------------------- -- README.markdown | 5 + 2 files changed, 5 insertions(+), 243 deletions(-) delete mode 100644 README create mode 100644 README.markdown
    • 44 Notez que le message de retour de Git inclut fréquemment des choses comme 34f06b7, qui sont en fait liés à la représentation interne des repositories. Votre message de retour diffèrera concernant ces détails, mais elle devrait correspondre à la sortie présentée ci-dessus. Après avoir fusionné les changements, vous pouvez mettre en ordre vos branches en supprimant la branche sujet avec git branch -d : $ git branch -d modify-README Deleted branch modify-README (was 2c92bef). Cette étape est optionnelle, et en réalité il est assez courant de laisser les branches sujet intactes. De cette façon vous pouvez basculer davant en arrière entre les branches maitresse et sujets, fusionner les changements chaque fois que vous atteignez un point final. Comme mentionné plus haut, il est aussi possible dabandonner les changements de la branche sujet, dans ce cas avec la commande git branch -D: # Pour lillustration seulement ; ne le faites pas, au risque de gâcher une branche $ git checkout -b topic-branch $ <really screw up the branch> $ git add . $ git commit -a -m "Screwed up" $ git checkout master $ git branch -D topic-branch Contrairement au drapeau -d, le drapeau -D détruira la branche même si nous navons pas fusionné les changements. Push Maintenant que nous avons actualisé le fichier README, nous pouvons « pusher » les changements vers GitHub pour voir le résultat. Puisque nous avons déjà effectué un push (Section 1.3.4), sur la plupart des systèmes nous pouvons ommettre dindiquer origin master, et jouer simplement la commande git push :23 $ git push Sur certains systèmes, cette commande provoquera une erreur :
    • 45 $ git push fatal: The current branch master is not tracking anything. (Traduction : fatal : la branche maitresse ne piste rien en ce moment.)Dans ce cas, vous devrez jouer la commande git push origin master comme à la section 1.3.4.Comme promis, GitHub remet gentiment en forme le fichier en utilisant Markdown (Illustration 1.10). Illustration 1.10: Le fichier README amélioré (version anglaise), formaté avec Markdown.1.4 DéploiementMême à ce stade peu avancé, nous allons déjà déployer en production notre application Rails mêmerudimentaire. Cette étape est optionnelle, mais déployer assez tôt et souvent nous permet de « capturer » leplus rapidement possible les problèmes de déploiement dans le cycle de développement. Lalternative —déployer lapplication seulement après un effort laborieux enfermé dans lenvironnement de développement —conduit souvent à de terribles maux de tête dintégration quand le temps du lancement est arrivé.24Déployer une application Rails était difficile, mais lécosystème de déploiement Rails a rapidement mûri cesdernières années et il existe maintenant plusieurs grandes options. Celles-ci incluent les hôtes partagés ou lesserveurs virtuels privés comme Phusion Passenger (un module pour les web serveurs Apache et Nginx25), descompagnies de déploiement « tous-services » telles que Engine Yard ou Rails Machine et des clouds EngineYard Cloud ou Heroku.Mon option de déploiement Rails favorite est Heroku, qui est une plate-forme hôte construite spécialementpour déployer les applications web Rails et autres applications Ruby.26 Heroku facile incroyablement ledéploiement des applications Rails — pourvu que votre code source soit sous contrôle de version Git (cestencore une autre raison de suivre les étapes dutilisation de Git de la section 1.3 si vous ne lavez pas encore fait)La fin de cette section est dédiée au déploiement de notre application vers Heroku.
    • 46 1.4.1 Réglages Heroku Après la création dun compte Heroku, installez le gem Heroku : $ [sudo] gem install heroku Comme avec GitHub (Section 1.3.4), en utilisant Heroku vous aurez besoin de créer une clé SSH si vous nen avez pas déjà une, et indiquer ensuite à Heroku votre clé publique pour utiliser Git pour « pusher » le repository de lApplication exemple vers les serveurs : $ heroku keys:add Enfin, utilisez la commande heroku pour créer une place sur les serveurs Heroku pour lapplication exemple (Extrait 1.7). Extrait 1.7. Créer une nouvelle application sur Heroku. $ heroku create Created http://severe-fire-61.heroku.com/ | git@heroku.com:severe-fire- 61.git Git remote heroku added Oui, cest ça. La commande heroku command crée un nouveau sous-domaine juste pour notre application, accessible pour un affichage immédiat. Il ny a rien à afficher encore, mais nous allons mettre ce déploiement au travail. 1.4.2 Déploiement Heroku, première étape Pour déployer lapplication vers Heroku, la première étape consiste à utiliser Git pour « pusher » lapplication vers Heroku : $ git push heroku master (Note : certains lecteurs du tutoriel ont reporté avoir rencontré une erreur à cette étape liée à SQLite : rake aborted! no such file to load -- sqlite3 Le réglage décrit dans ce chapitre fonctionne parfaitement sur la plupart des systèmes, le mien compris, mais si vous rencontrez ce problème vous devriez essayer dactualiser votre Gemfile avec le code de lextrait 1.8, qui empêche Heorku dessayer de charger le gem sqlite3-ruby).
    • 47Extrait 1.8. Un Gemfile avec la correction Heroku nécessaire sur certains systèmes. source http://rubygems.org gem rails, 3.0.5 gem sqlite3-ruby, 1.3.2, :group => :development1.4.3 Déploiement Heroku, seconde étapeIl ny a pas de seconde étape ! Nous lavons déjà accomplie (Illustration 1.11). Pour voir la toute nouvelleapplication déployée, vous pouvez visiter ladresse que vous voyez quand vous jouez la commande herokucreate (p.e. extrait 1.7, mais avec ladresse de votre propre application, pas la mienne).27 Vous pouvez aussiutiliser un argument avec la commande heroku qui provoque le lancement automatique du navigateur avec labonne adresse : $ heroku open Illustration 1.11: La première application du tutoriel Rails sur Heroku.Une fois le déploiement réussi, Heroku fournit une belle interface pour administrer et configurer votreapplication (Illustration 1.12).
    • 48 Illustration 1.12: La belle interface sur Heroku. 1.4.4 Commandes Heroku Il existe de nombreuses commandes Heroku, et nous ne ferons que les effleurer dans ce livre. Prenons une minute pour en montrer seulement une en renommant notre application comme suit : $ heroku rename railstutorial Nutilisez pas ce nom ; je lutilise déjà moi-même ! En fait, vous ne devriez peut-être pas vous soucier de cette étape tout de suite ; utiliser ladresse par défaut fournie par Heroku est une bonne chose. Mais si vous tenez réellement à renommer votre application, vous pouvez implémenter la sécurité de lapplication mentionnée au début de cette section en utilisant un obscur et aléatoire nom de sous-domaine, comme le suivant : hwpcbmze.heroku.com seyjhflo.heroku.com jhyicevg.heroku.com Avec un nom de domaine aléatoire, comme celui-ci, un internaute ne pourra visiter votre site que si vous lui en fournissez ladresse (en passant, comme aperçu de lincroyable « compacticité » de Ruby, voici le code que jai utilisé pour générer aléatoirement ce sous-domaine : (a..z).to_a.shuffle[0..7].join
    • 49Joli, non ?)En plus de supporter les sous-domaines, Heroku supporte aussi les domaines personnalisés (en fait, le Site dututoriel Ruby on Rails se trouve sur Heroku ; si vous lisez ce livre online, vous êtes justement en train deregarder le site hébergé par Heroku !). Consultez la documentation Heroku pour en savoir plus sur les domainspersonnalisés et les autres sujets Heroku.1.5 ConclusionNous avons parcouru un long chemin dans ce chapitre : installation, développement, réglage delenvironnement, contrôle de version et déploiement. Si vous voulez partager vos progrès, sentez-vous libredenvoyer un tweet ou une notification Facebook avec quelque chose comme : Japprends Ruby on Rails avec le tutoriel @railstutorial! http://railstutorial.org/Tout ce qui reste à faire, vous savez, est en fait de commencer à apprendre Rails. Allons-y !
    • 50 Chapitre 2 Une application Démo Dans ce chapitre, nous développerons une simple application de démonstration pour révéler un peu de la puissance de Rails. Lobjectif est dobtenir une vue densemble de la programmation Ruby on Rails (et du développement web en général) en générant rapidement une application par lutilisation du générateur déchaffaudage (scaffold generators).28 Comme explicité dans le Box 1.1, la suite du livre adoptera une approche opposée à celle-ci, en développant une application étape après étape, en expliquant chaque nouveau concept qui apparaitra, mais pour un aperçu rapide (et quelques rapides gratifications), il ny a rien de mieux que la construction par échaffaudage. Lapplication Démo résultant de cette construction nous permettra dinteragir avec elle par le biais des URLs, en nous donnant un aperçu de la structure type dune application Rails, incluant un premier exemple de larchitecture REST préconisée par Rails. Comme avec lApplication Exemple que nous développerons par la suite, lapplication Démo consistera en des utilisateurs (users) et leur micro-messages (microposts) associés pour constituer une application minimaliste de type Twitter. Les fonctionnalités seront pour le moins sous-exploitées, et beaucoup détapes du développement vous paraitront tout simplement magiques, mais ne vous inquiétez pas : lApplication Exemple complète développera une application similaire depuis le tout départ au chapitre 3, et je fournirai dabondantes références concernant tous les points abordés. Dans le même temps, soyez patient et gardez la foi — lobjectif de ce tutoriel est de vous conduire au-delà de la superficialité de lapproche par échaffaudage pour acquérir une compréhension profonde de Rails. 2.1 Planifier lapplication Dans cette section, nous allons poser les plans de notre Application Démo. Comme dans la section 1.2.3, nous commencerons par générer le squelette de lapplication en utilisant la commande rails : $ cd ~/rails_projects $ rails new demo_app $ cd demo_app Maintenant, utilisons léditeur de texte pour actualiser le contenu du fichier Gemfile utile à Bundler avec le code de lextrait 2.1. Extrait 2.1. Un fichier Gemfile pour lapplication Démo. source http://rubygems.org gem rails, 3.0.7 gem sqlite3-ruby, 1.3.2, :require => sqlite3
    • 51(Rappelez-vous de la section 1.2.4 : vous pouvez avoir besoin de la version 1.2.5 du gem sqlite3-ruby si vous êtes sous OS X Leopard.) Nous installons alors et incluons les gems en utilisant bundle : $ bundle installPour finir, nous initialiserons un repository Git et ferons le premier dépôt :29 $ git init $ git add . $ git commit -m "Initial commit" Illustration 2.1: Créer un repository pour lapplication Démo sur GitHub.Vous pouvez aussi, optionnellement, créer un nouveau repository (Illustration 2.1) et le « pusher » versGitHub : $ git remote add origin git@github.com:<username>/demo_app.git $ git push origin masterNous sommes prêts à présent à construire lapplication elle-même. La première étape typique quand onfabrique une application web est de créer un modèle de données, qui est une représentation de la structurenécessaire à notre application. Dans notre cas, lapplication Démo sera un micro-blog, avec uniquement desutilisateurs et de courts messages. Nous commencerons donc par le modèle de données pour les utilisateurs delapplication (Section 2.1.1), et nous ajouterons ensuite un modèle de données pour les micro-messages(microposts) (Section 2.1.2).
    • 52 2.1.1 Modéliser les utilisateurs Il y a autant de modèles de données utilisateurs sur le web quil y a de formulaires dinscription ; nous adopterons définitivement une approche minimaliste. Les utilisateurs de notre application Démo possèderont un identifiant unique appelé id, un nom affiché publiquement (de type chaine de caractères, string) et une adresse mail (de type également string) qui servira de double au nom dutilisateur (username). Un résumé du modèle de données pour les utilisateurs (en anglais) est visible dans l illustration 2.2. Illustration 2.2: Le modèle de données pour les utilisateurs. Comme nous le verrons en commençant la section 6.1.1, le label users (utilisateurs) dans lillustration 2.2 correspond à une table dans la base de données, et les attributs id, nom (name en anglais) et email correspondent aux colonnes de cette table. 2.1.2 Modéliser les micro-messages Le cœur du modèle de données des micro-messages (microposts) est souvent plus simple que celui des utilisateurs : un micro-message possède seulement un champ id (identifiant) et un champ content (contenu) pour le texte du micro-message (de type string).30 Demeure cependant quelques complications : nous voulons associer chaque micro-message à un utilisateur particulier ; nous accomplirons cela en enregistrant le user_id (identifiant dutilisateur) de lauteur du message. Le résultat apparait dans lillustration 2.3. Illustration 2.3: Le modèle de données pour les micro-messages.
    • 53Nous verrons à la section 2.3.3 (et plus profondément au chapitre 11) comment cet attribut user_id nouspermet dexprimer de façon succinte le fait quun utilisateur est potentiellement associé à plusieurs micro-messages.2.2 La ressouce Utilisateur (Users)Dans cette section, nous allons implémenter le modèle de données utilisateurs de la section 2.1.1, en mêmetemps quune interface web pour ce modèle. La combinaison des deux constituera une ressource utilisateurs(Users resource) qui nous permettra de penser les utilisateurs comme des objets (objects) qui peuvent êtrecréés, consultés, actualisés et supprimés sur le web via le protocole HTTP (le traducteur tient à préciser quilsinscrit en faux contre le fait de « penser les utilisateurs comme des objets » :-).NdT).Comme promis dans lintroduction, notre ressource utilisateurs sera créée par un programme de générationdéchaffaudage, présent de façon standard dans chaque projet Rails. Largument de la commande scaffold(échaffaudage) est la version singulier du nom de la ressource (dans ce cas : User), auquel on peut ajouter enparamètres optionnels les attributs du modèle de données :31 $ rails generate scaffold User nom:string email:string invoke active_record create db/migrate/20100615004000_create_users.rb create app/models/user.rb invoke test_unit create test/unit/user_test.rb create test/fixtures/users.yml route resources :users invoke scaffold_controller create app/controllers/users_controller.rb invoke erb create app/views/users create app/views/users/index.html.erb create app/views/users/edit.html.erb create app/views/users/show.html.erb create app/views/users/new.html.erb create app/views/users/_form.html.erb invoke test_unit create test/functional/users_controller_test.rb invoke helper create app/helpers/users_helper.rb invoke test_unit
    • 54 create test/unit/helpers/users_helper_test.rb invoke stylesheets create public/stylesheets/scaffold.css En ajoutant nom:string et email:string, nous nous sommes arrangés pour que le modèle de données User prenne la forme définie dans lillustration 2.2 (notez quil ny a nul besoin dinclure un paramètre pour lattribut id : il est créé automatiquement par Rails.32) Pour pouvoir utiliser lapplication Démo, nous avons dabord besoin de migrer (migrate) la base de données en utilisant Rake (Box 2.1): $ rake db:migrate == CreateUsers: migrating ==================================================== -- create_table(:users) -> 0.0017s == CreateUsers: migrated (0.0018s) =========================================== Cette procédure actualise simplement la base de données avec notre nouveau modèle de données utilisateurs (users). Nous en apprendrons plus à propos des migrations de la base de données au début de la section 6.1.1. Cela fait, nous pouvons lancer le serveur web local en utilisant la commande rails s, qui est le raccourci de la commande rails server: $ rails s Lapplication Démo devrait maintenant être accessible à ladresse http://localhost:3000/ de votre navigateur. Box 2.1.Rake Dans la tradition Unix, lutilitaire make joue un rôle important pour construire des programmes exécutable à partir de leur code source ; plus dun hacker sest entrainé à mémoriser la ligne : $ ./configure && make && sudo make install … utilisée couramment pour compiler du code sur les systèmes Unix (incluant Linux et les systèmes OS X de Mac).
    • 55 Rake signifie Ruby make, un langage similaire à make écrit en Ruby. Rails utilise intensivement Rake, spécialement pour les innombrables petites tâches administratives nécessaires quand on développe des applications web faisant un usage soutenu des bases de données. La commande rake db:migrate est probablement la plus courante, mais il y en existe beaucoup dautres ; vous pouvez trouver une liste des tâches propres aux bases de données en utilisant le paramètre -T db : $ rake -T db Pour voir toutes les tâches Rake utilisables, jouez la commande : $ rake -T La liste est impressionnante, mais ne vous inquiétez pas, vous navez pas besoin de connaitre toutes, ni même la plupart, de ces commandes. En arrivant à la fin de ce Tutoriel Rails, vous connaitrez les principales dentre elles. 2.2.1 Un tour de lutilisateur Rejoindre lurl racine http://localhost:3000/ affichera la même page Rails par défaut présentée dans lillustration 1.3, mais en générant léchaffaudage de la ressource utilisateurs nous avons également créé un grand nombre de pages permettant de manipuler ces utilisateurs. Par exemple, la page pour afficher les utilisateurs, en ajoutant /users (http://localhost:3000/users) ou et la page pour créer un nouvel utilisateur en ajoutant /users/new.33 La suite de cette section présentera un tour rapide de ces pages utilisateurs. En suivant ce tour, il peut être utile de se référer à la table 2.1, qui présente la correspondance entre les pages et les URLs respectives. URL Action Page/users index Page listant les utilisateurs/users/1 show Page de lutilisateur did 1/users/new new Page pour créer un nouvel utilisateur/users/1/edit edit Page dédition de lutilisateur did 1 Table 2.1: Correspondance entre pages et URLs de la ressource Users.
    • 56 Nous commençons avec la page listant tous les utilisateurs de notre application, appelée index ; comme nous pouvons nous y attendre, il ny a pour le moment aucun utilisateur (illustration 2.4). Illustration 2.4: La page index initiale de la ressource utilisateurs (/users). Pour créer un nouvel utilisateur, visitons la page new, de lillustration 2.5 (au chapitre 8, cette page deviendra la page dinscription). Illustration 2.5: La page de nouvel utilisateur (/users/new).
    • 57Nous pouvons créer un nouvel utilisateur en entrant les valeurs du nom et de lemail dans les champs detexte correspondants et en cliquant ensuite sur le bouton de création (Create User). Le résultat est la pagedutilisateur show (afficher) de lillustration 2.6 la présente (le message vert de bienvenue est obtenu enutilisant la messagerie flash qui sera abordée dans la section 8.3.3). Notez que lURL est à présent/users/1 ; comme vous pouvez vous en douter, le nombre 1 est lidentifiant de lutilisateur (lattribut id)de lillustration 2.2. À la section 7.3, cette page deviendra la page du profil de lutilisateur. Illustration 2.6: La page affichant lutilisateur (/users/1 — version anglaise).Pour modifier les informations de lutilisateur, nous nous rendons sur la page edit (modifier)(Illustration 2.7). En modifiant les informations de lutilisateur et en cliquant sur le bouton dactualisation(Update User), nous changerons les informations dans lapplication Démo (Illustration 2.8) (comme nous leverrons en détail en abordant le chapitre 6, les données de lutilisateur sont stockées dans la base de données).Nous ajouterons les fonctionnalités edit/update à lApplication Exemple dans la section 10.1.
    • 58 Illustration 2.7: La page dédition de lutilisateur (/users/1/edit — version anglaise). Illustration 2.8: Un utilisateur dont les informations ont été actualisées. Nous allons maintenant créer un second utilisateur en revisitant la page new et en soumettant un second jeu dinformations (Il peut être utile de cliquer dabord le lien « Back » pour revenir à la liste des utilisateurs. NdT) ; lindex (la liste) en résultant est présenté dans lillustration 2.9. La section 10.3 permettra de polisser cette page dindex affichant tous les utilisateurs.
    • 59 Illustration 2.9: La page dindex des utilisateurs (/users) avec un autre utilisateur.Ayant vu comment créer, afficher et éditer les utilisateurs, nous en arrivons maintenant à voir comment lesdétruire (Illustration 2.10). Vous devriez pouvoir vérifier quun clic sur le lien mis en exergue danslillustration 2.10 détruit le second utilisateur, réduisant la page dindex à un seul utilisateur (si cela nefonctionne pas, assurez-vous que JavaScript est bien activé dans votre navigateur ; Rails utilise JavaScript pourexécuter la requête de destruction dun utilisateur). La section 10.4 ajoute la suppression de lutilisateur àlApplication Exemple, en prenant soin de restreindre lusage de cette fonctionnalité aux seuls administrateursdu site (utilisateurs de classe « administrateur »).
    • 60 Illustration 2.10: Destruire un utilisateur. 2.2.2 MVC en action Maintenant que nous avons accompli un rapide aperçu de la ressource utilisateurs (Users), examinons une partie singulière de cette ressource dans le contexte du concept Modèle-Vue-Contrôleur introduit à la section 1.2.6. Notre stratégie sera de décrire le résultat dun « hit » typique de navigateur — une visite à la page dindex des utilisateurs, à ladresse /users — en terme de MVC (Illustration 2.11). Illustration 2.11: Un diagramme détaillé du MVC dans Rails. 1. Le navigateur reçoit une requête pour lURL /users ;
    • 61 2. Rails route (dirige) /users vers une action index dans le contrôleur Users (Utilisateurs) ; 3. Laction index demande au modèle User de récupérer tous les utilisateurs (User.all) ; 4. Le modèle User tire tous les utilisateurs de la base de données ; 5. Le modèle User retourne au contrôleur la liste des utilisateurs ; 6. Le contrôleur place les utilisateurs dans la variable @users, variable qui est passée à la vue index ; 7. La vue utilise le code Ruby embarqué pour rendre la page au format HTML ; 8. Le contrôleur renvoie le code HTML au navigateur, qui affiche enfin la page.34Nous commençons par une requête provenant du navigateur — par exemple, après avoir tapé une URL dans labarre dadresse du navigateur ou avoir cliqué un lien (étape 1 de lillustration 2.11). Cette requête atteint lerouteur Rails (étape 2), qui la dispatche vers laction de contrôleur adéquate basée sur lURL (et, comme nousle verrons dans le Box 3.1, le type de requête). Le code pour créer la carte des URLs utilisateur vers les actionsde contrôleur de la ressource Users est présenté dans lillustration 2.2 ;35 ce code définit effectivement la tabledes paires URL/action définies dans la table 2.1. Extrait 2.2. Le routage Rails, avec une règle pour la ressource Users. config/routes.rb DemoApp::Application.routes.draw do resources :users . . . endLes pages du tour de la section 2.2.1 correspondent aux actions dans le controller des utilisateurs, qui est une collection dactions correspondantes ; le contrôleur généré par lécheffaudage est présentéschématiquement dans lextrait 2.3. Remarquez la notation class UsersController <ApplicationController ; cest un exemple de classe Ruby avec héritage (nous discuterons brièvement deshéritages de classe à la section 2.3.4 et couvrirons ces deux sujets plus en détail à la section 4.4). Extrait 2.3. Schéma du contrôleur utilisateurs (UsersController). app/controllers/users_controller.rb class UsersController < ApplicationController
    • 62 def index . . . end def show . . . end def new . . . end def create . . . end def edit . . . end def update . . . end def destroy .
    • 63 . . end end Vous pouvez noter quil y a plus dactions que de pages ; les actions index, show, new et edit correspondent toutes à la section 2.2.1, mais on trouve en supplément les actions create, update, et destroy. Ces actions ne retournent pas, typiquement, de pages (bien quelles puissent le faire dans certains cas) ; leur rôle principal est plutôt de modifier les informations des utilisateurs dans la base de données. Cette liste complète des actions du contrôleur, résumée dans la table 2.2, représente limplémentation de larchitecture REST en Rails (Box 2.2). Notez, dans la table 2.2, quil y a certains chevauchements dans les URLs ; par exemple, les actions utilisateur show (afficher) et update (actualiser) correspondent toutes deux à lURL /users/1. La distinction entre lune et lautre se fait grâce à la méthode de requête HTTP (HTTP request method) à laquelle elles répondent. Nous en apprendrons plus à propos de ces méthodes de requête HTTP à la section 3.2.2. Requête HTTP URL Action ProposGET /users index page listant tous les utilisateursGET /users/1 show page affichat lutilisateur did 1GET /users/new new page pour créer un nouvel utilisateurPOST /users create créer un nouvel utilisateurGET /users/1/edit edit page pour éditer lutilisateur did 1PUT /users/1 update actualiser lutilisateur did 1DELETE /users/1 destroy détruire lutilisateur did 1 Table 2.2: Routages RESTful fournit par la ressource Users dans lextrait 2.2. Box 2.2.REpresentational State Transfer (REST) Si vous en lisez plus sur le développement web Ruby on Rails, vous noterez un grand nombre dallusions à « REST », acronyme de REpresentational State Transfer (NdT. Qui pourrait se traduire par « Représentation de létat de transfert »). REST est un style darchitecture pour le développement des systèmes en réseau et les logiciels tels que le World Wide Web et les applications web. Bien que la théorie REST soit quelque peu abstraite, dans le contexte des applications Rails REST signifie que la plupart des composants de lapplication
    • 64 (comme les utilisateurs ou les micro-messages) sont modelés comme des ressources qui peuvent être créées, lues, actualisées et supprimées — opérations qui correspondent aux opérations CRUD des bases de données relationnelles tout comme aux quatres méthode de requête HTTP fondamentales : POST, GET, PUT et DELETE (nous en apprendrons plus sur les requêtes HTTP à la section 3.2.2 et spécialement dans le Box 3.1). En tant que développeur dapplication Rails, le style RESTful vous aide à faire des choix quant à quel contrôleur ou quelles actions écrire : vous structurez simplement lapplication en utilisant les ressources qui peuvent être créées, lues, actualisées ou supprimées. Dans les cas des utilisateurs et des micro-messages, ce processus est simple, puisquelles sont par défaut des ressources de plein droit. Au chapitre 12, nous aborderons un exemple où les principes REST nous permettent de modéliser un problème subtil, le « suivi des utilisateurs », dune façon naturelle et efficiente. Pour examiner la relation entre contrôleur utilisateur et modèle utilisateur, concentrons-nous sur une version simplifiée de laction index vue dans lextrait 2.4.36 Extrait 2.4. Laction utilisateur index simplifiée pour lapplication Démo. app/controllers/users_controller.rb class UsersController < ApplicationController def index @users = User.all end . . . end Cette action index implémente la ligne @users = User.all (étape 3), qui demande au modèle utilisateur de récupérer de la base de données une liste de tous les utilisateus (étape 4), et de les placer dans une variable @users (prononcez “hate-iou-zeur-ze”) (étape 5). Le modèle utilisateur lui-même apparait dans lextrait 2.5 ; bien que plutôt ordinaire, il arrive équipé dun grand nombre de fonctionnalités grâce à lhéritage de classe (section 2.3.4 et section 4.4). En particulier, en utilisant la librairie Rails de nom Active Record, le code de lextrait 2.5 se débrouille pour que User.all retourne tous les utilisateurs. Extrait 2.5. Le modèle Utilisateur (User) pour lapplication Démo. app/models/user.rb class User < ActiveRecord::Base end
    • 65Une fois la variable @users renseignée, le contrôleur appelle la vue (view) (étape 6), vue de lextrait 2.6.Les variables commençant par le caractère @, appelées variables dinstance (instance variables), sontautomatiquement accessibles dans la vue ; dans ce cas précis, la vue index.html.erb de lextrait 2.6 boucleitérativement sur la liste @users et retourne une ligne HTML à chaque itération.37 Extrait 2.6. La vue pour lindex utilisateurs (vous nêtes pas supposés le comprendre tout de suite). app/views/users/index.html.erb <h1>Liste des utilisateurs</h1> <table> <tr> <th>Nom</th> <th>Email</th> <th></th> <th></th> <th></th> </tr> <% @users.each do |user| %> <tr> <td><%= user.nom %></td> <td><%= user.email %></td> <td><%= link_to Montrer, user %></td> <td><%= link_to Modifier, edit_user_path(user) %></td> <td><%= link_to Détruire, user, :confirm => Êtes-vous certain ?, :method => :delete %></td> </tr> <% end %> </table> <br /> <%= link_to New User, new_user_path %>La vue convertit son contenu en code HTML (étape 7), qui est alors retourné par le contrôleur au navigateur quiva lafficher (étape 8).
    • 66 2.2.3 Faiblesse de cette ressource utilisateur Bien quexcellent pour donner un aperçu général de Rails, la ressource Utilisateurs de léchaffaudage souffre dun certain nombre de faiblesses. • Pas de validation de données. Notre modèle utilisateur accepte aussi bien et sans broncher les noms vides ou les adresses mail invalides ; • Pas dauthentification. Nous navons aucune notion didentification ou de déconnexion, et aucun moyen de prévenir un utilisateur quelconque dexécuter une opération quelconque ; • Pas de tests. Techniquement, ça nest pas tout à fait juste — léchaffaudage inclut des tests rudimentaires — mais les test générés automatiquement son laids et rigides, et ils ne testent pas la validation des données, lauthentification ou autres besoins personnalisés ; • Pas de mise en page. Il ny a pas de charte graphique ; • Pas de compréhension réelle. Si vous comprenez le code de léchaffaudage, alors vous navez certainement aucune besoin de lire ce livre. 2.3 La ressource micro-messages Après avoir généré et exploré la ressource utilisateurs, nous nous tournons à présent vers la ressource associée des micro-messages. Tout au long de cette section, je recommande de comparer les éléments de la ressource micro-messages avec les éléments analogues de la ressource utilisateur de la section 2.2 ; vous devriez constater que les deux ressources sapparente lune à lautre en de nombreux points. La structure RESTful des applications Rails est mieux « absorbée » grâce à cette sorte de répétition formelle ; vraiment, constater le parallèle de structure entre les ressources Utilisateurs et Micro-messages, même à cette étape peu avancée, est une des raisons dêtre première de ce chapitre (comme nous le verrons, lécriture dapplications plus solides que lexemple enfantin de ce chapitre demande des efforts considérables — nous ne reverrons pas la ressource Micro-messages avant le chapitre 11 — et je ne voulais pas retarder son introduction aussi loin). 2.3.1 Un petit tour du micro-message Comme avec la ressource utilisateurs, nous allons générer le code de léchaffaudage de la ressource des micro- messages (Microposts) en utilisant le code rails generate scaffold, dans ce cas en implémentant le modèle de données de lillustration 2.3:38 $ rails generate scaffold Micropost content:string user_id:integer invoke active_record create db/migrate/20100615004429_create_microposts.rb create app/models/micropost.rb invoke test_unit create test/unit/micropost_test.rb
    • 67 create test/fixtures/microposts.yml route resources :microposts invoke scaffold_controller create app/controllers/microposts_controller.rb invoke erb create app/views/microposts create app/views/microposts/index.html.erb create app/views/microposts/edit.html.erb create app/views/microposts/show.html.erb create app/views/microposts/new.html.erb create app/views/microposts/_form.html.erb invoke test_unit create test/functional/microposts_controller_test.rb invoke helper create app/helpers/microposts_helper.rb invoke test_unit create test/unit/helpers/microposts_helper_test.rb invoke stylesheets identical public/stylesheets/scaffold.cssPour actualiser notre base de données avec le nouveau modèle de données, il est nécessaire de demander unenouvelle migration comme dans la section 2.2 : $ rake db:migrate == CreateMicroposts: migrating =============================================== -- create_table(:microposts) -> 0.0023s == CreateMicroposts: migrated (0.0026s) ======================================Maintenant nous sommes en mesure de créer des micro-messages de la même façon que nous avons créé desutilisateurs dans la section 2.2.1. Comme vous pouvez vous en douter, le générateur déchaffaudage a actualiséle fichier de routage Rails avec des règles pour la ressource Microposts (Micro-messages), comme le montrelextrait 2.7.39 Comme pour les utilisateurs, la règle des routages resources :microposts dirige les URLsdes micro-messages vers les actions correspondantes dans le contrôleur Microposts, suivant la table 2.3. Extrait 2.7. Le routage Rails, avec une nouvelle règle pour les ressources Microposts. config/routes.rb
    • 68 DemoApp::Application.routes.draw do resources :microposts resources :users . . . end Requête HTTP URL Action Page ou Opération GET /microposts index Page listant tous les micro-messages GET /microposts/1 show Page affichant le micro-message did 1 GET /microposts/new new Page créant une nouveau micro-message POST /microposts create Crée le nouveau micro-message GET /microposts/1/edit edit Page pour éditer le micro-message did 1 PUT /microposts/1 update Actualiser le micro-message did 1 DELETE /microposts/1 destroy Détruire le micro-message did 1 Table 2.3: Routes RESTful fournies par la ressource Microposts de lextrait 2.7. La contrôleur Microposts lui-même apparait dans sa forme schématique dans lextrait 2.8. Notez que, hormis le MicropostsController à la place du UsersController, lextrait 2.8 est identique au code de lextrait 2.3. Cest une conséquence directe de larchitecture REST commune aux deux ressources. Extrait 2.8. Le contrôleur Microposts en forme schématique. app/controllers/microposts_controller.rb class MicropostsController < ApplicationController def index . . . end
    • 69 def show . . . end def new . . . end def create . . . end def edit . . . end def update . . . end def destroy . . . endend
    • 70 Pour faire quelques micro-messages, nous entrons les informations dans la page de création des micro-messages, /microposts/new de lillustration 2.12. Illustration 2.12: La page de création du micro-message (/microposts/new). À ce stade, poursuivez et créez un ou deux micro-messages, en vous assurant que lun au moins possède un user_id de 1 pour correspondre à lidentifiant du premier utilisateur créé dans la section 2.2.1. Le résultat devrait sapparenter à celui de lillustration 2.13. Illustration 2.13 : La page dindex des micro-messages (/microposts).
    • 712.3.2 Appliquer le micro aux micro-messagesTout micro-message digne de ce nom devrait avoir les moyens de faire respecter la longueur de son texte.Implémenter cette contrainte en Rails est très simple grâce aux validations ; pour accepter les micro-messagesdau plus 140 caractères (à la Twitter), nous utilisons une validation de longueur (length). À ce stade, vousdevriez ouvrir le fichier app/models/micropost.rb dans votre éditeur de texte ou votre IDE et le rempliravec le contenu de lextrait 2.9 (lutilisation de validates dans lextrait 2.9 est caractéristique de la version 3de Rails ; si vous avez travaillé précédemment avec Rails 2.3, vous devriez comparer cet usage à lutilisation delancien validates_length_of). Extrait 2.9. Contraindre la longueur maximale des micro-messages à 140 caractères avec la validation de longueur. app/models/micropost.rb class Micropost < ActiveRecord::Base validates :content, :length => { :maximum => 140 } endLe code de lextrait 2.9 peut sembler plutôt mystérieux — nous couvrirons la question des validations plusprofondément à la section 6.2 — mais ses effets sont significatifs si nous nous rendons à la page de création desmicro-messages et que nous entrons un messages de plus de 140 caractères. Comme nous pouvons le voir danslillustration 2.14, Rails renvoie un message derreur indiquant que le contenu du message est trop long (nousen apprendrons plus à propos des messages derreur à la section 8.2.3.) Illustration 2.14 : Message derreur à léchec de la création dun message.
    • 72 2.3.3 Un utilisateur possède plusieurs micro-messages Lune des fonctionnalités les plus puissantes de Rails est la possibilité de former des associations entre plusieurs modèles de données. Dans le cas de notre modèle utilisateur, chaque utilisateur produit potentiellement plusieurs messages. Nous pouvons exprimer cela en code en actualisant les modèles User (Utilisateur) et Micropost (Micro-message) conformément à lextrait 2.10 et lextrait 2.11. Extrait 2.10. Un utilisateur possède plusieurs micro-messages. app/models/user.rb class User < ActiveRecord::Base has_many :microposts end Extrait 2.11. Un micro-message appartient à un utilisateur. app/models/micropost.rb class Micropost < ActiveRecord::Base belongs_to :user validates :content, :length => { :maximum => 140 } end Nous pouvons visualiser le résultat de cette association dans lillustration 2.15. Grâce à la colonne user_id de la table microposts, Rails (en utilisant Active Record) peut déduire les micro-messages associés à chaque utilisateur. Illustration 2.15 : Lassociation entre les micro-messages et les utilisateurs. Au chapitre 11 et au chapitre 12, nous utiliserons lassociation entre utilisateurs et micro-messages pour afficher tous les micro-messages dun utilisateur donné et pour construire une alimentation en messages à la façon de Twitter. Pour le moment, nous pouvons examiner les implications de cette association en utilisant la console, qui est un outil utile pour interagir avec les applications Rails. Nous invoquons tout dabord la console avec
    • 73rails console en ligne de commande, puis récupérons le premier utilisateur dans la base de données entapant User.first (en plaçant le résultat dans la variable first_user — premier_utilisateur) :40 $ rails console >> first_user = User.first => #<User id: 1, nom: "Michael Hartl", email: "michael@example.org", created_at: "2010-04-03 02:01:31", updated_at: "2010-04-03 02:01:31"> >> first_user.microposts => [#<Micropost id: 1, content: "First micropost!", user_id: 1, created_at: "2010-04-03 02:37:37", updated_at: "2010-04-03 02:37:37">, #<Micropost id: 2, content: "Second micropost", user_id: 1, created_at: "2010-04-03 02:38:54", updated_at: "2010-04-03 02:38:54">] >> exit(Jajoute la dernière ligne pour montrer comment sortir de la console Rails, et sur la plupart des systèmes vouspouvez taper Ctrl-d pour obtenir le même résultat). Ici nous avons obtenu les micro-messages de lutilisateur enutilisant le code first_user.microposts : grâce à ce code, Active Record retourne automatiquement tousles micro-messages dont le user_id est égal à lidentifiant du first_user (1, dans ce cas précis). Nous enapprendrons plus à propos des facilités des associations avec Active Record au chapitre 11 et au chapitre 12.2.3.4 Hiérarchie des héritagesNous terminons notre discussion sur lapplication Démo par une brève description des hiérarchies des classescontrôleur (controller) et modèle (model) dans Rails. Cette discussion naura vraiment de sens pour vous que sivous possédez une expérience de la programmation orientée objet (OOP) ; si vous navez pas étudié lOOP,sentez-vous libre de sauter cette section. En particulier, si vous nêtes pas familier des classes (en discussion à lasection 4.4), je vous suggère de revenir ultérieurement à cette section.Nous commençons par lhéritage de structure pour les modèles. En comparant lextrait 2.12 et lextrait 2.13,vous voyez que le modèle User (Utilisateur) tout comme le modèle Micropost (Micro-message) hérite deActiveRecord::Base (via le signe <), qui est la classe de base pour les modèles fournis par ActiveRecord ; undiagramme résumant cette relation est présenté par lillustration 2.16. Cest en héritant deActiveRecord::Base que nos modèles dobjet acquièrent la faculté de communiquer avec la base dedonnées, traitent les colonnes de cette base comme des attributs Ruby, etc. Extrait 2.12. La classe-objet User, avec héritage. app/models/user.rb class User < ActiveRecord::Base .
    • 74 . . end Extrait 2.13. La classe-objet Micropost, avec héritage. app/models/micropost.rb class Micropost < ActiveRecord::Base . . . end illustration 2.16: La hiérarchie dhéritage pour les modèles User et Micropost. La structure héritée pour les contrôleurs est juste un peu plus complexe. En comparant lextrait 2.14 avec lextrait 2.15, nous voyons que le contrôleur Users (Utilisateurs) comme le contrôleur Microposts (Micro- messages) héritent du contrôleur Application. En examinant lextrait 2.16, nous voyons que ApplicationController lui-même hérite de ActionController::Base ; cest la classe-objet de base pour les contrôleurs fournis par la librairie Rails « Action Pack ». Les relations entre ces classes sont présentées dans lillustration 2.17. Extrait 2.14. La classe-objet UsersController, avec lhéritage. app/controllers/users_controller.rb class UsersController < ApplicationController . . end
    • 75 Extrait 2.15. La classe-objet MicropostsController, avec héritage. app/controllers/microposts_controller.rb class MicropostsController < ApplicationController . . . end Extrait 2.16. La classe-objet ApplicationController, avec héritage. app/controllers/application_controller.rb class ApplicationController < ActionController::Base . . . end Illustration 2.17: La hiérarchie dhéritage pour les contrôleurs Users (Utilisateurs) et Microposts (Micro- messages).Comme pour lhéritage du modèle, en héritant en fin de compte de ActionController::Base, le contrôleurUsers tout comme le contrôleur Microposts héritent dun grand nombre de fonctionnalités, telles que lacapacité de manipuler les objets du modèle, de filtrer les requêtes HTTP entrantes, et de rendre les vues en codeHTML. Puisque tous les contrôleurs Rails héritent de ApplicationController, les règles définies dans lecontrôleur de lapplication sont automatiquement appliquées à toutes les actions à lintérieur de lapplication.Par exemple, nous verrons dans la section 8.2.4 comment une règle définie dans le contrôleur de lapplication
    • 76 nous permet de filtrer tous les mots de passe de tous les fichiers, évitant ainsi léventualité dun sérieux trou de sécurité. 2.3.5 Déployer lapplication Démo Avec lachèvement de la ressource Micropost, il est temps à présent de « pusher » le repository vers GitHub :41 $ git add . $ git commit -a -m "Fin de lapplication demo" $ git push Vous pouvez également déployer lapplication démo vers Heroku : $ heroku create $ git push heroku master $ heroku rake db:migrate (Si cela ne fonctionne pas chez vous, voyez la note juste au-dessus de lextrait 1.8 pour une solution possible) Remarquez la ligne finale ici, qui lance la migration de la base de données sur le serveur Heroku. Cela actualise la base de données sur Heroku avec le modèle de données user/micropost nécessaire. Si vous voulez également « pusher » les données (data), vous pouvez le faire en utilisant le gem taps et db:push : $ [sudo] gem install taps $ heroku db:push 2.4 Conclusion Nous sommes arrivés à la fin de la « vue den haut » dune application Rails. Lapplication Démo développée dans ce chapitre possède plusieurs points forts et une multitude de faiblesses. Points forts • Aperçu général de Rails ; • Introduction au principe M-V-C ; • Avant goût de larchitecture REST ; • Initiation à la modélisation de données ; • Une application à base de données fonctionnelle, en mode production.42 Points faibles • Pas de mise en forme ni de style ;
    • 77 • Pas de pages statiques (telles que la page daccueil ou laide) ; • Pas de mot de passe utilisateur ; • Pas davatars utilisateurs ; • Pas didentification ; • Pas de sécurité ; • Pas dassociation automatique entre utilisateur et micro-messages ; • Pas de notion de suivi de message (du côté auteur comme lecteur) ; • Pas dalimentation de micro-messages ; • Pas de « Développement Dirigé par le Test » ; • Pas de réelle compréhension…La suite de ce tutoriel sappuie sur les points forts pour continuer de développer et se consacre à éliminersystématiquement tous les points faibles.
    • 78 Chapitre 3 Pages statiques courantes Dans ce chapitre, nous commencerons à développer lApplication Exemple qui nous servira de base tout au long de ce tutoriel. Bien que cette Application Exemple puissent éventuellement avoir des utilisateurs, des micro-messages et un patch dauthentification complet, nous commencerons par un sujet en apparence plus limité : la création de pages statiques. Malgré son apparente simplicité, la création de pages statiques est une exercice hautement formateur, riche en implications — un parfait commencement pour notre application naissante ! Bien que Rails soit destiné à créer des sites web dynamiques avec base de données, il excèle aussi à faire ce genre de pages statiques que nous pouvons créer simplement par des fichiers de code HTML brut. En fait, utiliser Rails même pour des pages statiques produit un avantage certain : nous pouvons ajouter seulement une petite quantité de code dynamique. Nous verrons comment dans ce chapitre. Chemin faisant, nous aurons un avant-goût du testing automatique, qui nous aidera à nous sentir plus confiant en notre code. Plus loin encore, avoir une bonne suite de tests nous permettra de restructurer notre code en toute confiance, en changeant sa forme sans altérer sa fonction. Comme au chapitre 2, avant de commencer nous avons besoin de créer un nouveau projet Rails, cette fois appelé sample_app (Application Exemple) : $ cd ~/rails_projects $ rails new sample_app -T $ cd sample_app Ici, loption -T ajoutée à la commande rails demande à Rails de ne pas générer le dossier test associé au frameword par défaut Test::Unit. Cela ne signifie pas que nous nexécuterons pas de tests ; au contraire, dès la section 3.2 nous utiliserons un framework de test alternatif appelé RSpec pour réaliser une suite approfondie de tests. Comme à la section 2.1, notre prochaine étape consiste à utiliser un éditeur de texte pour actualiser le fichier Gemfile avec les gems nécessaires à notre application (souvenez-vous que vous pouvez avoir besoin de la version 1.2.5 du gem sqlite3-ruby, en fonction de votre système dexploitation). Dun autre côté, pour lApplication Exemple, nous aurons aussi besoin de deux nouveaux gems : le gem RSpec et le gem de la librairie RSpec spécifique à Rails. Le code pour les inclure est montré dans lextrait 3.1 (note : si vous préférez installer tous les gems nécessaire à lApplication Exemple, vous devriez utiliser le code de lextrait 10.42 dès à présent). Extrait 3.1. Un Gemfile pour lApplication Exemple. source http://rubygems.org
    • 79 gem rails, 3.0.7 gem sqlite3-ruby, 1.3.2, :require => sqlite3 group :development do gem rspec-rails, 2.5.0 end group :test do gem rspec, 2.5.0 gem webrat, 0.7.1 endCeci inclut rspec-rails en mode développement, de telle sorte que nous avons accès aux génératersspécifiques à RSpec, et il inclut également rspec en mode de test dans le but de jouer les tests (nousincluons aussi un gem pour Webrat, un utilitaire de test qui a lhabitude dêtre installé automatiquement en tantque dépendance mais a besoin ici dêtre inclus explicitement). Pour installer et inclure les gems RSpec, nousutilisons bundle install comme dhabitude : $ [sudo] bundle installPour demander à Rails dutiliser RSpec plutôt que Test::Unit (qui est utilisé par défaut), nous avonsbesoin dinstaller les fichiers nécessaires à RSpec. Cela peut être accompli avec rails generate : $ rails generate rspec:installCeci fait, il ne nous reste plus quà initialiser le repository Git :43 $ git init $ git add . $ git commit -m "Depot Initial"Tout comme la première application, je suggère dactualier le fichier README (situé dans le dossier racine delapplication) pour être plus utile et descriptif, comme montré dans lextrait 3.2. Extrait 3.2. Un fichier README amélioré pour lApplication Exemple. # Tutoriel Ruby on Rails : Application Exemple
    • 80 Cest lApplication Exemple pour le [*Tutoriel Ruby on Rails : Apprendre Rails par lexemple*](http://railstutorial.org/) par [Michael Hartl](http://michaelhartl.com/). Changez ce fichier pour utiliser lextension markdown et déposez les changements : $ git mv README README.markdown $ git commit -a -m "Amelioration du README" Illustration 3.1: Créer le repository de lApplication Exemple sur GitHub. Puisque nous utiliserons cette Application Exemple jusquà la fin de ce livre, cest une bonne idée de faire un repository sur GitHub (illustration 3.1) et de la « pusher » : $ git remote add origin git@github.com:<username>/sample_app.git $ git push origin master (Notez que, comme résultat de cette étape, le repository sur http://github.com/railstutorial/sample_app contient le code source de toute lApplication Exemple. Vous êtes encouragé à le consulter en guise de référence, avec deux mises en garde : (1) vous en apprendrez beaucoup plus si vous écrivez vous-même le code source, plutôt que de vous appuyer sur une version achevée ; (2) Il peut exister des différences mineures entre le repository GitHub et le code de ce livre. Cela est dû dun côté à lincorporation des exercices du livre et dun autre côté au repository utilisé dans les screencasts du tutoriel Rails, qui inclut quelques tests supplémentaires). Bien entendu, nous pouvons optionnellement déployer lapplication vers Heroku même à ce stade peu avancé :
    • 81 $ heroku create $ git push heroku master(Si cela ne fonctionne pas pour vous, consultez la note au-dessus de lextrait 1.8 pour des solutions possibles.) Àmesure que vous avancez dans le livre, je vous recommande de « pusher » et de déployer lapplicationrégulièrement avec : $ git push $ git push herokuCela posé, nous sommes prêts à développer lApplication Exemple.3.1 Pages statiquesRails propose deux principales manières de créer des pages web statiques. Primo, Rails peut utiliser des pagesvraiment statiques contenant du code HTML brut. Secondo, Rails nous permet de définir des vues (views)contenant du HTML brut, que Rails peut rendre de telle sorte que les serveurs web puisse les envoyer aunavigateur.Afin de prendre nos repères, il est utile de se remémorer la struture du dossier Rails de la section 1.2.3(illustration 1.2). Dans cette section, nous travaillerons principalement sur les dossiers app/controllers(contenant les contrôleurs, controllers) et app/views (contenant les vues, views) (à la section 3.2, nousajouterons même un dossier de notre cru).3.1.1 Vraies pages statiquesCommençons avec les vraies pages statiques. Rappelez-vous, de la section 1.2.5, que toute application Rails seprésente comme une application fonctionnelle minimale générée par le script rails, avec une page daccueil àladresse http://localhost:3000/ (illustration 1.3).
    • 82 Illustration 3.2: Le fichier public/index.html. Pour savoir doù cette page provient, jetez-un coup dœil au fichier public/index.html (illustration 3.2). Comme le fichier contient ses propres informations de mise en page, cest un peu le bazard, mais ça fonctionne : par défaut, Rails renvoie tout fichier du dossier public directement à votre navigateur.44 Dans le cas du fichier spécial index.html, vous navez même pas à indiquer le nom du fichier dans lURL, puisque le nom index.html est le nom de fichier par défaut. Vous pouvez linclure si vous voulez, cependant ; ladresse : http://localhost:3000/ … et ladresse : http://localhost:3000/index.html … sont équivalentes. Comme vosu pouvez vous y attendre, si nous le voulons nous pouvons fabriquer nos propres fichiers HTML statiques, et les déposer dans le même dossier public que le fichier index.html. Par exemple, créons un fichier avec un message de salutation amicale (extrait 3.3) :45
    • 83 $ mate public/hello.html Extrait 3.3. Un fichier HTML classique, avec une salutation amicale. public/hello.html <!DOCTYPE html> <html> <head> <title>Salutations</title> </head> <body> <p>Salut le monde&nbsp;!</p> </body> </html>Nous pouvons voir dans lextrait 3.3 la structure caractéristique dun document HTML : un type de document(document type), ou doctype, qui est une déclaration aux navigateurs de la version du code HTML que nousemployons (dans ce cas, HTML5) ;46 une section head, dans le cas présent contenant le texte « Greeting » àlintérieur dune balise title ; et une section body (corps), dans le cas présennt avec le texte « Salut lemonde& ! » à lintérieur dune balise paragraphe p (lindentation du texte est optionnelle — HTML nest passensible aux espaces « blanches », et ignore aussi bien les tabulations que les espaces — mais cela rend lastructure du document plus lisible) Comme attendu, en visitant ladressehttp://localhost:3000/hello.html, Rails rend aussitôt cette page (illustration 3.3).Notez que le titre affiché au sommet de la fenêtre du navigateur dans lillustration 3.3 correspond au contenu dela balise title, cest-à-dire « Salutations ».
    • 84 Illustration 3.3: Notre vrai fichier HTML statique (http://localhost:3000/hello.html). Puisque ce fichier na valeur que de démonstration, nous navons pas vraiment envie quil fasse partie de notre applicaton, donc il est peut-être mieux de le détruire une fois leuphorie de cette création passée : $ rm public/hello.html Nous laisserons de côté le fichier index.html pour le moment, mais nous pourrions bien sûr leffacer aussi, éventuellement : nous ne désirons pas que la racine de notre application soit la page par défaut de Rails montrée dans lillustration 1.3. Nous verrons dans la section 5.2 comment changer ladresse http://localhost:3000/ pour quelle pointe vers autre chose que le fichier public/index.html. 3.1.2 Les pages statiques avec Rails La capacité de retourner des fichiers HTML statiques est bien, mais cela nest pas particulièrement utile pour créer des applications web dynamiques. Dans cette section, nous ferons un premier pas vers la création de pages dynamiques en créant un jeu dactions Rails, qui sont une façon plus puissante de définir des URLs que les fichiers statiques.47 Les actions Rails sont regroupées à lintérieur de contrôleurs (controllers) (le « C » dans le MVC de la section 1.2.6), qui contient des jeux dactions reliées par un but commun. Nous avons eu un aperçu des contrôleurs au chapitre 2, et approfondirons pour en avoir une compréhension plus profonde une fois que nous explorerons mieux larchitecture REST (à partir du chapitre 6) ; par essence, un contrôleur est le conteneur dun groupe de pages web (peut-être dynamiques). Pour commencer, rappelez-vous, section 1.3.5, que lorsque nous utilisons Git, cest une bonne pratique dexécuter notre travail sur une autre branche que la branche maitresse. Si vous utilisez Git pour le contrôle de versions, vous devriez jouer ces commandes : $ git checkout -b static-pages Rails est fourni avec un script pour créer des contrôleurs, appelé generate ; tout ce dont il a besoin pour faire son tour de magie, cest le nom du contrôleur. Puisque nous créons ce contrôleur pour traiter les pages statiques (courantes), nous lappellerons simplement le contrôleurs Pages, et planifierons de faire des actions pour la page daccueil, la page de contact et la page « À Propos ». Le script generate prend une liste optionnelle dactions, donc nous incluerons certaines de ces actions initiales directement en ligne de commande : Extrait 3.4. Générer un contrôleur de Pages. $ rails generate controller Pages home contact
    • 85 create app/controllers/pages_controller.rb route get "pages/contact" route get "pages/home" invoke erb create app/views/pages create app/views/pages/home.html.erb create app/views/pages/contact.html.erb invoke rspec create spec/controllers/pages_controller_spec.rb create spec/views/pages create spec/views/pages/home.html.erb_spec.rb create spec/views/pages/contact.html.erb_spec.rb invoke helper create app/helpers/pages_helper.rb invoke rspec(notez que, puisque nous avons installé RSpec avec le code rails generate rspec:install, la générationde contrôleur crée automatiquement des fichiers de test RSpec dans le dossier spec/).Ici, jai intentionnellement « oublié » la page À Propos pour que nous puissions voir comment lajouter « à lamain » à la section 3.2.La génération de pages de contrôleurs de lextrait 3.4 actualise automatiquement le fichier de routage, nomméconfig/routes.rb, que Rails utilise pour trouver la correspondance entre URLs et pages web. Cest notrepremière rencontre avec le dossier config, donc il peut être utile dy jeter un coup dœil (illustration 3.4). Ledossier config est lendroit où Rails rassemble les fichiers nécessaires à la configuration de lapplication —doù bien sûr son nom.
    • 86 Illustration 3.4: Contenu du dossier config de lApplication Exemple. Puis nous avons généré les actions home et contact, le routage de fichier possède déjà des règles pour chacun deux, comme on peut le voir dans lextrait 3.5. Extrait 3.5. Le routage pour laction home et laction contact dans le contrôleur Pages. config/routes.rb SampleApp::Application.routes.draw do get "pages/home" get "pages/contact" . . end Ici la règle : get "pages/home" … se charge de la requête pour lURL /pages/home pour la page daccueil (action home) dans le contrôleur Pages. Plus encore, en utilisant get nous nous arrangeons pour que la route réponde à la requête GET, qui est un des verbes HTTP fondamentaux supporté par le Hypertext Transfer Protocol (Box 3.1). Dans notre cas, cela signifie que lorsque nous générons une action home à lintérieur du contrôleur Pages nous atteignons automatiquement une page à ladresse /pages/home. Pour voir le résultat, « tuez » le serveur avec Ctrl-C, jouez la commande rails server, et naviguez alors à ladresse /pages/home (illustration 3.5).
    • 87 Illustration 3.5: La vue de laccueil brute (/pages/home) générée par Rails. Box 3.1.GET, et caeteraLe Hypertext Transfer Protocol (HTTP) définit quatre opérations de base, correspondant aux quatre verbesGET (obtenir), POST (poster), PUT (déposer) et DELETE (effacer). Cela fait référence aux opérationsentre un ordinateur client (classiquement, un navigateur web comme Firefox ou Safari) et un serveur(typiquement un serveur web tel que Apache ou Nginx) (il est important de comprendre quen développant desapplications web sur un ordinateur local, le client et le serveur sont la même machine physique, mais en généralils sont différents). Laccent mis sur les verbes HTTP est typique des frameworks (à commencer par Rails)influencés par larchitecture REST, que nous avons abordée brièvement au chapitre 2 et que nous apprendronsmieux connaitre au chapitre 8.GET est lopération HTTP la plus courante, utilisée pour lire des données sur le web ; elle signifie simplement« atteint une page », et chaque fois que vous visitez une page, telle que google.com ou craigslist.org, votrenavigateur soumet une requête GET. La requête POST est lopération suivante la plus courante ; cest larequête envoyée par votre navigateur quand vous soumettez un formulaire. Dans les applications Rails, lesrequêtes POST sont utilisées généralement pour créer des choses (bien que le protocope HTTP permet aussi àla requête POST de procéder à des actualissations) ; par exemple, la requête POST envoyée quand voussoumettez un formulaire pour la création dun nouvel utilisateur sur votre site web. Les deux autres verbes,PUT et DELETE, sont utilisés pour actualiser et détruire des choses sur le serveur distant. Ces requêtes sontmoins courantes que GET et POST puisque les navigateurs sont incapables de les envoyer nativement, maiscertains frameworks web (comme Ruby on Rails) ont des façons intelligentes de simuler ces demandes avec lesnavigateurs.
    • 88 Pour comprendre doù ces pages proviennent, commençons par jeter un œil au contrôleur Pages dans un éditeur de texte ; vous devriez voir quelque chose comme le code de lextrait 3.6 (vous pouvez noter que, contrairement aux contrôleurs Users et Microposts de lapplication Démo du chapitre 2, le contrôleur Pages ne respecte pas les conventions REST). Extrait 3.6. Le contrôleur Pages contruit par lextrait 3.4. app/controllers/pages_controller.rb class PagesController < ApplicationController def home end def contact end end Nous voyons ici que le fichier pages_controller.rb définit une classe appelée PagesController. Les classes sont simplement une façon pratique dorganiser les fonctions (appelées aussi méthodes) comme les actions home et contact, qui sont définies en utilisant le mot-clé def. Le signe « < » indique que PagesController hérite de la classe Rails ApplicationController ; comme nous le verrons dans un instant, cela signifie que nos pages possèdent une grande quantité de fonctionnalités spécifiques à Rails. Nous en apprendrons plus sur les classes et lhéritage dans la section 4.4). Dans le cas du contrôleur Pages, ces deux méthodes sont initialement vides : def home end def contact end En pur Ruby, ces méthodes ne feraient tout simplement… rien. En Rails, la situation est différente ; PagesController est une classe Ruby, mais parce quelle hérite de la classe ApplicationController le comportement de ses méthodes est spécifique à Rails : en visitant lURL /pages/home, Rails consulte le contrôleur Pages et exécute le code de laction home, et rend alors la vue (le caractère « V » de MVC de la section 1.2.6) correspondant à laction. Dans le cas présent, laction home est vide, donc tous les appels à /pages/home rendent simplement la vue. Donc, à quoi ressemble une vue, et où la trouvons-nous ?
    • 89Si vous jetez un nouveau coup dœil à la sortie de lextrait 3.4, vous devriez être en mesure de deviner lacorrespondance entre les actions et les vues : une action comme home possède une vue correspondante appeléehome.html.erb. Nous apprendrons à la section 3.3 ce que la partie .erb signifie ; la partie .html ne devraitpas vous surprendre puisquelle ressemble à HTML (extrait 3.7). Extrait 3.7. La vue générée pour la page daccueil (Home). app/views/pages/home.html.erb <h1>Pages#home</h1> <p>Find me in app/views/pages/home.html.erb</p>Il en va de même de la vue de laction contact (extrait 3.8). Extrait 3.8. La vue générée pour la page Contact. app/views/pages/contact.html.erb <h1>Pages#contact</h1> <p>Find me in app/views/pages/contact.html.erb</p>Ces deux vues sont juste des espaces réservés : elles possèdent une entête (à lintérieur de la balise h1) et unparagraphe (la balise p) contenant le chemin daccès au fichier correspondant. Nous allons ajouter quelque (trèsléger) contenu dynamique à partir de la section 3.3, mais telles quelles, ces vues marquent un point important :les vues Rails peuvent ne contenir que du code HTML statique. Du point de vue du navigateur, le code HTMLdes fichiers de la section 3.1.1 et la méthode controller/action des pages délivrées sont indistinctes : tout ce quevoit le navigateur est de lHTML.Dans la suite de ce chapitre, nous ajouterons dabord laction about que nous avons « oubliée » à lasection 3.1.2, un petit peu de contenu dynamique et nous effecturons les premiers pas vers la stylisation despages grâce aux CSS. Avant de poursuivre, si vous utilisez Git, cest une bonne idée dajouter dès maintenant lesfichiers du contrôleur Pages au repository : $ git add . $ git commit -m "Ajout dun controleur Pages"3.2 Nos premiers testsSi vous demandez à cinq développeurs Rails comment tester une partie quelconque de code, vous obtiendrezquinze réponses différentes — mais ils seront tous daccord sur le fait que vous devrez écrire ces tests. Cest danscet esprit que nous approcherons le testing de notre Application Exemple, en écrivant de solides tests sans sepréoccuper trop pour le moment de leur perfection. Ne prenez pas les tests du Tutoriel Rails pour paroledévangile ; ils sappuient sur le style que jai développé au cours de mes propres travaux ainsi quà la lecture du
    • 90 code dautres développeurs. À mesure que vous étofferez votre expérience en tant que développeur Rails, il ny a aucun doute que vous développerez vos propres préférences et votre propre style en matière de test. En complément de lécriture des tests au cours du développement de lApplication Exemple, nous ferons aussi le choix de plus en plus fréquent décrire ces tests avant décrire le code de lapplication — une approche connue sous le nom « Développement Dirigée par le Test » (test-driven development, ou TDD).48 Notre exemple spécifique sera dajouter une page « À Propos » à notre exemple de site. Heureusement, ajouter cette page supplémentaire nest pas difficile — vous devriez même être en mesure de deviner la procédure en vous basant sur les exemples de la section précédente — ce qui signifie que nous pouvons nous concentrer sur le testing, qui contient pas mal didées nouvelles. Tout dabord, tester lexistence dune page peut paraitre superflu, mais lexpérience montre que ça ne lest pas. Tellement de choses peuvent mal tourner quand on développe un logiciel quavoir une bonne batterie de tests est inestimable pour assurer sa qualité. Plus encore, il est courant pour les programmes informatiques — et spécialement les applications — dêtre étendus, et chaque fois que vous faites un changement vous risquez dintroduire des erreurs. Lécriture de tests ne garantit pas que ces erreurs ne surviendront pas, mais ils les rendent beaucoup plus faciles à localiser et à corriger. Plus encore, en écrivant des tests pour des bogues qui surviennet vraiment, nous pouvons réduire leur probabilité. (Comme noté à la section 1.1.1, si vous trouvez que le testing est trop écrasant, poursuivez en les sautant au cours dune première lecture. Une fois que vous aurez une bonne maitrise de Rails and Ruby, vous pourrez y revenir et apprendre le testing dans un second temps) 3.2.1 Outils de test Pour écrire des tests pour notre Application Exemple, notre outil principal sera le framework appelé RSpec, qui est un langage de domaine spécifique pour décrire le comportement du code, doublé dun programme (appelé rspec) pour vérifier le comportement attendu. Conçu pour tester nimporte quel programme Ruby, RSpec a connu un regain important dans la communauté Rails. Obie Fernandez, auteur de The Rails 3 Way, a qualifié RSpec de « La Manière Rails Way » et je suis daccord.49 Si vous avez suivi les étapes de lintroduction, RSpec est déjà installé via le Bundler Gemfile (extrait 3.1) et la commande bundle install. Autotest Autotest est un outil qui joue en permanence votre suite de tests en arrière-plan, en sappuyant sur les changements de fichiers spécifiques que vous faites. Par exemple, si vous modifiez un fichier contrôleur, Autotest jouera les tests pour ce contrôleur spécifique. Le résultat est un feedback instantané sur létat de vos tests. Nous en apprendrons davantage sur Autotest quand nous le verrons en actions (section 3.2.2).
    • 91Linstallation de Autotest est optionnelle, et sa configuration peut être un peu délicate, mais si vousparvenez à le faire fonctionner sur votre système, je suis certain que vous le trouverez tout comme moi trèsutile. Pour installer Autotest, installez les gems autotest et autotest-rails-pure50 commesuit : $ [sudo] gem install autotest -v 4.4.6 $ [sudo] gem install autotest-rails-pure -v 4.1.2La prochaine étape dépend de votre plateforme. Je passerai par les étapes pour le système OS X, puisque cestcelui que jutilise, et donnerai ensuite les références vers les posts de blog qui parlent dAutotest sur Linus etWindows. Sur OS X, vous devrez installer Growl (si vous ne lavez pas déjà) et installer alors les gemsautotest-fsevent et autotest-growl : 51 $ [sudo] gem install autotest-fsevent -v 0.2.4 $ [sudo] gem install autotest-growl -v 0.2.9Si FSEvent ne sinstalle pas proprement, vérifiez bien que Xcode est installé sur votre système.Pour utiliser les gems Growl et FSEvent, faites un fichier de configuration Autotest dans votre dossier Railsracine et remplissez-le avec le contenu de lextrait 3.9 (ou de lextrait 3.10 si lextrait 3.9 renvoie une erreur survotre système) : $ mate .autotest Extrait 3.9. Le fichier de configuration .autotest pour Autotest sur OS X. require autotest/growl require autotest/fsevent Extrait 3.10. Un fichier .autotest alternatif nécessaire sur certains systèmes. require autotest-growl require autotest-fsevent(Note : cela créera une configuration Autotest pour lApplication Exemple seulement ; si vous voulez partagercette configuration Autotest avec les autres projets Rails ou Ruby, vous devriez crér un fichier .autotestplutôt dans votre dossier accueil (home) : $ mate ~/.autotest
    • 92 où ~ (tilde) est le symbole Unix pour le « dossier home »). Si vous tournez sous Linux avec le bureau Gnome, vous devrez essayer les étapes décrites à ladresse Automate Everything, qui installe sur Linux un système semblable aux notifications Growl sur OS X. Les utilisateurs Windows devront essayer dinstaller Growl pour Windows et suivre ensuite les instructions de la page GitHub pour autotest-growl. Les utilisateurs Linux tout comme les utilisateurs Windows peuvent avoir à jeter un œil à la page autotest-notification ; le lecteur du Tutoriel Rails Fred Schoeneman a écrit un compte-rendu intéressant sur son blog : À Propos des notifications Autotest.52 3.2.2 TDD : Rouge, Vert, Restructurer Dans un « Développement Dirigé par les Tests » (TDD), nous écrivons dabord un test échec : dans notre cas, une pièce de code qui exprime lidée quil devrait y avoir une page « À Propos » (about). Nous lançons alors le test, dans notre cas en ajoutant laction about et la vue correspondante. La raison typique pour laquelle nous ne faisons pas linverse — limplémentation dabord, puis le test — permet de nous assurer que nous testons vraiment la fonctionnalité que nous ajoutons. Avant dutiliser le processus TDD, jétais souvent surpris de découvrir que mes tests, en réalité, testaient la mauvaise chose, quand ils ne testaient pas, tout simplement, rien du tout. En sassurant que, dans un premier temps, le test échoue et ensuite seulement réussit, nous pouvons être plus confiant sur le fait que nous accomplissons le bon test. Il est important de comprendre que TDD nest pas toujours le bon outil pour accomplir le travail. En particulier, quand vous nêtes pas sûr du tout de comment résoudre un problème de programmation donné, il est souvent utile de laisser tomber les tests un moment pour écrire seulement le code de lapplication, juste pour sentir à quoi la solution pourrait ressembler (dans le langage de lExtreme Programming (XP), cette étape dexploration est appelée une spike — une « pointe », un gros clou). Une fois que vous avez trouvé la forme générale de la solution, vous pouvez alors utiliser TDD pour implémenter une version plus efficiente. Une façon de procéder au développement dirigé par le test est un cycle connu sous le terme « Rouge, Vert, Restructurer ». La première étape, le Rouge, se réfère à lécriture dun test conduisant à léchec, que de nombreux outils de test indiquent avec la couleur rouge. Létape suivante, Vert, se réfère à un test conduisant à la réussite, indiqué par la couleur (attendue) verte. Une fois que nous avons un test (ou une batterie de tests) qui réussit, nous sommes libres de restructurer notre code, den changer la forme (en éliminant les répétitions par exemple) sans en changer la fonction. Nous navons pas encore de couleur, donc commençons par le Rouge. RSpec (et le testing en général) peut être intimidant de prime abord, donc nous allons utiliser les tests générés par les rails generate controller Pages (les Pages de génération de contrôleur de Rails) de lextrait 3.4 pour commencer. Puisque je ne suis pas partisan de séparer les tests pour les Vues de ceux pour les Helpers (les « Assistants ». NdT), séparation que jai
    • 93toujours trouvée soit fragile soit redondante, notre première étape va consister à les effacer. Si vous utilisezGit, vous pouvez le faire comme suit : $ git rm -r spec/views $ git rm -r spec/helpersDans le cas contraire, supprimez-les directement : $ rm -rf spec/views $ rm -rf spec/helpersNous allons traiter les tests pour les Vues et les Helpers directement dans les tests du contrôleur commençant àla section 3.3.Pour commencer avec RSpec, jetez un œil aux spécifications du contrôleurs Pages53 que nous venons de générer(extrait 3.11). Extrait 3.11. Les spécifications du contrôleur Pages généré. spec/controllers/pages_controller_spec.rb require spec_helper describe PagesController do describe "GET home" do it "should be successful" do get home response.should be_success end end describe "GET contact" do it "should be successful" do get contact response.should be_success end end end
    • 94 (« should be successful » signifie « devrait réussir ». NdT) Ce code est en pur Ruby, mais même si vous avez étudié Ruby précédemment il ne vous semblera pas très familier. Cest parce que RSpec utilise la malléabilité de Ruby pour définir son propre langage de domaine (domain-specific language (DSL)) construit juste pour le testing. Le point important ici est que vous navez pas besoin de comprendre la syntaxe RSpec pour pouvoir utiliser RSpec. Ça peut sembler magique dans un premier temps, mais RSpec est conçu pour se lire plus ou moins comme de langlais, et si vous suivez les exemples du script généré et les autres exemples de ce tutoriel, vous le maitriserez rapidement. Lextrait 3.11 contient deux blocs descriptifs (blocs describe, décrire), chacun avec un exemple (par exemple un bloc commençant avec it "…" do — il "…" fait). Concentrons-nous sur le premier pour sentir ce qui se passe : describe "GET home" do it "devrait réussir" do get home response.should be_success end end La première ligne indique que nous décrivons (describe) une opération GET pour laction home. Cest juste une description, et ça peut être ce que vous voulez ; RSpec sen fiche, mais vous ou dautres lecteurs humains probablement pas. Dans ce cas, "GET ’home’" indique que le test correspond à une requête HTTP GET, comme discutée dans le Box 3.1. Alors la spécification dit que lorsque vous visitez la page daccueil, cela devrait réussir. Comme avec la première ligne, ce qui vient à lintérieur des guillemets nintéresse pas RSpec, ça nest pertinent quaux lecteurs humains. La troisième ligne, get ’home’, est la première ligne qui véritablement accomplit quelque chose. À lintérieur de RSpec, cette ligne soumet vraiment une requête GET ; en dautres termes, elle agit comme un navigateur et pointe vers la page, dans ce cas /pages/home (il sait automatiquement quil doit toucher le contrôleur Pages puisque cest un test de ce contrôleur Pages ; il sait quil doit atteindre la page daccueil parce que nous lui disons explicitement). Enfin, la quatrième ligne dit que la réponse (response) de notre application devrait indiquer un succès (elle devrait par exemple retourner un code détat de 200 ; voir Box 3.2). Box 3.2.Codes de réponse HTTP Après quun client (tel quun navigateur web) envoie une requête correspondant à un des verbes HTTP (Box 3.1), le serveur web répond par un code numérique indiquant le statut HTTP de la réponse. Par exemple, un code détat de 200 signifie la réussite et un code détat de 301 signifie une « redirection permanente ». Si vous avez installé curl, un client en ligne de commande qui peut traiter les requêtes HTTP, vous pouvez voir
    • 95cela directement avec, par exemple, www.google.com (où les drapeaux --head empêchentcurl de retourner la page complète) : $ curl --head www.google.fr HTTP/1.1 200 OK . . .Ici Google indique que la requête a été exécutée avec succès en retournant le code détat 200 OK. Acontrario, google.com est redirigé de façon permanente (vers www.google.com,naturellement), indiquant un code détat 301 (une « redirection 301 ») : $ curl --head google.com HTTP/1.1 301 Moved Permanently Location: http://www.google.com/ . . .(Note : le résultat retourné ci-dessus peut varier légèrement suivant les pays)Quand nous écrivons response.should be_success (la_réponse.devrait être_un_succès) dans un testRSpec, RSpec vérifie que la réponse de notre application à la requête est un code détat de 200.Il est temps maintenant de faire jouer nos tests. Il existe plusierurs façons différentes et équivalentes de lefaire.54 Une façon de jouer tous les tests consiste à utiliser le script rspec en ligne de commande comme suit :55 $ rspec spec/ ....
    • 96 Finished in 0.07252 seconds 2 examples, 0 failures Sur certains système (spécialement ceux utilisant linvite de commande Windows), vous pourriez avoir un message derreur à ce stade indiquant des difficultés à trouver le programme rspec : C:Sitessample_app>rspec spec/ rspec is not recognized as an internal or external command, operable program or batch file. Dans ce cas, vous aurez besoin dexécuter rspec par le Bundler en utilisant la commande bundle exec : C:Sitessample_app>bundle exec rspec spec/ .... Finished in 0.07252 seconds 2 examples, 0 failures Malheureusement, de nombreuses autres choses peuvent mal tourner à ce niveau. Si un test échoue, assurez- vous davoir bien migré la base de données avec rake db:migrate comme cela est décrit à la section 1.2.5. Si RSpec ne fonctionne pas du tout, essayez de le désinstaller et de le ré-installer : $ gem uninstall rspec rspec-rails $ bundle install Si ça ne fonctionne toujours pas et que vous utilisez RVM, essayez de supprimer le gemset du tutoriel Rails et de ré-installer les gems : $ rvm gemset delete rails3tutorial $ rvm --create use 1.9.2@rails3tutorial $ rvm --default 1.9.2@rails3tutorial $ gem install rails -v 3.0.7 $ bundle install (Si ça continue de ne pas fonctionner… je suis à court didées.)
    • 97En jouant rspec spec/, rspec est un programme fourni par RSpec, tandis que spec/ est le dossier oùvous voulez que rspec sexécute. Vous pouvez également jouer seulement les specs dans un sous-dossierparticulier. Par exemple, la commande suivante joue uniquement ls specs des contrôleurs : $ rspec spec/controllers/ .... Finished in 0.07502 seconds 2 examples, 0 failuresVous pouvez aussi jouer seulement un fichier : $ rspec spec/controllers/pages_controller_spec.rb .... Finished in 0.07253 seconds 2 examples, 0 failuresLe résultat de ces trois commandes est le même puisque le spec du contrôleur Pages est actuellement notre seulfichier de test. Au cours de la suite de ce livre, je ne montrerai pas toujours la sortie du testing, mais vous devrezjouer rspec spec/ (ou une de ses variantes) régulièrement en poursuivant — ou, même mieux, utiliserAutotest pour jouer automatiquement la batterie de tests. En parlant de ça…Si vous avez intallés Autotest, vous pouvez le faire travailler sur vos tests RSpec en utilisant la commandautotest : $ autotestSi cela ne fonctionne pas, essayez : $ bundle exec autotestSi vous utilisez un Mac avec les notifications Growl activées, vous devriez pouvoir répliquer mes réglagesmontrés dans lillustration 3.6. Avec Autotest jouant en arrière-tâche et les notifications Growl vous disant létatde vos tests, vous pourriez bien devenir accro du développement TDD.
    • 98 Illustration 3.6: Autotest (via autotest) en action avec les notifications Growl. Spork Vous avez peut-être noté que les ressources-système liées à la gestion dune suite de tests peuvent être considérables. Cela sexplique par le fait que chaque fois que RSpec joue les tests, il doit recharger tout lenvironnement Rails. Le serveur de test Spork56 a pour but de régler ce problème. Spork charge une seule fois lenvironnement, et maintient alors un pool de processus pour jouer les prochains tests. Combiné à Autotest, Spork est particulièrement utile. Configurer et faire fonctionner Spork peut être difficile, et cest un sujet de niveau plutôt avancé. Si vous vous sentez dépassé, nhésitez pas à passer cette section. La première étape consiste à ajouter la dépendance gem spork au Gemfile (extrait 3.12). Extrait 3.12. Un Gemfile pour lApplication Exemple. source http://rubygems.org gem rails, 3.0.7 gem sqlite3-ruby, 1.3.2, :require => sqlite3 group :development do gem rspec-rails, 2.5.0 end
    • 99 group :test do gem rspec, 2.5.0 . . . gem spork, 0.9.0.rc5 endEnsuite, installez-le : $ bundle installAmorcer la configuration Spork : $ spork --bootstrapMaintenant nous devons éditer le fichier de configuration RSpec, spec/spec_helper.rb, pour quelenvironnement soit chargé dans le bloc prefork, pour navoir à le charger quune seule fois (extrait 3.13). Extrait 3.13. Ajout du chargement de lenvironnement dans le bloc Spork.prefork. spec/spec_helper.rbNote : utilisez seulement ce code si vous utilisez Spork. Si vous essayez dutiliser lextrait 3.13 sans Spork, lasuite de test de votre application ne fonctionnera pas. require spork Spork.prefork do # En charger plus dans ce bloc accélèrera les tests. Cependant, # si vous changez de configuration ou le code des # librairies chargées ici, vous devrez redémarrer spork. ENV["RAILS_ENV"] ||= test require File.expand_path("../../config/environment", __FILE__) require rspec/rails # Requires supporting files with custom matchers and macros, etc, # in ./support/ and its subdirectories. Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| require f}
    • 100 Rspec.configure do |config| # == Mock Framework # # If you prefer to use mocha, flexmock or RR, uncomment the appropriate line: # # config.mock_with :mocha # config.mock_with :flexmock # config.mock_with :rr config.mock_with :rspec config.fixture_path = "#{::Rails.root}/spec/fixtures" # If youre not using ActiveRecord, or youd prefer not to run each of your # examples within a transaction, comment the following line or assign false # instead of true. config.use_transactional_fixtures = true end end Spork.each_run do end Avant de lancer Spork, nous pouvons obtenir une référence du temps que prend notre batterie de tests comme suit : $ time rspec spec/ .. Finished in 0.09606 seconds 2 examples, 0 failures real 0m7.445s user 0m5.248s sys 0m1.475s
    • 101Ici la suite de tests prend plus de 7 secondes pour être exécutée même si les tests effectifs jouent en moinsdune dizaine de secondes. Pour accélérer cela, nous pouvons ouvrir une fenêtre terminal dédiée aux tests, nousrendre dans le dossier Rails racine et démarrer un serveur Spork : $ spork Using RSpec Loading Spork.prefork block... Spork is ready and listening on 8989!Dans une autre fenêtre Terminal, nous pouvons maintenant lancer notre batterie de tests avec loption --drb 57 et vérifier que le temps de chargement de lenvironnement est considérablement réduit : $ time rspec --drb spec/ .. Finished in 0.10519 seconds 2 examples, 0 failures real 0m0.803s user 0m0.354s sys 0m0.171sComme attendu, les temps ont été considérablement réduits.Pour lancer RSpec et Spork avec Autotest, nous avons besoin de configurer RSpec pour utiliser loption --drb par défaut, ce que nous pouvons faire en lajoutant au fichier de configuration .rspec dans le dossierracine de Rails (extrait 3.14). Extrait 3.14. Ajout de loption --drb au fichier .rspec. --colour --drbAvec ce fichier .rspec actualisé, la suite de tests devrait fonctionner aussi vite que précédemment, même sansloption explicite --drb : $ time rspec spec/ ..
    • 102 Finished in 0.10926 seconds 2 examples, 0 failures real 0m0.803s user 0m0.355s sys 0m0.171s Bien entendu, jouer time ici na de sens que pour lillustration ; normalement, vous avez juste à jouer : $ rspec spec/ … ou : $ autotest … sans la commande time. Un petit conseil quand vous utilisez Spork : si vos tests échouent quand vous pensez quils devraient réussir, le problème peut venir du chargement du prefork de Spork, qui peut parfois empêcher le chargement de certains fichiers utiles. Dans le doute, quittez alors le serveur Spok (avec Control-C) et relancez-le : $ spork Using RSpec Loading Spork.prefork block... Spork is ready and listening on 8989! ^C $ spork Rouge Maintenant voyons la partie Rouge de notre cycle Rouge-Vert en écrivant un test qui va échouer sur la page « À Propos » (page about). En suivant les modèles de lextrait 3.11, vous pouvez probablement deviner le test adéquat (extrait 3.15). Extrait 3.15. Le spec du contrôleur Pages avec un test déchec pour la page « À Propos ». spec/controllers/pages_controller_spec.rb require spec_helper
    • 103 describe PagesController do render_views describe "GET home" do it "devrait réussir" do get home response.should be_success end end describe "GET contact" do it "devrait réussir" do get contact response.should be_success end end describe "GET about" do it "devrait réussir" do get about response.should be_success end end endNotez que nous avons ajouté une ligne pour dire à RSpec de rendre les vues à lintérieur des tests du contrôleur.En dautres termes, par défaut, RSpec teste juste les actions à lintérieur dun test dun contrôleur de test ; sivous voulez en plus quil rende les vues, nous devons lui demander explictement via la deuxième ligne : describe PagesController do render_views . . .Cela assure que si les tests réussissent, la page est vraiment là.
    • 104 Le nouveau test tente datteindre (get) laction about, et indique que la réponse en résultant doit être un succès. À dessein, cela échoue (avec un message derreur rouge), comme le montre lillustration 3.7 (rspec spec/) et lillustration 3.8 (autotest) (si vous testez les vues dans les contrôleurs comme le préconise ce tutoriel, cela vaut la peine de noter que changer le fichier de la vue ninvitera pas Autotest à jouer le test de contrôleur correspondant. Il existe probablement une façon de configurer Autotest pour faire cela automatiquement, mais habituellement je bascule juste vers le contrôleur et presse la touche « espace-retour » pour que le fichier soit marqué comme modifié. Sauver le contrôleur conduit Autotest à jouer les tests comme prévu). Illustration 3.7: Specification de léchec pour la page About (« À Propos ») en utilisant rspec spec/.
    • 105 Illustration 3.8: Spécification de léchec de la page About en utilisant Autotest.Cest Rouge. Maintenant obtenons le Vert.VertRappelez-vous, de la section 3.1.2, que nous pouvons générer une page statique avec Rails en créant une actionet la vue correspondante avec le nom de la page. Dans notre cas, la page « À Propos » aura dabord besoin duneaction appelée about dans le contrôleur Pages. Ayant écrit un test déchec, nous pouvons maintenant être sûrquen le faisant réussir, nous avons bien créé une page about qui fonctionne.En suivant les modèles fournis par home et contact dans lextrait 3.6, commençons dabord par ajouterlaction about dans le contrôleur Pages (extrait 3.16). Extrait 3.16. Le contrôleur Pages avec lajout de laction about. app/controllers/pages_controller.rb class PagesController < ApplicationController def home end def contact end
    • 106 def about end end Ensuite, nous allons ajouté laction about au fichier de routage (extrait 3.17). Extrait 3.17. Ajout de la route about. config/routes.rb SampleApp::Application.routes.draw do get "pages/home" get "pages/contact" get "pages/about" . . . end Pour finir, nous allons ajouter la Vue about. Éventuellement, nous pourrions la remplir avec quelque chose de plus informatif, mais pour le moment nous copierons seulement le contenu des vues générées (extrait 3.7 et extrait 3.8) pour la vue about (extrait 3.18). Extrait 3.18. Une page « À Propos » souche. app/views/pages/about.html.erb <h1>Pages#about</h1> <p>Find me in app/views/pages/about.html.erb</p> Jouer les specs et regarder les actualisations depuis Autotest (illustration 3.9) devrait retourner un message vert : $ rspec spec/
    • 107 Illustration 3.9: Autotest de retour au Vert : tous les tests réussissent.Bien sûr, ça nest jamais une mauvaise idée de jeter un œil à la page dans un navigtaeur pour sassurer que lestests ne sont pas totalement absurdes (illustration 3.10). Illustration 3.10: La nouvelle page (brute de décoffrage) « À Propos » (/pages/about).
    • 108 Restructurer Maintenant que nous sommes au Vert, nous sommes libre de restructurer notre code en changeant sa forme sans changer sa fonction. Souvent, le codage se fait au feeling, ce qui signifie quil devient rapidement laid, bouffi et plein de répétitions. Lordinateur sen fiche, bien sûr, mais pas les humains, donc il est important de garder le code base le plus propre possible en le restructurant fréquemment. Avoir une bonne batterie de tests (qui réussissent) est un outil inestimable à cet égard, car il réduit considérablement la probabilité dintroduire des bogues en cours de restructuration. Notre Application Exemple est un peu trop petite pour la restructurer maintenant, mais lodeur de code sale sinfiltre par chaque fissure, donc nous naurons pas à attendre bien longtemps : nous serons contraints de restructurer dès la section 3.3.3 de ce chapitre. 3.3 Pages (un peu) dynamiques Maintenant que nous avons créé les actions et les vues pour quelques pages statiques, nous allons les rendre très légèrement dynamiques en ajoutant du contenu qui change en fonction des pages : nous devons changer le titre de chaque page pour quil reflète son contenu. Que cela représente vraiment un contenu dynamique est discutable, mais en tout cas il présente les fondements nécessaires pour le contenu indiscutablement dynamique du chapitre 8. (Si vous avez passé ce qui concernait le TDD de la section 3.2, assurez-vous de créer une page « À Propos » ici en utilisant le code de lextrait 3.16, de lextrait 3.17 et de lextrait 3.18.) 3.3.1 Tester un changement de titre Notre plan est déditer les pages Home (Accueil), Contact et About (À Propos) pour ajouter le type de structure HTML que nous avons vu dans lextrait 3.3, en incluant des titres qui changent pour chacune des pages. Cest un sujet épineux de décider lequel de ces changements tester, et en général, tester le code HTML peut se révéler hasardeux entendu que le contenu tend à changer fréquemment. Nous garderons nos tests le plus simple possible en ne testant que le titre de la page. Page URL Titre de base Variable titre Accueil /pages/home "Simple App du Tutoriel Ruby on Rails" " | Accueil" Contact /pages/contact "Simple App du Tutoriel Ruby on Rails" " | Contact" À Propos /pages/about "Simple App du tutoriel Ruby on Rails" " | À Propos"
    • 109 Table 3.1: Les pages statiques (courantes) pour lApplication Exemple.À la fin de cette section, nos trois pages statiques auront des titres de la forme « Simple App du Tutoriel Rubyon Rails | Accueil », où la dernière partie de titre variera en fonction de la page (Table 3.1). Nous construironsles tests daprès lextrait 3.15, en ajoutant des tests sur le titre suivant le modèle de lextrait 3.19. Extrait 3.19. Un test de titre. it "doit avoir le bon titre" do get home response.should have_selector("title", :content => "Simple App du Tutoriel Ruby on Rails | Accueil") endCe code utilise la méthode have_selector à lintérieur de RSpec ; la documentation sur have_selector estétonnament éparse, mais ce quelle fait, cest de comparer le contenu dun élément HTML (dun « selector »)avec un contenu donné. En dautres termes, le code : response.should have_selector("title", :content => "Simple App du Tutoriel Ruby on Rails | Accueil")… sassure que le contenu entre les balises <title></title> est bien "Simple App du TutorielRuby on Rails | Accueil".58 Il est utile de mentionner que le contenu na pas besoin dêtre exact ; toutesous-chaine fonctionne aussi, donc le code : response.should have_selector("title", :content => " | Accueil")… fonctionnera tout aussi bien.59Notez que dans lextrait 3.19 jai coupé le texte à lintérieur du have_selector en deux lignes ; cela nousapprend quelque chose dimportant à propos de la syntaxe Ruby : Ruby ne se soucie pas des retours à la ligne.60La raison pour laquelle jai choisi de couper le code en deux est que je préfère garder des lignes de codeinférieures à 80 signes pour une question de lisibilité.61 À lheure actuelle, je trouve le formatage de ce codeplutôt laid ; la section 3.5 comprend un exercice de restructuration qui lui refera une beauté.62Ajouter les nouveaux tests pour chacune de nos trois pages statiques en suivant le modèle de lextrait 3.19 nousfournit notre nouveau spec du contrôleur Pages (extrait 3.20).
    • 110 Extrait 3.20. Le spec du contrôleur Pages avec le test des titres. spec/controllers/pages_controller_spec.rb require spec_helper describe PagesController do render_views describe "GET home" do it "devrait réussir" do get home response.should be_success end it "devrait avoir le bon titre" do get home response.should have_selector("title", :content => "Simple App du Tutoriel Ruby on Rails | Accueil") end end describe "GET contact" do it "devrait réussir" do get contact response.should be_success end it "devrait avoir le bon titre" do get contact response.should have_selector("title", :content => "Simple App du Tutoriel Ruby on Rails | Contact") end end describe "GET about" do it "devrait réussir" do get about response.should be_success
    • 111 end it "devrait avoir le bon titre" do get about response.should have_selector("title", :content => "Simple App du Tutoriel Ruby on Rails | À Propos") end end endNotez que la ligne render_views introduite dans lextrait 3.15 est nécessaire pour que le test des titresfonctionne.Avec ces tests en place, nous pouvons jouer : $ rspec spec/… ou utiliser Autotest pour vérifier que notre code est maintenant Rouge (échec des tests).3.3.2 Réussir les tests de titreNous allons maintenant faire en sorte que nos tests de titre réussissent, et en même temps ajouter une structureHTML valide. Commençons avec la page daccueil (extrait 3.21), en utilisant le même squelette HTML basiqueque la page « hello » de lextrait 3.3.Note : avec Rails 3, le générateur de contrôleur crée un fichier de mise en forme (layout), dont nousexpliquerons brièvement la fonction, mais que nous devons pour le moment supprimer avant de poursuivre : $ rm app/views/layouts/application.html.erb Extrait 3.21. La vue pour la page daccueil avec la structure HTML complète. app/views/pages/home.html.erb <!DOCTYPE html> <html> <head> <title>Simple App du Tutoriel Ruby on Rails | Accueil</title> </head> <body>
    • 112 <h1>Simple App</h1> <p> Ceci est la page daccueil de lApplication Exemple du <a href="http://railstutorial.org/">Ruby on Rails Tutorial</a>. </p> </body> </html> Lextrait 3.21 utilise le titre testé dans lextrait 3.20 : <title>Simple App du Tutoriel Ruby on Rails | Accueil</title> Les tests pour la page daccueil devraient maintenant réussir. Nous obenons toujours du Rouge à cause des tests des pages Contact et « À Propos », et nous allons pouvoir obtenir du Vert avc le code de lextrait 3.22 et de lextrait 3.23. Extrait 3.22. La vue pour la page de Contact avec la structure HTML complète. app/views/pages/contact.html.erb <!DOCTYPE html> <html> <head> <title>Simple App du Tutoriel Ruby on Rails | Contact</title> </head> <body> <h1>Contact</h1> <p> Contact Ruby on Rails Tutorial à propos de lApplication Exemple à <a href="http://railstutorial.org/feedback">feedback page</a>. </p> </body> </html> Extrait 3.23. La vue pour la page « À Propos » avec la structure HTML complète. app/views/pages/about.html.erb <!DOCTYPE html> <html> <head> <title>Simple App du Tutoriel Ruby on Rails | À Propos</title> </head>
    • 113 <body> <h1>À Propos de nous</h1> <p> <a href="http://railstutorial.org/">Le Tutoriel Ruby on Rails</a> est un projet de livre et de screencasts pour apprendre le développement web avec <a href="http://rubyonrails.org/">Ruby on Rails</a>. Ceci est lApplication Exemple du tutoriel. </p> </body> </html>Ces pages exemples introduisent la balise ancre tag a, qui crée des liens vers les URLs données (appelés « href »ou « hypertext reference » dans le contexte dune balise ancre) : <a href="http://railstutorial.org/">Tutoriel Ruby on Rails</a>Vous pouvez voir les résultats dans lillustration 3.11. Illustration 3.11: Une page accueil minimale pour lApplication Exemple (/pages/home).
    • 114 3.3.3 Variables dinstance et Ruby embarqué Nous avons déjà fait beaucoup de choses dans cette section, générer trois pages valides en utilisant les contrôleurs et actions Rails, mais ce sont de pures pages statiques HTML et elles ne font donc pas la démonstration de la puissance de Rails. Plus encore, elles souffrent de terribles duplications : • Les titres de page sont presque (mais pas tout à fait) exactement semblables ; • « Simple App du Tutoriel Ruby on Rails » est commun aux trois titres ; • Lentière structure du code HTML est répétée dans chacune des pages. Ce code répété est une violation du principe « Don’t Repeat Yourself » (DRY — « Ne vous répétez pas ») ; dans cette section et la suivante, nous allons « DRYer » notre code en supprimant les répétitions. Paradoxalement, nous allons passer la première étape délimination des duplications en en ajoutant plus encore : nous allons faire que les titres des pages, qui sont en général les mêmes, correspondre exactement. Cela rendra plus simple la suppression en un coup de toutes les répétions. La technique implique de créer des instances de variables à lintérieur de nos actions. Puisque les titres des pages Accueil, Contact et À Propos ont un contenu variable, nous allons renseigner la variable @titre (prononcez « hate titre ») avec le titre approprié à chaque action (extrait 3.24). Extrait 3.24. Le contrôleur Pages avec le titre suivant la page. app/controllers/pages_controller.rb class PagesController < ApplicationController def home @titre = "Accueil" end def contact @titre = "Contact" end def about @titre = "À Propos" end end Une déclaration comme :
    • 115 @titre = "Accueil"… est un assignement, qui crée dans ce cas une nouvelle variable @titre de valeur "Accueil". Le signearobase, @, dans le nom @titre indique que cest une variable dinstance. Les variables dinstance ont un sensplus général en Ruby (voir la section 4.2.3), mais en Rails leur rôle est principalement de lier les actions et lesvues : toute variable dinstance définie dans laction home est automatiquement accessible dans la vuehome.html.erb, et aisni de suite pour toutes les paires action/vue.63Nous pouvons voir comment cela fonctionne en remplaçant le titre littéral « Accueil » par le contenu de lavariable @titre dans la vue home.html.erb (extrait 3.25). Extrait 3.25. La Vue pour la page daccueil avec un titre Ruby embarqué. app/views/pages/home.html.erb <!DOCTYPE html> <html> <head> <title>Simple App du Tutoriel Ruby on Rails | <%= @titre %></title> </head> <body> <h1>Sample App</h1> <p> Cest la page daccueil de lApplication Exemple du <a href="http://railstutorial.org/">Tutoriel Ruby on Rails</a>. </p> </body> </html>Lextrait 3.25 est notre premier exemple de Ruby embarqué, également appelé ERb (vous savez maintenantpourquoi les fichiers des vues HTML possèdent lextension .html.erb). ERb est le principal mécanisme Railspour inclure du contenu dynamique dans les pages web.64 Le code : <%= @titre %>… indique par lutilisation de <%= ... %> que Rails devra insérer le contenu de la variable @titre, quelquil puisse être. Quand nous visitons la page /pages/home, Rails exécute le corps de laction home, quiprocède à la déclaration @titre = "Accueil", donc dans le cas présent : <%= @titre %>
    • 116 … sera remplacé par « Accueil ». Rails rend alors la vue, en utilisant ERb pour insérer la valeur de @titre dans le gabarit, que le serveur web envoie alors à votre navigateur comme code HTML. Le résultat est exactement le même quauparavant, à la différence près que la partie variable du titre est générée dynamiquement par ERb. Nous pouvons vérifier que tout fonctionne en jouant les tests de la section 3.3.1 et en constatant quils réussissent toujours. Nous pouvons alors faire les remplacements correspondants dans les pages Contact et À Propos (extrait 3.26 et extrait 3.27). Extrait 3.26. La vue pour la page Contact avec un titre en Ruby embarqué. app/views/pages/contact.html.erb <!DOCTYPE html> <html> <head> <title>Simple App du Tutoriel Ruby on Rails | <%= @titre %></title> </head> <body> <h1>Contact</h1> <p> Contact du Tutoriel Ruby on Rails à propos de lApplication Exemple à la <a href="http://railstutorial.org/feedback">page de feedback</a>. </p> </body> </html> Extrait 3.27. La vue de la page À Propos avec un titre en Ruby embarqué. app/views/pages/about.html.erb <!DOCTYPE html> <html> <head> <title>Simple App du Tutoriel Ruby on Rails | <%= @titre %></title> </head> <body> <h1>À Propos de nous</h1> <p> <a href="http://railstutorial.org/">Le Tutoriel Ruby on Rails</a> est un projet pour faire un livre des screnncasts pour apprendre le développement web avec <a href="http://rubyonrails.org/">Ruby on Rails</a>. Cest
    • 117 une Application Exemple pour le tutoriel. </p> </body> </html>Comme précédemment, les tests devraient réussir.3.3.4 Éliminer les duplications avec les layoutsMaintenant que nous avons remplacé la partie variable des titres de la page avec une variable dinstance et ERb,chacune de nos pages ressemble à quelque chose comme : <!DOCTYPE html> <html> <head> <title>Simple App du Tutoriel Ruby on Rails | <%= @titre %></title> </head> <body> Contenu </body> </html>En dautres mots, toutes nos pages sont identiques en structure, incluant même le titre (à cause du code Rubyembarqué), avec pour seule exception le contenu de chaque page.Ne serait-il pas intéressant de structurer les éléments communs dans une sorte de gabarit global (un layout), etque le contenu du body soit inséré sur cette page de base ? Vraiment, ce serait bien, et Rails nous y conviegentiment en utilisant un fichier spécial appelé application.html.erb, qui doit résider dans le dossierlayouts. Pour capturer le squelette de la structure, créez le fichier application.html.erb et copiez-y lecontenu de lextrait 3.28. Extrait 3.28. Le layout du site de lApplication Exemple. app/views/layouts/application.html.erb <!DOCTYPE html> <html> <head> <title>Simple App du Tutoriel Ruby on Rails | <%= @titre %></title> <%= csrf_meta_tag %> </head>
    • 118 <body> <%= yield %> </body> </html> Remarquez ici la ligne spéciale : <%= yield %> Ce code est responsable de linsertion du contenu de chaque page dans le layout. Comme pour <%= @titre %>, la balise <% ... %> indique du Ruby embarqué, et le signe égal dans <%= ... %> assure que les résultats de lévaluation de lexpression soient insérés à ce point exact du template (ne vous inquiétez pas pour la signification du mot « yield » ( )rendement) dans ce contexte ;65 ce qui importe cest dêtre certain quavec ce layout, la visite de la page /pages/home convertira le contenu du fichier home.html.erb en HTML et linsèrera alors à la place du <%= yield %>. Maintenant que nous avons un layout pour le site, nous avons aussi saisi lopportunité dajouter une fonctionnalité de sécurité pour chaque page. Lextrait 3.28 ajoute ce code : <%= csrf_meta_tag %> … qui utilise la méthode Rails csrf_meta_tag qui empêche le cross-site request forgery (CSRF), un type dattaque web malicieuse. Ne vous pré-occupez pas trop du détail (je ne le fais pas) ; sachez simplement que Rails travaille dur pour la sécurité de votre application. Bien sûr, les vues des extrait 3.25, extrait 3.26 et extrait 3.27 contiennent toujours toute la structure de code HTML que nous venons tout juste dintroduire dans le layout, donc il nous faut la retirer, en ne laissant que le contenu intérieur. Les vues clarifiées en résultant apparaissent dans les extrait 3.29, extrait 3.30 et extrait 3.31. Extrait 3.29. La vue Accueil avec la structure HTML supprimée. app/views/pages/home.html.erb <h1>Sample App</h1> <p> Cest la page daccueil de lApplication Exemple du <a href="http://railstutorial.org/">Tutoriel Ruby on Rails</a>. </p> Extrait 3.30. La vue Contact avec la structure HTML supprimée. app/views/pages/contact.html.erb
    • 119 <h1>Contact</h1> <p> Contact du Tutoriel Ruby on Rails à propos de lApplication Exemple à la <a href="http://railstutorial.org/feedback">page de feedback</a>. </p> Extrait 3.31. La vue À Propos avec la structure HTML supprimée. app/views/pages/about.html.erb <h1>About Us</h1> <p> <a href="http://railstutorial.org/">Ruby on Rails Tutorial</a> est un projet de livre et de screencasts pour apprendre le développement web avec <a href="http://rubyonrails.org/">Ruby on Rails</a>. Cest lApplication Exemple de ce tutoriel. </p>Avec ces vues redéfinies, les pages Accueil, Contact et À Propos avec exactement les mêmes que précédemment— cest-à-dire que nous les avons restructurées avec succès — mais elles contiennent beaucoup moins deduplications de code. Et, comme requis, les tests réussissent toujours.3.4 ConclusionVu de lextérieur, ce chapitre na pas accompli grand chose : nous lavons commencé avec des pages statiques, etnous le finissons avec… les pages le plus souvent statiques. Mais les apparences sont trompeuses : endéveloppant en termes de Contrôleurs Rails, dActions Rails et de Vues Rails, nous sommes maintenant enmesure dajouter nimporte quel contenu dynamique à notre site. Voir comment cela joue exactement est latâche que se propose la suite de ce tutoriel.Avant de poursuivre, prenez le temps de « commettre » vos changements et les fusionner avec la branchemaitresse. Dans la section 3.1.2, nous avons créé une branche Git pour le développement des pages statiques. Sivous navez pas fait de dépôts à mesure que nous avancions, commencez par en faire un en indiquant que nousavons atteint un point darrêt : $ git add . $ git commit -m "Fini avec les pages statiques"Fusionnez alors les changements dans la branche maitresse en utilisant la même technique que dans lasection 1.3.5 :
    • 120 $ git checkout master $ git merge static-pages Une fois que vous atteignez un point darrêt comme celui-ci, cest habituellement une bonne idée de « pusher » votre code sur le repository à distance (qui, si vous avez suivi les étapes de la section 1.3.4 doit se trouver sur GitHub) : $ rspec spec/ $ git push Si vous le désirez, à ce point vous pouvez même déployer lapplication actualisée sur Heroku : $ rspec spec/ $ git push heroku Notez que dans les deux cas jai joué rspec spec/, juste pour massurer que tous les tests réussissaient toujours. Faire jouer les tests avant de « pusher » du code ou de déployer lapplication est une habitude à cultiver. 3.5 Exercises 1. Créez une page daide pour lApplication Exemple. Écrivez dabord un test pour vérifier lexistence dune page à lURL /pages/help. Écrivez ensuite un second test pour le titre « Simple App du Tutoriel Ruby on Rails | Aide ». Faites le nécessaire pour que ces tests réussissent, et remplissez alors la page Aide avec le contenu de lextrait 3.32. 2. Vous avez peut-être noté quelques répétitions dans les specs du contrôleur Pages (extrait 3.20). En particulier, la base du titre, « Simple App du Tutoriel Ruby on Rails », est la même pour tous les tests de titre. En utilisant la facilité RSpec before(:each), qui exécute un code avant chaque cas testé, remplissez lextrait 3.33 pour définir une instance de variable @base_title qui éliminera cette redondance (ce code utilise deux nouveaux éléments : un symbole, :each, et une opération de concaténation de chaine, +. Nous en apprendrons plus sur ces deux éléments au chapitre 4, et nous reverrons before(:each) à la section 6.2.1). Notez que, avec la base du titre capturée dans une variable dinstance, nous sommes maintenant en mesure daligner :content avec le premier caractère à lintérieur de chaque parenthèse ouverte. Cest ma convention préférée pour formater du code découpé en plusieurs lignes. Extrait 3.32. Code pour la page daide proposée. app/views/pages/help.html.erb <h1>Aide</h1>
    • 121<p> Obtenez de laide sur le Tutoriel Ruby on Rails sur la <a href="http://railstutorial.org/help">page daide du Tutoriel Rails</a>. Pour obtenir de laide sur cette Application Exemple, voyez le <a href="http://railstutorial.org/book">livre du Tutoriel Rails</a>.</p> Extrait 3.33. Le spec du contrôleur Pages avec une base de titre. spec/controllers/pages_controller_spec.rbrequire spec_helperdescribe PagesController do render_views before(:each) do # # Define @base_title here. # end describe "GET home" do it "devrait réussir" do get home response.should be_success end it "devrait avoir le bon titre" do get home response.should have_selector("title", :content => @base_title + " | Home") end end describe "GET contact" do it "devrait réussir" do get contact response.should be_success end
    • 122 it "devrait avoir le bon titre" do get contact response.should have_selector("title", :content => @base_title + " | Contact") end end describe "GET about" do it "devrait réussir" do get about response.should be_success end it "devrait avoir le bon titre" do get about response.should have_selector("title", :content => @base_title + " | About") end end end
    • 123chapitre 4 Rails au goût RubyEn sappuyant sur les exemples du chapitre 3, ce chapitre explore quelques éléments Ruby importants pourRails. Ruby est un vaste langage, mais heureusement le sous-ensemble nécessaire pour être productif en tantque développeur Rails est relativement limité. Plus encore, ce sous-ensemble est différent de lapprochehabituelle de lapprentissage de Ruby, ce pourquoi, si votre but est de construire des applications webdynamiques, je recommande dapprendre Rails dabord, en picorant quelques éléments Ruby chemin faisant.Pour devenir un expert Rails, vous avez besoin de comprendre Ruby plus profondément, et ce livre vous donnede solides fondations sur lesquelles former cette expertise. Comme indiqué à la section 1.1.1, après avoir achevéce Tutoriel Rails je vous suggère de lire des livres de pur Ruby, tels que Beginning Ruby, The Well-GroundedRubyist ou The Ruby Way.Ce chapitre couvre beaucoup de points, et il est possible que vous ne les saisissiez pas du premier coup.Rassurez-vous, jy reviendrai fréquemment au cours des prochains chapitres.4.1 MotivationComme nous lavons vu dans le dernier chapitre, il est possible de développer le squelette dune applicationRails, et même de commencer à la tester, sans aucune connaissance du langage Ruby. Nous avons fait cela ennous appuyant sur le code de contrôleur et de test généré automatiquement et en suivant les exemples trouvés.Cette situation ne peut tout de même pas durer éternellement, cependant, et nous ouvrirons ce chapitre avec uncertain nombre dajouts au site qui vont nous permettre de nous confronter à nos limites en langage Ruby.4.1.1 Un « helper » pour le titreQuand nous avons vu la dernière fois notre application, nous avions juste actualisé les pages statiquesclassiques pour utiliser le layout Rails dans le but déliminer les redondances de code présentes dans nos vues(extrait 4.1). Extrait 4.1. Le layout du site de lapplication exemple. app/views/layouts/application.html.erb <!DOCTYPE html> <html> <head> <title>Simple App du Tutoriel Ruby on Rails | <%= @titre %></title> <%= csrf_meta_tag %> </head> <body> <%= yield %> </body>
    • 124 </html> Ce layout fonctionne bien, mais il contient une partie qui pourrait être encore améliorée. Rappelez-vous que la ligne de titre : Simple App du Tutoriel Ruby on Rails | <%= @titre %> … sappuie sur la définition de @titre dans les actions, telle que : class PagesController < ApplicationController def home @titre = "Home" end . . Mais que se passe-t-il si nous ne définissons pas la variable @titre ? Cest une bonne convention graphique davoir toujours une base de titre sur chaque page, avec une variable optionnelle si on a besoin dêtre plus précis. Nous avons presque accompli cela avec notre présent layout, à une nuance près : comme vous pouvez le voir si vous supprimez la déclaration de @titre dans lune des actions, en labsence de la variable @titre le titre saffiche comme suit : Simple App du Tutoriel Ruby on Rails | En dautres termes, il y a une base de titre satisfaisante, mais il y a aussi un caractère de barre verticale « | » à la fin de cette base de titre. Une façon courante de traiter ce cas est de définir un helper (un « assistant », un « auxiliaire ». NdT), qui est une fonction conçue pour être utilisée avec les vues. Définissons un helper titre qui retourne une base de titre, « Simple App du Tutoriel Ruby on Rails », si aucune variable @titre nest définie, et ajoute une barre verticale dans le cas où cette variable serait définie (extrait 4.2).66 Extrait 4.2. Définir un helper de titre. app/helpers/application_helper.rb module ApplicationHelper # Retourner un titre basé sur la page. def titre
    • 125 base_titre = "Simple App du Tutoriel Ruby on Rails" if @titre.nil? base_titre else "#{base_titre} | #{@titre}" end end endCe code peut sembler très simple aux yeux dun développeur Rails expérimenté, mais il est en réalité plein denouvelles idées Ruby : modules, commentaires, déclaration de variable locale, booléens, contrôle de flux,interpolation de chaine, et retour de valeur. Nous allons passer en revue chacune de ces idées au cours de cechapitre.Maintenant que nous avons un helper, nous pouvons lutiliser pour simplifier notre layout en remplaçant : <title>Simple App du Tutoriel Ruby on Rails | <%= @titre %></title>… par : <title><%= titre %></title>… comme on peut le voir dans lextrait 4.3. Notez en particulier le basculement de la variable dinstance @titrevers la méthode de lhelper titre (sans le signe arobase, @). En utilisant Autotest ou rspec spec/, vousdevriez pouvoir vérifier que les tests du chapitre 3 réussissent toujours. Extrait 4.3. Le layout du site de lapplication exemple. app/views/layouts/application.html.erb <!DOCTYPE html> <html> <head> <title><%= titre %></title> <%= csrf_meta_tag %> </head> <body> <%= yield %> </body> </html>
    • 126 4.1.2 Feuilles de styles en cascade (CCS — Cascading Style Sheets) Il y a une seconde addition à notre site qui peut sembler simple mais introduit de nouveaux concepts Ruby : linclusion de feuilles de styles dans le layout de notre site. Bien que ce soit un livre sur le développement web, pas sur le design web, nous utiliserons les feuilles de styles en cascade (CSS) pour donner un minimum de style à notre application exemple, et nous utiliserons pour cela le framework Blueprint CSS. Pour commencer, téléchargez le dernier Blueprint CSS (pour la simplicité, je présuppose que vous téléchargez Blueprint dans un dossier Downloads, mais utilisez le dossier que vous voulez). En utilisant soit le mode en ligne de commande soit un outil graphique, copiez le dossier de Blueprint CSS blueprint dans le dossier public/stylesheets de votre application exemple, un dossier spécial où Rails conserve les feuilles de styles. Sur mon Mac, la commande ressemble à celle-ci, mais dans le détail elle peut varier pour vous : $ cp -r ~/Downloads/joshuaclayton-blueprint-css-<version number>/blueprint > public/stylesheets/ Ici cp est la commande Unix pour copier, et le drapeau -r copie récursivement (nécessaire pour copier tous les sous-dossiers) (comme mentionné brièvement à la section 3.2.1.1, le tilde ~ signifie «dossier home» en Unix). Note : vous ne devriez pas coller le signe « > » dans votre terminal. Si vous collez la première ligne avec ce signe et pressez la touche retour-chariot, vous verrez « > » indiquant une continuation de ligne. Vous devrez alors coller dans la seconde ligne et presser la touche Retour-chariot une nouvelle fois pour exécuter la commande. Notez aussi que vous aurez à renseigner le numéro de version à la main, puisquil change à chaque actualisation de Blueprint. Enfin, assurez-vous de ne pas taper : $ cp -r ~/Downloads/joshuaclayton-blueprint-css-<version number>/blueprint/ > public/stylesheets/ … qui a une balance (« / ») à la fin …/blueprint/. Cela déposerait le contenu du dossier Blueprint dans le dossier public/stylesheets au lieu de déplacer le dossier blueprint lui-même. Une fois que les feuilles de styles se trouvent dans le bon dossier, Rails fournit un helper pour les inclure dans nos pages en utilisant du Ruby embarqué (extrait 4.4). Extrait 4.4. Ajout de feuilles de styles au layout de lapplication exemple. app/views/layouts/application.html.erb <!DOCTYPE html> <html> <head> <title><%= titre %></title>
    • 127 <%= csrf_meta_tag %> <%= stylesheet_link_tag blueprint/screen, :media => screen %> <%= stylesheet_link_tag blueprint/print, :media => print %> </head> <body> <%= yield %> </body> </html>Concentrons-nous sur les nouvelles lignes : <%= stylesheet_link_tag blueprint/screen, :media => screen %> <%= stylesheet_link_tag blueprint/print, :media => print %>Elles utilisent lhelper Rails intégré stylesheet_link_tag, détaillé dans lAPI Rails.67 La première lignestylesheet_link_tag inclut la feuille de style blueprint/screen.css pour lécran (par exemple lemoniteur de votre ordinateur), et la seconde inclut blueprint/print.css pour limpression (lhelper ajouteautomatiquement lextension .css au nom de fichier sil est absent, donc je lai omis pour la brièveté). Commepour lhelper de titre, pour un développeur Rails expérimenté ces lignes semblent vraiment simples, mais ellescontiennent au moins quatre nouvelles idées : les méthodes Rails intégrées, linvocation de méthode aveclomission des parenthèses, les symboles et les tableaux hash. Ce chapitre couvre aussi ces nouvelles idées (nousverrons le code HTML produit par ces feuilles de styles dans lextrait 4.6 de la section 4.3.4).En passant, notons que les nouvelles feuilles de styles ne changent pas laspect de notre site, cest seulement unbon début (illustration 4.1). Nous travaillerons sur cette fondation au chapitre 5.68
    • 128 Illustration 4.1: La page daccueil avec les nouvelles feuilles de styles Blueprint. 4.2 Chaines de caractères et méthodes Notre outil principal pour apprendre Ruby sera la console Rails , qui est un outil en ligne de commande pour intéragir avec les applications Rails. La console elle-même est construite au sommet de Ruby (irb), elle a donc accès à toute la puissance du langage (comme nous le verrons à la section 4.4.4, la console a aussi accès à lenvironnement Rails). Démarrez la console en ligne de commande comme suit :69 $ rails console Loading development environment (Rails 3.0.7) >> Par défaut, la console commence en environnement de développement, qui est lun des trois environnements séparés définis par Rails (les deux autres environnements sont lenvironnement de test et lenvironnement de production). Cette distinction ne sera pas importante dans ce chapitre ; nous en apprendrons plus sur les environemments à la section 6.3.1. La console est un puissant outil dapprentissage, et vous devrez toujours vous sentir libre de lexplorer — ne vous inquiétez pas, vous ne casserez (probablement) rien. En utilisant la console, tapez Ctrl-C si vous vous retrouvez coincé, ou Ctrl-D pour quitter cette console. Tout au long de ce chapitre, vous pourrez trouver utile de consulter lAPI Ruby.70 Cet API est emballée (peut- être même trop emballée) avec des informations ; par exemple, pour en apprendre plus sur les chaines de caractères Ruby vous pouvez consulter lentrée de lAPI Ruby pour la classe String. 4.2.1 Commentaires Les commentaires Ruby commencent par le signe dièse # et sétendent jusquà la fin de la ligne. Ruby (et donc Rails) ignore les commentaires, mais ils sont utiles pour les lecteurs humains (à commencer, souvent, par lauteur du code lui-même !). Dans le code : # Retourne un titre propre à la page. def titre . . .
    • 129… la première ligne est un commentaire indiquant le rôle joué par la fonction qui le suit.Dordinaire, vous nincluez pas de commentaires dans les sessions de console, mais dans le but de cetapprentissage, jen incluerai dans ce qui suit, comme cela : $ rails console >> 17 + 42 # Addition dentiers => 59Si vous poursuivez cette section en tapant ou en copiant-collant les commandes dans votre console, vous pouvezbien sûr omettre ces commentaires ; la console les ignorera de toute façon.4.2.2 Chaines de caractèresla chaine de caractères (Strings) est probablement la structure de données la plus importante des applicationsweb, puisque les pages web consistent en fin de compte en des chaines de texte envoyées par le serveur auxnavigateurs. Commençons à explorer ces chaines de caractères avec la console, cette fois en commençant avecrails c (si vous avez déjà quitté votre console. NdT), qui est un raccourci pour la commande railsconsole : $ rails c >> "" # Une chaine vide => "" >> "foo" # Une chaine non vide => "foo"Voici des chaines littérales, créées en utilisant les guillemets doubles « " ». La console écrit le résultat delévaluation de chaque ligne, qui dans le cas dune chaine littérale est juste la chaine elle-même.Nous pouvons aussi concaténer des chaines avec lopérateur + : >> "foo" + "bar" # Concaténation de chaines => "foobar"Ici, lévaluation de "foo" plus "bar" donne la chaine "foobar".71Une autre façon de contruire des chaines de caractère se fait via linterpolation en utilisant la syntaxe spéciale#{} :72
    • 130 >> first_name = "Michael" # Déclaration de variable => "Michael" >> "#{first_name} Hartl" # Interpolation de chaine => "Michael Hartl" Ici, nous avons assigné la valeur "Michael" à la variable first_name et lavons interpolée à lintérieur de la chaine "#{first_name} Hartl". Nous pouvons aussi assigner les deux chaines à une variable : >> first_name = "Michael" => "Michael" >> last_name = "Hartl" => "Hartl" >> first_name + " " + last_name # Concaténation, avec une espace entre les deux => "Michael Hartl" >> "#{first_name} #{last_name}" # Linterpolation équivalente => "Michael Hartl" Notez que les deux expressions finales sont équivalentes, mais je préfère la version interpolée ; avoir à ajouter une simple espace " " semble un peu maladroit (« espace » est féminine en typographie. NdT). Impression Pour imprimer une chaine de caractères, la fonction Ruby utilisée le plus couramment est puts (prononcez « poute esse », pour «put string») : >> puts "foo" # put string foo => nil La méthode puts entraine un effet secondaire : lexpression puts "foo" affiche la chaine à lécran et ne retourne ensuite litéralement rien du tout : nil (nul), qui est une valeur Ruby spéciale pour « rien du tout » (par la suite, je supprimerai parfois la partie « => nil » pour la simplicité). Lutilisation de puts ajoute automatiquement un caractère de nouvelle ligne n à la sortie ; la méthode print liée ne le fait pas : >> print "foo" # imprime la chaine (~˜= puts, mais sans nouvelle ligne) foo=> nil
    • 131 >> print "foon" # = puts "foo" foo => nilChaines de caractères « apostrophées »Tous les exemples précédents utilisaient les chaines entre guillemets, mais Ruby supporte aussi les chainesentre apostrophes. Pour de nombreux usages, les deux types de chaines sont en fait identiques : >> foo # Une chaine apostrophe simple => "foo" >> foo + bar => "foobar"Il existe cependant une différence de taille ; Ruby ne procède à aucune interpolation à lintérieur dune chaineapostrophe simple : >> #{foo} bar # Les chaines apostrophe empêchent linterpolation => "#{foo} bar"Notez comment la console retourne des valeurs en utilisant des chaines entre guillemets (tel que « # » ci-dessus), ce qui demande dutiliser le caractère spécial déchappement (backslash, « »).Si les chaines entre guillemets peuvent faire tout ce que font les chaines entre apostrophes, et peuventinterpoler, quelle est lutilité des chaines apostrophiées ? Elles sont souvent utiles parce quelles sont vraimentlittérales, et contiennent réellement les caractères que vous tapez. Par exemple, le caractère déchappement« backslash » est un caractère spécial sur la plupart des systèmes, comme dans le retour à la ligne n. Si vousvoulez quune variable contienne un caractère déchappement littéral, les apostrophes seront plus pratiques : >> n # Une combinaison à échappement littéral => "n"Comme avec le caractère « # » de notre exemple précédent, Ruby a besoin déchapper le caractèredéchappement lui-même ; à lintérieur dune chaine entre guillemets, un échappement littéral doit êtrereprésenté par deux échappements. Pour un court exemple comme celui-là, il ny a pas un gain énorme, mais sily a beaucoup de choses à échapper ça peut être dune grande utilité : >> Les nouvelles lignes (n) et les tabulations (t) utilisent toutes deux les échappements .
    • 132 => "Les nouvelles lignes (n) et les tabulations (t) utilisent toutes deux les échappements ." 4.2.3 Objets et passage de message Tout, en Ruby, les chaines et même la valeur nil (nul), sont des objets. Nous verrons le sens technique de cela à la section 4.4.2, et je pense que personne na jamais compris ce quétait un objet en en lisant la définition dans un livre ; vous devez vous faire une idée intuitive des objets en consultant un grand nombre dexemples. Il est beaucoup plus facile de décrire ce que les objets font, qui est de répondre aux messages. Un objet comme une chaine de caractères, par exemple, peut répondre au message length (longueur), qui retourne le nombre de signes/caractères de la chaine : >> "foobar".length # Passer le message "length" à la chaine => 6 Typiquement, les messages qui peuvent être passés aux objets sont appelés des méthodes, qui sont des fonctions définies pour ces objets.73 Les chaines répondent aussi à la méthode empty? (vide ?) : >> "foobar".empty? => false # = Faux >> "".empty? => true # = Vrai Notez le point dinterrogation à la fin de la méthode empty?. Cest une convention Ruby indiquant que la valeur de retour est booléenne : true (vrai) ou false (faux). Les booléens sont particulièrement utiles pour le contrôle de flux : >> s = "foobar" >> if s.empty? >> "La chaine est vide" >> else >> "La chaine nest pas vide" >> end => "La chaine nest pas vide" Les booléens peuvent aussi être combinés en utilisant les opérateurs && (« et »), || (« ou ») et ! (« pas ») : >> x = "foo"
    • 133 => "foo" >> y = "" => "" >> puts "Les deux chaines sont vides" if x.empty? && y.empty? => nil >> puts "Lune des chaines est vide" if x.empty? || y.empty? "Lune des chaines est vide" => nil >> puts "x nest pas vide" if !x.empty? "x nest pas vide"Puisque tout en Ruby est objet, il en découle que nil (la valeur nul) est un objet, donc elle peut répondre aussiaux méthodes. On en trouve un exemple avec la méthode to_s qui peut dans labsolu convertir tout objet enchaine de caractères : >> nil.to_s => ""Cela apparait certainement comme une chaine vide, comme nous pouvons le vérifier en chainant les messagesque nous passons à nil: >> nil.empty? NoMethodError: You have a nil object when you didnt expect it! You might have expected an instance of Array. The error occurred while evaluating nil.empty? ( Traduction du message derreur : ErreurDAbsenceDeMethode: Vous avez un objet null inattendu ! Vous attendiez peut-être une instance de tableau (Array) Lerreur est survenue en évaluant lexpression nil.empty? ) >> nil.to_s.empty? # Chainage du message => trueNous voyons ici que lobjet nil ne répond pas de lui-même à la méthode empty?, mais nil.to_s le fait.Il existe une méthode spéciale pour tester la nullité, que vous devriez être en mesure de deviner :
    • 134 >> "foo".nil? => false >> "".nil? => false >> nil.nil? => true Si vous retournez à lextrait 4.2, vous verrez que lhelper de titre (titre) teste pour voir si la variable @titre est nil en utilisant la méthode nil?. Cest le signe quil y a quelque chose de spécial à propos des variables dinstance (les variables commençant par le signe « @ »), qui peut être mieux compris en les comparant aux variables ordinaires. Par exemple, supposons que nous entrions les deux variables titre et @titre à la console sans les définir avant : >> titre # Houps ! Nous navons pas défini la variable titre. NameError: undefined local variable or method `titre >> @titre # Une variable dinstance dans la console => nil >> puts "Il ny a pas de telle variable dinstance." if @titre.nil? Il ny a pas de telle variable dinstance. => nil >> "#{@titre}" # Interpolation de @titre quand elle est nulle => "" Vous pouvez voir dans cet exemple que Ruby se plaint si nous essayons dévaluer une variable locale, mais ne renvoie pas cette plainte pour une variable dinstance qui nexiste pas ; au lieu de ça, les variables dinstance prennent la valeur nil si elles ne sont pas définies. Cela explique pourquoi le code : Simple App du Tutoriel Ruby on Rails | <%= @titre %> … devient : Simple App du Tutoriel Ruby on Rails | … quand @titre est nil : Le Ruby embarqué insère la chaine correspondant à la variable donnée, et la chaine correspondant à la valeur nil est une chaine vide "".
    • 135Le dernier exemple montre aussi un usage alternatif du mot-clé if : Ruby vous permet décrire des blocsqui ne sont évalués que si lexpression suivant le if est vraie. Il existe un mot-clé complémentaire, unless(sauf), qui fonctionne de la même façon, mais à lopposé : >> string = "foobar" >> puts "La chaine #{string} nest pas vide." unless string.empty? La chaine string foobar nest pas vide. => nilIl est important de noter que lobjet nil est spécial, dans le sens où cest le seul objet Ruby qui est faux dans uncontexte booléen, à part bien sûr lobjet false lui-même : >> if nil >> true >> else >> false # nil est faux >> end => falseEn particulier, tous les autres objets Ruby sont true, même 0 : >> if 0 >> true # 0 (et toute autre valeur que nil et false) est vrai >> else >> false >> end => true4.2.4 Définition des méthodesLa console nous permet de définir des méthodes de la même façon que nous le faisions avec laction home delextrait 3.6 ou lhelper de titre de lextrait 4.2 (définir des méthodes dans la console est un peu lourd, etdordinaire on utilise plutôt un fichier, mais cest pratique ici pour les besoins de la démonstration). Parexemple, définissons une fonction string_message qui prend un seul argument et retourne un message enfonction de la nullité ou non de largument : >> def string_message(string) >> if string.empty? >> "Cest une chaine vide !"
    • 136 >> else >> "La chaine nest pas vide." >> end >> end => nil >> puts string_message("") Cest une chaine vide ! >> puts string_message("foobar") La chaine nest pas vide. Notez que les fonctions Ruby ont un retour implicite, ce qui signifie quelles retournent la valeur de la dernière expression évaluée — dans ce cas, lun des deux messages, en fonction du fait que largument string de la fonction est vide ou non. Mais Ruby possède aussi une façon de définir le retour de façon explicite ; la fonction suivante est équivalente à celle ci-dessus : >> def string_message(string) >> return "Cest une chaine vide !" if string.empty? >> return "La chaine nest pas vide." >> end Le lecteur attentif pourra noter que le second return ici est en fait superfétatoire — étant la dernière expression de la fonction, la chaine "La chaine nest pas vide." sera retournée sans égard pour le mot- clé return, mais utiliser return aux deux endroits présente une symétrie plaisante. 4.2.5 Retour à lhelper titre Nous sommes maintenant en mesure de comprendre lhelper titre de lextrait 4.2 :74 module ApplicationHelper # Retourne un titre propre à la page. # Commentaire de documentation def titre # Définition de la méthode base_titre = "Simple App du Tutoriel Ruby on Rails" # Assignement de variable if @titre.nil? # Test booléen pour la nullité base_titre # Retour implicite else
    • 137 "#{base_titre} | #{@titre}" # Interpolation de chaine end end endCes éléments — fonction, définition, assignement de variable, tests booléens, contrôle de flux et extrapolationde chaine — se combinent pour créer une méthode dhelper compacte à utiliser dans notre layout. Lélémentfinal est module ApplicationHelper : le code dans les modules Ruby peut être divisé en classes Ruby. Enécrivant du Ruby ordinaire, vous écrivez souvent des modules et les inclurez explicitement vous-mêmes, maisdans notre cas Rails gère linclusion automatiquement pour nous. Le résultat est que la méthode titre estautomagiquement (sic) accessible à toutes nos vues.4.3 Autres structures de donnéesBien que les applications web produisent en dernier lieu des chaines de caractères, la fabrication de ces chainespeut requérir lutilisation de tout autant dautres structures de données. Dans cette section, nous allons étudierquelques structures de données Ruby importante pour la conception dapplications Rails.4.3.1 Tableaux (Arrays) et rangs (ranges)Un tableau (array) est juste une liste déléments dans un ordre particulier. Nous navons pas encore abordé lestableaux dans ce Tutoriel Rails, mais les comprendre offre de bonnes bases pour comprendre les tables dehachage (hashes) (section 4.3.3) et les aspects de la modélisation des données de Rails (tels que lassociationhas_many vue à la section 2.3.3 et couverte plus largement à la section 11.1.2).Jusquici nous avons passé beaucoup de temps à comprendre les chaines de caractères, et il existe une façonnaturelle de passer des chaines aux tableaux en utilisant la méthode split (scinder) : >> "foo bar baz".split # Scinde une chaine en trois éléments => ["foo", "bar", "baz"]Le résultat de cette opération est un tableau de trois chaines. Par défaut, split divise une chaine en tableau enla scindant selon les espaces blancs (whitespace), mais vous pouvez scinder tout aussi bien selon nimportequelle autre signe ou caractère : >> "fooxbarxbazx".split(x) => ["foo", "bar", "baz"]
    • 138 Conformément à la convention respectée par de nombreux langages informatiques, les tableaux Ruby sont zero- offset (décalage-zéro), ce qui signifie que le premier élément dans la liste possède lindex 0, le deuxième lindex 1, et ainsi de suite : >> a = [42, 8, 17] => [42, 8, 17] >> a[0] # Ruby utilise les crochets pour accéder aux éléments. => 42 >> a[1] => 8 >> a[2] => 17 >> a[-1] # Les indices peuvent même être négatifs ! => 17 Nous voyons ici que Ruby utilise des crochets pour accéder aux éléments du tableau. En addition à cette notation par crochets, Ruby offre des synonymes pour laccès à certains éléments particuliers :75 >> a # Pour se rappeler ce quest a => [42, 8, 17] >> a.first => 42 >> a.second => 8 >> a.last => 17 >> a.last == a[-1] # Comparaison, avec == => true La dernière ligne introduit lopérateur de comparaison dégalité « == », que Ruby partage avec de nombreux autres langages, tout comme les opérateurs liés « != » (« différent de… »), etc. : >> x = a.length # Les tableaux répondent aussi à la méthode length. => 3 >> x == 3 => true >> x == 1 => false
    • 139 >> x != 1 => true >> x >= 1 => true >> x < 1 => falseEn addition à length (aperçu dans la première ligne ci-dessus), les tableaux répondent à une foule dautresméthodes : >> a.sort => [8, 17, 42] >> a.reverse => [17, 8, 42] >> a.shuffle => [17, 42, 8]Vous pouvez aussi faire des ajouts aux tableaux avec lopérateur « push », << : >> a << 7 # Pousser 7 dans le tableau [42, 8, 17, 7] >> a << "foo" << "bar" # Chainer les ajouts [42, 8, 17, 7, "foo", "bar"]Ce dernier exemple montre que vous pouvez chainer les ajouts ensemble, et aussi que, contrairement auxtableaux dans dautres langages, les tableaux Ruby peuvent contenir des éléments de types différents (dans cecas, des chaines de caractères et des entiers).Nous avons vu précédemment que split convertissait une chaine de caractères en tableau. Nous pouvonségalement faire le chemin inverse avec la méthode join (joindre) : >> a => [42, 8, 17, 7, "foo", "bar"] >> a.join # Joindre avec rien => "428177foobar" >> a.join(, ) # Joindre avec virgule-espace => "42, 8, 17, 7, foo, bar"
    • 140 Très proches des tableaux arrays, on trouve les rangs (ranges), qui peuvent probablement être plus facilement compréhensibles en les convertissant en tableaux à laide de la méhode to_a (« to_a » pour « to array », vers un tableau) : >> 0..9 => 0..9 >> 0..9.to_a # Oops, call to_a on 9 ArgumentError: bad value for range (NdT. ErreurArgument: mauvaise valeur pour le rang) >> (0..9).to_a # Utiliser les parenthèses pour appeler to_a sur un rang => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] Bien que 0..9 soit un rang valide, la seconde expression ci-dessus montre que nous avons besoin dajouter des parenthèses pour lui appliquer la méthode. Les Rangs sont utiles pour tirer des éléments dun tableau : >> a = %w[foo bar baz quux] # Utilisez %w pour faire un tableau de chaines. => ["foo", "bar", "baz", "quux"] >> a[0..2] => ["foo", "bar", "baz"] Les rangs fonctionnent aussi avec les caractères : >> (a..e).to_a => ["a", "b", "c", "d", "e"] 4.3.2 Blocs Les tableaux (arrays) et les rangs (ranges) répondent tous deux à une foule de méthodes qui acceptent des blocs. Ces blocs sont en même temps une des fonctionnalités les plus puissantes et de celles qui entrainent le plus de confusion : >> (1..5).each { |i| puts 2 * i } 2 4 6 8
    • 141 10 => 1..5Ce code appelle la méthode each (chaque) sur le rang (1..5) et lui passe le bloc { |i| puts 2 * i }. Labarre verticale autour du nom de la variable dans |i| est la syntaxe Ruby pour une variable de bloc, et cest à laméthode de savoir ce quelle doit faire avec le bloc , dans ce cas précis, la méthode each du rang peut traiter lebloc avec une simple variable locale, que nous avons appelée i, et elle exécute simplement le bloc pour chaquevaleur de ce rang (elle exécute puts 2 * i avec i = 1 — premier terme du rang —, elle exécute puts 2 * iavec i = 2, etc. jusquà i = 5 — dernier élément du rang —).Les accolades sont une façon dindiquer un bloc, mais il existe une autre façon : >> (1..5).each do |i| ?> puts 2 * i >> end 2 4 6 8 10 => 1..5Les blocs peuvent comprendre plus dune ligne, et en comprennent souvent plus dune. Dans le Tutoriel Railsnous suivrons la convention courante dutiliser les accolades seulement pour les blocs dune courte ligne et lasyntaxe do..end pour les blocs à ligne longue ou à plusieurs lignes : >> (1..5).each do |number| ?> puts 2 * number >> puts -- >> end 2 -- 4 -- 6 -- 8 --
    • 142 10 -- => 1..5 Ici jai utilisé number au lieu de i juste pour souligner que nimporte quel nom de variable peut convenir. Sans avoir de substantielles connaissances en programmation, il ny pas de raccourci pour comprendre les blocs ; vous avez juste besoin den rencontrer beaucoup, et à la longue vous vous y ferez.76 Heureusement, les humains sont assez bons pour faire des généralisations à partir dexemples concrets ; en voilà dautres, en incluant deux qui utilisent la méthode map : >> 3.times { puts "Betelgeuse!" } # 3.fois prend un bloc sans variable. "Betelgeuse!" "Betelgeuse!" "Betelgeuse!" => 3 >> (1..5).map { |i| i**2 } # La notation ** correspond à puissance. => [1, 4, 9, 16, 25] >> %w[a b c] # Rappel : fait des tableaux de chaines. => ["a", "b", "c"] >> %w[a b c].map { |char| char.upcase } => ["A", "B", "C"] Comme vous pouvez le voir, la méthode map retourne le résultat après avoir appliqué le bloc donné à chaque élément dun tableau ou dun rang. En passant, nous sommes maintenant en mesure de comprendre la ligne de Ruby que javais posée à la section 1.4.4 pour générer des sous-domaines aléatoires : (a..z).to_a.shuffle[0..7].join Déconstruisons-la pas à pas : >> (a..z).to_a # Un tableau de lalphabet => ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"] >> (a..z).to_a.shuffle # Mélangeons-le
    • 143 => ["c", "g", "l", "k", "h", "z", "s", "i", "n", "d", "y", "u", "t", "j", "q", "b", "r", "o", "f", "e", "w", "v", "m", "a", "x", "p"] >> (a..z).to_a.shuffle[0..7] # Tirons les huit premiers éléments. => ["f", "w", "i", "a", "h", "p", "c", "x"] >> (a..z).to_a.shuffle[0..7].join # join pour faire une chaine. => "mznpybuj"4.3.3 Tables de hachage et symbolesLes tables de hachage (ou tableaux dynamiques) sont essentiellement une généralisation des tableaux-arrays :vous pouvez les considérer basiquement comme des tableaux, mais qui ne sont pas limités aux indices entiers(en fait, certains langages, spécialement Perl, appellent les tables de hachage des tableaux associatifs pour cetteraison). Au lieu de ça, les indices dune table de hachage, ou keys (clés), peuvent être presque nimporte quelobjet. Par exemple, nous pouvons utiliser des chaines de caractères comme clés : >> user = {} # {} est une table de hachage vide. => {} >> user["first_name"] = "Michael" # Clé "first_name", valeur "Michael" => "Michael" >> user["last_name"] = "Hartl" # Clé "last_name", valeur "Hartl" => "Hartl" >> user["first_name"] # Laccès aux éléments fonctionne comme => "Michael" # sur les tableaux. >> user # Une représentation littérale de la # table de hachage => {"last_name"=>"Hartl", "first_name"=>"Michael"}Les tables de hachages sont indiquées avec des accolades contenant des paires clé-valeur ; une paire daccoladessans clé-valeurs — cest-à-dire {} — est une table vide. Il est important de noter que les accolades pour lestables de hachage nont rien à voir avec les accolades pour les blocs (oui, ça peut être source de confusion). Bienque les tables de hachage ressemblent aux tableaux, une différence importante est que les tables ne garantissentpas, en règle générale, de garder leurs éléments dans un ordre particulier.77 Si lordre importe, utilisez untableau.Plutôt que de définir les tables de hachage item après item en utilisant les crochets, il est facile dutiliser leurreprésentation littérale :
    • 144 >> user = { "first_name" => "Michael", "last_name" => "Hartl" } => {"last_name"=>"Hartl", "first_name"=>"Michael"} Ici jai utilisé la convention habituelle de Ruby qui consiste à placer une extra espace aux deux extrémités de la table de hachage — une convention ignorée par la sortie de la console… (ne me demandez pas pourquoi les espaces sont conventionnelles ; probablement quelques uns des premiers programmeurs influents aimaient laspect des extra espaces, et la convention a pris). Jusquici nous avons utilisé des chaines comme clé de hachage, mais en Rails il est bien plus courant dutiliser plutôt des symbols (symboles). Les symboles ressemblent aux chaines de caractères, mais sont préfixés par le signe « deux points » au lieu dêtre entourés par des guillemets ou des apostrophes. Par exemple, :nom est un symbole. Vous pouvez penser les symboles comme des chaines sans le bagage correspondant :78 >> "nom".split() => ["n", "o", "m"] >> :nom.split() NoMethodError: undefined method `split for :nom:Symbol (Traduction : ErreurAucuneMethode : méthode `split indéfinie pour le symbole :nom) >> "foobar".reverse => "raboof" >> :foobar.reverse NoMethodError: undefined method `reverse for :foobar:Symbol (Traduction : ErreurAucuneMethode : méthode `reverse indéfinie pour le symbole :foobar) Les symboles sont un type de données spécial de Ruby partagé avec seulement quelques autres langages, donc ils peuvent sembler bizarres au début, mais Rails les utilise beaucoup, donc vous vous y habituerez vite. En termes de symboles comme clé de table de hachage, nous pouvons définir une table personne comme suit : >> personne = { :nom => "Michael Hartl", :email => "michael@example.com" } => {:nom=>"Michael Hartl", :email=>"michael@example.com"} >> personne[:nom] # Accès à la valeur correspondant à :nom. => "Michael Hartl" >> personne[:password] # Accès à la valeur dune clé indéfinie. => nil
    • 145Nous voyons ici, dans le dernier exemple, que la valeur pour une clé indéfinie est simplement la valeurnil (nul).Les valeurs dune table de hachage peuvent virtuellement être nimporte quoi, même dautres tables de hachage,comme on peut le voir dans lextrait 4.5. Extrait 4.5. Tables imbriquées. >> params = {} # Défini une table appelée params (paramètres). => {} >> params[:user] = { :nom => "Michael Hartl", :email => "mhartl@example.com" } => {:nom=>"Michael Hartl", :email=>"mhartl@example.com"} >> params => {:user=>{:nom=>"Michael Hartl", :email=>"mhartl@example.com"}} >> params[:user][:email] => "mhartl@example.com"Ces sortes de tables de table, ou tables imbriquées, sont intensivement utilisées par Rails, comme nous leverrons au début de la section 8.2.Comme pour les tableaux et les rangs, les tables de hachage répondent à la méthode each. Par exemple,considérons une table appelée flash avec des clés pour deux conditions, :success (succès) et :error(erreur) : >> flash = { :success => "Ça marche !", :error => "Raté… :-(" } => {:success=>"Ça marche !", :error=>"Raté… :-("} >> flash.each do |key, value| ?> puts "La clé #{key.inspect} a la valeur #{value.inspect}" >> end La clé :success a la valeur "Ça marche !" La clé :error a la valeur "Raté… :-("Notez que, tandis que la méthode each des tableaux prend un bloc avec une seule variable, each pour lestables de hachage en prend deux, une variable pour la clé et une variable pour la valeur. Ainsi, la méthode eachpour les tables de hachage fait son itération à travers la table une paire clé-valeur à la fois.Lexemple précédent utilise la méthode utile inspect, qui retourne une chaine avec une représentationlittérale de lobjet qui lappelle :
    • 146 >> puts (1..5).to_a # Rend une liste comme chaine. 1 2 3 4 5 >> puts (1..5).to_a.inspect # Rend une liste littérale. [1, 2, 3, 4, 5] >> puts :nom, :nom.inspect name :nom >> puts "Ça marche !", "Ça marche !".inspect Ça marche ! "Ça marche !" En passant, utiliser inspect pour imprimer un objet est assez courant pour quil y ait un raccourci pour le faire, la fonction p : >> p :nom # Identique à puts :nom.inspect :nom 4.3.4 CSS Revisité Il est temps maintenant de revisiter les lignes de lextrait 4.4 utilisées dans le layout pour inclure les feuilles de styles en cascade : <%= stylesheet_link_tag blueprint/screen, :media => screen %> <%= stylesheet_link_tag blueprint/print, :media => print %> Nous sommes maintenant tout près de pouvoir les comprendre. Comme mentionné brièvement à la section 4.1.2, Rails définit une fonction spéciale pour inclure les feuilles de styles, et : stylesheet_link_tag blueprint/screen, :media => screen … est un appel à cette fonction. Mais il demeure deux mystères. Primo, où sont les parenthèses ? En Ruby, elles sont optionnelles ; ces deux lignes sont équivalentes : # Les parenthèses optionnelles à lappel de la fonction. stylesheet_link_tag(blueprint/screen, :media => screen) stylesheet_link_tag blueprint/screen, :media => screen
    • 147Secondo, largument :media ressemble à une table de hachage, mais où sont les accolades ? Quand unetable de hachage est le dernier argument dune fonction, ses accolades sont optionnelles ; ces deux lignes sontdonc équivalentes : # Accolades optionnelles sur largument final. stylesheet_link_tag blueprint/screen, { :media => screen } stylesheet_link_tag blueprint/screen, :media => screenDonc, nous voyons maintenant que chacune des lignes : <%= stylesheet_link_tag blueprint/screen, :media => screen %> <%= stylesheet_link_tag blueprint/print, :media => print %>… appellent la fonction stylesheet_link_tag avec deux arguments : une chaine de caractères, indiquant lechemin daccès (relatif) au fichier de la feuille de styles CSS, et une table de hachage, indiquant le type de médiaconcerné (’screen’ pour lécran de lordinateur et ’print’ pour la version imprimée). Grâce au balisage<%= %>, les résultats sont insérés dans le template par ERb, et si vous regardez le code source de la pagedans votre navigateur vous pourrez voir le code nécessaire pour inclure une feuille de styles (extrait 4.6).79 Extrait 4.6. Le code HTML produit par linclusion dune CSS. <link href="/stylesheets/blueprint/screen.css" media="screen" rel="stylesheet" type="text/css" /> <link href="/stylesheets/blueprint/print.css" media="print" rel="stylesheet" type="text/css" />4.4 Classes RubyNous avons dit auparavant que tout dans Ruby était objet, et dans cette section nous serons amenés à définir lesnôtres. Ruby, comme beaucoup de langages orienté-objet, utilise les classes pour organiser les méthodes ; cesclasses sont alors instanciées pour créer des objets. Si vous êtes débutant en programmation orienté-objet(POO), cela peut ressembler à du charabia, donc étudions quelques exemples concrets.4.4.1 ConstructeursNous avons vu beaucoup dexemples dutilisation des classes pour instancier des objets, mais nous devonsencore le faire de façon explicite. Par exemple, nous avons instancié une chaine de caractère en utilisant lesguillemets, qui est le constructeur littéral de chaines :
    • 148 >> s = "foobar" # Un constructeur littéral de chaine utilisant les guillemets => "foobar" >> s.class => String Nous voyons ici que la chaine répond à la méthode class, et retourne simplement la classe à laquelle elle appartient (« String », Chaine). Plutôt que dutiliser un constructeur littéral, nous pouvons utiliser le contructeur nommé équivalent, ce qui implique dappeler la méthode new sur le nom de la classe :80 >> s = String.new("foobar") # Un constructeur nommé pour les chaines => "foobar" >> s.class => String >> s == "foobar" => true Cest équivalent au constructeur littéral, mais cest plus explicite sur ce que nous faisons. Les Tableaux (Arrays) fonctionnent de la même façon que les chaines : >> a = Array.new([1, 3, 2]) => [1, 3, 2] Les tables de hachage (Hash), en revanche, sont différentes. Tandis que le constructeur de tableau Array.new prend une valeur initiale pour le tableau, Hash.new prend une valeur par défaut pour la table, qui est la valeur de la table pour une clé inexistante : >> h = Hash.new => {} >> h[:foo] # Essai daccès à la valeur dune clé :foo inexistante. => nil >> h = Hash.new(0) # pour que les clés inexistantes retournent 0 plutôt que nil. => {} >> h[:foo]
    • 149 => 04.4.2 Héritage de classeQuand on apprend les classes, il est utile de trouver la hiérarchie de classe en utilisant la méthodesuperclass : >> s = String.new("foobar") => "foobar" >> s.class # Trouver la classe de s. => String >> s.class.superclass # Trouver la superclasse de la classe String. => Object >> s.class.superclass.superclass # Ruby 1.9 -> BasicObject => BasicObject >> s.class.superclass.superclass.superclass => nilDans le diagramme de cette héritage hiérarchique dans lillustration 4.2, nous voyons que la superclasse deString (Chaine) est la classe Object et la superclasse de la classe Object est la classe BasicObject, maisBasicObject ne possède pas de superclasse. Ce modèle est vrai pour chaque objet Ruby : remontez lahiérarchie des classes assez loin, et chaque classe Ruby héritera en fin de compte de la classe BasicObject,qui na pas de superclasse elle-même. Cest lexplication technique de «tout en Ruby est objet». Illustration 4.2: La hiérarchie des héritages pour la classe String.Pour comprendre les classes un peu plus profondément, il ny a rien de mieux que de construire les nôtres.Construison une classe Mot possédant une méthode palindrome? qui retourne true (vrai) si le mot est unpalindrome (sil sépelle de la même façon à lendroit et à lenvers) :
    • 150 >> class Mot >> def palindrome?(string) >> string == string.reverse >> end >> end => nil Nous pouvons lutiliser comme suit : >> w = Mot.new # Fait un nouvel objet Mot. => #<Mot:0x22d0b20> >> w.palindrome?("foobar") => false >> w.palindrome?("level") => true Si cet exemple vous semble un peu artificiel, tant mieux ; cest à dessein. Cest étrange de créer une nouvelle classe juste pour créer une méthode qui prend une chaine de caractère comme argument. Puisquun mot est une chaine de caractères, il semble naturel que notre classe Mot hérite de la classe String, comme nous le voyons dans lextrait 4.7 (vous devriez quitter la console et la relancer pour effacer lancienne définition de Mot). Extrait 4.7. Définir une classe Mot dans la console. >> class Mot < String # Mot hérite de String. >> # Renvoie true si la chaine est son propre inverse. >> def palindrome? >> self == self.reverse # self est la chaine elle-même. >> end >> end => nil Ici Mot < String est la syntaxe dhéritage Ruby (discuté brièvement à la section 3.1.2), qui sassure que, en plus de la nouvelle méthode palindrome?, les mots héritent des mêmes méthodes que les chaines de caractères : >> s = Mot.new("level") # Fait un nouveau Mot, initialisé à "level". => "level" >> s.palindrome? # Mot possèdent la méthode palindrome?. => true
    • 151 >> s.length # Mot héritent aussi de toutes les méthodes des chaines. => 5Puisque la classe Mot hérite de la classe String, nous pouvons utiliser la console pour voir la hiérarchie desclasses explicitement : >> s.class => Mot >> s.class.superclass => String >> s.class.superclass.superclass => ObjectCette hiérarchie est présentée dans lillustration 4.3. Illustration 4.3: La hiérarchie de lhéritage pour une classe (non-intégré) Mot de lextrait 4.7.Dans lextrait 4.7, notez que vérifier que le mot est son propre inverse implique davoir accès au mot à lintérieurde la classe Mot. Ruby nous permet de le faire en utilisant le mot-clé self : à lintérieur de la classe Mot, selfest lobjet lui-même, ce qui signifie que nous pouvons utiliser : self == self.reverse… pour vérifier que le mot est un palindrome.81
    • 152 4.4.3 Modifier les classes dorigine Bien que lhéritage soit une idée puissante, dans le cas des palindromes il peut être plus naturel dajouter la méthode palindrome? à la classe String elle-même, ce qui, entre autres choses, nous permettrait dappeler palindrome? sur une chaine littérale, ce que nous ne pouvons pas encore faire : >> "level".palindrome? NoMethodError: undefined method `palindrome? for "level":String (Traduction : ErreurAbsenceMethode : méthode `palindrome? indéfinie pour la chaine "level" Assez étonnamment, Ruby vous laisse le faire ; les classes Ruby peuvent être ouvertes et modifiée, permettant au commun des mortels dy ajouter ses propres méthodes :82 >> class String >> # Retourne vrai si la chaine est son propre inverse. >> def palindrome? >> self == self.reverse >> end >> end => nil >> "kayak".palindrome? => true (Je ne sais pas ce qui est le plus sympa : que Ruby nous laisse ajouter des méthodes à des classes intégrées ou que « kayak » soit un palindrome.) Modifier les classes intégrées est une technique puissante, mais avec un grand pouvoir vient une grande responsabilité, et il est considéré comme une mauvaise chose dajouter des méthodes aux classes intégrées sans avoir une réelle bonne raison de le faire. Rails a réellement de bonnes raisons ; par exmple, dans les applications web, nous voulons souvent nous assurer que les variables ne soient pas vierges (blank) — par exemple le nom dun utilisateur doit être autre chose que des espaces ou autre espace blanc — donc Rails ajoute la méthode blank? à Ruby. Puisque la console Rails inclut automatiquement les extensions Rails, nous pouvons voir un exemple ici (celui-ci ne fonctionne pas en plein irb) : >> "".blank? => true >> " ".empty?
    • 153 => false >> " ".blank? => true >> nil.blank? => trueNous voyons quune chaine constituée despaces nest pas vide (empty), mais elle est vierge (blank). Notez aussique nil est vierge ; puisque nil nest pas une chaine de caractères, cest une indication que Rails, en réalité,ajoute la méthode booléenne blank? à la classe de base String, qui est elle-même (comme nous lavons vu audébut de cette section) un Objet. Nous verrons dautres exemples daddition de Rails aux classes Ruby à lasection 9.3.2.4.4.4 Une classe contrôleurTous ces propos sur les classes et lhéritage peut avoir ravivé quelque souvenir, puisque nous les avons déjà vuauparavant, dans le contrôleur Pages (extrait 3.24): class PagesController < ApplicationController def home @titre = "Accueil" end def contact @titre = "Contact" end def about @titre = "À Propos" end endVous êtes maintenant en mesure dapprécier, au moins vaguement, ce que ce code signifie : PagesControllerest une classe qui hérite de la classe ApplicationController, et arrive toute équipée des méthodes home(accueil), contact, et about (à propos), chacune delle définissant une variable dinstance @titre (titre).Puisque chaque session de console Rails charge lenvironnement local Rails, nous pouvons même créer uncontrôleur explicitement et examiner sa hiérarchir de classe :83 >> controller = PagesController.new
    • 154 => #<PagesController:0x22855d0> >> controller.class => PagesController >> controller.class.superclass => ApplicationController >> controller.class.superclass.superclass => ActionController::Base >> controller.class.superclass.superclass.superclass => ActionController::Metal >> controller.class.superclass.superclass.superclass.superclass => AbstractController::Base >> controller.class.superclass.superclass.superclass.superclass.superclass => Object Le diagramme de cette hiérarchie est présenté dans lillustration 4.4. Illustration 4.4: La hiérarchir dhéritage du contrôleur Pages. Nous pouvons même appeler les actions du contrôleur à lintérieur de la console, tout comme des méthodes :
    • 155 >> controller.home => "Accueil"Cette valeur de retour "Accueil" vient de lassignement @titre = "Accueil" dans laction home.Mais attendez — les actions nont pas de valeurs de retour, du moins pas celles qui nous intéressent. Pour ce quiest de laction home, comme nous lavons vu au chapitre 3, sa fonction est de rendre une page web. Et je suiscertain de ne pas me souvenir avoir jamais appelé PagesController.new quelque part. Que se passe-t-ildonc ?…Ce qui se passe, cest que Rails est écrit en Ruby, mais que Rails nest pas Ruby. Certaines classes Rails sontutilisées comme des objets Ruby ordinaires, mais dautres sont conçues pour les capacités magiques de Rails.Rails est sui generis, et devrait être étudié et compris indépendamment de Ruby. Cest pourquoi, si votre intérêtprincipal est de programmer des applications web, je vous recommande détudier Rails dabord, puisdapprendre Ruby avant de revenir à Rails.4.4.5 La classe utilisateurNous terminons notre tour de Ruby avec une classe de notre propre cru, une classe utilisateur User qui anticipele modèle utilisateur qui sera utilisé au chapitre 6.Jusquici nous avons entré les définitions des classes à la console, mais cela devient rapidement fatigant ; nousallons plutôt créer un fichier example_user.rb dans notre dossier racine Rails et allons le remplir avec lecode de lextrait 4.8 (souvenez-vous de la section 1.1.3 : la racine de Rails est le dossier racine de votreapplication ; par exemple, la racine Rails de ma propre application exemple est/Users/mhartl/rails_projects/sample_app). Extrait 4.8. Code pour un exemple dutilisateur. example_user.rb class User attr_accessor :nom, :email def initialize(attributes = {}) @nom = attributes[:nom] @email = attributes[:email] end def formatted_email "#{@nom} <#{@email}>"
    • 156 end end Il y a beaucoup de choses dun coup ici, donc reprenons ce code pas à pas. La première ligne : attr_accessor :nom, :email … crée un accesseur dattribut (attribute accessor) pour le nom de lutilisateur et son adresse email. Cela crée les méthodes «getter» (« obteneur ») et «setter» (« assigneur ») qui nous permettent comme leur nom lindique dobtenir (get) et dassigner (set) les variables dinstance @nom et @email. La première méthode, initialize, est spéciale en Ruby : cest la méthode appelée quand nous exécutons le code User.new. Cette méthode particulière initialize reçoit un argument, attributes (attributs) : def initialize(attributes = {}) @nom = attributes[:nom] @email = attributes[:email] end Ici la variable attributes a une valeur par défaut égale à une table de hachage vide, donc nous pouvons définir un utilisateur sans nom et sans adresse email (en vous souvenant de la section 4.3.3 : les tables de hachage retournent nil pour une clé inexistante, donc attributes[:nom] sera nil sil ny a pas de clé :nom, même chose pour attributes[:email]). Enfin, notre classe définit une méthode appelée formatted_email qui utilise les valeurs assignées aux variables @nom et @email pour construire une version bien formatée de ladresse email en utilisant linterpolation de chaine (section 4.2.2) : def formatted_email "#{@nom} <#{@email}>" end Lançons la console, appelons (require) le code de lexemple dutilisateur et essayons notre classe User ainsi définie : >> require ./example_user # Cest la façon de charger le code de example_user. => ["User"] >> example = User.new
    • 157 => #<User:0x224ceec @email=nil, @nom=nil> >> example.nom # nul puisque attributes[:nom] est nul => nil >> example.nom = "Exemple dutilisateur" # Assigne un nom non nul => "Exemple dutilisateur" >> example.email = "user@example.com" # et une adresse email non nulle => "user@example.com" >> example.formatted_email => "Exemple dutilisateur <user@example.com>"Ici le ’.’ signifie en Unix « dossier courant », et ’./example_user’ dit à Ruby de chercher un fichierdexemple dutilisateur dans ce dossier courant. Le code suivant crée un exemple dutilisateur vide et définit lenom et ladresse mail en assignant directement les attributs respectifs (lassignement « depuis lextérieur » estrendu possible par la ligne attr_accessor de lextrait 4.8). Quand nous écrivons : example.name = "Exemple dutilisateur"… Ruby assigne (set) la valeur Exemple dutilisateur à la variable @nom (de même pour lattribut email),que nous utilisons alors pour la méthode formatted_email.Souvenez-vous daprès la section 4.3.4 que nous pouvons omettre les accolades pour une table de hachage enargument final dune fonction, nous pouvons donc créer une autre utilisateur avec des valeurs prédéfinies enpassant une table de hachage à la méthode initialize : >> user = User.new(:nom => "Michael Hartl", :email => "mhartl@example.com") => #<User:0x225167c @email="mhartl@example.com", @nom="Michael Hartl"> >> user.formatted_email => "Michael Hartl <mhartl@example.com>"Nous verrons en abordant le chapitre 8 quinitialiser des objets en utilisant un attribut de type table de hachageest courant pour les applications Rails.4.5 Exercises 1. En utilisant lextrait 4.9 comme guide, combinez les méthode split (découper), shuffle (mélanger), et join (joindre) pour écrire une fonction qui mélange les lettres dune chaine de caractère donnée. 2. En vous appuyant sur lextrait 4.10, ajoutez une méthode shuffle à la classe String.
    • 158 3. Créez trois tables de hachage appelées personne1, personne2 et personne3, contenant les clés :prenom pour le prénom et :nom pour le patronyme. Créez alors des paramètres de telle sorte que params[:pere] soit la personne1, params[:mere] soit la personne2 et params[:enfant] soit la personne3. Vérifiez que, par exemple, params[:pere][:prenom] ait la bonne valeur. 4. Trouvez une version en ligne de lAPI Ruby et consultez la méthode de hachage (Hash) merge (fusionner). Extrait 4.9. Squelette dune fonction chaine shuffle. >> def string_shuffle(s) >> s.split().?.? >> end => nil >> string_shuffle("foobar") Extrait 4.10. Squelette dune méthode shuffle attachée à la classe String. >> class String >> def shuffle >> self.split().?.? >> end >> end => nil >> "foobar".shuffle
    • 159Chapitre 5 Poursuivre la mise en pageDans le but de faire un bref tour de Ruby au chapitre 4, nous avons ajouté quelques feuilles de styles en cascade(CSS) à la mise en page de notre site (section 4.1.2). Dans ce chapitre, nous allons ajouter quelques styles denotre cru, tout en ajoutant à notre layout des liens vers certaines pages (telles que la page Accueil ou la page « ÀPropos ») que nous avons créées précédemment. Chemin faisant, nous apprendrons des choses sur les partiels(partials), le routage Rails et les tests dintégration. Nous terminerons en passant une étape importante :permettre à nos utilisateurs de sinscrire sur notre site.5.1 Ajout de structureCe Tutoriel Rails est un livre sur le développement web, pas sur le design web, mais ce serait un peu déprimantde travailler sur un site qui ressemblerait à une pouillerie, donc dans cette section nous allons structurer un peunotre mise en page et lui donner un minimum de style avec les CSS. Nous donnerons aussi du style à notrecode, pour ainsi dire, en utilisant les partiels (partials) pour mieux organiser le layout lorsquil deviendra tropfouilli.Quand on construit des applications web, il est souvent utile davoir le plus tôt possible un aperçu général delinterface de lutilisateur. Tout au long de la suite de ce libre, jajouterai donc des maquettes (appelées aussiwireframes dans le contexte du web), qui sont des croquis de ce à quoi pourra ressembler lapplication.84 Dansce chapitre, nous développerons principalement les pages statiques introduites à la section 3.1, en incluant unlogo du site, une entête de navigation et un pied de page. Une maquette pour la plus importante de ces pages, lapage daccueil, est présentée dans lillustration 5.1 (vous pouvez voir le résultat final dans lillustration 5.8. Vousnoterez quelle différe en quelques détails — par exemple, le pied de page contient quatre liens au lieu de trois —mais ça ne pose pas de problème, puisquune maquette na pas vocation à être exacte).
    • 160 Illustration 5.1: Une maquette (anglaise) de la page Accueil de lapplication exemple. Comme dhabitude, si vous utilisez Git pour le contrôle de version, ce serait une bonne chose de créer une nouvelle branche : $ git checkout -b filling-in-layout Vous pouvez avoir encore le fichier example_user.rb du chapitre 4 dans le dossier de votre projet, si cest le cas, vous devriez peut-être le supprimer. 5.1.1 Navigation La dernière fois que nous avons vu le layout du site application.html.erb dans lextrait 4.3, nous venions juste dajouter les feuilles de styles Blueprint en utilisant lhelper Rail stylesheet_link_tag. Il est temps dajouter un certains nombre de styles, un tout spécialement pour le navigateur Internet Explorer et un pour notre fichier CSS personnalisé qui sera bientôt ajouté. Nous ajouterons aussi quelques divisions (div), quelques identifiants id et quelques classes class, et le départ du matériel de navigation de notre site. Le fichier complet est présenté dans lextrait 5.1 ; les explications des différentes parties suit lextrait. Si vous préférez ne pas remettre à plus tard votre plaisir, vous pouvez voir le résultat dans lillustration 5.2 (note : je vous laccorde, ça nest pas encore très gratifiant). Extrait 5.1. Le layout du site avec la structure ajouté. app/views/layouts/application.html.erb <!DOCTYPE html> <html> <head> <title><%= title %></title> <%= csrf_meta_tag %> <!--[if lt IE 9]> <script src="http://html5shiv.googlecode.com/svn/trunk/html5.js"></script> <![endif]--> <%= stylesheet_link_tag blueprint/screen, :media => screen %> <%= stylesheet_link_tag blueprint/print, :media => print %> <!--[if lt IE 8]><%= stylesheet_link_tag blueprint/ie %><![endif]--> <%= stylesheet_link_tag custom, :media => screen %> </head> <body> <div class="container">
    • 161 <header> <%= image_tag("logo.png", :alt => "Application Exemple", :class => "round") %> <nav class="round"> <ul> <li><%= link_to "Accueil", # %></li> <li><%= link_to "Aide", # %></li> <li><%= link_to "Inscription", # %></li> </ul> </nav> </header> <section class="round"> <%= yield %> </section> </div> </body> </html>Jetons un coup dœil aux nouveaux éléments en commençant par le haut. Comme noté brièvement à lasection 3.1, Rails 3 utilise HTML5 par défaut (comme le doctype <!DOCTYPE html> lindique) ; puisque lestandard HTML5 est récent, certains navigateurs (spécialement Internet Explorer) ne le supportent pas encorecomplètement, donc nous incluons un peu de code JavaScript (connu sous le nom anglais de “Shiv HTML5”)pour contourner ce problème : <!--[if lt IE 9]> <script src="http://html5shiv.googlecode.com/svn/trunk/html5.js"></script> <![endif]-->La syntaxe quelque peu bizarre : <!--[if lt IE 9]>… ninclut les lignes entre les balises <!-- et --> que si la version dInternet Explorer (IE) est inférieure à laversion 9 (if lt IE 9). La syntaxe bizarre [if lt IE 9] nest pas une partie de Rails ; Cest en réalité uncommentaire conditionnel supporté par les navigateurs IE pour ce genre de situation. Cest une bonne chose,aussi, parce que cela signifie que nous pouvons inclure la feuille de style supplémentaire seulement pour lesversions dIE inférieures à 9, laissant les autres navigateurs tels que Firefox, Chrome ou Safari non affectés.
    • 162 Après les lignes pour inclure la feuille de style Blueprint (introduite dans lextrait 4.4), il y a une autre ligne spécifique à IE – ligne spécifique qui cette fois inclut une feuille de style si la version de IE est inférieur à 8 (if lt IE 8): <!--[if lt IE 8]><%= stylesheet_link_tag blueprint/ie %><![endif]--> IE possède un grand nombre de comportements bien à lui (spécialement avant la version 8) et Blueprint est fourni avec un fichier dédié ie.css qui fixe un certain nombre de ces problèmes. Après la feuille de style IE vient un lien pour une feuille de style qui nexiste pas encore, custom.css, où nous coderons les styles de notre cru : <%= stylesheet_link_tag custom, :media => screen %> CSS est très conciliant, et même si le fichier nexiste pas encore notre page fonctionnera quand même bien (nous créerons le fichier custom.css à la section 5.1.2.) La prochaine section place un conteneur div autour des éléments de navigation et de contenu de notre site, qui consiste en une balise div de classe (class) container. Ce conteneur div est demandé par Blueprint (voyez le tutoriel Blueprint pour plus dinformation). Viennent ensuite les éléments header et section ; le header (entête) contient le logo de lapplication (que vous pourrez télécharger plus tard) et les éléments de navigation du site (nav). Enfin, on trouve un élément section contenant le contenu principal du site : <div class="container"> <header> <%= image_tag("logo.png", :alt => "Application exemple", :class => "round") %> <nav class="round"> <ul> <li><%= link_to "Accueil", # %></li> <li><%= link_to "Aide", # %></li> <li><%= link_to "Inscription", # %></li> </ul> </nav> </header> <section class="round"> <%= yield %> </section> </div>
    • 163La balise div, en HTML, est une division générique ; elle sert simplement à part diviser le document enparties distinctes. Dans le vieux style HTML, les tables div sont utilisés pour presque toutes les divisions, maisHTML5 ajoute les éléments header, nav et section pour les divisions communes à beaucoup dapplications.On peut assigner des classes (class) et identifiant (id) à tous les éléments HTML, dont les divs et lesnouveaux éléments HTML5 85 ; ce sont simplement des labels, et sont nécessaires pour styliser en CSS(section 5.1.2). La différence principale entre les class et id réside dans le fait que les classes peuvent êtreutilisés plusieurs fois alors que les identifiants, par nature, sont uniques dans la page.Dans le header se trouve un helper Rails appelé image_tag : <%= image_tag("logo.png", :alt => "Application Exemple", :class => "round") %>Notez que, comme avec stylesheet_link_tag (section 4.3.4), nous passons une table doptions, dans ce casdéfinissant les attribuets alt et class de la balise image en utilisant les symboles :alt et :class. Pourrendre cela plus clair, regardons ce que cette balise produit en HTML :86 <img alt="Application Exemple" class="round" src="/images/logo.png" />Lattribut alt définit ce qui sera affiché si limage est introuvable,87 et la classe sera utilisée pour styliser le logoà la section 5.1.2.(les helpers Rails prennent souvent des tables doptions comme celle-ci, nous offrant laflexibilité dajouter des options HTML arbitraire sans jamais quitter Rails). Vous pouvez voir le résultat danslillustration 5.2 ; nous ajouterons limage du logo à la fin de cette section.Le deuxième élément à lintérieur de lheader du layout, la balise nav, est une liste de liens pour la navigation,construite en utilisant une balise de liste non ordonnée ul et des balises ditems li : <nav class="round"> <ul> <li><%= link_to "Accueil", # %></li> <li><%= link_to "Aide", # %></li> <li><%= link_to "Inscription", # %></li> </ul> </nav>Cette liste utilise lhelper Rails link_to pour créer des liens (que vous avons créés directement avec la baliseancre a à la section 3.3.2) ; le premier argument est le texte du lien et le second est lURL du lien. Nousdéfinirons les URLs avec des noms de route à la section 5.2.3, mais pour le moment nous utilisons lURL souche
    • 164 ’#’ utilisé couramment en design web. Une fois que Rails a exécuté ce layout et évalué le code Ruby embarqué, la liste ressemble à : <nav class="round"> <ul> <li><a href="#">Accueil</a></li> <li><a href="#">Aide</a></li> <li><a href="#">Inscription</a></li> </ul> </nav> Notre layout est maintenant achevé, et nous pouvons regarder le résultat en visitant, par exemple, la page daccueil. En anticipant lajout dutilisateurs à notre site au chapitre 8, ajoutons un lien dinscription à la vue home.html.erb (extrait 5.2). Extrait 5.2. Une page daccueil avec un lien dinscription. app/views/pages/home.html.erb <h1>Application Exemple</h1> <p> Cest la page daccuile pour lapplication exemple du <a href="http://railstutorial.org/">Tutoriel Ruby on Rails</a>. </p> <%= link_to "Sinscrire&nbsp;!", #, :class => "signup_button round" %> Comme avec le précédent usage de link_to, cela crée simplement un lien souche de la forme : <a href="#" class="signup_button round">Sinscrire !</a> Notez encore le thème désormais récurrent de la table doptions, utilisée dans ce cas pour ajouter quelques classes CSS à une balise ancre. Vous pouvez noter que la balise a ici possède deux classes, séparées par une espace : <a href="#" class="signup_button round"> Cest pratique pour le cas courant dun élément avec deux styles différents.
    • 165Nous sommes maintenant prêts à tirer les fruits de notre dur labeur (illustration 5.2).88 Plutôt décevant,dites-vous ? Peut-être. Heureusement, cependant, nous avons fait du bon travail en rendant nos élémentsHTML sensibles aux id et class, ce qui nous met en bonne position pour mettre notre site en forme grâce auxCSS. Illustration 5.2: La page Accueil (/pages/home) sans logo ou CSS personnalisé (version anglaise). Avant que nous stylisions avec les CSS, remplaçons le texte alternatif du logo (balise alt) avec une image ; vous pouvez télécharger le logo de lapplication exemple à ladresse :http://railstutorial.org/images/sample_app/logo.pngDéposez le logo dans le dossier public/images pour que Rails puisse le trouver. Le résultat apparait danslillustration 5.3.
    • 166 Illustration 5.3: La page Accueil (/pages/home) avec un logo mais toujours pas de style personnalité. 5.1.2 CSS personnalisés À la section 5.1.1, vous pouvez avoir noté que les éléments CSS sont sémantiques, cest-à-dire quils ont une signification en anglais qui transcende la structure de la page. Par exemple, au lieu davoir à écrire que le menu de navigation doit être aligné en haut à droite, nous utilisons lélément « nav ». Cela nous donne une considérable flexibilité pour construire un layout avec CSS. Commençons par remplir le fichier custom.css avec lextrait 5.3 (il ny a que quelques règles dans lextrait 5.3. Pour avoir une idée de ce que ces règles CSS font, il est souvent utile de les commenter en utilisant les commentaires CSS, cest-à-dire en les plaçant entre /* … */, et voir ce que ça change). Extrait 5.3. CSS pour le conteneur, le body et les liens. public/stylesheets/custom.css .container { width: 710px; } body { background: #cff; } header {
    • 167 padding-top: 20px; } header img { padding: 1em; background: #fff; } section { margin-top: 1em; font-size: 120%; padding: 20px; background: #fff; } section h1 { font-size: 200%; } /* Liens */ a { color: #09c; text-decoration: none; } a:hover { color: #069; text-decoration: underline; } a:visited { color: #069; }Vous pouvez voir le résultat de ce code CSS dans lillustration 5.4. Il y a beaucoup de CSS ici, mais il a une formeconsistante. Chaque règle se réfère à une class, un id ou une balise HTML, ou une combinaison des trois,suivi par une liste des commandes de style. Par exemple : body {
    • 168 background: #cff; } … change la couleur de fond de la balise body en « baby blue », tandis que : header img { padding: 1em; background: #fff; } met une couche de padding de 1 em (grossièrement la largeur de la lettre M) atour de limage (img) à lintérieur de la balise header. Cette règle définit aussi la couleur de fond #fff, qui correspond au code du blanc.89 Similairement : .container { width: 710px; } … stylise un élément de class container, dans ce cas en lui donnant une largeur de 710 pixels (correspondant à 18 colonnes Blueprint).90 Le point . dans .container indique que la règle stylise une class appelée “container” (comme nous le verrons à la section 8.2.3, le signe dièse # identifie une règle pour styliser un id CSS de la même manière quun point indique une class CSS).
    • 169 Illustration 5.4: La page Accueil (/pages/home) avec des couleurs personnalisées.Changer les couleurs est sympa, mais les liens de navigation sont toujours « pendus » au côté gauche de la page.Bougeons-les à un meilleur endroit et donnons-leur une apparence plus belle avec les règles de navigation delextrait 5.4. Le résultat apparait dans lillustration 5.5 (dans certains des exemples de code du livre, incluantlextrait 5.4, jutilise trois points alignés verticalement pour indiquer du code omis. Quand vous reproduisez cecode à la main, prenez soin de ne pas taper ces points ; de la même manière, si vous copiez-collez ce code,assurez-vous de ne pas prendre ces points). Extrait 5.4. Navigation CSS. public/stylesheets/custom.css . . . /* Navigation */ nav { float: right; } nav { background-color: white; padding: 0 0.7em; white-space: nowrap; } nav ul { margin: 0; padding: 0; } nav ul li { list-style-type: none; display: inline-block; padding: 0.2em 0; } nav ul li a {
    • 170 padding: 0 5px; font-weight: bold; } nav ul li a:visited { color: #09c; } nav ul li a:hover { text-decoration: underline; } Ici, nav ul stylise une balise ul à lintérieuur dune balise nav, nav ul li stylise une balise li à lintérieur dune balise ul à lintérieur dune balise nav, et ainsi de suite. Illustration 5.5: La page Accueil (/pages/home) avec la navigation stylisée (version anglaise). En pénultième étape, nous allons rendre le lien à notre page dinscription un peu moins évident (bien que nous nen ayons rien à faire pour notre application exemple, sur un site réel il est naturellement assez important de rendre le lien dinscription très proéminent — et pourquoi donc ?… NdT). Lextrait 5.5 montre le code CSS pour rendre gros, vert et cliquable le lien dinscription (pour quun clic nimporte où dans la boite puisse suivre ce lien). Extrait 5.5. CSS pour rendre le bouton dinscription gros, vert et cliquable.
    • 171 public/stylesheets/custom.css . . . /* Bouton inscription */ a.signup_button { margin-left: auto; margin-right: auto; display: block; text-align: center; width: 190px; color: #fff; background: #006400; font-size: 150%; font-weight: bold; padding: 20px; }Il y a beaucoup de règles ici ; comme dhabitude, « commentez » une ligne et rechargez la page si vous voulezvoir à quoi elles sert. Le résultat final est un lien dinscription difficile à rater (illustration 5.6). Illustration 5.6: La page Accueil (/pages/home) avec un bouton dinscription proéminent.
    • 172 En touche finale, nous allons utiliser les classes round que nous avons placées sur plusieurs des éléments de notre site. Bien que la dureté des coins des boites soit… terribles, il est un peu plus amical de les adoucir pour ne pas couper en tranches nos utilisateurs. Nous pouvons accomplir cela en utilisant le code CSS de lextrait 5.6, pour obtenir le résultat de lillustration 5.7. Extrait 5.6. Règles CSS pour des coins arrondis. public/stylesheets/custom.css . /* Coins arrondis */ .round { -moz-border-radius: 10px; -webkit-border-radius: 10px; border-radius: 10px; } Il est important de noter que cet astuce des bords arrondis fonctionne sur Firefox, Safari, Opera, et de nombreux autres navigateurs, mais que ça ne fonctionne pas sur Internet Explorer (encore lui… NdT). Il existe une façon dobtenir des arrondis sur tous les navigateurs, mais aucune qui ne soit aussi facile que celle-ci, donc nous prenons le risque de laisser nos utilisateurs IE avec quelques petites coupures aux doigts. Illustration 5.7: La page Accueil (/pages/home) avec coins arrondis.
    • 1735.1.3 PartielsBien que le layout de lextrait 5.1 remplisse son role, il devient un peu encombré de code ; il y a plusieurs lignesdinclusion de fichiers CSS et même plus de lignes dheader pour ce qui nest logiquement que deux idées. Nouspouvons ranger ces sections en utilisant la facilité de Rails qui sappelle les partiels (partials). Jetons dabord unœil à laspect du layout après que les partiels auront été définis (extrait 5.7). Extrait 5.7. Le layout du site avec des partiels pour les feuilles de styles et lentête. app/views/layouts/application.html.erb <!DOCTYPE html> <html> <head> <title><%= title %></title> <%= csrf_meta_tag %> <%= render layouts/stylesheets %> </head> <body> <div class="container"> <%= render layouts/header %> <section class="round"> <%= yield %> </section> </div> </body> </html>Dans lextrait 5.7, nous avons remplacé les lignes de feuilles de styles par un simple appel à un helper Railsappellé render : <%= render layouts/stylesheets %>Leffet de cette ligne est de trouver un fichier appelé app/views/layouts/_stylesheets.html.erb,dévaluer son contenu et den insérer le contenu dans la vue.91 (Rappelez-vous que <%= ... %> est lasyntaxe du « Ruby embarqué » nécessaire pour évaluer une expression Ruby et insérer le résultat à lintérieurdu template.) Remarquez le tiret bas au début du nom du fichier _stylesheets.html.erb ; ce tiret bas est laconvention universelle pour nommer les partiels, et parmi dautres choses rend possible didentifier dun coupdœil tous les partiels dans un dossier.
    • 174 Bien entendu, pour obtenir que le partiel fonctionne, nous devons remplir son contenu; dans le cas du partiel des feuilles de styles, cest juste les quatre ligne incluse de lextrait 5.1 ; le résultat apparait dans lextrait 5.8. (Techniquement, le HTML shiv inclut Javascript, pas CSS. En dautres mots, son but est de permettre à Internet Explorer de comprendre CSS avec HTML5, donc logiquement il doit rester dans le partiel des feuilles de style.) Extrait 5.8. Un partiel pour les feuilles de styles incluses. app/views/layouts/_stylesheets.html.erb <!--[if lt IE 9]> <script src="http://html5shiv.googlecode.com/svn/trunk/html5.js"></script> <![endif]--> <%= stylesheet_link_tag blueprint/screen, :media => screen %> <%= stylesheet_link_tag blueprint/print, :media => print %> <!--[if lt IE 8]><%= stylesheet_link_tag blueprint/ie %><![endif]--> <%= stylesheet_link_tag custom, :media => screen %> Similairement, nous pouvons déplacer le matériel de lentête dans un partiel montré dans lextrait 5.9 et linsérer dans le layout avec un autre appel à render : <%= render layouts/header %> Extrait 5.9. Un partiel avec lentête du site. app/views/layouts/_header.html.erb <header> <%= image_tag("logo.png", :alt => "Application exemple", :class => "round") %> <nav class="round"> <ul> <li><%= link_to "Accueil", # %></li> <li><%= link_to "Aide", # %></li> <li><%= link_to "Connexion", # %></li> </ul> </nav> </header> Maintenant que nous savons faire des partiels, ajoutons au site un pied de page qui soit harmonisé avec lentête. Dès à présent vous devez pouvoir devnier que nous lappelerons _footer.html.erb et le déposer dans le dossier des layouts (extrait 5.10). Extrait 5.10. Un partiel pour le pied de page du site.
    • 175 app/views/layouts/_footer.html.erb <footer> <nav class="round"> <ul> <li><%= link_to "À Propos", # %></li> <li><%= link_to "Contact", # %></li> <li><a href="http://news.railstutorial.org/">News</a></li> <li><a href="http://www.railstutorial.org/">Tutoriel Rails</a></li> </ul> </nav> </footer>Comme pour lentête, dans le pied de page nous utilisons link_to pour les liens internes vers les pages ÀPropos et Contact et initier les URLs avec ’#’ pour le moment (comme pour header, la balise footer estnouvelle en HTML5.)Nous pouvons rendre le partial du pied de page dans le layout en suivant le même modèle que les partiels desfeuilles de styles et de lentête (extrait 5.11). Extrait 5.11. La layout du site avec un partiel pour le pied de page. app/views/layouts/application.html.erb <!DOCTYPE html> <html> <head> <title><%= title %></title> <%= csrf_meta_tag %> <%= render layouts/stylesheets %> </head> <body> <div class="container"> <%= render layouts/header %> <section class="round"> <%= yield %> </section> <%= render layouts/footer %> </div> </body> </html>
    • 176 Bien sûr, le pied de page sera laid tant que nous ne lui aurons pas appliqué un peu de style (extrait 5.12). Le résultat est présenté dans lillustration 5.8. Extrait 5.12. Ajout de CSS pour le pied de page. public/stylesheets/custom.css . footer { text-align: center; margin-top: 10px; width: 710px; margin-left: auto; margin-right: auto; } footer nav { float: none; } Remarquez ici la règle : footer nav { float: none; } … qui écrase la règle précédente : nav { float: right; } … de telle sorte que le pied de page est centré en bas de page plutôt que dêtre poussé vers la droite comme les liens de navigation dans lentête. Cette convention davoir des successions de règles, avec des règles ultérieures écrasant des règles antérieures, est ce qui explique le « en cascase » dans le « Cascade Style Sheets » (Feuilles de Styles en Cascade).
    • 177 Illustration 5.8: La page Accueil (/pages/home) avec un pied de page ajouté.5.2 Liens pour la mise en pageMaintenant que nous avons fini notre layout du site avec une stylisation décente, il est temps de commencer àremplir les liens que nous avons amorcés avec ’#’. Bien sûr, nous pourrions coder les liens en dur comme : <a href="/pages/about">À Propos</a>… mais ce nest pas la façon de faire Rails. Primo, ce serait bien si lURL pour la page « À Propos » était/about plutôt que /pages/about ; plus encore, Rails utilise conventionnellement des routesnommées qui permet dutiliser du code comme : <%= link_to "À Propos", about_path %>De cette manière le code a un sens plus clair, et il est également plus flexible puisque nous navons quà changerla définition de about_path pour que les URLs changent partout où about_path est utilisé.La liste complète de nos liens planifiés (« routés ») est présentée dans la Table 5.1, avec leur carte vers les URLset les routes. Nous les implémenterons toutes sauf la dernière dici la fin de ce chapitre (la dernière seraimplémentée au chapitre 9.)
    • 178 Page URL Route nommée Accueil / root_path À Propos /about about_path Contact /contact contact_path Aide /help help_path Inscription /signup signup_path Connexion /signin signin_path Table 5.1: Carte des Routes et URLs des liens du site. 5.2.1 Test dintégration Avant décrire les routes de notre application, nous allons poursuivre en appliquant notre « Développement Dirigé par les Tests » et donc écrire des tests pour ces routes. Il existe plusieurs façons de tester le routage, et nous allons profiter de cette opportunité pour introduire la notion de test dintégration, qui nous permet de simuler un navigateur accédant à notre application et, de cette manière, de pouvoir la tester de bout en bout. Comme nous le verrons en abordant la section 8.4, tester le routage nest quun début. Nous commençons par générer un test dintégration pour les liens du layout de lapplication exemple : $ rails generate integration_test layout_links invoke rspec create spec/requests/layout_links_spec.rb Notez que le générateur ajoute automatiquement _spec.rb au nom de notre fichier test, ce qui donne donc spec/requests/layout_links_spec.rb (en RSpec, les tests dintégration sont aussi appelés des request specs — des requêtes spec, ou tout simplement des « specs ») ; les origines de cette terminologie me reste obscures). Notre test dintégration utilisera la même fonction get que celle utilisée à la section 3.2 dans le spec du contrôleur Pages, avec du code comme : describe "GET home" do it "devrait réussir" do
    • 179 get home response.should be_success end endDans cette section, nous voulons tester des URLs comme « / » et « /about », mais nous ne pouvons pasobtenir (get) ces URLs à lintérieur du test simple de contrôleur — les tests de contrôleur connaissentseulement les URLs définis pour ce contrôleur précis. En contraste, les test dintégration ne sont pas limités parde telles restrictions, puisquils sont désignés comme tests intégrés pour lapplication complète et ainsi peuventobtenir (get) la page quils veulent.En suivant le modèle du spec du contrôleur Pages, nous pouvons écrire un spec dintégration pour chacune despages de la table 5.1 que nous avons déjà créées, cest-à-dire les pages « Accueil », « À Propos », « Contact » et« Aide ». Pour être sûr que la bonne page (cest-à-dire la bonne vue) soit rendue dans chaque cas, nous allonstester la validité du titre en utilisant have_selector. Les définitions du test apparaissent dans lextrait 5.13. Extrait 5.13. Test dintégration pour les routes. spec/requests/layout_links_spec.rb require spec_helper describe "LayoutLinks" do it "devrait trouver une page Accueil à /" do get / response.should have_selector(title, :content => "Accueil") end it "devrait trouver une page Contact at /contact" do get /contact response.should have_selector(title, :content => "Contact") end it "should have an À Propos page at /about" do get /about response.should have_selector(title, :content => "À Propos") end it "devrait trouver une page Iade à /help" do get /help
    • 180 response.should have_selector(title, :content => "Aide") end end Ces tests devraient pour le moment échouer — être au Rouge — (puisque les routes ne sont pas encore bien définies) ; nous les passerons au Vert à la section 5.2.2. En passant, si vous navez pas de page daide, il serait temps den ajouter une (si vous avez résolu les exercices de la section 3.5 du chapitre 3, vous en avez déjà une). Dabord, ajoutez laction help au contrôleur Pages (extrait 5.14). Ensuite, créez la vue correspondante (extrait 5.15). Extrait 5.14. Ajout de laction help (aide) au contrôleur Pages. app/controllers/pages_controller.rb class PagesController < ApplicationController . . . def help @title = "Aide" end end Extrait 5.15. Ajout dune vue pour la page daide. app/views/pages/help.html.erb <h1>Aide</h1> <p> Obtenez de laide sur le Tutoriel Ruby on Rails Tutorial sur la <a href="http://railstutorial.org/help">page daide du Tutoriel Rails</a>. Pour obtenir de laide sur cette application exemple, consultez le <a href="http://railstutorial.org/book">livre du Tutoriel Rails</a>. </p> Il y a un détail final pour gérer les changements précédents : si vous lancez Autotest, vous pourrez noter quil ne joue pas le test dintégration. Cest à dessein, puisque les tests dintégration peuvent être lents et donc entraver le cycle rouge-vert-restructuration, mais je trouve quand même préférable de dire à Autotest de lancer les tests du dossier spec/requests (extrait 5.16 ou extrait 5.17). Extrait 5.16. Ajouts à .autotest nécessaire pour jouer les tests dintégration avec Autotest sur OS X. Autotest.add_hook :initialize do |autotest|
    • 181 autotest.add_mapping(/^spec/requests/.*_spec.rb$/) do autotest.files_matching(/^spec/requests/.*_spec.rb$/) end end Extrait 5.17. Ajouts à .autotest nécessaire pour jouer les tests dintégration avec Autotest sur Ubuntu Linux. Autotest.add_hook :initialize do |autotest| autotest.add_mapping(%r%^spec/(requests)/.*rb$%) do|filename, _| filename end endNe vous souciez pas de où vient ce code ; je ne connais pas non plus lAPI Autotest. Pour ce cas précis, jai lancéGoogle sur des termes de recherche tels que “rspec autotest integration” et jai trouvé ce code, et lorsque je laiintroduit dans mon fichier .autotest, ça a fonctionné.5.2.2 Routes RailsMaintenant que nous avons des tests pour les URLs désirés, il est temps de les faire fonctionner. Comme noté àla section 3.1.2, le fichier que Rails utilise pour le routage des URL est le fichier config/routes.rb. Si vousjetez un coup dœil au fichier par défaut des routes, vous verrez que cest plutôt le bazar, mais cest un bazarutile — plein dexemples de routages à décommenter. Je vous suggère de le lire, et je vous suggère aussi de jeterun œil à larticle des Guides Rails “Rails Routing from the outside in” pour un traitement plus en profondeurdes routes. Pour le moment, cependant, nous en resterons là avec les exemples de lextrait 5.18.92 Extrait 5.18. Routes pour les pates statiques. config/routes.rb SampleApp::Application.routes.draw do match /contact, :to => pages#contact match /about, :to => pages#about match /help, :to => pages#help . . . endLextrait 5.18 contient des routes personnalisées pour les pages contact, about (à propos), et help (aide) ;nous prendrons soin de la page daccueil elle-même dans extrait 5.20 (puisque nous utiliserons des routes
    • 182 personnalisées dans lextrait 5.18 exclusivement à partir de maintenant, nous avons saisi lopportunité de supprimer les routes du contrôleur Pages (get "pages/home", etc.) vues dans lextrait 3.17). Si vous lisez attentivement le code de lextrait 5.18, vous pouvez probablement imaginer ce quil fait ; par exemple, nous pouvons voir que : match /about, :to => pages#about … trouve le ’/about’ et le route vers laction about du contrôleur Pages. Avant, cétait plus explicite : nous utilisions get ’pages/about’ pour atteindre le même endroit, mais /about est plus succint. Ce qui nest pas évident, cest que match ’/about’ crée aussi automatiquement des routes nommées (named routes) à utiliser dans les contrôleurs et dans les vues : about_path => /about about_url => http://localhost:3000/about Notez que about_url correspond à lURL entière http://localhost:3000/about (avec localhost:3000 remplacé par le nom de domaine, tel que example.com, pour un site déployé online). Comme abordé à la section 5.2, pour obtenir juste /about, vous utilisez about_path (le Tutoriel Rails utilise la forme path (chemin) pour la consistance, mais la différence importe souvent peu en pratique). Ces routes maintenant définies, les tests pour les pages « À Propos », « Contact » et « Aide » devraient maintenant réussir (comme dhabitude, utilisez Autotest ou rspec spec/ pour vérifier). Il ne reste plus que le test pour la page daccueil. Pour établir la route vers la page daccueil, nous pourrions utiliser un code tel que : match /, :to => pages#home Ça nest pas nécessaire, pourtant ; Rails possède des instructions spéciales pour lURL racine / (“slash”) localisé plus bas dans le fichier (extrait 5.19). Extrait 5.19. La proposition à décommenter pour définir la route racine. config/routes.rb SampleApp::Application.routes.draw do . . . # You can have the root of your site routed with "root"
    • 183 # just remember to delete public/index.html. # root :to => "welcome#index" . . . endEn utilisant lextrait 5.19 comme modèle, nous arrivons à lextrait 5.20 pour router lURL « / » vers la pagedaccueil. Extrait 5.20. Ajout dune care pour la route racine. config/routes.rb SampleApp::Application.routes.draw do match /contact, :to => pages#contact match /about, :to => pages#about match /help, :to => pages#help root :to => pages#home . . . endCe code dirige lURL racine « / » vers /pages/home, et fournit aussi lURL aux helpers comme suit : root_path => / root_url => http://localhost:3000/Nous devrions aussi tenir compte du commentaire de lextrait 5.19 et effacer public/index.html pourempêcher Rails de retourner la page par défaut (illustration 1.3) quand nous visitons « / ». Vous pouvez bienentendu simplement détruire le fichier, mais si vous utilisez Git pour le contrôle de version il existe une façonde dire à Git quune destruction a été faite en même temps quil le détruit, en utilisant git rm: $ git rm public/index.html $ git commit -am "Removed default Rails page"Vous vous souvenez peut-être, à la section 1.3.5, que nous avons utilisé la commande Git git commit -a -m"Message", avec des drapeaux pour “tous les changements” (-a) et un message (-m). Comme montré ci-
    • 184 dessus, Git nous laisse aussi enrouler ces deux drapeaux dans un seul en utilisant git commit -am "Message". Avec ça, toutes les routes pour les pages statiques fonctionnent, et les tests devraient réussir. Maintenant nous avons juste à renseigner les liens du layout. 5.2.3 Routes nommées Posons les routes nommées créées à la section 5.2.2 pour quelles fonctionnent dans notre layout. Cela va consister à renseigner le second argument des fonctions link_to avec le nom propre à la route. Par exemple, nous convertirons : <%= link_to "À Propos", # %> … en : <%= link_to "À Propos", about_path %> … et ainsi de suite. Nous allons commencer dans le partiel de lentête, _header.html.erb (extrait 5.21), qui contient des liens vers les pages daccueil et daide. Pendant que nous y serons, nous suivrons une convention web courante et lierons le logo également à la page daccueil. Extrait 5.21. Partiel de lentête avec des liens. app/views/layouts/_header.html.erb <header> <% logo = image_tag("logo.png", :alt => "Application exemple", :class => "round") %> <%= link_to logo, root_path %> <nav class="round"> <ul> <li><%= link_to "Accueil", root_path %></li> <li><%= link_to "Aide", help_path %></li> <li><%= link_to "Sidentifier", # %></li> </ul> </nav> </header>
    • 185Nous naurons pas de route nommée pour le lien “Inscription” jusquau chapitre 9, donc nous le laisseronsà ’#’ pour le moment. Notez que ce code définit la variable locale logo pour la balise de limage logo, etensuite la lie à la ligne suivante : <% logo = image_tag("logo.png", :alt => "Application exemple", :class => "round") %> <%= link_to logo, root_path %>Cest un peu plus propre que de tout rassembler dans une seule ligne. Cest spécialement important de noterque le ERb pour lassignement de variable ne contient pas de signe égal (=) ; cest seulement <% ... %>,parce que nous ne voulons pas que cette ligne soit insérée dans le template (utiliser une variable locale de cettemanière est une façon de faire mais ça nest pas la seule. Une façon encore plus propre consiste à définir unhelper logo ; voyez la section 5.5.)Lautre endroit où on peut trouver des liens, cest le partiel du pied de page, _footer.html.erb, qui contientdes liens pour les pages « À Propos » et « Contact » (extrait 5.22). Extrait 5.22. Partiel du pied de page avec des liens. app/views/layouts/_footer.html.erb <footer> <nav class="round"> <ul> <li><%= link_to "À Propos", about_path %></li> <li><%= link_to "Contact", contact_path %></li> <li><a href="http://news.railstutorial.org/">News</a></li> <li><a href="http://www.railstutorial.org/">Rails Tutorial</a></li> </ul> </nav> </footer>Avec ça, notre layout a des liens vers toutes les pages statiques créées dans le chapitre 3, et donc, par exemple,« /about » se rend à la page « À Propos » (illustration 5.9).En passant, il est important de noter que, bien que nous nayons pas testé, en fait, la présence des liens dans lelayout, nos tests échoueront si les routes ne sont pas définies. Vous pouvez le vérifier en commentant les routesde lextrait 5.18 et en jouant votre suite de tests. Pour une méthode de testing qui sassure en fait que le lienconduise au bon endroit, voyez la section 5.5.
    • 186 Illustration 5.9: La page À Propos à /about. 5.3 Inscription de lutilisateur : une première étape Pierre angulaire de notre travail sur le layout et le routage, nous allons faire dans cette section une route pour la page dinscription, ce qui signifiera de créer un second contrôleur. Cest un premier pas important vers la possibilité pour les utilisateurs de senregistrer sur notre site ; nous aborderons létape suivante, la modélisation des utilisateurs, au chapitre 6, et nous achèverons le travail au chapitre 8. 5.3.1 Contrôleur des utilisateurs (Users ) Cela fait un moment que nous avons créé notre premier contrôleur, le contrôleur Pages, à la section 3.1.2. Il est temps den créer un second, le contrôleur Users (Utilisateurs ). Comme précédemment, nous utiliserons la commande generate pour faire le contrôleur le plus simple pour remplir nos besoins actuels, cest-à-dire un contrôleur avec la souche dune page dinscription pour les nouveaux utilisateurs. En suivant les conventions de larchitecture REST préconisée par Rails, nous appellerons laction new du contrôleur et la passerons comme argument à generate controller pour la créer automatiquement (extrait 5.23). Extrait 5.23. Générer un contrôleur Users (avec une action new). $ rails generate controller Users new create app/controllers/users_controller.rb route get "users/new" invoke erb create app/views/users create app/views/users/new.html.erb
    • 187 invoke rspec create spec/controllers/users_controller_spec.rb create spec/views/users create spec/views/users/new.html.erb_spec.rb invoke helper create app/helpers/users_helper.rb invoke rspec create spec/helpers/users_helper_spec.rbComme avec le contrôleur Pages, cela génère vue et specs helper dont nous navons pas besoin, doncdétruisons-les : $ rm -rf spec/views $ rm -rf spec/helpersLe générateur de contrôleur construit deux choses : un contrôleur Users et un test par défaut, qui vérifie quelaction new réponde bien à la requête GET (Box 3.1) ; le code est présenté dans lextrait 5.24. Il devrait voussembler familier ; il suit exactement la même forme que le spec du contrôleur Pages vue précédemment à lasection 3.3.1 (extrait 3.20). Extrait 5.24. Tester la page dinscription. spec/controllers/users_controller_spec.rb require spec_helper describe UsersController do describe "GET new" do it "devrait réussir" do get new response.should be_success end end endPar construction, le contrôleur Users possède déjà sa propre action new et le template new.html.erb pourfaire réussir ce test (extrait 5.25) (pour voir la page, à ladresse /users/new, vous devez peut-êtrerelancer le serveur). Extrait 5.25. Action pour la page dinscription du nouvel utilisateur.
    • 188 app/controllers/users_controller.rb class UsersController < ApplicationController def new end end Pour respecter lesprit du « Développement Dirigé par les Tests », ajoutons un second test de notre cru qui échouera en testant le titre (title) qui devrait contenir la chaine "Inscription" (extrait 5.26). Assurez- vous dajouter render_views comme nous lavons fait dans le spec du contrôleur Pages (extrait 3.20) ; sinon, le test échouera même après avoir ajouté le titre adéquat. Extrait 5.26. Un test sur le titre de la page dinscription. spec/controllers/users_controller_spec.rb require spec_helper describe UsersController do render_views describe "GET new" do it "devrait réussir" do get new response.should be_success end it "devrait avoir le titre adéquat" do get new response.should have_selector("title", :content => "Inscription") end end end Ce test utilise la méthode have_selector que nous avons vue précédemment (section 3.3.1) ; notez que, comme à la section 3.3.1, have_selector a besoin de la ligne render_views puisquil teste la vue associée à laction et doit donc la rendre pour pouvoir la tester.
    • 189Bien sûr, cest à dessein que ce test, pour le moment, doit échouer (Rouge). Pour avoir un titrepersonnalisé, nous avons besoin de créer une variable dinstance @titre comme à la section 3.3.3. Nouspouvons ainsi obtenir le Vert avec le code de lextrait 5.27. Extrait 5.27. Définir le titre personnalisé pour la page de nouvel utilisateur. app/controllers/users_controller.rb class UsersController < ApplicationController def new @titre = "Inscription" end end5.3.2 URL de linscriptionAvec le code de la section 5.3.1, nous avons déjà une page fonctionnelle pour les nouveaux utilisateurs àladresse /users/new, mais vous vous souvenez que daprès la Table 5.1 nous voulons que lURL soitplutôt /signup. Comme à la section 5.2, nous allons dabord écrire un test (extrait 5.28). Extrait 5.28. Simple test dintégration pour le lien dinscription utilisateur. spec/requests/layout_links_spec.rb require spec_helper describe "LayoutLinks" do . . . it "devrait avoir une page dinscription à /signup" do get /signup response.should have_selector(title, :content => "Inscription") end endNotez que cest le même fichier que celui utilisé pour les autres liens du layout, même si la page dinscription estdans un contrôleur différent. Avoir la capacité datteindre des pages dans divers contrôleurs est lun desnombreux avantages de lutilisation des tests dintégration.La dernière étape consiste à faire une route nommée pour les inscriptions. Nous suivrons les exemples delextrait 5.18 et ajouterons une règle match ’/signup’ pour lURL de linscription (extrait 5.29).
    • 190 Extrait 5.29. Une route pour la page dinscription. config/routes.rb SampleApp::Application.routes.draw do get "users/new" match /signup, :to => users#new match /contact, :to => pages#contact match /about, :to => pages#about match /help, :to => pages#help root :to => pages#home . . . end Notez que nous avons gardé la règle get "users/new", qui a été généré pendant la génération automatique du contrôleur à lextrait 5.23. Pour le moment, cette règle est nécessaire pour router /users/new correctement, mais il ne suit pas proprement les conventions REST (Table 2.2), et nous léliminerons à la section 6.3.3. À ce stade, le test dinscription de lextrait 5.28 devrait réussir. Tout ce que qui nous reste à faire est dajouter le propre lien au bouton sur la page daccueil. Comme avec les autres routes, match ’/signup’ nous donne la route nommée gives signup_path, que nous allons utiliser dans lextrait 5.30. Extrait 5.30. Lier le bouton à la page dinscription. app/views/pages/home.html.erb <h1>Application exemple</h1> <p> Cest la page daccueil pour lapplication exemple du <a href="http://railstutorial.org/">Tutoriel Ruby on Rails</a>. </p> <%= link_to "Sinscrire !", signup_path, :class => "signup_button round" %>
    • 191Cela fait, nous en avons fini avec les liens et les routes nommées, au moins jusquà ce que nous ajoutionsla route pour sidentifier (se connecter) (chapitre 9). La page dinscription dun nouvel utilisateur résultant de cetravail (à lURL /signup) est présentée dans lillustration 5.10. Illustration 5.10: La nouvelle page dinscription à ladresse /signup (version anglaise).5.4 ConclusionDans ce chapitre, nous avons arrangé la mise en forme de notre application et avons peaufiné le routage. Lasuite du livre se consacrera à donner du contenu à cette Application Exemple : dabord, en ajoutant desutilisateurs qui peuvent sinscrire, sidentifier et se déconnecter ; ensuite, en ajoutant des micro-messagesdutilisateur ; et, enfin, en ajoutant des relations de suivi entre ces utilisateurs.Si vous vous servez de Git pour le contrôle de vos versions, noubliez pas de « commettre » votre dépôt(commit) et de fusionner votre branche actuelle avec la branche maitresse (et, juste par paranoïa, jouez vostests avant) : $ rspec spec/ $ git add . $ git commit -m "Fin du layout et des routages" $ git checkout master $ git merge filling-in-layout
    • 192 Vous pouvez aussi vouloir « pusher » votre travail sur GitHub, ou déployer votre application actualisée sur Heroku : $ git push $ git push heroku 5.5 Exercises 1. Remplacez la variable locale logo de lextrait 5.21 par une méthode helper du même nom, pour que le nouveau partiel ressemble à lextrait 5.31. Utilisez le code de lextrait 5.32 pour vous aider à démarrer. 2. Vous pouvez avoir noté que nos tests pour les liens du layout testent le routage mais ne vérifient pas en fait que ces routages conduisent effectivement sur les bonnes pages. Une façon dimplémenter ces tests est dutiliser visit et click_link (cliquer le lien) à lintérieur du test dintégration RSpec. Remplissez le code de lextrait 5.33 pour vérifier que tous les liens du layout sont proprement définis. Extrait 5.31. Partiel dentête avec un helper logo de lextrait 5.32. app/views/layouts/_header.html.erb <header> <%= link_to logo, root_path %> <nav class="round"> <ul> <li><%= link_to "Accueil", root_path %></li> <li><%= link_to "Aide", help_path %></li> <li><%= link_to "Connexion", # %></li> </ul> </nav> </header> Extrait 5.32. Un modèle pour lhelper logo. app/helpers/application_helper.rb module ApplicationAideer def logo # Fill in. end # Retourne un titre basé sur la page.
    • 193 def titre . . . endend Extrait 5.33. Un test pour les liens sur le layout. spec/requests/layout_links_spec.rbrequire spec_helperdescribe "LayoutLinks" do . . . it "devrait avoir le bon lien sur le layout" do visit root_path click_link "À Propos" response.should have_selector(title, :content => "À Propos") click_link "Aide" response.should # fill in click_link "Contact" response.should # fill in click_link "Accueil" response.should # fill in click_link "Sinscrire !" response.should # fill in endend
    • 194 Chapitre 6 Modéliser et afficher les utilisateurs, partie I Nous avons terminé le chapitre précédent en créant une « souche » de page pour linscription des utilisateurs (section 5.3) ; tout au long des trois prochains chapitres, nous allons nous attacher à tenir la promesse implicite que contient cette page. La première étape critique est de créer un modèle de données pour les utilisateurs de notre site, ainsi quune façon de conserver ces données. Accomplir cette tâche est le but de ce chapitre et du suivant (chapitre 7), et nous allons donner aux utilisateurs la possibilité de sinscrire au chapitre 8. Une fois que lApplication Exemple pourra créer de nouveaux utilisateurs, nous leur offrirons la possibilité de sidentifier (de se connecter) et de se déconnecter (chapitre 9), et au chapitre 10 (section 10.2) nous apprendrons à protéger nos pages contre les accès malveillants. Pris dans son ensemble, le matériel du chapitre 6 au chapitre 10 développe un système Rails complet didentification (login) et dauthentification. Comme vous le savez peut-être, il existe une variété de solutions dauthentifications clé-en-main dans le monde Rails ; Le Box 6.1 explique pourquoi (au moins dans un premier temps) il est bon de composer la vôtre. Box 6.1.Composer votre propre système dauthentification En pratique, toutes les applications web, de nos jours, requièrent un système didentification et dauthentification. Ça nest donc pas étonnant que la plupart des frameworks web contiennent pléthore doptions pour implémenter de tels systèmes, Rails ne faisant pas exception à la règle. Les exemples de systèmes dauthentification et dauthentification incluent Clearance, Authlogic, Devise ou CanCan (tout comme les solutions non spécifiquement Rails construites au sommet de OpenID ou OAuth). Il est raisonnable de se demander pourquoi nous devrions réinventer la roue. Pourquoi ne pas tout simplement utiliser une solution clé-en-main plutôt quen composer une nous-mêmes ? Il y a plusieurs raisons à ça. Dabord, il nexiste pas de réponse standard dauthentification purement Rails ; lier ce tutoriel à une solution spécifique laisserait grand ouvert le risque que notre choix particulier devienne obsolète et passé de mode. Plus encore, même si nous tombons juste, le code de base du projet utilisé devrait continuer à évoluer, rendant toute explication rapidement obsolète dans le tutoriel. Enfin, introduire toute la machinerie dauthentification dun seul coup serait un désastre pédagogique — pour prendre un seul exemple, Clearance contient plus de 1000 lignes de code et crée un modèle de donnée compliqué dès le départ. Les systèmes dauthentification sont par ailleurs des défis de programmation riches denseignement ; composer notre propre solution signifie que nous avons lopportunité de considérer chacun des aspects lun après lautre, nous conduisant à une compréhension bien plus profonde tant de lauthentification que de Rails lui-même. Je vous encourage donc à étudier les concepts présentés du chapitre 6 au chapitre 10 pour vous donner les meilleurs bases pour vos projets futurs. En temps voulu, si vous décidez dutiliser un système dauthentification
    • 195clé-en-main pour votre application, vous serez en mesure dune part de le comprendre et dautre part deladapter à vos besoins spécifiques.En parallèle de notre modélisation des données, nous développerons une page web pour afficher lesutilisateurs, ce qui nous servira de première étape vers limplémentation de larchitecture REST pour lesutilisateurs (discutée brièvement à la section 2.2.2). Mais nous nirons pas profondément dans ce chapitre,notre but final pour les pages de profil dutilisateur étant de montrer lavatar (le gravatar) de lutilisateur, sesdonnées basiques et une liste de micro-messages, comme le montre la maquette de lillustration 6.1.93(lillustration 6.1 contient notre premier exemple de texte lorem ipsum, texte qui connait une histoire fascinanteque vous devriez lire un jour). Dans ce chapitre, nous coucherons les fondations essentielles de notre pagedaffichage de lutilisateur, et nous entrerons dans ses détails au chapitre 7. Illustration 6.1: Une maquette approximative de la page de profil de lutilisateur.Comme dhabitude, si vous utilisez le contrôle de version Git, il est temps de faire une nouvelle branche pour lamodélisation des utilisateurs : $ git checkout master $ git checkout -b modeling-users(La première ligne ici sert uniquement à sassurer que vous partiez bien de la branche maitresse, pour que labranche-sujet modeling-users parte bien de cette branche master. Vous pouvez ne pas jouercette commande si vous vous trouvez déjà sur la branche maitressse.)
    • 196 6.1 Modèle Utilisateur Bien que le but ultime des trois prochains chapitres soit de créer une page dinscription au site, il ne serait pas très judicieux daccepter les informations dinscription tout de suite puisque nous navons pour le moment aucun endroit où les stoker. Ainsi, la première étape dans linscription des utilisateurs va être de faire une structure de données pour capturer et consigner leurs informations. En Rails, la structure de données par défaut pour un modèle de donnée est appelée, assez naturellement, un modèle (model) (cest le « M » de « MVC » de la section 1.2.6). La solution par défaut de Rails au problème de la persistance consiste tout simplement à utiliser une base de données qui consigne à long terme nos données, et la librairie Rails par défaut pour interagir avec cette base de données est appelée Active Record.94 La librairie Active Record est fournie avec une foule de méthodes pour créer, sauver et chercher des objets- données sans avoir à utiliser le langage structuré de requête (SQL)95 utilisé par les base de données relationnelles. Plus encore, Rails possède une fonctionnalité appelée migrations pour permettre aux définitions des données dêtre écrites en pur Ruby sans avoir à apprendre le langage de définition des données SQL (DDL).96 Leffet est que Rails vous épargnent tous les détails de la consignation des données. Dans ce livre, en utilisant SQLite pour le développement et Heroku pour le déploiement (section 1.4), nous avons poussé ce sujet encore plus loin, jusquau point où nous navons plus du tout à nous pré-occuper de comment Rails consigne les données, même pour les applications en mode production.97 6.1.1 Migrations de la base de données Vous vous souvenez peut-être, section 4.4.5, que nous avons déjà rencontré, via la construction personnalisée dune classe User (Utilisateur), des objets utilisateur possédant les attributs nom et email. Cette classe a servi dexemple utile, mais elle manquait de la propriété essentielle de persistance : quand nous avions créé un objet utilisateur via la console Rails, il disparaissait aussitôt que nous quittions cette console. Notre but dans cette section sera de créer un modèle pour les utilisateurs qui ne disparaisse pas aussi facilement. Comme avec la classe User à la section 4.4.5, nous allons commencer par modéliser lutilisateur avec deux attributs, nom et email, dont le dernier sera utilisé comme nom-utilisateur (username) unique.98 (nous ajouterons un attribut « mot de passe » à la section 7.1). Dans lextrait 4.8, nous avons réalisé cela avec la méthode Ruby attr_accessor : class User attr_accessor :nom, :email . . . end
    • 197En utilisant Rails pour modéliser les utilisateurs nous navons pas besoin didentifier explicitement lesattributs. Comme noté brièvement plus haut, pour consigner les données, Rails utilise la base de donnéesrelationnelle par défaut, qui consiste en des tables composées de rangées de données, où chaque rangéepossède des colonnes pour chaque attribut de la donnée. Par exemple, pour consigner des utilisateurs avec desnoms et des emails, nous créerons une table users (utilisateurs) avec les colonnes nom et email dont chaquerangée correspondra à un utilisateur particulier. En nommant les colonnes de cette façon, nous laissons ActiveRecord deviner par lui-même les attributs de lobjet utilisateur.Voyons comment il fonctionne (si cette discussion vous semble trop abstraite, soyez patient ; les exemples enligne de commande qui commencent à la section 6.1.3 et les copies décran du navigateur de base de données delillustration 6.3 et de lillustration 6.8 devraient rendre les choses plus claires). À la section 5.3.1, vous voussouvenez (extrait 5.23) que nous avons créé un contrôleur pour les utilisateurs (avec une action new), enutilisant la commande : $ rails generate controller Users newIl existe une commande analogue pour faire un modèle : generate model ; lextrait 6.1 montre la commandepour générer un modèle User (Utilisateur) avec deux attributs, nom et email. Extrait 6.1. Générer un modèle User. $ rails generate model User nom:string email:string invoke active_record create db/migrate/<timestamp>_create_users.rb create app/models/user.rb invoke rspec create spec/models/user_spec.rb(Notez que, contrairement à la convention du pluriel pour les noms de contrôleur, les noms des modèles sontsingulier : un contrôleur Users mais un modèle User.) En passant les paramètres optionnels nom:string etemail:string, nous précisons à Rails les deux attributs que nous voulons, en précisant le type de ces attributs(dans ce cas, le type string, cest-à-dire chaine de caractère). Comparez cela avec linclusion des noms dactionsdans lextrait 3.4 et lextrait 5.23.Lun des résultats de la commande generate de lextrait 6.1 est un nouveau fichier appelé une migration. Lesmigrations fournissent un moyen daltérer la structure de la base de données de façon incrémentielle, de tellesorte que notre modèle de données peut adapter progressivement les changements requis au cours dudéveloppement. Dans le cas du modèle User, la migration est créée automatiquement par le script de générationde modèle ; il crée une table users (utilisateurs) avec deux colonnes, nom et email, comme le montrelextrait 6.2 (nous verrons à la section 6.2.4 comment faire une migration à partir de rien).
    • 198 Extrait 6.2. Migration pour le modèle User (pour créer une table users). db/migrate/<timestamp>_create_users.rb class CreateUsers < ActiveRecord::Migration def self.up create_table :users do |t| t.string :nom t.string :email t.timestamps end end def self.down drop_table :users end end Notez que le nom de la migration est préfixé par un temps (timestamp) basé sur le temps de création de la migration. Dans les tout premiers jours des migrations, les noms de fichier étaient préfixés par un entier incrémenté, ce qui provoquait des conflits dans une équipe de collaborateurs quand les différents programmeurs se retrouvaient avec des numéros identiques. Sauf très improbable concordance de création à la milliseconde près, utiliser les timestamps évite efficacement de telles collisions. Concentrons-nous sur la méthode self.up, qui utilise une méthode Rails appelée create_table (créer_table) pour créer une table dans la base de données pour consigner les utilisateurs (lutilisation de self — soi-même — dans self.up lidentifie comme une méthode de classe. Ça na pas dimportance pour le présent, mais nous étudierons les méthodes de classe quand nous en inventerons à la section 7.2.4). La méthode create_table accepte un bloc (section 4.3.2) avec une variable de bloc, dans ce cas appelée t (« t » pour « table »). À lintérieur du bloc, la méthode create_table utilise lobjet t pour créer les colonnes nom et email dans la base de données, tous deux de type string.99 Ici le nom de la table est pluriel (users) même si le nom du modèle, lui, est singulier, ce qui reflète la convention linguistique suivie par Rails : un modèle représente un simple utilisateur, tandis que la table de la base de données consiste en plusieurs utilisateurs. La dernière ligne du bloc, t.timestamps, est une commande spéciale qui crée deux colonnes magiques de nom created_at (créé_le… ) et updated_at (actualisé_le…), qui sont des signatures de temps qui senregistrent automatiquement quand un utilisateur donné est créé ou modifié (nous verrons des exemples concrets de ces colonnes magiques en abordant la section 6.1.3). Le modèle de données complet représenté par sa migration est affiché dans lillustration 6.2.
    • 199 Illustration 6.2: Le modèle de données des utilisateurs produit par lextrait 6.2.Nous pouvons lancer la migration, opération connue sous le nom anglais « migration up » en utilisant lacommande rake (Box 2.1) comme suit :100 $ rake db:migrate(Vous pouvez vous souvenir que nous avons déjà eu à jouer cette commande, à la section 1.2.5 ou encore auchapitre 2.) La première fois que db:migrate est lancé, un fichier db/development.sqlite3 est créé (ouactualisé sil existe déjà. NdT), qui est une base de données SQLite101. Nous pouvons voir la structure de la basede données en utilisant lexcellent Navigateur de données SQLite pour ouvrir le fichierdb/development.sqlite3 (illustration 6.3) ; comparez-le avec le diagramme de lillustration 6.2. Vouspouvez noter quil y a une colonne dans lillustration 6.3 qui na pas été prise en compte par la migration : lacolonne id (identifiant). Comme indiqué brièvement à la section 2.2, cette colonne est créée automatiquement,et elle est utilisée par Rails pour identifier de façon unique une rangée.
    • 200 Illustration 6.3: The Navigateur de base de données SQLite avec notre nouvelle table users. Vous avez probablement deviné que lancer db:migrate exécute la commande self.up dans le fichier migration. Quen est-il, donc, du code self.down (soi-même.en-bas ) ? Comme vous pouvez le deviner, down migre vers le bas (cest-à-dire vers le passé), inversant les effets de la migration. Dans notre cas, cela signifie détruire la table users de la base de données : class CreateUsers < ActiveRecord::Migration . . . def self.down drop_table :users end end Vous pouvez exécuter down avec rake en utilisant largument db:rollback : $ rake db:rollback Cest souvent utile quand vous réalisez quil y a une autre colonne que vous voulez ajouter mais que vous ne voulez pas vous embêter à faire une nouvelle migration : vous pouvez revenir en arrière, ajouter la colonne souhaitée, et reprocéder alors à la migration (ce nest pas toujours pratique, et nous apprendrons comment ajouter autrement une colonne à une table existante à la section 7.1.2). Si vous êtes revenu en arrière pour la base de données, procédez à nouveau à sa migration : $ rake db:migrate 6.1.2 Le fichier modèle Nous avons vu comment la génération du modèle User de lextrait 6.1 générait un fichier de migration (extrait 6.2), et nous avons vu dans lillustration 6.3 les résultats dopérer la migration : cela actualise un fichier appelé development.sqlite3 en créant une table users avec les colonnes id, nom, email, created_at et updated_at. Lextrait 6.1 a créé aussi le modèle lui-même ; la suite de cette section est dédiée à lexplicitation de ce modèle.
    • 201Nous commençons par regarder le code du modèle User, qui se trouve dans le fichier user.rb àlintérieur du dossier app/models/ ; il est, pour ne pas dire, très compact (extrait 6.3). Extrait 6.3. Le tout nouveau modèle User. app/models/user.rb class User < ActiveRecord::Base endSi vous vous souvenez de la section 4.4.2, la syntaxe class User < ActiveRecord::Base signifie que laclasse User hérite de ActiveRecord::Base, de telle sorte que le modèle User possède automatiquement lesfonctionnalités de la classe ActiveRecord::Base. Bien entendu, la connaissance de cet héritage ne sert à riensi nous ne savons pas ce que ActiveRecord::Base contient. Nous allons justement en avoir un avant-goûtdans un moment. Mais avant de continuer, deux tâches sont à accomplir.Annotation du modèleBien que cela ne soit pas strictement nécessaire, vous pourriez trouver utile dannoter vos modèles Rails enutilisant le gem annotate-models (extrait 6.4). Extrait 6.4. Ajout du gem annotate-models au fichier Gemfile. source http://rubygems.org . . . group :development do gem rspec-rails, 2.5.0 gem annotate-models, 1.0.4 end group :test do . . . end(Nous plaçons le gem annotate-models dans le bloc group :development (analogue au group:test) parce que les annotations ne sont pas nécessaires dans les applications en mode production.) Nouslinstallons ensuite avec bundle :
    • 202 $ bundle install Ce gem ajoute une commande appelée annotate, qui introduit simplement des commentaires sur le modèle de données dans le fichier du modèle : $ annotate Annotated User Le résultat apparait dans lextrait 6.5. Extrait 6.5. Le modèle User annoté. app/models/user.rb # == Schema Information # Schema version: <timestamp> # # Table nom: users # # id :integer not null, primary key # nom :string(255) # email :string(255) # created_at :datetime # updated_at :datetime # class User < ActiveRecord::Base end Je trouve quavoir un modèle de données visible dans les fichiers de modèle aide à se souvenir des attributs que le modèle possède, mais les extraits de code présentés ici omettront souvent cette information, par souci de brièveté. Attributs accessibles. Une autre étape, pas strictement nécessaire, mais qui peut être une bonne idée de prudence, est de dire à Rails quels attributs du modèle sont accessibles, cest-à-dire quels attributs peuvent être modifiés par des utilisateurs extérieurs (tels que les utilisateurs soumettant des requêtes à laide de leur navigateur internet). Nous réalisons cela avec la méthode attr_accessible (extrait 6.6). Nous verrons au chapitre 10 quutiliser attr_accessible est important pour prévenir le mass assignment (assignement en masse), un trou de sécurité désepérément courant et souvent sérieux dans beaucoup dapplications Rails.
    • 203 Extrait 6.6. Rendre les attributs nom et email accessibles. app/models/user.rb class User < ActiveRecord::Base attr_accessible :nom, :email end6.1.3 Créer des objets utilisateurNous avons accompli un beau boulot préparatoire, et il est temps à présent den tirer profit et den apprendreplus sur Active Record en jouant avec notre nouveau modèle User créé. Comme au chapitre 4, notre outil dechoix est la console Rails. Puisque nous ne voulons pas (encore) faire des changements dans notre base dedonnées, nous allons démarrer la console dans un bac à sable (sandbox) : $ rails console --sandbox Loading development environment in sandbox (Rails 3.0.7) Any modifications you make will be rolled back on exit >>Comme lindique le message « Any modifications you make will be rolled back on exit » (Toute modificationque vous ferez sera oubliée en quittant le bac à sable. NdT ), en commençant dans un bac à sable, la consoleannulera tous les changements opérés sur la base de données à la fin de cette session bac à sable.En travaillant à la console, il est utile de garder un œil sur le journal de développement (development log), quienregistre les états de bas niveau de SQL renvoyés par Active Record, comme montrés à illustration 6.4. Lafaçon dobtenir cette sortie par une commande en ligne Unix est de tailer le journal : $ tail -f log/development.logLe drapeau -f assure que tail affichera les lines additionnelles comme elles sont écrites. Je recommande degarder une fenêtre de Terminal ouverte pour suivre le journal en travaillant à la console.
    • 204 Illustration 6.4: Suivre le journal de développement. Dans la session de console de la section 4.4.5, nous avons créé un nouvel objet utilisateur avec User.new, auquel nous navions accès quaprès avoir appelé le fichier de lutilisateur exemple de lextrait 4.8. Avec les modèles, la situation est différente ; comme vous pouvez vous en souvenir de la section 4.4.4, la console Rails charge automatiquement lenvironnement Rails, ce qui inclut les modèles. Cela signifie que nous pouvons créer un nouvel objet utilisateur sans travail supplémentaire : >> User.new => #<User id: nil, nom: nil, email: nil, created_at: nil, updated_at: nil> Nous voyons ici la représentation par défaut dun objet utilisateur, qui affiche les mêmes attributs que dans lillustration 6.3 et lextrait 6.5. Appelé sans arguments, User.new retourne un objet dont tous les attributs en la valeur nulle (nil). À la section 4.4.5, nous nous sommes arrangés avec lexemple de la classe User pour quelle utilise une table dinitialisation pour définir les attributs de lobjet ; ce choix était motivé par Active Record, qui permet aux objets dêtre initialisés de cette façon : >> user = User.new(:nom => "Michael Hartl", :email => "mhartl@example.com") => #<User id: nil, nom: "Michael Hartl", email: "mhartl@example.com", created_at: nil, updated_at: nil> Ici nous voyons que les attributs nom et email ont été définis comme voulu.
    • 205Si vous suivez le journal de développement, vous avez peut-être noté quaucune nouvelle ligne na étéaffichée. Ceci parce quappeler User.new naffecte pas la base de données ; cela crée simplement un nouvelobjet Ruby en mémoire. Pour sauver lobjet utilisateur dans la base de données, nous appelons la méthodesave de la variable dinstance user : >> user.save => trueLa méthode save retourne true (vrai) si elle réussit et false (faux) dans le cas contraire (actuellement,toutes les sauvegardes doivent réussir ; nous verrons des cas à la section 6.2 où certains échouent). Dès quevous sauvez, vous devez voir une ligne du journal de développeent avec une commande SQL pour INSERTINTO "users" (INSÉRER DANS "users"). Grâce aux nombreuses méthodes fournies par Active Record, nousnaurons jamais besoin de requête SQL dans ce livre, et jomettrai donc de commenter les commandes SQL.Mais vous pouvez apprendre beaucoup en surveillant le journal de développement.Vous avez peut-être noté que le nouvel objet utilisateur possédait les valeurs nil pour les attributs id et lescolonnes magiques created_at et updated_at. Voyons si notre save y a changé quelque chose : >> user => #<User id: 1, nom: "Michael Hartl", email: "mhartl@example.com", created_at: "2010-01-05 00:57:46", updated_at: "2010-01-05 00:57:46">Nous voyons quà lattribut id a été assigné la valeur 1, et que les colonnes magiques se sont vues assigner letemps et la date courants.102 Pour le moment, les timestamps de création et de modification sont identiques ;nous verrons quils différeront à la section 6.1.5.Comme avec la classe User de la section 4.4.5, les instances du modèle User (« user », ici, est une instance de laclasse User) permettent un accès à leurs attributs en utilisant la notation par point :103 >> user.nom => "Michael Hartl" >> user.email => "mhartl@example.com" >> user.updated_at => Tue, 05 Jan 2010 00:57:46 UTC +00:00
    • 206 Comme nous le verrons au chapitre 8, il est souvent pratique de créer et de sauver un modèle en deux étapes comme nous lavons fait ci-dessus, mais Active Record nous laisse aussi les combiner en une seule étape avec User.create (Utilisateur.créer) : >> User.create(:nom => "A Nother", :email => "another@example.org") => #<User id: 2, nom: "A Nother", email: "another@example.org", created_at: "2010-01-05 01:05:24", updated_at: "2010-01-05 01:05:24"> >> foo = User.create(:nom => "Foo", :email => "foo@bar.com") => #<User id: 3, nom: "Foo", email: "foo@bar.com", created_at: "2010-01-05 01:05:42", updated_at: "2010-01-05 01:05:42"> Notez que User.create, plutôt que de retourner true ou false, retourne lobjet User lui-même, que nous pouvons optionnellement assigner à une variable (comme la variable foo dans la seconde commande ci- dessus). Linverse de la méthode create (créer) est la méthode destroy (détruire) : >> foo.destroy => #<User id: 3, nom: "Foo", email: "foo@bar.com", created_at: "2010-01-05 01:05:42", updated_at: "2010-01-05 01:05:42"> Bizarrement, destroy, comme create, retourne lobjet en question, bien que je ne puisse me souvenir davoir jamais utiliser le retour dune méthode destroy. Encore plus bizarrement peut-être, lobjet pourtant détruit par destroy existe toujours en mémoire : >> foo => #<User id: 3, nom: "Foo", email: "foo@bar.com", created_at: "2010-01-05 01:05:42", updated_at: "2010-01-05 01:05:42"> Comment pouvons-nous être sûrs, alors, davoir détruit un objet ? Et pour les objets sauvés et non détruits, comment pouvons-nous récupérer les utilisateurs de la base de données ? Il est temps dapprendre à utiliser Active Record pour y répondre. 6.1.4 Recherche dans les objets Utilisateurs (Users) Active Record fournit plusieurs options pour retrouver des objets. Utilisons-les pour retrouver le premier utilisateur que nous avons créé puis nous vérifierons que le troisième (foo) a été détruit. Nous allons commencer avec lutilisateur existant :
    • 207 >> User.find(1) => #<User id: 1, nom: "Michael Hartl", email: "mhartl@example.com", created_at: "2010-01-05 00:57:46", updated_at: "2010-01-05 00:57:46">Ici nous avons passé lidentifiant de lutilisateur à User.find ; Active Record retourne lutilisateurcorrespondant à cet attribut id.Voyons si lutilisateur avec un identifiant de 3 existe toujours dans la base de données : >> User.find(3) ActiveRecord::RecordNotFound: Couldnt find User with ID=3Puisque nous avons détruit notre troisième utilisateur à la section 6.1.3, Active Record ne peut pas le retrouverdans la base de données. Ou plus exactement, find a provoqué une exception, qui est une manière dindiquerun évènement exceptionnel dans lexécution dun programme — dans ce cas, un id Active Record inexistant, cequi a conduit find à provoquer une exception ActiveRecord::RecordNotFound.104En addition au find générique, Active Record nous permet aussi de retrouver des utilisateurs par des valeursdattributs spécifiques : >> User.find_by_email("mhartl@example.com") => #<User id: 1, nom: "Michael Hartl", email: "mhartl@example.com", created_at: "2010-01-05 00:57:46", updated_at: "2010-01-05 00:57:46">Puisque nous utiliserons ladresse email comme nom dutilisateur (usernames), cette façon de récupérer desobjets enregistrés sera utile pour permettre aux utilisateurs de sidentifier à notre site (chapitre 8).105Nous allons terminer avec deux des manières classiques de retrouver des utilisateurs. Dabord, il y a la méthodefirst (premier) : >> User.first => #<User id: 1, nom: "Michael Hartl", email: "mhartl@example.com", created_at: "2010-01-05 00:57:46", updated_at: "2010-01-05 00:57:46">Naturellement, first retourne simplement le premier utilisateur de la base de données. Il y a aussi all(tous) : >> User.all => [#<User id: 1, nom: "Michael Hartl", email: "mhartl@example.com",
    • 208 created_at: "2010-01-05 00:57:46", updated_at: "2010-01-05 00:57:46">, #<User id: 2, nom: "A Nother", email: "another@example.org", created_at: "2010-01-05 01:05:24", updated_at: "2010-01-05 01:05:24">] all, qui signifie « tous » en anglais, retourne comme on peut sy attendre un tableau (section 4.3.1) de tous les utilisateurs enregistrés dans la base de données. 6.1.5 Actualisation des objets Utilisateurs (Users) Une fois des objets créés, il est fréquent de les actualiser. Il existe deux façons classiques de le faire. Dabord, nous pouvons assigner les attributs individuellement, comme nous lavons fait à la section 4.4.5 : >> user # Pour lafficher à titre de rappel => #<User id: 1, nom: "Michael Hartl", email: "mhartl@example.com", created_at: "2010-01-05 00:57:46", updated_at: "2010-01-05 00:57:46"> >> user.email = "mhartl@example.net" => "mhartl@example.net" >> user.save => true Notez que létape finale est nécessaire pour écrire les changements dans la base de donées. Nous pouvons voir ce quil arrive en cas doubli de sauvegarde en utilisant reload, qui recharge lobjet en fonction des informations contenues dans la base de données : >> user.email => "mhartl@example.net" >> user.email = "foo@bar.com" => "foo@bar.com" >> user.reload.email => "mhartl@example.net" Maintenant que nous avons actualisé lutilisateur, la valeur des colonnes magiques de temps diffèrent, comme promis à la section 6.1.3 : >> user.created_at => "2010-01-05 00:57:46" >> user.updated_at => "2010-01-05 01:37:32"
    • 209La seconde façon dactualiser les attributs est dutiliser la méthode update_attributes : >> user.update_attributes(:nom => "The Dude", :email => "dude@abides.org") => true >> user.nom => "The Dude" >> user.email => "dude@abides.org"La méthode update_attributes accepte une table dattributs, et en cas de succès accomplit dun couplactualisation et la sauvegarde (en retournant true pour indiquer que la sauvegarde a pu se faire). Il estimportant de noter que, une fois que vous avez défini laccessibilité de certains attributs avecattr_accessible (section 6.1.2.2), seuls ces attributs peuvent être modifiés en utilisant la méthodeupdate_attributes. Si, au cours de vos développements, vous trouvez tout à coup que votre modèlecommence mystérieusement à refuser dactualiser certaines colonnes, assurez-vous que ces colonnes soientbien incluses dans lappel à attr_accessible.6.2 Validations de lutilisateurLe modèle User que nous avons créé à la section 6.1 possède maintenant des attributs nom et email quifonctionnent, mais ils sont complètement « génériques » : nimporte quelle chaine de caractères (mêmelespace) est en ce moment valide dans tous les cas. Et pourtant, les noms et les adresses email sont plusspécifiques que ça. Par exemple, le nom ne devrait pas être vierge, et lemail devrait être conforme au formatcaractéristique des adresses email. Plus encore, puisque nous allons utiliser ladresse comme unique nomdutilisateur (username) quand un nouvel utilisateur sinscrira, nous ne devrions pas lui permettre dentrer uneadresse déjà contenue dans la base de données.En bref, nous ne devrions pas permettre que nom et email soit nimporte quelle chaine de caractères ; nousdevrions forcer certaines contraintes sur ces valeurs. Active Record nous permet dimposer de telles contraintesen utilisant les validations. Dans cette section, nous couvrions plusieurs des cas les plus courants, en validant laprésence (presence), la longueur (length), le format et lunicité (uniqueness). Nous ajouterons à la section 7.1.1une validation finale courante, la confirmation de ladresse. Et nous verrons à la section 8.2 comment lesvalidations nous renvoient des messages derreur utiles quand lutilisateur soumet des données qui les violent.Comme pour les autres fonctionnalités de notre application exemple, nous allons ajouter les validations aumodèle User en suivant toujours le « Développement Dirigé par les Tests ». Puisque nous avons changé lemodèle de données, cest une bonne idée de préparer le test de la base de données avant de poursuivre : $ rake db:test:prepare
    • 210 Cela sassure simplement que le modèle de données de la base de données en mode test, db/test.sqlite3, reflète la base de données en mode de développement, db/development.sqlite3. 6.2.1 Valider la présence Nous allons commencer par un test de la présence de lattribut nom attribute. Bien que la première étape en TDD consiste à écrire un test échouant (section 3.2.2), nous nen savons pas encore assez dans ce cas à propos des validations pour écrire le test approprié, donc nous allons dabord écrire la validation, en utilisant la console pour la comprendre. Ensuite nous commenterons la validation pour lexclure, écrirons un test échouant, et vérifierons que décommenter les validations fait bien réussir le test. Cette procédure peut paraitre pédante pour un test aussi simple, mais jai trop vu106 de « simples » tests échouer ; être méticuleux à propos de la TDD est simplement la seule manière dêtre confiant sur le fait que nous testons la bonne chose à tester (la technique dexclusion par commentaire — que jappellerai « ex-commenter ». NdT — est utile, aussi, quand on vient au secours dune application dont le code est déjà écrit mais quelle ne possède — quelle horreur ! — aucun test). La façon de valider la présence dun attribut nom est dutiliser la méthode validates avec largument :presence => true, comme montré dans lextrait 6.7. Largument :presence => true est une table doptions à un élément ; souvenez-vous, section 4.3.4, que les accolades sont optionnelles lorsque les tableaux sont les arguments finaux dune méthode (comme noté à la section 5.1.1, lutilisation de tables doptions est un théme récurrent en Rails). Extrait 6.7. Valider la présence dun attribut nom. app/models/user.rb class User < ActiveRecord::Base attr_accessible :nom, :email validates :nom, :presence => true end Comme discuté brièvement à la section 2.3.2, lutilisation de validates est caractéristique de Rails 3 (en Rails 2.3, nous aurions dû plutôt écrire validates_presence_of :nom). Lextrait 6.7 peut sembler quelque peu magique, mais validates est juste une méthode, telle que lest aussi attr_accessible. Une formulation équivalente de lextrait 6.7 en utilisant les parenthèses serait : class User < ActiveRecord::Base attr_accessible(:nom, :email) validates(:nom, :presence => true)
    • 211 endUtilisons la console pour voir les effets de lajout de la validation sur notre modèle User :107 $ rails console --sandbox >> user = User.new(:nom => "", :email => "mhartl@example.com") >> user.save => false >> user.valid? => falseIci, user.save retourne false (faux), indiquant un sauvegarde défectueuse. Dans la commande finale, nousutilisons la méthode valid?, qui retourne false (false) quand lobjet rate une validation ou plus, et true(vrai) quand toutes les validations réussissent (souvenez-nou, section 4.2.3, que Ruby utilise le pointdinterrogation pour signifier les méthodes booléennes). Dans ce cas, nous navons quune seule validation,donc nous savons laquelle échoue, mais il peut toujours être utile de vérifier en utilisant lobjet errors(erreurs) généré par léchec : >> user.errors.full_messages => ["Name cant be blank"](Le message derreur contient un indice qui révèle que Rails valide la présence dun attribut en utilisant laméthode blank?, ce que nous avons vu à la fin de la section 4.4.3.)Voilà pour les tests échouant. Pour sassurer que notre test naissant échouera, « ex-commentons » la validation(extrait 6.8). Extrait 6.8. Ex-commenter une validation pour sassurer que le test échoue. app/models/user.rb class User < ActiveRecord::Base attr_accessible :nom, :email # validates :nom, :presence => true endComme dans le cas de la génération de contrôleur (par exemple lextrait 5.23), la commande de génération demodèle dans lextrait 6.1 produit une spec initiale pour tester les utilisateurs, mais dans ce cas elle estpratiquement vierge (extrait 6.9).
    • 212 Extrait 6.9. Le spec User par défaut, pratiquement vierge. spec/models/user_spec.rb require spec_helper describe User do pending "add some examples to (or delete) #{__FILE__}" end Cette spec utilise simplement la méthode pending (en attendant) pour indiquer que nous devrions soit remplir le spec avec quelque chose de plus utile, soit le supprimer. Nous pouvons en voir les effets en jouant ce spec du modèle User : $ rspec spec/models/user_spec.rb * Finished in 0.01999 seconds 1 example, 0 failures, 1 pending Pending: User add some examples to (or delete) /Users/mhartl/rails_projects/sample_app/spec/models/user_spec.rb (Not Yet Implemented) Nous suivrons le conseil du spec par défaut en le remplissant avec des exemples RSpec, montrés dans lextrait 6.10. Extrait 6.10. Le spec User initial. spec/models/user_spec.rb require spec_helper describe User do before(:each) do @attr = { :nom => "Example User", :email => "user@example.com" } end it "devrait créer une nouvelle instance dotée des attributs valides" do User.create!(@attr) end it "devrait exiger un nom" end
    • 213Nous avons vu require et describe auparavant, le plus récemment dans lextrait 5.28. La lignesuivante est un bloc before(:each) ; ceci a été couvert brièvement par un exercice (extrait 3.33), et tout cequil fait est de jouer le code à lintérieur du bloc avant chaque exemple — dant ce cas en définissant linstancede variable @attr comme tableau dinitialisation.Le premier exemple est juste un « check de santé » vérifiant que le modèle User fonctionne. Il utiliseUser.create! (lire « create bang », « créer bang » ), qui fonctionne comme la méthode create vue à lasection 6.1.3 à la différence près quelle provoque une exception (une erreur)ActiveRecord::RecordInvalid si la création échoue (similairement à lexceptionActiveRecord::RecordNotFound que nous avons pu voir à la section 6.1.4). Aussi longtemps que lesattributs sont valides, elle ne provoque aucune erreur, et le test réussira.La ligne finale est le test pour la présence de lattribut nom — ou plutôt, il devrait être le test effectif, sil y avaitquelque chose dans cet attribut. Au lieu de ça, le test est juste une esquisse, mais une esquisse utile : cest unespec de mise en attente, qui est un moyen décrire une description du comportement de lapplication sans sesoucier encore de son implémentation. Lextrait 6.9 montre un exemple spec de mise en attente utilisant unappel explicite à la méthode pending (mise en attente) ; dans ce cas, puisque nous avons inclus seulement lapartie it de lexemple… it "devrait exiger un nom"… RSpec déduit lexistence dune spec de mise en attente.Les specs de mise en attente sont bien traités par les programmes pour jouer des specs, comme vu pourAutotest dans lillustration 6.5, et la sortie de rspec spec/ est utile de la même façon. Les specs de mise enattente sont utiles en tant qu« emplacements réservés » pour des tests que nous savons avoir à écrire mais quenous ne voulons pas implémenter immédiatement.
    • 214 Illustration 6.5: Autotest (via autotest) avec un spec User de mise en attente. Dans le but de remplir le spec de mise en attente, nous avons besoin dune manière de créer une table dattributs avec un nom invalide (la table @attr est valide par construction, avec un attribut nom non vierge). La méthode Hash merge (fusion de Table de hachage) accomplit la chose, comme nous pouvons le voir dans la console rails : >> @attr = { :nom => "Example User", :email => "user@example.com" } => {:nom => "Example User", :email => "user@example.com"} >> @attr.merge(:nom => "") => {:nom => "", :email => "user@example.com"} Avec merge (fusionne) en main, nous sommes prêts à faire une nouvelle spec (utilisant une astuce que jexpliquerai dans un instant) comme le montre lextrait 6.11. Extrait 6.11. Un test échouant pour la validation de lattribut nom. spec/models/user_spec.rb describe User do before(:each) do @attr = { :nom => "Example User", :email => "user@example.com" } end
    • 215 . . . it "exige un nom" do bad_guy = User.new(@attr.merge(:nom => "")) bad_guy.should_not be_valid end endIci nous utilisons merge pour créer un nouvel utilisateur appelée bad_guy (mauvais_garcon) avec un nomvierge. La deuxième ligne utilise alors la méthode RSpec should_not (ne_devrait_pas) pour vérifier quelutilisateur en résultant nest pas valide. Lastuce à laquelle je faisais allusion ci-dessus fait référence àbe_valid (être_valide) : nous avons appris plus haut dans cette section quun objet utilisateur (user)répondait à la méthode booléenne valid?. RSpec adopte une convention utile qui nous permettait de testernimporte quelle méthode booléenne en supprimant le point dinterrogation et en préfixant la méthode avecbe_ (être). En dautres mots… bad_guy.should_not be_valid… est équivalent à : bad_guy.valid?.should_not == truePuisque ça sonne plus proche du langage naturel (au moins pour les anglophones. NdT), écrire should_notbe_valid (ne_devrait_pas etre_valide) est définitivement plus idiomatiquement correct en RSpec.Avec ça, notre nouveau test devrait échouer, ce que nous pouvons vérifier avec Autotest ou en jouant le fichieruser_spec.rb en utilisant le script spec : $ rspec spec/models/user_spec.rb .F 1) User exige un nom FAILED expected valid? to return false, got true ./spec/models/user_spec.rb:14: 2 examples, 1 failure
    • 216 Maintenant décommentons la validation (cest-à-dire que de lextrait 6.8 nous allons revenir à lextrait 6.7) pour faire réussir le test : $ rspec spec/models/user_spec.rb .. 2 examples, 0 failures Bien sûr, nous voulons aussi valider la présence de ladresse email. Le test (extrait 6.12) est analogue à celui pour lattribut nom. Extrait 6.12. Un test de présence de lattribut email. spec/models/user_spec.rb describe User do before(:each) do @attr = { :nom => "Example User", :email => "user@example.com" } end . . . it "exige une adresse email" do no_email_user = User.new(@attr.merge(:email => "")) no_email_user.should_not be_valid end end Limplémentation est aussi virtuellement la même, comme vu dans lextrait 6.13. Extrait 6.13. Valider la présence des attributs nom et email. app/models/user.rb class User < ActiveRecord::Base attr_accessible :nom, :email validates :nom, :presence => true validates :email, :presence => true end
    • 217À présent, tous les tests devraient réussir, donc les validations de « présence » sont complets.6.2.2 Validation de la longueur (length)Nous avons contraint notre modèle utilisateur dexiger un nom (et une adresse mail) pour chaque utilisateur,mais nous devons aller plus loin : les noms dutilisateur seront affichés sur le site exemple, donc nous devonsintroduire quelques limites de longueur. Avec tout le travail que nous avons accompli à la section 6.2.1, cetteétape est facile.Nous commençons par un test. Il ny a pas de science exacte pour choisir une longueur maximum ; nous allonsdéclarer que 50 caractères sont une limite maximale acceptable, ce qui signifie que les noms de 51 caractèresseront trop longs (extrait 6.14). Extrait 6.14. Un test pour la validation de longueur du nom. spec/models/user_spec.rb describe User do before(:each) do @attr = { :nom => "Example User", :email => "user@example.com" } end . . . it "devrait rejeter les noms trop longs" do long_nom = "a" * 51 long_nom_user = User.new(@attr.merge(:nom => long_nom)) long_nom_user.should_not be_valid end endDe façon pratique, nous avons utilisé la « multiplication de chaine » dans lextrait 6.14 pour construire unechaine de 51 caractères. Nous pouvons voir comment cela fonctionne en utilisant la console : >> s = "a" * 51 => "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" >> s.length => 51
    • 218 Le test de lextrait 6.14 devrait échouer. Pour le voir réussir, nous avons besoin de connaitre largument de validation pour contraindre la longueur, :length, qui sutilise avec le paramètre :maximum pour placer la limite supérieure (extrait 6.15). Extrait 6.15. Ajout dune validation de longueur pour lattribut nom. app/models/user.rb class User < ActiveRecord::Base attr_accessible :nom, :email validates :nom, :presence => true, :length => { :maximum => 50 } validates :email, :presence => true end Avec la réussite de notre suite de tests, nous pouvons avancer vers une validation plus difficile : le format de lemail.. 6.2.3 Validation de format Nos validations pour lattribut nom forcent seulement des contraintes minimales — seuls les noms non vierges et inférieurs à 51 caractères seront acceptés — mais bien sûr lattribut email doit satisfaire des exigences plus strictes. Jusquici nous navons rejeté que ladresse vierge ; dans cette section, nous exigerons des adresses email quelles soient conformes au modèle habituel user@example.com. Ni les tests ni la validation ne seront exhaustives, mais juste assez bonne pour accepter les adresses les plus valides et rejeter les plus invalides. Nous allons commencer avec une paire de tests impliquant une collection dadresses valides et dadresses invalides. Pour faire ces collections, il est utile de se souvenir de la méthode pour faire des listes de strings, vue dans la session de console : >> %w[foo bar baz] => ["foo", "bar", "baz"] >> adresses = %w[user@foo.com THE_USER@foo.bar.org first.last@foo.jp] => ["user@foo.com", "THE_USER@foo.bar.org", "first.last@foo.jp"] >> adresses.each do |address| ?> puts address >> end user@foo.com THE_USER@foo.bar.org first.last@foo.jp
    • 219Ici, nous « bouclons » sur les éléments du tableau adresses en utilisant la méthode each (section 4.3.2).Cette tehnique bien en main, nous sommes prêts à écrire quelques tests de validation de format basiquedadresses (extrait 6.16). Extrait 6.16. Tests pour la validation du format des adresses mail. spec/models/user_spec.rb describe User do before(:each) do @attr = { :nom => "Example User", :email => "user@example.com" } end . . . it "devrait accepter une adresse email valide" do adresses = %w[user@foo.com THE_USER@foo.bar.org first.last@foo.jp] adresses.each do |address| valid_email_user = User.new(@attr.merge(:email => address)) valid_email_user.should be_valid end end it "devrait rejeter une adresse email invalide" do adresses = %w[user@foo,com user_at_foo.org example.user@foo.] adresses.each do |address| invalid_email_user = User.new(@attr.merge(:email => address)) invalid_email_user.should_not be_valid end end endComme noté ci-dessus, cest loin dêtre exhaustif, mais nous vérifions les formes dadresse email valides les pluscourantes user@foo.com, THE_USER@foo.bar.org (capitales, tiret plat et domaines composés), etfirst.last@foo.jp (le nom dutilisateur standard de lentreprise first.last, avec un domaine de deuxlettres jp), en plus de quelques adresses de forme invalide.
    • 220 Le code de lapplication pour la validation du format des emails utilise une expression régulière (ou regex, pour « regular expression » en anglais) pour définir le format, aux côtés de largument :format pour la méthode validates (extrait 6.17). Extrait 6.17. Valider le format de lemail avec une expression régulière. app/models/user.rb class User < ActiveRecord::Base attr_accessible :nom, :email email_regex = /A[w+-.]+@[a-zd-.]+.[a-z]+z/i validates :nom, :presence => true, :length => { :maximum => 50 } validates :email, :presence => true, :format => { :with => email_regex } end Ici, email_regex est une expression régulière, appelé aussi regex. Le code : email_regex = /A[w+-.]+@[a-zd-.]+.[a-z]+z/i . . . validates :email, :presence => true, :format => { :with => email_regex } … sassure que seules les adresses mail qui correspondent au pattern de lexpression régulière seront considérées comme valides. Mais doù vient ce pattern ?… Les expressions régulières consistent en un langage concis (certains diront illisible) pour « matcher » les modèles de texte ; apprendre à construire une expression régulière est un art, et pour commencer, jai mis email_regex en petites pièces (Table 6.1).108 Pour apprendre vraiment les expressions régulières, cependant, je considère que le formidable éditeur dexpression régulières Rubular (illustration 6.6) est simplement essentiel.109 Le site Rubular est une magnifique interface interactive pour fabriquer des expressions régulières, tout autant quun site de référence rapide et pratique aux expressions. Je vous encourage à étudier la Table 6.1 avec une fenêtre de navigateur ouverte sur Rubular — aucune lecture conséquente sur les expressions régulières ne pourra remplacer une ou deux heures passées à jouer avec Rubular.
    • 221 Expression Sens/A[w+-.]+@[a-zd-.]+.[a-z]+z/i Pattern complet/ marque le début de lexpressionA cherche le début de la chaine[w+-.]+ au moins un caractère de mot, le signe « + », le signe « - » ou le point@ arobase[a-zd-.]+ au moins une lettre, un chiffre, un tiret ou un point. un point[a-z]+ au moins une lettrez cherche la fin de la chaine/ marque la fin de lexpression régulièrei insensible à la casse Table 6.1: Détail de lexpression régulière pour les emails de lextrait 6.17. En passant, il existe en fait une expression régulière complète pour vérifier les adresses emails en concordance avec le standard officiel, mais ça ne vaut vraiment pas la peine. Celle de lextrait 6.17 est bien, peut-être même meilleure que lexpression officielle.110
    • 222 Illustration 6.6: Limpressionnant éditeur dexpressions régulières Rubular. Les tests devraient tous réussir maintenant (en fait, les tests pour les adresses mails valides auraient dû toutes réussir ; puisque les expressions régulières sont notoirement sujettes à erreur, les tests de validation de lemail sont ici à titre de test de cohérence sur email_regex). Cela signifie quil ne reste quune seule contrainte à ajouter : forcer ladresse email à être unique. 6.2.4 Validation dunicité Pour forcer lunicité des adresses email (de telle sorte que nous puissons les utiliser comme username — nom dutilisateur), nous allons utiliser loption :unique de la méthode validates. Mais soyez averti : il existe un piège majeur, donc ne survolez pas cette section, lisez-là soigneusement. Nous commencerons, comme dhabitude, avec nos tests. Dans nos précédents tests de modèle, nous avons principalement utilisé User.new, qui crée juste un objet Ruby en mémoire, mais pour les tests dunicité nous avons besoin en fait de déposer un enregistrement dans la base de données.111 Le (premier) test de duplication demail apparait dans lextrait 6.18. Extrait 6.18. Un test pour le rejet de la duplication dune adresse mail. spec/models/user_spec.rb describe User do before(:each) do @attr = { :nom => "Example User", :email => "user@example.com" } end . . . it "devrait rejeter un email double" do # Place un utilisateur avec un email donné dans la BD. User.create!(@attr) user_with_duplicate_email = User.new(@attr) user_with_duplicate_email.should_not be_valid end end La méthode ici est de créer un utilisateur et alors dessayer de faire un autre utilisateur qui possèderait la même adresse email (nous utilisons la méthode « bruyante » create!, dabord vue à lextrait 6.10, de telle sorte
    • 223quelle provoquera une exception, une erreur, si quelque chose ne tournait pas rond. En utilisant create,sans le « bang » !, nous risquerions dobtenir une erreur silencieuse dans nos tests, une source potentielle debogue insaisissable). Nous pouvons obtenir que ce test réussisse avec le code de lextrait 6.19.112 Extrait 6.19. Valider lunicité de ladresse mail. app/models/user.rb class User < ActiveRecord::Base . . . validates :email, :presence => true, :format => { :with => email_regex }, :uniqueness => true endNous nen avons pas fini, cependant. Les adresses mails sont sensibles à la casse — foo@bar.com se rend aumême endroit que FOO@BAR.COM ou FoO@BAr.coM — donc nos validations doivent aussi couvrir ce cas. Noustestons cela avec le code de lextrait 6.20. Extrait 6.20. Un test pour le rejet dune adresse mail existente, insensible à la casse. spec/models/user_spec.rb describe User do before(:each) do @attr = { :nom => "Example User", :email => "user@example.com" } end . . . it "devrait rejeter une adresse email invalide jusquà la casse" do upcased_email = @attr[:email].upcase User.create!(@attr.merge(:email => upcased_email)) user_with_duplicate_email = User.new(@attr) user_with_duplicate_email.should_not be_valid end end
    • 224 Ici nous utilisons la méthode upcase sur les chaines (vue brièvement à la section 4.3.2). Ce test fait la même chose que le premier, mais plutôt sur une adresse capitalisée. Si ce test vous semble un petit peu abstrait, prenez votre console et tapez : $ rails console --sandbox >> @attr = { :nom => "Example User", :email => "user@example.com" } => {:nom => "Example User", :email => "user@example.com"} >> upcased_email = @attr[:email].upcase => "USER@EXAMPLE.COM" >> User.create!(@attr.merge(:email => upcased_email)) >> user_with_duplicate_email = User.new(@attr) >> user_with_duplicate_email.valid? => true Bien entendu, pour le moment, user_with_duplicate_email.valid? est true (vrai), puisque cest un test déchec, mais nous voulons quil retourne false (faux). Heureusement, :uniqueness (unicité) accepte une option, :case_sensitive, justement dans ce but (extrait 6.21). Extrait 6.21. Valider lunicité de ladresse mail, en ignorant la casse. app/models/user.rb class User < ActiveRecord::Base . . . validates :email, :presence => true, :format => { :with => email_regex }, :uniqueness => { :case_sensitive => false } end Notez que nous avons simplement remplacé true par :case_sensitive => false ; Rails en déduit que :uniqueness (lunicité ) devrait être true (vraie). À ce point, notre application fait (quelque peu) en sorte que ladresse mail soit unique, et notre suite de tests devrait réussir. Lavertissement dunicité Il y a juste un petit problème, le piège auquel je faisais allusion ci-dessus : Utiliser validates :uniqueness ne garantit en rien lunicité.
    • 225« Oh ?… Mais quest-ce qui peut bien mal tourner ?… » Cela : 1. Alice sinscrit à lapplication exemple, avec ladresse mail alice@wonderland.com ; 2. Accidentellement, Alice clique deux fois sur le bouton « Soumettre », envoyant deux requêtes en succession rapide ; 3. La séquence suivante se produit : la requête 1 crée un utilisateur en mémoire qui réussit la validation, la requête 2 fait de même, lutilisateur de la requête 1 (Alice) est enregistré, lutilisateur de la requête 2 (ALice) est aussi enregistré ; 4. Résultat : deux enregistrements dutilisateur avec la même adresse email, malgré la validation dunicité.Si la séquence ci-dessus ne vous semble pas plausible, croyez-moi, elle lest. Cela survient sur nimporte quelsite Rails connaissant un trafic important.113 Heureusement, la solution est simple à implémenter ; nous avonsjuste besoin de forcer lunicité au niveau de la base de données elle-même. Notre méthode consiste à créer unindex de base de données sur la colonne email, et ensuite dexiger que cet index soit unique.Lindex email représente une actualisation à nos impératifs du modèle de données, ce qui (comme discuté à lasection 6.1.1) est traité par Rails en utilisant les migrations. Nous avons vu à la section 6.1.1 que générer lemodèle User créait automatiquement une nouvelle migration (extrait 6.2) ; dans le cas présent, nous ajoutonsde la structure à un modèle existant, donc nous avons besoin de créer une migration directement en utilisant legénérateur migration : $ rails generate migration add_email_uniqueness_indexContrairement à la migration pour les utilisateurs, la migration pour lunicité de ladresse mail nest pas pré-définie, donc nous avons besoin de remplir son contenu par le code de lextrait 6.22.114 Extrait 6.22. La migration pour forcer lunicité de ladresse mail. db/migrate/<timestamp>_add_email_uniqueness_index.rb class AddEmailUniquenessIndex < ActiveRecord::Migration def self.up add_index :users, :email, :unique => true end def self.down remove_index :users, :email end
    • 226 end Ce code utilise une méthode Rails appelée add_index pour ajouter lindex sur la colonne email de la table users. Lindex par lui-même ne contraint pas lunicité, mais cest loption :unique => true qui le fait. Létape finale est de migrer la base de données : $ rake db:migrate Maintenant, le scénario dAlice fonctionnera parfaitement : la base de données sauvera lenregistrement basé sur la première requête, et rejettera la seconde pour violation de la contrainte dunicité de ladresse mail (une erreur apparaitra dans le journal Rails, mais elle ne provoque aucun dommage. Vous pouvez bien entendu saisir lexception ActiveRecord::StatementInvalid qui est provoquée — voyez Insoshi pour un exemple — mais dans ce tutoriel nous ne nous occuperons pas de cette étape). Lajout de lindex sur lattribut email accomplit un second but, auquel jai fait brièvement allusion à la section 6.1.4 : il fixe (au sens de « régler ». NdT) aussi un problème sérieux de find_by_email, comme lexplique le Box 6.2. Box 6.2.Index de Bases de données En créant une colonne dans la base de données, il est important de considérer si nous aurons besoin de trouver les enregistremens par cette colonne. Considérez, par exemple, lattribut email créé par la migration dans lextrait 6.2. Quand nous permettrons aux utilisateurs de sidentifier à lapplication exemple, en abordant le chapitre 8, nous aurons besoin de trouver lenregistrement de lutilisateur correspondant à ladresse email soumise ; malheureusement, sappuyant sur un modèle de données naïf, la seule façon de retrouver un user par son adresse email est de passer en revue chaque rangée dutilisateur dans la base de données et de comparer son email à celui donné. Ce processus est connu dans le business des bases de données comme un full-table scan (scanner complet de la table), et pour un site réel contenant des milliers dutilisateurs, cest une très vilaine chose. Placer un index sur la colonne de lemail fixe ce problème. Pour comprendre un index de base de données, on peut prendre lanalogie dun index de livre. Dans un livre, pour trouver toutes les occurences dun mot donné, disons « foobar », vous aurez à consulter chaque page, une par une, pour trouver chaque « foobar ». Avec un index de livre, dun autre côté, vous pouvez juste chercher « foobar » dans lindex et voir toutes les pages qui contiennent ce mot. Un index de base de données fonctionne essentiellement de la même façon. 6.3 Affichage des utilisateurs Nous nen avons pas tout à fait fini avec le modèle utilisateur de base — nous avons encore besoin dajouter des mots de passe, tâche qui sera accomplie au chapitre 7 — mais nous en avons fait suffisamment pour faire une
    • 227page minimale pour pouvoir afficher les informations de lutilisateur. Cela permettra une introduction endouceur à lorganisation de type REST des actions utilisateur de notre site. Puisque cest juste unedémonstration grossière pour le moment, il ny aura pas de tests dans cette section ; nous les ajouterons quandnous étofferons la vue de lutilisateur à la section 7.3.6.3.1 Débuggage et environnement RailsÀ titre de préparation de lajout de pages dynamique dans notre Application Exemple, il est temps à présentdajouter des informations de débuggage au layout de notre site (extrait 6.23). Cela affiche des informationsutiles sur chaque page en utilisant la méthode intégrée debug et la variable params (que nous étudierons plusen détail à la section 6.3.2), comme le montre lillustration 6.7. Extrait 6.23. Ajout dinformation de débuggage au layout du site. app/views/layouts/application.html.erb <!DOCTYPE html> <html> . . . <body> <div class="container"> . . . <%= render layouts/footer %> <%= debug(params) if Rails.env.development? %> </div> </body> </html>Puisque nous ne voulons pas afficher les informations de débuggage à lutilisateur de lapplication déployéeonline, nous utilisons : if Rails.env.development?… pour les restreindre à lenvironnement de développement. Bien que nous ayons déjà abordé lévidence desenvironnements Rails précédemment (tout récemment à la section 6.1.3), cest la première fois que cela nousimporte vraiment.
    • 228 Illustration 6.7: La page daccueil de lapplication exemple (/) avec des informations de débuggage en bas de page. Rails arrive équipé de trois environnements : test, development, et production.115 Lenvironnement par défaut de la console est lenvironnement développement : $ rails console Loading development environment (Rails 3.0.7) >> Rails.env => "development" >> Rails.env.development? => true >> Rails.env.test? => false Comme vous pouvez le voir, Rails fournit un objet Rails avec un attribut env et des méthodes booléennes associées. En particulier, Rails.env.development? est true (vrai) seulement dans lenvironnement développement, donc le code Ruby embarqué : <%= debug(params) if Rails.env.development? %> … ne sera pas inséré dans les applications en mode production ou test (insérer les informations de débuggage pour les tests ne cause probablement pas de dommage, mais ça nest probablement pas bon non plus, donc il est mieux de restreindre laffichage des débuggage à lenvironnement de développement seulement).
    • 229Quand vous avez besoin de jouer la console dans un environnement différent (pour débugguer un test parexemple), vous pouvez passer lenvironnement en paramètre au script console : $ rails console test Loading test environment (Rails 3.0.7) >> Rails.env => "test"Comme pour la console, development (développement) est lenvironnement par défaut du server Rails local,mais vous pouvez aussi le faire jouer dans un environnement différent : $ rails server --environment productionSi vous voyez votre application jouer en mode production, elle ne fonctionnera pas sans base de données,laquelle peut être créée en jouant rake db:migrate en mode production : $ rake db:migrate RAILS_ENV=production(Je trouve déroutant que la console, le serveur, et les commandes de migration précisent lenvironnement,quand ce nest pas lenvironnement par défaut, de trois façons mutuellement incompatibles, cest pourquoi jaipris le soin de les montrer les trois.)En passant, si vous avez déployé votre application exemple sur Heroku, vous pouvez voir son environnement enutilisant la commande heroku qui fournit sa propre console à distance : $ heroku console Ruby console for yourapp.heroku.com >> Rails.env => "production" >> Rails.env.production? => trueNaturellement, puisque Heroku est une plateforme pour les sites en mode production, il joue chaqueapplication dans cette environnement production.6.3.2 Modèle User, Vue, ContrôleurAfin de faire une page pour voir un utilisateur, nous allons utiliser le modèle User pour créer un utilisateur dansla base de données, créer une vue (View) pour afficher quelques unes de ses informations, et ajouter alors une
    • 230 action au contrôleur (Controller) pour traiter les requêtes du navigateur. En dautres termes, pour la première fois dans ce tutoriel, nous allons voir en un seul endroit les trois éléments de larchitecture MVC (Modèle-Vue- Contrôleur) dont nous avons parlé pour la première fois à la section 1.2.6. Notre première étape consiste à créer un utilisateur en utilisant la console, que nous prendrons soin de ne pas démarrer en mode bac à sable puisque cette fois le but est de sauver lenregistrement dans la base de données : $ rails console Loading development environment (Rails 3.0.7) >> User.create!(:nom => "Michael Hartl", :email => "mhartl@example.com") => #<User id: 1, nom: "Michael Hartl", email: "mhartl@example.com", created_at: "2010-01-07 23:05:14", updated_at: "2010-01-07 23:05:14"> Pour vérifier deux fois que ça a marché, regardons la rangée dans la base de données de développement en utilisant le Navigateur de base de données (illustration 6.8). Notez que les colonnes correspondent aux attributs du modèle de données défini à la section 6.1. Illustration 6.8: Une rangée utilisateur dans la base de données SQLite db/development.sqlite3. Ensuite vient la vue, qui est minimale pour mettre laccent sur le fait que cest juste une démonstration (extrait 6.24). Nous utilisons le fichier standard de Rails pour afficher un utilisateur,
    • 231app/views/users/show.html.erb ; contrairement à la vue new.html.erb, que nous avons crééeavec le générateur de lextrait 5.23, le fichier show.html.erb nexiste pas encore, donc vous devez le créer à lamain. Extrait 6.24. Une vue brute pour afficher les informations de lutilisateur. app/views/users/show.html.erb <%= @user.nom %>, <%= @user.email %>Cette vue utilise du code Ruby embarqué pour afficher le nom et ladresse mail de lutilisateur, en assumantlexistence dune variable dinstance appelée @user. Bien sûr, la page finale daffichage de lutilisateur sera trèsdifférente, et naffichera pas publiquement ladresse email.Enfin, nous ajouterons laction show au contrôleur Users (correspondant à la vue show.html.erb) avec lecode de lextrait 6.25, qui définit la variable dinstance @user nécessaire à la vue. Extrait 6.25. Le contrôleur Users avec une action show. app/controllers/users_controller.rb class UsersController < ApplicationController def show @user = User.find(params[:id]) end def new @title = "Sinscrire" end endIci nous avons donné un peu plus de nous-mêmes en utilisant le paramètre (params), objet standard de Railspour récupérer lidentifiant (id) de lutilisateur. Quand nous ferons la requête appropriée au contrôleur User,params[:id] sera lid utilisateur 1, donc leffet est le même que la commande find (trouver) : User.find(1)… que nous avons vu à la section 6.1.4.Bien que la vue et laction show soient maintenant toutes deux définies, nous navons toujours pas de moyen devoir la page elle-même. Cela nécessite de définir la règle adéquate dans le fichier de routage de Rails, commenous allons le voir dans la section suivante.
    • 232 6.3.3 Une ressource Users Nous méthode pour afficher la page daffichage de lutilisateur va suivre les conventions de larchitecture REST préconisée pour les applications Rails. Ce style est basé sur les idées de transfer détat représentationnel (representational state transfer) identifié et nommé par lexpert en informatique Roy Fielding dans sa dissertation doctorale Architectural Styles and the Design of Network-based Software Architectures (Styles architecturaux et conception des architectures de logiciels de réseau).116 Le style de conception REST met laccent sur la représentation des données en tant que ressources qui peuvent être créées, affichées, actualisées et détruites — quatre actions correspondant au quatre opérations fondamentales POST, GET, PUT et DELETE définies par le standard HTTP (Box 3.1). Quand on suit les principes REST, les ressources sont typiquement référencées en utlisant des noms de ressource et un identifiant unique. Quest-ce que ça signifie dans le contexte des utilisateurs — auxquels nous penserons en tant que « ressource Users » ? Cela signifie que nous devrions voir lutilisateur avec un identifiant de 1 en envoyant une requête GET à lURL /users/1. Ici, laction show est implicite dans le type de requête — quand les fonctionnalités REST de Rails sont activées, les requêtes GET sont automatiquement traitées par laction show action. Illustration 6.9: Leffet initial de lURL /users/1. Malheureusement, lURL /users/1 ne fonctionne pas encore, à cause dune erreur de routage (illustration 6.9). Nous pouvons faire fonctionner lURL utilisateur de style REST en ajoutant users (utilisateurs) comme ressource au fichier config/routes.rb, comme vu dans lextrait 6.26.
    • 233 Extrait 6.26. Ajout dune ressource Users au fichier de routage. config/routes.rb SampleApp::Application.routes.draw do resources :users match /signup, :to => users#new . . . endAprès avoir ajouté les routes pour la ressource Users, lURL /users/1 fonctionne parfaitement(illustration 6.10). Illustration 6.10: La page daffichage de lutilisateur à lURL /users/1 après avoir ajouté une ressource Users.Vous avez peut-être noté que lextrait 6.26 a effacé la ligne : get "users/new"… vue auparavant dans lextrait 5.29. Cest parce que la ligne de ressource ajoutée dans lextrait 6.26 najoutepas seulement une URL /users/1 fonctionnelle ; elle confère à notre application exemple toutes les
    • 234 actions nécessaires à une ressource Users RESTful,117 et un grand nombre de routes nommées (section 5.2.3) pour générer les URLs utilisateur. La résultante des correspondances entre URLs, actions et routes nommées est montrée dans la Table 6.2 (à comparer avec la Table 2.2). Au cours des trois prochains chapitres, nous couvrirons toutes les autres entrées de la Table 6.2 à mesure que nous remplirons toutes les actions nécessaires pour faire de Users une ressource pleinement conforme à REST. HTTP request URL Action Route nommée But GET /users index users_path liste des utilisateurs GET /users/1 show user_path(1) page affichant lutilisateur did 1 GET /users/new new new_user_path page créant un nouvel utilisateur (signup) POST /users create users_path créer un nouvel utilisateur GET /users/1/edit edit edit_user_path(1) page dédition de lutilisateur did 1 PUT /users/1 update user_path(1) actualiser lutilisateur did 1 DELETE /users/1 destroy user_path(1) détruire lutilisateur did 1 Table 6.2: Routes RESTful fournies par la ressource Users dans lextrait 6.26. Les params en débuggage (debug) Avant de quitter la page daffichage de lutilisateur, nous allons prendre un moment pour examiner les informations de débuggage produite par lextrait 6.23. Si vous regardez attentivement lillustration 6.10, vous verrez quelle fournit des informations utiles à propos de la page rendue :118 --- !map:ActiveSupport::HashWithIndifferentAccess action: show id: "1" controller: users Cest une représentation YAML119 des params, laquelle (comme suggéré par le « Hash » du nom HashWithIndifferentAccess, qui signifie « table de hachage ») est basiquement une table de hachage. Nous voyons que son contrôleur est users, son action est show, et son attribut id est "1". Bien que vous aurez rarement loccasion dutiliser params[:controller] ou params[:action], utiliser params[:id] pour extraire lidentifiant de lURL est un idiome Rails très courant. En particulier, nous utilisons le code :
    • 235 User.find(params[:id])… dans lextrait 6.25 pour trouver lutilisateur did 1 (la méthode find sait comment convertir la chaine decaractères "1" en entier 1).Linformation debug fournit souvent des retours utiles au cours du développement des applications Rails et jevous suggère dadopter lhabitude de le consulter chaque fois que votre application se comporte de façoninattendue.6.4 ConclusionCe chapitre est la première moitié du processus en deux temps de la création dun modèle utilisateur (User)fonctionnel. Nos utilisateurs ont maintenant des attributs nom et email, chacun paré de validationscontraignant leur valeur. Nous avons aussi amorcé une page daffichage de lutilisateur et une ressourceutilisateur basée sur larchitecture REST. Au chapitre 7, nous achèverons le processus en ajoutant des mots depasse et des vues de lutilisateur plus utiles.Si vous utilisez Git, il serait bien de commettre un nouveau dépôt si vous ne lavez pas fait depuis un moment : $ git add . $ git commit -m "Finished first cut of the User model"6.5 Exercises 1. Lisez lAPI Rails à lentrée pour ActiveRecord::Base pour avoir une idée de ses capacités. 2. Étudiez lentrée pour la méthode validates pour en apprendre plus sur ses capacités et ses options. 3. Passer un peu de temps (une ou deux heures) à vous amuser avec Rubular.
    • 236 Modéliser et afficher les utilisateurs, chapitre 7 partie II Au chapitre 6, nous avons créé la première itération dun modèle User (Utilisateur) pour représenter les utilisateurs de notre application, mais le job a été à moitié accompli. Virtuellement nimporte quel site avec des utilisateurs, incluant le nôtre, a besoin dun système dauthentification, mais pour le moment nimporte quel utilisateur qui peut sinscrire au site possède un nom et une adresse mail, et nous navons aucun moyen de vérifier leur identité. Dans ce chapitre, nous allons ajouter lattribut mot de passe (password) nécessaire à une inscription initiale de lutilisateur (chapitre 8) et à une identification (une connexion) avec une combinaison email/mot de passe (chapitre 9). Au cours du processus, nous ré-utiliserons plusieurs des idées du chapitre 6, incluant les migrations et les validations, et aussi introduire de nouvelles idées telles que les attributs virtuels, les méthodes privées et les fonctions de rappel (callbacks) dActive Record. Dès que nous aurons un attribut mot_de_passe fonctionnel, nous ferons une action et une vue pour afficher les profils des utilisateurs (section 7.3). Vers la fin de ce chapitre, nos profils dutilisateur afficheront les noms et les photos du profil (comme indiqué dans la maquette de lillustration 7.1), et ils seront bien testés avec des « utilisateurs dusine ». Avant davancer, ré-initialisons la base de données avec rake db:reset, ce qui va retirer tous les vieux exemples dutilisateurs des sessions précédentes : $ rake db:reset Illustration 7.1: Une maquette du profil utilisateur fait à la section 7.3.
    • 2377.1 Mots de passe peu sécurisésFaire des mots de passe robustes requiert beaucoup de machinerie, donc nous allons diviser le processus endeux principales étapes. Dans cette section, nous allons faire un attribut mot_de_passe et ajouter desvalidations. Le modèle User (Utilisateur) en résultant sera complètement fonctionnel mais pas du tout sécurisé,avec des mots de passe enregistrés « en clair » dans la base de données. À la section 7.2, nous réglerons ceproblème en cryptant les mots de passe avant de les sauver, protégeant ainsi notre site des attaques toujourspossibles.7.1.1 Validations du mot de passeQuand bien même nous navons pas encore ajouté une colonne pour les mots de passe à notre base de données,nous allons déjà commencer par écrire leurs tests. Notre projet initial est davoir des tests pour valider laprésence, la longueur et la confirmation des mots de passe. Cest notre plus gros bloc de tests jusquici, voyez sivous pouvez le lire tout dun tenant. Si vous restez coincé, ça vous aidera sans doute de revoir les validationsanalogues de la section 6.2 ou sauter directement au code de lapplication de lextrait 7.2.Dans le but de minimiser les typos dans les mots de passe, quand on fera la page dinscription de lutilisateur auchapitre 8 nous adopterons la convention courante que les utilisateurs confirment leur mot de passe. Pourcommencer, revoyons la table dattributs vue dans lextrait 6.20: describe User do before(:each) do @attr = { :nom => "Utilisateur exemple", :email => "user@example.com" } end . . . endPour écrire des tests pour les mots de passe, nous aurons besoin dajouter deux nouveaux attributs à la table@attr, password (mot de passe) et password_confirmation (motdepasse_confirmation). Comme vouspouvez certainement le deviner, lattribut password_confirmation sera utilisé pour létape de confirmationdu mot de passe.Écrivons des tests pour la présence du mot de passe et sa confirmation, avec des tests confirmant que le mot depasse a une longueur valide (réduite à quelque chose arbitrairement fixée entre 6 et 40 caractères). Le résultatapparait dans lextrait 7.1.
    • 238 Extrait 7.1. Des tests pour les validations du mot de passe. spec/models/user_spec.rb require spec_helper describe User do before(:each) do @attr = { :nom => "Utilisateur exemple", :email => "user@example.com", :password => "foobar", :password_confirmation => "foobar" } end it "devrait créer une nouvelle instance avec des attributs valides" do User.create!(@attr) end . . . describe "password validations" do it "devrait exiger un mot de passe" do User.new(@attr.merge(:password => "", :password_confirmation => "")). should_not be_valid end it "devrait exiger une confirmation du mot de passe qui correspond" do User.new(@attr.merge(:password_confirmation => "invalid")). should_not be_valid end it "devrait rejeter les mots de passe (trop) courts" do short = "a" * 5 hash = @attr.merge(:password => short, :password_confirmation => short) User.new(hash).should_not be_valid end
    • 239 it "devrait rejeter les (trop) longs mots de passe" do long = "a" * 41 hash = @attr.merge(:password => long, :password_confirmation => long) User.new(hash).should_not be_valid end end endNotez sans lextrait 7.1 comme nous collectons dabord un set dattributs utilisateur valides dans @attr. Si pourquelque raison que ce soit ces attributs ne sont pas valides — comme ce devrait être le cas, par exemple, si nousnavons pas implémenté les confirmations proprement — alors la première étape : it "devrait créer une nouvelle instance avec des attributs valides" do User.create!(@attr) end… rencontrera une erreur. Les tests suivants vérifieront alors chaque validation à son tour, en utilisant la mêmetechnique @attr.merge introduite la première fois dans lextrait 6.11.Voyons maintenant le code de lapplication, qui contient une astuce. En fait, elle contient deux astuces. Dabord,vous pouvez vous attendre, à ce point, à ce quon joue une migration pour ajouter un attribut password à notremodèle User, comme nous lavions fait pour les attributs nom et email dans lextrait 6.1. Mais ce nest pas lecas : nous nallons enregistrer quun mot de passe crypté dans la base de données ; pour le mot de passe, nousallons introduire la notion dattribut virtuel (cest-à-dire un attribut qui ne correspond pas à une colonne de labase de données) en utilisant la méthode attr_accessor, comme nous lavons fait avec la méthodeattr_accessible pour les attributs nom et email pour lexemple dutilisateur à la section 4.4.5. Lattributpassword ne sera jamais écrit dans la base de données, mais nexistera quen mémoire pour permettre létapede confirmation du mot de passe (implémentée ci-dessous) et létape de cryptage (implémentée à lasection 7.1.2 et section 7.2).La seconde astuce est que nous nallons pas introduire un attribut password_confirmation, pas même unvirtuel. À la place, nous utiliserons la validation spéciale : validates :password, :confirmation => true… qui va automatiquement créer un attribut virtuel appelé password_confirmation, tout en confirmantdans le même temps quil correspond exactement à lattribut password.
    • 240 Ainsi préparé à comprendre limplémentation, jetons un coup dœil au code lui-même (extrait 7.2). Extrait 7.2. Validation pour lattribut password. app/models/user.rb class User < ActiveRecord::Base attr_accessor :password attr_accessible :nom, :email, :password, :password_confirmation . . . # Crée automatique lattribut virtuel password_confirmation. validates :password, :presence => true, :confirmation => true, :length => { :within => 6..40 } end Comme promis, nous utilisons attr_accessor :password pour créer un attribut password virtuel (comme à la section 4.4.5). Puis, puisque nous allons accepter les mots de passe et leur confirmation comme part du processus dinscription au chapitre 8, nous avons besoin dajouter le mot de passe et sa confirmation à la liste des attributs accessibles (mentionnée la première fois à la section 6.1.2.2), ce que nous faisons à la ligne : attr_accessible :nom, :email, :password, :password_confirmation Ensuite viennent les validations du mot de passe. Elles requièrent la présence dun :password (comme, par exemple dans lextrait 6.7) et incluent :confirmation => true qui rejette les utilisateurs dont le mot de passe et sa confirmation ne correspondent pas. Nous avons aussi une deuxième application de la validation de la longueur ; dans lextrait 6.15 nous contraignons lattribut nom à avoir moins de 50 caractères en utilisant loption :maximum : validates :nom, :presence => true, :length => { :maximum => 50 } Pour la validation de la longueur du mot de passe, nous avons plutôt utilisé loption :within, en lui passant le rang120 6..40 pour forcer la contrainte de longueur. 7.1.2 La migration du mot de passe À ce point, nous devons considérer que nous nenregistrons le mot de passe de lutilisateur nulle part ; puisque nous avons décidé dutiliser un mot de passe virtuel, plutôt que de lenregistrer dans la base de données, il
    • 241nexiste quen mémoire. Comment pouvons-nous utiliser ce mot de passe pour lidentification ? Lasolution consiste à créer un attribut séparé dédié à la consignation du mot de passe, et notre stratégie consisteraà utiliser un mot de passe virtuel comme matériel brut pour un mot de passe crypté, que nous enregistreronsdans la base de données à linscription de lutilisateur (chapitre 8) et que nous récupérerons plus tard pourlidentification de lutilisateur (chapitre 9).Planifions lenregistrement du mot de passe crypté en utilisant un attribut encrypted_password dans notremodèle User (Utilisateur). Nous discuterons des détails de limplémentation à la section 7.2, mais nous pouvonscommencer avec nos tests du mot de passe crypté en notant que le mot de passe crypté devrait au moins exister.Nous pouvons tester cela en utilisant la méthode Ruby respond_to? (répond_à ? ), qui accepte un symbole etretourne true (vrai) si lobjet répond à la méthode ou lattribut donné et false (faux) dans le cas contraire : $ rails console --sandbox >> user = User.new >> user.respond_to?(:password) => true >> user.respond_to?(:encrypted_password) => falseNous pouvons tester lexistence dun attribut encrypted_password avec le code de lextrait 7.3, qui utiliselhelper de méthode RSpec respond_to. Extrait 7.3. Tester lexistence dun attribut encrypted_password. spec/models/user_spec.rb describe User do . . . describe "password encryption" do before(:each) do @user = User.create!(@attr) end it "devrait avoir un attribut mot de passe crypté" do @user.should respond_to(:encrypted_password) end end end
    • 242 Notez que dans le bloc before(:each) nous créons un utilisateur, plutôt que dappeler juste la méthode User.new. Nous pourrions en fait faire réussir ce test en utilisant User.new, mais (comme nous le verrons dans un instant) définir le mot de passe crypté exigera que lutilisateur soit enregistré dans la base de données. En utilisant create! dans ce premier cas ne crée pas de dommage, et le placer dans before(:each) nous permettra de garder tous les tests du mot de passe crypté dans un seul bloc describe. Pour obtenir la réussite de ce test, nous aurons besoin dune migration pour ajouter lattribut encrypted_password à la table users (utilisateurs) : $ rails generate migration add_password_to_users encrypted_password:string Ici le premier argument est le nom de la migration, et nous avons aussi fourni un second argument avec le nom et le type dattribut que nous voulons créer (comparez cela avec la génération originale de la table users de lextrait 6.1). Nous pouvons choisir le nom de migration que nous voulons, mais il est pratique de terminer le nom par _to_users (pour_users) pour que dans ce cas Rails puisse automatiquement construire une migration qui ajoute les colonnes à la table users. Plus encore, en incluant le second argument, nous donnons assez dinformation à Rails pour construire entièrement la migration pour nous, comme le montre lextrait 7.4. Extrait 7.4. La migration pour ajouter la colonne encrypted_password à la table users. db/migrate/<timestamp>_add_password_to_users.rb class AddPasswordToUsers < ActiveRecord::Migration def self.up add_column :users, :encrypted_password, :string end def self.down remove_column :users, :encrypted_password end end Ce code utilise la méthode add_column (ajouter_colonne) pour ajouter la colonne encrypted_password à la table users (et la méthode complémentaire remove_column pour la supprimer quand nous migrons la base en arrière). Le résultat est le modèle de données montré dans lillustration 7.2.
    • 243 Illustration 7.2: Le modèle User avec un attribut mot de passe (crypté) ajouté (version anglaise).Si nous jouons maintenant la migration et préparons le test de la base de données, le test devrait réussir,puisque le modèle User répondra à lattribut encrypted_password (assurez-vous de fermer toutes lesconsoles Rails lancées dans le « bac à sable » (sandbox) ; la sandbox verrouille la base de données et interdit lesmigrations). $ rake db:migrate $ rake db:test:prepareBien sûr, nous pouvons jouer toute la suite de tests avec rspec spec/, mais il est pratique parfois de jouerjuste une RSpec, ce que nous pouvons faire avec le drapeau -e (« exemple ») : $ rspec spec/models/user_spec.rb > -e "devrait avoir un attribut mot de passe crypté" . 1 example, 0 failures7.1.3 Fonction de rappel dans lActive RecordMaintenant que notre modèle User possède un attribut pour consigner le mot de passe, nous devons nousarranger pour générer et sauver le mot de passe crypté quand Active Record enregistre lutilisateur dans la basede données. Nous allons réaliser cela en utilisant une technique appelée une fonction de rappel (callback), quiest une méthode invoquée à un point particulier de la vie de lobjet Active Record. Dans le cas présent, nousutiliserons la fonction de rappel before_save pour créer encrypted_password juste avant que lutilisateurne soit enregistré.121Nous commençons avec un test pour lattribut mot de passe crypté. Puisque nous avons différé les détails delimplémentation — et, en particulier, la méthode de cryptage — à la section 7.2, dans cette section nous allons
    • 244 seulement nous assurer que lattribut encrypted_password dun utilisateur enregistré nest pas vierge. Nous faisons cela en combinant la méthode blank? sur les chaines de caractères (section 4.4.2) avec la convention RSpec pour les méthodes booléennes (vue pour la première fois dans le contexte des méthodes valid?/be_valid dans lextrait 6.11), rendant le test de lextrait 7.5. Extrait 7.5. Tester que lattribut encrypted_password nest pas vide. spec/models/user_spec.rb describe User do . . . describe "password encryption" do before(:each) do @user = User.create!(@attr) end . . . it "devrait définir le mot de passe crypté" do @user.encrypted_password.should_not be_blank end end end Ce code vérifie que encrypted_password.blank? nest pas vrai (true) en utilisant la construction should_not be_blank. Pour faire réussir ce test, nous déclarons une fonction de rappel appelée encrypt_password en passant un symbole de ce nom à la méthode before_save, et définissons ensuite une méthode encrypt_password pour procéder au cryptage. Avec before_save en place, Active Record appellera automatiquement la méthode correspondante avant denregistrer la donnée. Le résultat est présenté dans lextrait 7.6. Extrait 7.6. Fonction de rappel before_save pour créer lattribut encrypted_password. app/models/user.rb class User < ActiveRecord::Base . . .
    • 245 validates :password, :presence => true, :confirmation => true, :length => { :within => 6..40 } before_save :encrypt_password private def encrypt_password self.encrypted_password = encrypt(password) end def encrypt(string) string # Implémentation provisoire ! end endIci la fonction de rappel encrypt_password délègue en fait le cryptage à une méthode encrypt ; commesignalé dans le commentaire, cest seulement une implémentation provisoire — construit ainsi, lextrait 7.6 metsimplement le mot de passe crypté à la valeur du mot de passe non crypté, ce qui nest pas notre but. Mais çasuffit pour faire réussir notre test, et nous ferons que la méthode encrypt procède réellement au cryptage à lasection 7.2.Avant dessayer de comprendre limplémentation, notez dabord que les méthodes de cryptage sont placéesaprès le mot-clé private ; à lintérieur dune classe Ruby, toutes les méthodes définies après le mot-cléprivate (privé) sont utilisées de façon interne par lobjet et ne sont pas destinées à lutilisation publique.122 Àtitre dexemple, nous pouvons tester lobjet User dans la console : >> user = User.new >> user.encrypt_password NoMethodError: Attempt to call private method (Traduction : ErreurPasDeMethode : Tentative dappel dune méthode privée)Ici Ruby provoque une exception (une erreur) NoMethodError (erreur dabsence de méthode) en signalantpar une alerte que la méthode encrypt_password est privée.
    • 246 Dans le contexte présent, rendre les méthodes encrypt_password et encrypt privées nest pas strictement nécessaire, mais cest une bonne pratique de les rendre privées sauf si elles sont nécessaires à linterface publique.123 Maintenant que nous comprenons le mot-clé private, jetons un coup dœil à la méthode encrypt_password : def encrypt_password self.encrypted_password = encrypt(password) end Cest une méthode en une ligne (donc du meilleur genre !), mais elle ne contient pas seulement une mais deux subtilités. Primo , le membre gauche de la déclaration (self.encrypted_password) assigne explicitement lattribut encrypted_password en utilisant le mot-clé self (soi-même) (de la section 4.4.2 vous vous souvenez que la classe self se réfère à lobjet lui-même, ce qui pour le modèle User est simplement lutilisateur lui-même). Lutilisation de self est obligatoire dans ce contexte ; si nous omettons self et que nous écrivons : def encrypt_password encrypted_password = encrypt(password) end Ruby créera une variable locale appelée encrypted_password, ce qui nest pas du tout ce que nous voulons (elle nexisterait et ne serait utilisable que dans la méthode encrypt_password). Secondo, le membre droit de la déclaration (encrypt(password)) appelle la méthode encrypt (crypter) sur la variable password ; mais il ny a pas de variable password en vue. Dans la console, nous devrions accéder à lattribut mot de passe (password) au travers dun objet utilisateur : >> user = User.new(:password => "foobar") >> user.password => "foobar" À lintérieur de la classe User, lobjet utilisateur étant self, nous pourrions écrire : def encrypt_password self.encrypted_password = encrypt(self.password) end
    • 247… en analogie avec lexemple en console, remplacez simplement user par self. Mais le self est icioptionnel (dans un membre dexpression droit), donc pour la brièveté nous pouvons écrire simplement : def encrypt_password self.encrypted_password = encrypt(password) end… comme dans lextrait 7.6 ci-dessus (bien sûr, comme nous lavons noté, le self nest pas optionnel quandnous assignons la valeur dun attribut (dans le membre gauche dune expression), donc nous devons écrireself.encrypted_password dans ce cas).7.2 Mots de passe sécurisésAvec le code de la section 7.1, en principe nous avons fini : bien que le mot de passe « crypté » soit le même quele mot de passe non crypté, la possibilité denregistrer un mot de passe crypté dans la base de données nousfournit les fondations nécessaires pour lidentification et lauthentification de lutilisateur.124 Les normes de ceTutoriel Rails doivent être beaucoup plus strictes, cependant : tout développeur web digne de ce nom doitsavoir implémenter un système de mot de passe avec un hachage sécurisé à sens unique (secure one-wayhashing). Dans cette section, nous construirons le matériel puisé de la section 7.1 pour implémenter un telsystème robuste de mot de passe.7.2.1 Un test de mot de passe sécuriséComme mentionné à la section 7.1.3, toute la machinerie pour le cryptage du mot de passe sera soigneusementrangé dans la région private du modèle User, ce qui représente une difficulté particulière pour les tests. Nousaurions besoin dune espèce dinterface publique que nous pourrions exposer au reste de lapplication. Un desaspects utiles du « Développement Dirigé par les Tests » est que, en agissant comme un client pour coder notreapplication, les tests nous motivent à concevoir une interface utilisable dès le départ.Lauthentification des utilisateurs implique de pouvoir comparer la version cryptée dun mot de passe soumisavec la version cryptée du mot de passe de lutilisateur en question. Cela signifie que nous avons besoin dedéfinir une méthode pour accomplir la comparaison, méthode que nous appellerons has_password?(possède_un_motdepasse? ) ; ce sera notre partie publique de linterface pour la machinerie de cryptage.125 Laméthode has_password? testera si lutilisateur possède le même mot de passe que celui soumis lors delinscription (sera écrit au chapitre 9) ; un squelette de méthode pour has_password? est présenté danslextrait 7.7. Extrait 7.7. Une méthode has_password? pour les utilisateurs. app/models/user.rb class User < ActiveRecord::Base
    • 248 . . . before_save :encrypt_password # Retour true (vrai) si le mot de passe correspond. def has_password?(password_soumis) # Compare encrypted_password avec la version cryptée de # password_soumis. end private . . . end Avec cette méthode, nous pouvons écrire des tests comme dans lextrait 7.8, qui utilise les méthodes RSpec be_true (être_vrai) et be_false (être_faux) pour tester que has_password? retourne true ou false dans les cas appropriés. Extrait 7.8. Tests pour la méthode has_password?. spec/models/user_spec.rb describe User do . . . describe "Cryptage du mot de passe" do before(:each) do @user = User.create!(@attr) end . . describe "Méthode has_password?" do it "doit retourner true si les mots de passe coïncident" do @user.has_password?(@attr[:password]).should be_true
    • 249 end it "doit retourner false si les mots de passe divergent" do @user.has_password?("invalide").should be_false end end end endÀ la section 7.2.3, nous compléterons limplémentation de la méthode has_password? (et obtiendrons que letest réussisse dans le processus). Mais dabord nous avons besoin den apprendre un peu plus sur lasécurisation des mots de passe.7.2.2 Un peu de théorie sur la sécurisation des mots de passeLidée de base dun mot de passe crypté est simple : plutôt que denregistrer un mot de passe « en clair » dans labase de données (donc tel quel), nous enregistrons une chaine de caractères générée en utilisant une fonctionde hachage cryptographique (cryptographic hash function), qui est par essence irréversible, de telle sorte quemême un hacker en possession de la version cryptée du mot de passe sera incapable den déduire loriginal.Pour vérifier quun mot de passe soumis coïncide avec le mot de passe de lutilisateur, nous cryptons dabord lachaine de caractères soumise puis nous la comparons au mot de passe crypté enregistré. Rejoignons une sessionde console pour voir comment tout cela fonctionne : $ rails console >> require digest >> def secure_hash(string) >> Digest::SHA2.hexdigest(string) >> end => nil >> password = "secret" => "secret" >> encrypted_password = secure_hash(password) => "2bb80d537b1da3e38bd30361aa855686bde0eacd7162fef6a25fe97bf527a25b" >> submitted_password = "secret" => "secret" >> encrypted_password == secure_hash(submitted_password) => true
    • 250 Ici nous avons défini une fonction appelée secure_hash qui utilise une fonction de hachage cryptographique appelée SHA2, qui fait partie de la famille SHA des fonctions de hachage, que nous incluons dans Ruby par la librairie digest.126 Il nest pas utile de savoir exactement comment fonctionnent ces fonctions ; ce qui est important pour notre propos est quelles sont à sens unique : il ny a aucune façon informatisable de découvrir que : 2bb80d537b1da3e38bd30361aa855686bde0eacd7162fef6a25fe97bf527a25b … est le hachage SHA2 de la chaine "secret". Si vous y réfléchissez, cependant, nous avons toujours un problème : si un hacker entrait en possession des mots de passe hachés, il pourrait quand même avoir une chance de découvrir les mots de passe originaux. Par exemple, il pourrait supposer que nous utilisons SHA2 et ainsi écrire un programme pour comparer un hachage donné avec les valeurs possibles des mots de passe : >> hash = "2bb80d537b1da3e38bd30361aa855686bde0eacd7162fef6a25fe97bf527a25b" >> secure_hash("secede") == hash => false >> secure_hash("second") == hash => false >> secure_hash("secret") == hash => true Donc notre hacker obtient une correspondance — mauvaise nouvelle pour les utilisateurs qui utilisent le mot de passe "secret". Cette technique est connue sous le nom rainbow attack (attaque arc-en-ciel). Pour faire échouer une attaque arc-en-ciel éventuelle, on peut utiliser un salt (un sel, ou un grain de sel), une chaine de caractères qui sera différente pour chaque utilisateur.127 Une façon courante de sassurer lunicité (ou presque) est de hacher le temps courant (en UTC pour être indépendant de la zone de temps) avec le mot de passe, de telle sorte que deux utilisateurs auraient le même sel seulement sils sétaient inscrits exactement en même temps et avaient exactement le même mot de passe (ce qui, en théorie, est improbable, et en pratique, impossible). Voyons comment cela fonctionne en utilisant la fonction secure_hash définie plus haut à la console : >> Time.now.utc => Fri Jan 29 18:11:27 UTC 2010 >> password = "secret" => "secret" >> salt = secure_hash("#{Time.now.utc}--#{password}")
    • 251 => "d1a3eb8c9aab32ec19cfda810d2ab351873b5dca4e16e7f57b3c1932113314c8" >> encrypted_password = secure_hash("#{salt}--#{password}") => "69a98a49b7fd103058639be84fb88c19c998c8ad3639cfc5deb458018561c847"Dans les dernières lignes, nous avons haché le sel avec le mot de passe, pour retourner un mot de passe cryptéquil est virtuellement impossible de cracker (pour la clarté, les arguments des fonctions de hachage sontsouvent séparés par des « -- »).7.2.3 Implémenter has_password?En ayant fini avec la théorie, nous sommes maintenant en mesure de procéder à limplémentation. Nous allonsaller de lavant pour voir où nous allons. Chaque objet utilisateur connait son propre mot de passe crypté, doncpour le vérifier avec le mot de passe soumis, nous pouvons définir has_password? comme suit : def has_password?(password_soumis) encrypted_password == encrypt(password_soumis) endPuisque nous cryptons le mot de passe soumis en utilisant le même sel que celui utilisé pour le mot de passeoriginal, cette fonction retournera vrai si et seulement si le mot de passe soumis correspond.Puisque comparer le mot de passe dun utilisateur avec un mot de passe soumis implique de crypter le mot depasse soumis avec le sel, nous avons besoin denregistrer ce sel quelque part, donc la première étape vaconsister à ajouter une colonne salt à la table users : $ rails generate migration add_salt_to_users salt:stringComme avec la migration encrypted_password (section 7.1.2), cette migration porte un nom qui se finit par_to_users et passe un second argument contenant le nom de lattribut et son type (« String » ici, chaine decaractères), donc Rails construit automatiquement la bonne migration (extrait 7.9). Extrait 7.9. La migration pour ajouter la colonne salt à la table users. db/migrate/<timestamp>_add_salt_to_users.rb class AddSaltToUsers < ActiveRecord::Migration def self.up add_column :users, :salt, :string end def self.down
    • 252 remove_column :users, :salt end end Nous migrons alors la base de données et préparons le test de la base de données comme dhabitude : $ rake db:migrate $ rake db:test:prepare Le résultat est une base de données avec le modèle de données correspondant à lillustration 7.3. Illustration 7.3: Le modèle User avec un sel ajouté. Nous sommes enfin prêts pour limplémentation complète. Quand nous avons vu la dernière fois la fonction encrypt (extrait 7.6), elle ne faisait rien dautre que retourner la chaine de caractère quelle recevait en argument. Dans lidée de la section 7.2.2, nous sommes en mesure maintenant de renvoyer plutôt un hachage sécurisé (extrait 7.10).128 Extrait 7.10. La méthode has_password? avec un cryptage sécurisé. app/models/user.rb require digest class User < ActiveRecord::Base . . . before_save :encrypt_password def has_password?(password_soumis) encrypted_password == encrypt(password_soumis)
    • 253 end private def encrypt_password self.salt = make_salt if new_record? self.encrypted_password = encrypt(password) end def encrypt(string) secure_hash("#{salt}--#{string}") end def make_salt secure_hash("#{Time.now.utc}--#{password}") end def secure_hash(string) Digest::SHA2.hexdigest(string) end endCe code contient les deux mêmes subtilités que celles mentionnées à la section 7.1.3, nommément,lassignement dun attribut Active Record à laide de self à la ligne : self.salt = make_salt if new_record?… et lomission du mot-clé self dans le membre droit de ligne de code de la méthode encrypt : def encrypt(string) secure_hash("#{salt}--#{string}") endPuisque nous sommes à lintérieur de la classe, Ruby sait que salt se réfère à lattribut salt de lutilisateur.Il est important de noter aussi lutilisation de la méthode booléenne de lActive Record new_record?, quiretourne true (vrai) si lobjet na pas encore été enregistré dans la base de données. Puisque le sel est unidentifiant unique pour chaque utilisateur, nous ne voulons pas quil change chaque fois que lutilisateur sera
    • 254 actualisé (comme à la section 10.1), et en incluant new_record? nous nous assurons que le salt sera créé seulement une fois, à la création de lutilisateur129 (cette subtilité nimporte pas pour le moment, mais elle importera quand nous implémenterons une fonctionnalité « se souvenir de moi » à lidentification à la section 9.3.2). À ce stade, les tests de lextrait 7.8 devraient réussir : $ rspec spec/models/user_spec.rb -e "doit retourner true si les mots de passe coïncident" . 1 example, 0 failures $ rspec spec/models/user_spec.rb > -e "doit retourner false si les mots de passe divergent" . 1 example, 0 failures Nous pourrions aussi jouer tous les exemples dans un bloc describe particulier, mais nous devons prendre soin déchapper tous les caractères spéciaux dans les expressions régulières — dans ce cas, le point dinterrogation « ? » de la méthode "has_password?" : $ rspec spec/models/user_spec.rb -e "Méthode has_password?" Run filtered using {:full_description=>/(?-mix:Méthode has_password?)/} .. 2 examples, 0 failures Léchappement (« ») avant le point dinterrogation assure que linterpréteur dexpressions régulières RSpec interprète la chaine de caractère correctement, ce qui jouera les tests associés au bloc describe donné. 7.2.4 Une méthode dauthentification Avoir une méthode has_password? pour chaque utilisateur est une bonne chose, mais en elle-même elle nest pas très utile. Nous terminons notre discussion sur les mots de passe en utilisant has_password? pour écrire une méthode pour identifier un utilisateur par une combinaison email/mot de passe. Au chapitre 9, nous utiliserons cette méthode authenticate en identifiant les utilisateurs de notre site.
    • 255Nous pouvons avoir une idée de ce fonctionnement à laide de la console. Dabord, nous créons unutilisateur, et ensuite nous récupérons cet utilisateur par ladresse-mail pour vérifier quil a un mot de passedonné :130 $ rails console --sandbox >> User.create!(:nom => "Michael Hartl", :email => "mhartl@example.com", ?> :password => "foobar", :password_confirmation => "foobar") >> user = User.find_by_email("mhartl@example.com") >> user.has_password?("foobar") => trueEn utilisant ces idées, écrivons une méthode qui retournera un utilisateur identifié si les mots de passecorrespondent, et nul (nil) dans le cas contraire. Nous devrions pouvoir utiliser la méthode de classeauthenticate en procédant ainsi : User.authenticate(email, submitted_password)Nous commençons par les tests, que nous utiliserons pour spécifier le comportement que nous attendons deUser.authenticate. Il y a trois choses à vérifier : authenticate (1) devrait retourner nil quand lacombinaison email/mot de passe est invalide ou (2) quand aucun utilisateur nexiste avec ladresse mail fournie,et (3) devrait retourner lobjet utilisateur lui-même en cas de succès. Avec ces informations, nous pouvonsécrire les tests de authenticate comme dans lextrait 7.11. Extrait 7.11. Tests for the User.authenticate method. spec/models/user_spec.rb describe User do . . . describe "password encryption" do . . . describe "authenticate method" do it "devrait retourner nul en cas dinéquation entre email/mot de passe" do wrong_password_user = User.authenticate(@attr[:email], "wrongpass") wrong_password_user.should be_nil
    • 256 end it "devrait retourner nil quand un email ne correspond à aucun utilisateur" do nonexistent_user = User.authenticate("bar@foo.com", @attr[:password]) nonexistent_user.should be_nil end it "devrait retourner lutilisateur si email/mot de passe correspondent" do matching_user = User.authenticate(@attr[:email], @attr[:password]) matching_user.should == @user end end end end Nous sommes prêts maintenant pour limplémentation, ce qui fera réussir nos tests et nous montrera comment définir en bonus une méthode de classe. Nous avons mentionné les méthodes de classe plusieurs fois auparavant, tout récemment à la section 6.1.1 ; une méthode de classe est simplement une méthode attachée à une classe, plutôt quà une instance de cette classe. Par exemple, new (nouveau), find (trouver) et find_by_mail (trouver_par_le_mail) sont toutes des méthodes de classe dune classe User. En dehors de la classe, elles sont invoquées en utilisant le nom de la classe, comme dans User.find, mais à lintérieur de la classe nous pouvons omettre le nom de la classe. Box 7.1. Quest-ce que self ? Nous avons déjà expliqué que self est « lobjet lui-même », mais ce que ça signifie dépend du contexte. À lintérieur dune méthode ordinaire, self se réfère à une instance de la classe, cest-à-dire à lobjet lui-même. Par exemple, dans lextrait 7.10, self est un utilisateur (user) : def encrypt_password self.salt = make_salt if new_record? self.encrypted_password = encrypt(password) end À lintérieur de la méthode encrypt_password, self est lobjet utilisateur, donc self.salt est identique à user.salt en dehors de la méthode :
    • 257 $ rails console >> user = User.first >> user.salt => "d3b9af261c502947fbf32f78cb8179b16e62eabacf059451efee404328b2f537"Dun autre côté, lextrait 7.12 montre la définition de authenticate, qui utilise self pour définir uneméthode de classe ; ici, self est la classe User elle-même : def self.authenticate(email, submitted_password) . . . endParce ce quelle est définie dans une classe User, authenticate est invoquée directement sur le nom declasse User : >> user = User.authenticate(example@railstutorial.org, foobar) >> user.nom => "Utilisateur exemple"Il est important de noter une façon différente mais équivalente de définir une méthode de classeauthenticate montrée dans lextrait 7.12. Dabord, nous pourrions indiquer la classe User explicitement parson nom : def User.authenticate(email, submitted_password) . end(Certaines personnes peuvent trouver cette syntaxe plus claire, mais elle nest pas aussi correcte,idiomatiquement parlant.) Ensuite, nous pourrions utiliser le code suivant qui, franchement, mexplose lecerveau : class << self def authenticate(email, submitted_password) . . . end
    • 258 end Ce class << self bizarre amorce un bloc dans lequel toutes les nouvelles méthodes sont automatiquement des méthodes de classe. Je trouve cette syntaxe plutôt déroutante, mais il est possible que vous la rencontriez dans le code dautres programmeurs, donc ça vaut le coup de la connaitre (je recommande The Well-Grounded Rubyist de David A. Black si vous voulez creuser des détails de Ruby tels que celui-ci). La façon de définir une méthode de classe est dutiliser le mot-clé self dans la définition de la méthode (ce self nest pas le même que le self montré dans lextrait 7.10 ; voyez le Box 7.1.) Lextrait 7.12 montre cette construction dans le contexte de la méthode authenticate. Notez lappel à find_by_email, dans laquelle nous omettons le nom explicite de classe User puisque cette méthode est déjà à lintérieur de la classe User. Extrait 7.12. La méthode User.authenticate. app/models/user.rb class User < ActiveRecord::Base . def has_password?(submitted_password) encrypted_password == encrypt(submitted_password) end def self.authenticate(email, submitted_password) user = find_by_email(email) return nil if user.nil? return user if user.has_password?(submitted_password) end private . end Il existe plusieurs façons équivalentes décrire la méthode authenticate, mais je trouve limplémentation ci- dessus la plus claire. Elle traite deux cas (email invalide et correspondance exacte) avec le mot-clé explicite return, et traite le troisième cas (inadéquation du mot de passe) implicitement, puisque dans ce cas nous atteignons la fin de la méthode, ce qui retourne automatiquement la valeur nil (nulle). Voyez la section 7.5 pour quelques unes des autres manières possibles dimplémenter cette méthode.
    • 2597.3 Meilleures vues dutilisateursMaintenant que le modèle User est effectivement achevé,131 nous sommes en mesure dajouter un exempledutilisateur à la base de données de développement et de faire une page show (montrer, afficher) pour affichercertaines des informations de lutilisateur. Chemin faisant, nous ajouterons quelques tests au spec ducontrôleur User que nous avons entamé à la section 5.3.1.Avant de poursuivre, voyons où nous en sommes restés concernant le spec du contrôleur User (extrait 7.13).Nos tests pour la page daffichage de lutilisateur suivront cet exemple, mais nous devons comprendre quecontrairement aux tests de laction new les tests de laction show exigeront lutilisation dinstances du modèleUser. Cela sera rendu possible grâce à une technique appelée factories (usines). Extrait 7.13. Le spec du contrôleur User dans son état actuel. spec/controllers/users_controller_spec.rb require spec_helper describe UsersController do render_views describe "GET new" do it "devrait réussir" do get new response.should be_success end it "devrait avoir le bon titre" do get new response.should have_selector("title", :content => "Sign up") end end end7.3.1 Tester la page daffichage de lutilisateur (avec factories)Les tests pour le contrôleur User auront besoin des objets instances du modèle User, avec de préférence desvaleurs pré-définies (pour ne pas avoir à les rentrer toutes « à la main »). Par exemple, comme vu àlextrait 7.14, laction show du contrôleur Users a besoin dune instance de la classe User, donc les tests de cetteaction vont exiger que nous créions dune manière ou dune autre une variable @user. Nous allons accomplir
    • 260 cela avec un utilisateur dusine, qui est une façon pratique de définir un objet utilisateur et de linsérer à lintérieur de notre base de données de test.132 Extrait 7.14. Laction utilisateur show de lextrait 6.25. app/controllers/users_controller.rb class UsersController < ApplicationController def show @user = User.find(params[:id]) end . . end Nous allons utiliser les données dusine générées par Factory Girl (Fille dUsine),133, un gem Ruby produit par les gens valeureux de thoughtbot. Comme pour les autres gems Ruby, vous pouvez linstaller en ajoutant une ligne à votre Gemfile utilisé par Bundler (extrait 7.15) (puisque Factory Girl est nécessaire seulement pour les tests, nous lincluons dans le groupe :test). Extrait 7.15. Ajout de Factory Girl dans le Gemfile. source http://rubygems.org . . group :test do . . gem factory_girl_rails, 1.0 end Puis installez-le comme dhabitude : $ bundle install Nous sommes maintenant prêts à créer le fichier spec/factories.rb et à définir un utilisateur dusine, comme le montre lextrait 7.16. En plaçant le fichier factories.rb dans le dossier spec/, nous nous arrangeons pour que RSpec charge factories automatiquement chaque fois que nous jouons les tests. Extrait 7.16. Une factory pour simuler des objets de modèle User. spec/factories.rb
    • 261 # En utilisant le symbole :user, nous faisons que # Factory Girl simule un modèle User. Factory.define :user do |user| user.nom "Michael Hartl" user.email "mhartl@example.com" user.password "foobar" user.password_confirmation "foobar" endAvec la définition de lextrait 7.16, nous pouvons créer un User dusine dans les tests comme cela : @user = Factory(:user)Comme lindique le commentaire de la première ligne de lextrait 7.16, en utilisant le symbole :user nous nousassurons que Factory Girl devinera que nous voulons utiliser un modèle User, donc dans ce cas @user simuleraune instance de la classe User.Pour utiliser notre nouvel Utilisateur dusine dans le spec du contrôleur Users, nous allons créer une variable@user dans le bloc before(:each) et ensuite get (demander) la page daffichage et vérifier la réussite (toutcomme nous lavons fait avec la page new de lextrait 7.13), tout en vérifiant aussi que laction show récupèrelutilisateur correct de la base de données. Le résultat est présenté dans lextrait 7.17 (si vous utilisez Spork, vousavez peut-être à le redémarrer pour obtenir la réussite de ces tests). Extrait 7.17. Un test pour getting (obtenir ) la page utilisateur show, avec un utilisateur dusine. spec/controllers/users_controller_spec.rb require spec_helper describe UsersController do render_views describe "GET show" do before(:each) do @user = Factory(:user) end it "devrait réussir" do get :show, :id => @user response.should be_success
    • 262 end it "devrait trouver le bon utilisateur" do get :show, :id => @user assigns(:user).should == @user end end . . . end Hormis lutilisation dune usine, la réelle nouveauté ici est lutilisation de assigns(:user), qui est une fonctionnalité fournie par RSpec (via la librairie sous-jacente Test::Unit). La méthode assigns prend un argument symbolique (ici :user) et retourne la valeur qua la variable dinstance correspondante (ici @user — :user => @user) dans laction concernée, ici laction show du contrôleur User. En dautres termes, dans lextrait 7.17 le code : assigns(:user) … retourne la valeur de la variable dinstance : @user … dans laction show du contrôleur des utilisateurs. Le test : assigns(:user).should == @user … vérifie alors que la variable récupérée de la base de données dans laction correspond à linstance @user créée par Factory Girl. Il est important de noter que tous les programmeurs Rails nutilisent pas assigns dans ce contexte, lui préférant parfois la technique appelée le stubbing (Box 7.2). Box 7.2.Stuber ou ne pas Stuber ? Le code de lextrait 7.17 sappuie sur une méthode User.find dans laction du contrôleur pour récupérer le bon utilisateur de la base de données de test. Une autre manière datteindre le même résultat consiste à utiliser une technique appelée stubbing, à laide de la méthode RSpec stub! : before(:each)
    • 263 @user = Factory(:user) User.stub!(:find, @user.id).and_return(@user) endCe code sassure que tout appel à User.find avec lid donné retournera @user. Puisque cest juste ce quenous avons dans le code de lapplication (extrait 7.14), le stub fera que RSpec va intercepter lappel àUser.find et, au lieu de se connecter à la base de données, retournera plutôt @user.De nombreux programmeurs Rails, spécialement ceux utilisant RSpec, préfèrent cette approche parce quellesépare les tests des controleurs de la couche modèle. En vérité, la version Rails 2.3 de ce livre utilise stubs, auxcôté des techniques très proches de message expectations. Après avoir acquis plus dexpérience avec les stubs etles expectations, et spécialement après avoir répondu à beaucoup de questions de lecteurs du Tutoriel de laversion Rails 2.3 déroutés par ces problèmes, jen suis arrivé à la conclusion que le stubbing et les techniquesafférentes ne valaient pas tous ces troubles.Déterminer quand stuber les choses est difficile, et les messages expectations sont incroyablement subtils etsujets à erreurs (voir par exemple le Box 8.1 dans le Tutoriel Rails 2.3 Tutorial). On fait souvent lobjectionsuivante : « Mais maintenant les tests de contrôleur se connectent à la base de données de test ! ». Ce à quoi jeréponds maintenant par : « Et alors ? » Dans mon expérience ça na jamais compté. Je ne vois aucuneimpérieuse raison de ne pas atteindre la couche modèle dans les tests contrôleur, spécialement quand çaconduit à des tests plus simples. Si vous êtes intéressé par lapprentissage du stubbing et des techniques demessage expectation, je recommande la lecture du Tutoriel Ruby on Rails 2.3. Sinon, je suggère de ne pas tropse soucier dune séparation complète entre couche modèle et couche contrôleur dans les tests Rails. Bien que lestests contrôleur dans la suite de ce livre se connecteront à la base de données, à un niveau conceptuel, la partiedu MVC testée restera toujours claire.En passant, les tests devraient en principe se jouer plus vite quand les contrôleurs ne se connectent pas à la basede données, et pour la suite de tests de lapplication exemple complète de ce Tutoriel Rails, ils se connecteront— en environ deux dixièmes de seconde. Il reste deux autres détails dans lextrait 7.17 quil convient de souligner. Dabord, dans lappel de get, le test utilise le symbole :show au lieu de la chaine ’show’, ce qui est différent de la convention des autres tests (par exemple, dans lextrait 3.11 nous avons écrit get ’home’). Les deux, get :show… et get show
    • 264 … font la même chose, mais en testant les actions REST canoniques (Table 6.2) je préfère utiliser des symboles, ce qui pour dévidentes raisons semble plus naturel dans ce contexte.134 Ensuite, notez que la valeur de la clé de hachage :id, au lieu dêtre lattribut id de lutilisateur @user, est lobjet user lui-même : get :show, :id => @user Nous pourrions tout autant utiliser le code : get :show, :id => @user.id … pour accomplir la même chose, mais dans ce contexte Rails convertit automatiquement lobjet user vers lid correspondant.135 Utiliser la construction plus succincte : get :show, :id => @user … est un idiome Rails très fréquent. À cause du code que nous avons ajouté dans lextrait 6.25, le test de cette section réussit déjà. Si vous êtes un peu paranoïaque, vous pouvez ex-commenter la ligne : @user = User.find(params[:id]) … et vérifier que le test échoue, puis dé-commenter pour réussir à nouveau (nous sommes passés par le même processus une fois auparavant, à la section 6.2.1.) 7.3.2 Un nom et un Gravatar Dans cette section, nous allons améliorer le look de notre page daffichage de lutilisateur en ajoutant une entête avec le nom de lutilisateur et une image de profil. Cest une de ces situations où il est très difficile de prévoir les choses, et donc difficile dutiliser le « Développement Dirigé par les Tests », et souvent en construisant les vues jexpérimenterai le code HTML avant de me soucier des tests. Poursuivons pourtant pour le moment avec le TDD, et testons une entête de haut niveau (balise h1) contenant le nom de lutilisateur et une balise img de classe gravatar (nous expliquerons dans un instant le sens de cette seconde partie). Pour voir une page daffichage de lutilisateur fonctionner dans le navigateur, nous aurons besoin de créer un exemple dutilisateur dans la base de données en mode développement. Pour ce faire, démarrons la console (pas dans le bac à sable cette fois) et créons un utilisateur : $ rake db:reset
    • 265 $ rails console >> User.create!(:nom => "Utilisateur exemple", :email => "user@example.com", ?> :password => "foobar", :password_confirmation => "foobar")Les tests de cette section sont similaires aux tests de la page new vus dans lextrait 5.26. En particulier, nousutilisons la méthode have_selector pour vérifier le titre et le contenu de la balise h1, comme le montrelextrait 7.18. Extrait 7.18. Tests pour la page daffichage de lutilisateur. spec/controllers/users_controller_spec.rb require spec_helper describe UsersController do render_views describe "GET show" do . . . it "devrait avoir le bon titre" do get :show, :id => @user response.should have_selector("title", :content => @user.nom) end it "devrait inclure le nom de lutilisateur" do get :show, :id => @user response.should have_selector("h1", :content => @user.nom) end it "devrait avoir une image de profil" do get :show, :id => @user response.should have_selector("h1>img", :class => "gravatar") end end . . . end
    • 266 Ici la méthode RSpec have_selector vérifie la présence des balises title (titre) et h1 contenant le nom de lutilisateur. Le troisième exemple introduit un nouvel élément par le code h1>img, qui fait que la balise img est à lintérieur de la balise h1..136 De plus, nous voyons que have_selector peut prendre une option :class pour tester la classe CSS de lélément en question. Nous pouvons obtenir la réussite du test en définissant la variable @titre à utiliser dans lhelper titre (section 4.1.1), dans ce cas en lassignant à la valeur du nom de lutilisateur (extrait 7.19). Extrait 7.19. Un titre pour la page daffichage de lutilisateur. app/controllers/users_controller.rb class UsersController < ApplicationController def show @user = User.find(params[:id]) @titre = @user.nom end . . . end Ce code introduit un problème potentiel : un utilisateur peut entrer un nom avec du code malveillant — appelé attaque cross-site scripting — qui serait injecté à lintérieur de lapplication par lhelper titre défini dans lextrait 4.2. Avant Rails 3, la solution consistait à échapper les codes potentiellement problématiques en utilisant la méthode h (raccourci pour html_escape), mais avec Rails 3.0, tout le code Ruby embarqué est maintenant échappé par défaut.137 Par exemple, si un utilisateur essayait dinjecter un programme JavaScript malveillant en utilisant <script> dans son nom, léchappement HTML automatique le convertirait en &lt;script&gt;, le rendant complètement inoffensif. Voyons maintenant les autres tests. Créer un h1 à partir du nom de lutilisateur (échappé) est facile (extrait 7.20). Extrait 7.20. La page de profil avec un nom dutilisateur. app/views/users/show.html.erb <h1> <%= @user.nom %> </h1>
    • 267Faire réussir le test de img est plus astucieux. La première étape consiste à installer le gemgravatar_image_tag pour traiter chaque Gravatar dutilisateur, 138 qui est un « avatar reconnuglobalement ».139 Comme dhabitude, nous inclurons la dépendance gem dans le fichier Gemfile (extrait 7.21). Extrait 7.21. Ajout du gem Gravatar au Gemfile. source http://rubygems.org gem rails, 3.0.7 gem sqlite3-ruby, 1.3.2, :require => sqlite3 gem gravatar_image_tag, 1.0.0.pre2 . . .Installez-le avec bundle : $ bundle installVous devrez peut-être aussi redémarrer votre serveur web ici pour charger le nouveau gem Gravatarproprement.Les gravatars sont une façon pratique dinclure des images de profil utilisateur sans avoir à gérer la difficultédes téléchargement dimages, leur redimensionnement et leur sauvegarde.140 Chaque gravatar est associé à uneadresse email, donc le gem Gravatar est fourni avec une méthode dhelper appelée gravatar_image_tag quiprend une adresse mail comme argument : <%= gravatar_image_tag example@railstutorial.org %>Pour le moment, nous utilisons cela directement dans notre vue affichant lutilisateur, comme le montrelextrait 7.22 (nous construirons une méthode helper dans un moment). Le résultat apparait danslillustration 7.4, qui montre notre exemple avec limage gravatar par défaut. Extrait 7.22. La vue daffichage de lutilisateur avec un nom et un gravatar. app/views/users/show.html.erb <h1> <%= gravatar_image_tag @user.email %> <%= @user.nom %> </h1>
    • 268 Illustration 7.4: La page initiale daffichage de lutilisateur /users/1 avec le gravatar par défaut. Ce fonctionnement de Gravatar peut sembler magique, donc rejoignons la console pour comprendre un peu mieux ce qui se passe : $ rails console >> user = User.first >> user.update_attributes(:email => "example@railstutorial.org", ?> :password => "foobar", ?> :password_confirmation => "foobar") => true Notez que nous pouvons récupérer le premier (et, à ce point, le seul) utilisateur de la base de données avec la méthode pratique User.first. Dans létape update_attributes nous avons ré-assigné ladresse mail de lutilisateur, en la changeant en example@railstutorial.org. Comme vous pouvez le voir dans lillustration 7.5, ce changement produit laffichage dun nouveau gravatar : le logo du tutoriel Rails. Ce qui se passe, cest que Gravatar travaille en associant image et adresse email ; puisque user@example.com est une adresse invalide (le domaine example.com est réservé aux exemples), il ny a pas de gravatar associé à cette adresse. Mais à mon compte Gravatar jai associé ladresse example@railstutorial.org au logo du tutoriel Rails, donc en actualisant lutilisateur exemple avec ladresse mail, le gravatar change automatiquement.
    • 269 Illustration 7.5: La page daffichage de lutilisateur /users/1 avec le gravatar du Tutoriel Rails.Un helper GravatarÀ ce stade, le gravatar saffiche proprement, mais lexemple final de lextrait 7.18 ne réussit toujours pas. Cestparce que la classe CSS "gravatar", que nous voulons utiliser pour styliser les gravatars en CSS, nest pasencore spécifier dans la balise img du gravatar. Pour faire réussir le test, nous devons ajouter loption adéquateà la méthode gravatar_image_tag : <%= gravatar_image_tag @user.email, :class => "gravatar" %>Dun autre côté, puisque nous anticipons le fait que les gravatars apparaitront à différents endroits de notreapplication, il serait répétitif de mettre la classe chaque fois à la main. Ce serait mieux de faire une méthodedhelper qui élimine préventivement cette duplication..Cette situation peut vous rappeler de la répétition de la base du titre (« Simple application du Tutoriel Ruby onRails »), que nous avons résolue avec lhelper titre dans lhelper de lapplication (extrait 4.2). La solution iciest la même ; puisque les gravatars sont naturellement associés aux utilisateurs, nous allons définir uneméthode gravatar_for (gravatar_pour) dans le helper Users (le choix dutiliser le helper Users plutôt que lehelper Application est juste une commodité conceptuelle ; Rails rend tous les helpers accessibles dans les vues).Le résultat sera un code concis comme : <%= gravatar_for @user %>
    • 270 Lhelper gravatar_for devrait prendre un objet user et passer alors quelques options par défaut à lhelper gravatar_image_tag. Limplémentation apparait dans lextrait 7.23. Extrait 7.23. Définir une méthode dhelper gravatar_for. app/helpers/users_helper.rb module UsersHelper def gravatar_for(user, options = { :size => 50 }) gravatar_image_tag(user.email.downcase, :alt => user.nom, :class => gravatar, :gravatar => options) end end Le premier argument dans lappel à gravatar_image_tag passe la version minuscule de ladresse mail (en utilisant la méthode downcase).141 Ensuite la première option (deuxième argument) de gravatar_image_tag assigne le nom de lutilisateur à lattribut alt de la balise img (qui sera affiché sur les dispositifs qui ne peuvent pas rendre les images), tandis que la deuxième option définit la classe CSS pour le gravatar. La troisième option passe une table doptions en utilisant la clé :gravatar, qui (conformément à la documentation gem pour gravatar_image_tag) est utilisée pour définir les options pour gravatar_image_tag. Notez que la définition de la fonction définit une options par défaut142 pour la taille du gravatar143 en utilisant : option = { :size => 50 } Cela fixe la taille par défaut du gravatar à 50x50, ce qui nous permettra également de redéfinir la taille par défaut en utilisant un code comme : <%= gravatar_for @user, :size => 30 %> Si nous actualisons à présent le template daffichage de lutilisateur avec le code de lextrait 7.24, la page daffichage de lutilisateur apparaitra comme dans lillustration 7.6. Et puisque lhelper gravatar_for assigne à la balise img la classe CSS "gravatar", les tests de lextrait 7.18 devraient maintenant réussir. Extrait 7.24. Actualiser le template de laffichage de lutilisateur en utilisant gravatar_for. app/views/users/show.html.erb <h1> <%= gravatar_for @user %> <%= @user.nom %>
    • 271 </h1> Illustration 7.6: La page daffichage de lutilisateur avec gravatar_for.7.3.3 Une barre utilisateur latéraleMême si nos tests réussissent à présent, et que la page daffichage de lutilisateur sest considérablementaméliorée, il est encore possible de la polisser un petit peu plus. Dans lextrait 7.25, nous avons une balisetable avec un rangée tr et deux cellules td.144 Extrait 7.25. Ajout dune barre latérale pour la vue show de lutilisateur. app/views/users/show.html.erb <table class="profile" summary="Information profil"> <tr> <td class="main"> <h1> <%= gravatar_for @user %> <%= @user.nom %> </h1> </td> <td class="sidebar round"> <strong>Nom</strong> <%= @user.nom %><br /> <strong>URL</strong> <%= link_to user_path(@user), @user %> </td>
    • 272 </tr> </table> Ici nous avons utilisé la balise HTML <br /> pour forcer un retour à la ligne entre le nom de lutilisateur et lURL. Notez aussi lutilisation de user_path pour créer un lien cliquable qui permet aux utilisateurs de partager facilement lURL de leur profil. Cest seulement le première dun grand nombre de routes nommées (section 5.2.2) associées à la ressource User (extrait 6.26) ; nous en verrons beaucoup plus dans les prochains chapitres. Le code : user_path(@user) … retourne le path (chemin daccès) de lutilisateur, dans ce cas /users/1. Le code lié : user_url(@user) … retourne lURL entière, http://localhost:3000/users/1 (comparez avec les routes créées à la section 5.2.2.). Les deux sont des exemples de routes nommées créées par la ressource utilisateurs de lextrait 6.26 ; une liste de toutes les routes nommées apparait dans la Table 7.1. Route nommé Chemin users_path /users user_path(@user) /users/1 new_user_path /users/new edit_user_path(@user) /users/1/edit users_url http://localhost:3000/users user_url(@user) http://localhost:3000/users/1 new_user_url http://localhost:3000/users/new edit_user_url(@user) http://localhost:3000/users/1/edit Table 7.1: Routes nommées fournies par la ressource utilisateurs dans lextrait 6.26.
    • 273Notez que dans : <%= link_to user_path(@user), @user %>… user_path(@user) est le lien texte, tandis que ladresse est juste @user. Dans le contexte dun link_to,Rails convertit @user en lURL appropriée ; en dautres termes, le code ci-dessus est équivalent au code : <%= link_to user_path(@user), user_path(@user) %>Nimporte laquelle de ces formulations fonctionne bien, mais, comme dans lidiome :id => @user delextrait 7.17, utiliser juste @user est une convention Rails courante. Dans les deux cas, le code Ruby embarquéproduit le code HTML : <a href="/users/1">/users/1</a>Avec les éléments HTML et les classes CSS en place, nous pouvons styliser la page daffichage avec le code CSSmontré dans lextrait 7.26. La page en résultant est montrée dans lillustration 7.7. Extrait 7.26. CSS pour styliser la page daffichage de lutilisateur, incluant la barre latérale. public/stylesheets/custom.css . . . /* User show page */ table.profile { width: 100%; margin-bottom: 0; } td.main { width: 70%; padding: 1em; } td.sidebar { width: 30%; padding: 1em; vertical-align: top;
    • 274 background: #ffc; } .profile img.gravatar { border: 1px solid #999; margin-bottom: -15px; } Illustration 7.7: La page daffichage de lutilisateur /users/1 avec une barre latérale et du CSS. 7.4 Conclusion Dans ce chapitre, nous avons effectivement achevé le modèle User, donc nous sommes maintenant pleinement prêts pour inscrire de nouveaux utilisateurs et pour les laisser sidentifier de façon sécurisée avec une combinaison email/mot de passe. Plus encore, nous avons une belle première réduction de la page de profil de lutilisateur, donc après lidentification les utilisateurs auront un endroit où aller. 7.4.1 Dépôt Git Avant de poursuivre, nous devrions interrompre la boucle ouverte dans lintroduction du chapitre 6 en faisant un dépôt final pour la branche modeling-users et alors la fusionner avec la branche maitresse (master).145 Dabord, vérifiez que vous êtes bien sur la branche modeling-users : $ git branch master
    • 275 * modeling-usersComme indiqué à la section 1.3.5.1, lastérisque ici indique la branche courante, donc nous sommes vraimentprêts à déposer et à fusionner :146 $ git add . $ git commit -m "Modele utilisateur avec mot de passe et page profil" $ git checkout master $ git merge modeling-users7.4.2 déploiement HerokuSi vous avez déployé votre application exemple sur Heroku, vous pouvez la « pusher » maintenant : $ git push herokuMigrez alors la base de données sur le serveur à distance en utilisant la commande heroku : $ heroku rake db:migrateMaintenant, si vous voulez créer un exemple dutilisateur sur Heroku, vous pouvez utiliser la console Heroku : $ heroku console >> User.create!(:nom => "Utilisateur exemple", :email => "user@example.com", ?> :password => "foobar", :password_confirmation => "foobar")7.5 Exercices 1. Copiez chaque variante de la méthode authenticate des extraits 7.27 à 7.31 dans le modèle User, et vérifiez quils sont correct en jouant votre suite de tests. 2. Lexemple final authenticate (extrait 7.31) relève un défi particulier. Expérimentez avec la console pour voir si vous pouvez comprendre comment il fonctionne. 3. Comment pourriez obtenir que lhelper gravatar gravatar_for fonctionne si votre modèle User utilisait email_address au lieu de email pour représenter ladresse email ? Extrait 7.27. La méthode authenticate avec User à la place de self. def User.authenticate(email, submitted_password) user = find_by_email(email) return nil if user.nil?
    • 276 return user if user.has_password?(submitted_password) end Extrait 7.28. La méthode authenticate avec un troisième retour explicite. def self.authenticate(email, submitted_password) user = find_by_email(email) return nil if user.nil? return user if user.has_password?(submitted_password) return nil end Extrait 7.29. La méthode authenticate utilisant une déclaration if. def self.authenticate(email, submitted_password) user = find_by_email(email) if user.nil? nil elsif user.has_password?(submitted_password) user else nil end end Extrait 7.30. La méthode authenticate utilisanat une déclaration if et un retour implicite. def self.authenticate(email, submitted_password) user = find_by_email(email) if user.nil? nil elsif user.has_password?(submitted_password) user end end Extrait 7.31. La méthode authenticate utilisant lopérateur ternaire. def self.authenticate(email, submitted_password) user = find_by_email(email) user && user.has_password?(submitted_password) ? user : nil end
    • 277chapitre 8 InscriptionMaintenant que nous avons un modèle User fonctionnel, il est temps dajouter quelques capacités dont ne peutpas se passer un site web : laisser ses utilisateurs sinscrire — et ainsi tenir la promesse faite à la section 5.3,« Inscription de lutilisateur : une première étape ». Nous allons utiliser un formulaire HTML pour soumettreles informations dinscription de lutilisateur à notre application à la section 8.1, qui serviront à créer un nouvelutilisateur et à sauver ses attributs dans la base de données à la section 8.3. Comme dhabitude, nous allonsécrire des tests à mesure que nous développons, et à la section 8.4 nous utiliserons le support RSpec pour lasyntaxe de la navigation web pour écrire des tests dintégration succincts et expressifs.Puisque nous allons créer un nouvel utilisateur dans ce chapitre, il est peut-être nécessaire de supprimer tousles utilisateurs créés à la console dans la base de données (par exemple ceux de la section 7.3.2), de telle sorteque vos résultats correspondent à ceux montrés dans ce tutoriel. Vous pouvez faire cela comme suit : $ rake db:resetSi vous suivez votre développement en utilisant le contrôle de version, faites une branche sujet commedhabitude : $ git checkout master $ git checkout -b signing-up8.1 Formulaire dinscriptionSouvenez-vous daprès la section 5.3.1 que nous avons déjà des tests pour la page des nouveaux utilisateurs(signup page), vu à lorigine dans lextrait 5.26 et reproduits dans lextrait 8.1 (comme promis à la section 7.3.1,nous sommes passés de get ’new’ à get :new parce que cest ce que mes doigts veulent taper). De plus,nous avons vu dans lillustration 5.10 (et à nouveau dans lillustration 8.1) que cette page dinscription est pourle moment vierge : donc tout à fait inapte à inscrire de nouveaux utilisateurs. Le but de cette section sera decommencer à changer ce triste état des choses en produisant la maquette de formulaire dinscription delillustration 8.2. Extrait 8.1. Les tests pour la page dinscription des utilisateurs (vu dabord dans lextrait 5.26). spec/controllers/users_controller_spec.rb require spec_helper describe UsersController do render_views .
    • 278 . . describe "GET new" do it "devrait réussir" do get :new response.should be_success end it "devrait avoir le bon titre" do get :new response.should have_selector("title", :content => "Inscription") end end . . . end Illustration 8.1: Létat actuel de la page dinscription /signup.
    • 279 Illustration 8.2: Une maquette de la page dinscription.8.1.1 Utiliser form_forLélément HTML nécessaire pour soumettre des informations à un site à distane est un formulaire, ce quisuggère quune bonne première étape vers la registration des utilisateurs est de faire un formulaire susceptiblede recueillir leurs informations dinscription. Nous pouvons accomplir cela en Rails avec la méthode helperform_for (formulaire_pour) ; le résultat apparait dans lextrait 8.2 (les lecteurs familiers de Rails 2.x doiventnoter que form_for utilise maintenant la syntaxe ERb « pourcent-égal » pour insérer le contenu ; cest-à-direquoù Rails 2.x utilisait <% form_for ... %>, Rails 3 utilise maintenant <%= form_for... %>). Extrait 8.2. Un formulaire pour linscription du nouvel utilisateur. app/views/users/new.html.erb <h1>Inscription</h1> <%= form_for(@user) do |f| %> <div class="field"> <%= f.label :nom %><br /> <%= f.text_field :nom %> </div> <div class="field"> <%= f.label :email %><br /> <%= f.text_field :email %> </div> <div class="field">
    • 280 <%= f.label :password %><br /> <%= f.password_field :password %> </div> <div class="field"> <%= f.label :password_confirmation, "Confirmation" %><br /> <%= f.password_field :password_confirmation %> </div> <div class="actions"> <%= f.submit "Inscription" %> </div> <% end %> Étudions ce code bout par bout. La présence du mot-clé do indique que form_for comprend un bloc (section 4.3.2), qui possède une variable, que nous avons appelée f pour « formulaire ». À lintérieur de lhelper form_for, f est un objet qui représente un formulaire ; comme cest souvent le cas avec les helpers Rails, nous navons pas besoin de connaitre tous les détails de limplémentation, mais ce que nous avons besoin de connaitre est ce que lobjet f fait : quand appelé avec une méthode correspondant à un élément formulaire HTML — tel quun champ de texte (text field), un bouton radio ou un champ de mot de passe — il retourne le code pour cet élément spécifiquement conçu pour définir un attribut de lobjet @user. En dautres termes : <div class="field"> <%= f.label :nom %><br /> <%= f.text_field :nom %> </div> … renvoie le code HTML nécessaire pour construire dans la page un élément champ de texte (text_field) labélisé (label) susceptible de définir lattribut nom dun modèle User. Pour voir cela en action, nous avons besoin de naviguer et de regarder le code HTML produit en fait par ce formulaire, mais ici nous avons un problème : la page pour le moment échoue, parce que nous navons pas défini la variable dinstance @user — comme toute variable dinstance non définie (section 4.2.3), @user est pour le moment nil. De façon appropriée, si vous jouez votre suite de tests maintenant, vous verrez que la page dinscription échoue. Pour obtenir quelle saffiche et rendre notre formulaire, nous devons définir une variable @user dans laction du contrôleur correspondant à new.html.erb, cest-à-dire laction new dans le contrôleur Users. Lhelper form_for attend que @user soit un objet User, et puisque nous créons un nouvel utilisateur nous utiliserons simplement User.new, comme dans lextrait 8.3. Extrait 8.3. Ajout dune variable dinstance @user à laction new. app/controllers/users_controller.rb
    • 281 class UsersController < ApplicationController . . . def new @user = User.new @titre = "Inscription" end endAvec la variable @user ainsi définie, les tests devraient réussir à nouveau,147 et maintenant le formulaire (avecun peu de stylisation qui vient de lextrait 8.4) apparait comme dans lillustration 8.3. Extrait 8.4. Une très mince quantité de CSS pour le formulaire dinscription. public/stylesheets/custom.css . . . div.field, div.actions { margin-bottom: 10px; } Illustration 8.3: Le formulaire dinscription /signup pour de nouveaux utilisateurs.
    • 282 8.1.2 Le formulaire HTML Comme indiqué par illustration 8.3, la page dinscription est maintenant rendue proprement, indiquant que le code form_for dans lextrait 8.2 produit du code HTML valide. Si vous regardez ce code HTML pour le formulaire généré (en utilisant soit Firebug ou le menu « Code source de la page » de votre navigateur), vous devriez voir le balisage tel que dans lextrait 8.5. Bien que de nombreux détails ne soient pas utiles à notre propos, prenons un moment pour mettre en évidence les parties les plus importantes de sa structure. Extrait 8.5. Le code HTML pour le formulaire de lillustration 8.3. <form action="/users" class="new_user" id="new_user" method="post"> <div style="margin:0;padding:0;display:inline"> <input nom="authenticity_token" type="hidden" value="rB82sI7Qw5J9J1UMILG/VQL411vH5putR+JwlxLScMQ=" /> </div> <div class="field"> <label for="user_nom">Nom</label><br /> <input id="user_nom" nom="user[nom]" size="30" type="text" /> </div> <div class="field"> <label for="user_email">Email</label><br /> <input id="user_email" nom="user[email]" size="30" type="text" /> </div> <div class="field"> <label for="user_password">Password</label><br /> <input id="user_password" nom="user[password]" size="30" type="password" /> </div> <div class="field"> <label for="user_password_confirmation">Confirmation</label><br /> <input id="user_password_confirmation" nom="user[password_confirmation]" size="30" type="password" /> </div> <div class="actions"> <input id="user_submit" nom="commit" type="submit" value="Inscription" /> </div> </form>
    • 283Nous allons commencer avec la structure interne. En comparant lextrait 8.2 avec lextrait 8.5, nousvoyons que le Ruby embarqué : <div class="field"> <%= f.label :nom %><br /> <%= f.text_field :nom %> </div>… produit le code HTML : <div class="field"> <label for="user_nom">Nom</label><br /> <input id="user_nom" nom="user[nom]" size="30" type="text" /> </div>… et : <div class="field"> <%= f.label :password %><br /> <%= f.password_field :password %> </div>… produit le code HTML : <div class="field"> <label for="user_password">Password</label><br /> <input id="user_password" nom="user[password]" size="30" type="password" /> </div>Comme vu dans lillustration 8.4, les champs de saisie textuels (type="text") affichent simplement leurcontenu, tandis que les champs mot de passe (type="password") masquent leur contenu pour des questionsde sécurité, comme le montre lillustration 8.4.
    • 284 Illustration 8.4: Un formulaire rempli, montrant la différence entre les champs text et les champs password (mot de passe). Comme nous le verrons à la section 8.3, la clé pour créer un utilisateur est lattribut spécial name dans chaque input: <input id="user_nom" name="user[nom]" - - - /> . . . <input id="user_password" name="user[password]" - - - /> Ces valeurs name permettent à Rails de construire une table dinitialisation (via la variable params initialement vue à la section 6.3.2) pour créer un utilisateur en utilisant les valeurs fournies, comme nous le verrons à la section 8.2. Le second élément important est la balise form elle-même. Rails crée la balise form en utilisant lobjet @user : parce que chaque objet Ruby connait sa propre classe (section 4.4.1), Rails peut savoir que @user est de classe User ; plus encore, puisque @user est un nouvel utilisateur, Rails sait construire un formulaire avec la méthode post, qui est le verbe HTTP approprié pour créer un nouvel objet (Box 3.1): <form action="/users" class="new_user" id="new_user" method="post">
    • 285Ici les attributs class et id ne sont pas très utiles ; ce qui est important cest action="/users" etmethod="post". Ensemble, ils constituent les instructions pour prendre en compte une requête HTMLPOST à lURL « /users ». Nous en verrons les effets au cours des deux sections à venir.Enfin, notez le code plutôt obscur de la balise de nom « authenticity token » : <div style="margin:0;padding:0;display:inline"> <input name="authenticity_token" type="hidden" value="rB82sI7Qw5J9J1UMILG/VQL411vH5putR+JwlxLScMQ=" /> </div>Ici Rails utilise une valeur spéciale unique pour contrecarrer une attaque appelée contrefaçon (forgery) ;consultez the Stack Overflow entry on the Rails authenticity token si vous êtes intéressé par les détails de sonfonctionnement et de son importance. Heureusement, Rails prend soin du problème pour nous, et la baliseinput est hidden (cachée) donc vous navez pas à vous en soucier plus que ça ; mais comme elle saute aux yeuxen consultant le code HTML source, je voulais au moins vous en parler.8.2 Échec de linscriptionBien que nous ayons brièvement examiné le code HTML du formulaire de lillustration 8.3 (vu danslextrait 8.5), il sera encore plus compréhensible dans le contexte dun échec dinscription. Dans cette section,nous allons nous arranger pour soumettre un formulaire dinscription invalide qui devra redonner la pagedinscription (cf. la maquette de lillustration 8.5). Illustration 8.5: Une maquette pour léchec de la page dinscription.
    • 286 8.2.1 Tester léchec Souvenez-vous de la section 6.3.3 quajouter resources :users au fichier routes.rb (extrait 6.26) permettait de sassurer automatiquement que notre application Rails répondait aux URLs RESTful de la Table 6.2. En particulier, cela assurait quune requête POST pour /users était traitée par laction create. Notre stratégie pour laction create (créer) est dutiliser la soumission du formulaire pour fabriquer un nouvel objet utilisateur à laide de User.new, en essayant (et échouant) de sauver cet utilisateur, et alors de retourner la page dinscription pour une possible re-soumission. Notre tâche est décrire les tests pour cette action, et dajouter ensuite create au contrôleur Users pour le faire réussir. Commençons par revisiter le code du formulaire dinscription : <form action="/users" class="new_user" id="new_user" method="post"> Comme indiqué à la section 8.1.2, ce code HTML émet une requête POST pour lURL /users. En analogie à la méthode get, qui émet une requête GET à lintérieur des tests, nous utilisons la méthode post pour émettre une requête POST pour laction create. Comme nous allons le voir brièvement, create reçoit une table de hachage correspondant au type dobjet qui doit être créé ; puisque cest un test pour léchec de linscription, nous allons juste passer la table @attr avec une entrée vierge, comme montré dans lextrait 8.6. Cela revient à visiter la page dinscription et à cliquer sur le bouton de soumission « Sinscrire » sans avoir rempli aucun des champs de saisie du formulaire. Extrait 8.6. Tests pour léchec de linscription de lutilisateur. spec/controllers/users_controller_spec.rb require spec_helper describe UsersController do render_views . . . describe "POST create" do describe "échec" do before(:each) do @attr = { :nom => "", :email => "", :password => "", :password_confirmation => "" }
    • 287 end it "ne devrait pas créer dutilisateur" do lambda do post :create, :user => @attr end.should_not change(User, :count) end it "devrait avoir le bon titre" do post :create, :user => @attr response.should have_selector("title", :content => "Inscription") end it "devrait rendre la page new" do post :create, :user => @attr response.should render_template(new) end end end endLes deux tests finaux sont relativement simples : nous nous assurons que le titre est correct, et puis nousvérifions quune inscription défectueuse renvoie bien à nouveau la page dinscription dun nouvel utilisateur (enutilisant la méthode RSpec render_template). Le premier test, en revanche, est un peu plus délicat.Le but du test : it "ne devrait pas créer dutilisateur" do lambda do post :create, :user => @attr end.should_not change(User, :count) end… est censé vérifier que léchec de laction create ne crée pas dutilisateur dans la base de données. Pour cefaire, il introduit deux éléments nouveaux. Dabord, nous utilisons la méthode RSpec change pour retourner lechangement du nombre dutilisateurs dans la base de données : change(User, :count)
    • 288 Cela diffère de la méthode Active Record count, qui retourne simplement le nombre denregistrements du type donné dans la base de données. Par exemple, si vous avez ré-initialisé la base de données de développement au début de ce chapitre, ce compte devrait actuellement être de 0 : $ rails console >> User.count => 0 La seconde nouvelle idée est denrouler létape post :create dans un « package » en utilisant la construction Ruby appelée un lambda148 (une fonction anonyme), qui nous permet de vérifier quelle ne change pas le compte de User : lambda do post :create, :user => @attr end.should_not change(User, :count) Ce bloc lambda peut vous sembler étrange pour le moment, mais les autres exemples utilisés dans les tests à venir devraient vous rendre ce modèle plus clair. 8.2.2 Un formulaire fonctionnel Nous devons maintenant faire réussir les tests de la section 8.2.1 avec le code de lextrait 8.7. Cet extrait inclut une seconde utilisation de la méthode render, que nous avons vue pour la première fois dans le contexte des partiels (section 5.1.3) ; comme vous pouvez le voir, render fonctionne aussi bien dans les actions de contrôleur . Notez que nous avons pris la liberté dintroduire une structure if-else (si-sinon) qui nous permet de traiter les cas déchec et de succès séparément en se basant sur la valeur de @user.save. Extrait 8.7. Une action create qui peut traiter les échec dinscription (mais pas encore les succès). app/controllers/users_controller.rb class UsersController < ApplicationController . . . def create @user = User.new(params[:user]) if @user.save # Traite un succès denregistrement. else @titre = "Inscription"
    • 289 render new end end endLa meilleure façon de comprendre le fonctionnement du code de lextrait 8.7 est de soumettre le formulaireavec des données dinscription invalides ; le résultat apparait dans lillustration 8.6. Illustration 8.6: Échec dinscription avec une table params.Pour avoir une vision plus claire de la façon dont Rails traite la soumission, jetons un coup dœil plus précis à latable params dans les informations de débuggage en bas de la page de lillustration 8.6 : --- !map:ActiveSupport::HashWithIndifferentAccess commit: Inscription authenticity_token: rB82sI7Qw5J9J1UMILG/VQL411vH5puR+Jw1xL5cMQ= action: create controller: users user: !map:ActiveSupport::HashWithIndifferentAccess nom: Foo Bar password_confirmation: dude password: dude email: foo@invalid
    • 290 Nous avons vu en abordant la section 6.3.2 que la table params contenait des informations sur chaque requête ; dans le cas dune URL comme « /users/1 », la valeur de params[:id] est lid de lutilisateur correspondant (1 dans ce exemple). Dans le cas dun POSTing du formulaire dinscription, params contient plutôt une table de tableaux, une construction que nous avons vue pour la première fois à la section 4.3.3, qui introduisait la variable stratégiquement appelée params dans la session de console. Linformation de débuggage ci-dessus montre que la soumission du formulaire produit une table user avec des attributs correspondant aux valeurs soumises, où les clés viennent des attributs name des balises input du formulaire (extrait 8.2) ; par exemple, la valeur de : <input id="user_email" name="user[email]" size="30" type="text" /> … avec name "user[email]" est précisément lattribut email de la table user. Bien que les clés de la table apparaissent comme des chaines de caractère dans le message de débuggage, Rails utilise en interne des symboles, de telle sorte que params[:user] est la table des attributs de lutilisateur — en fait, exactement, les attributs nécessaires comme argument pour User.new, comme nous lavons initialement vu à la section 4.4.5 et revu dans lextrait 8.7. Cela signifie que la ligne : @user = User.new(params[:user]) … est équivalente à : @user = User.new(:nom => "Foo Bar", :email => "foo@invalid", :password => "dude", :password_confirmation => "dude") Cest exactement le format nécessaire pour initialiser le modèle objet User avec les attributs donnés. Bien entendu, instancier une telle variable a des implications dans le succès de linscription — comme nous le verrons à la section 8.3, une fois que @user est défini proprement, appeler @user.save est tout ce quil y a à faire pour achever lenregistrement — mais cela a des conséquences même dans léchec dinscription considéré ici. Notez dans lillustration 8.6 que les champs sont pré-remplis avec les données de la soumission défectueuse. Cest parce que form_for remplit automatiquement les champs avec les attributs de lobjet @user, donc, par exemple, si @user.nom vaut "Foo" alors : <%= form_for(@user) do |f| %> <div class="field"> <%= f.label :nom %><br /> <%= f.text_field :nom %> </div>
    • 291 . . .… produira le code HTML : <form action="/users" class="new_user" id="new_user" method="post"> <div class="field"> <label for="user_nom">Nom</label><br /> <input id="user_nom" name="user[nom]" size="30" type="text" value="Foo"/> </div> . . .Ici lattribut value de la balise input vaut "Foo", et cest ce qui apparait dans le champ de texte.8.2.3 Message derreur à linscriptionBien que ce ne soit pas strictement nécessaire, il est utile de retourner à lutilisateur des messages derreur encas déchec pour lui indiquer les problèmes à régler, qui permettront de réussir son inscription. Rails fournit detels messages en se basant sur les validations du modèle User. Par exemple, essayons de sauver un utilisateuravec une adresse mail invalide et un mot de passe trop court : $ rails console >> user = User.new(:nom => "Foo Bar", :email => "foo@invalid", ?> :password => "dude", :password_confirmation => "dude") >> user.save => false >> user.errors.full_messages => ["Email is invalid", "Password is too short (minimum is 6 characters)"]Ici, lobjet errors.full_messages (que nous avons vu brièvement à la section 6.2.1) contient un tableau demessages derreurs.
    • 292 Comme dans la console ci-dessus, léchec de lenregistrement de lextrait 8.7 génère une liste de messages derreurs associée à lobjet @user. Pour afficher les messages dans le navigateur, nous allons rendre un partiel de messages derreur sur la page new (extrait 8.8).149 Extrait 8.8. Code pour afficher les messages derreur sur le formulaire dinscription. app/views/users/new.html.erb <h1>Inscription</h1> <%= form_for(@user) do |f| %> <%= render shared/error_messages %> . . . <% end %> Notez ici que nous rendons (render) un partiel appelé « shared/error_messages » ; cela reflète une convention Rails courante qui définit que les partiels à utiliser par de multiples contrôleurs se trouvent dans un dossier dédié shared/ (partagé) (nous verrons cette convention pleinement remplie à la section 10.1.1). Cela signifie que nous devons créer ce nouveau dossier en même temps que le fichier du partiel _error_messages.html.erb. Le partiel lui-même est présenté dans lextrait 8.9. Extrait 8.9. Un partiel pour afficher les messages derreur à la soumissions du formulaire. app/views/shared/_error_messages.html.erb <% if @user.errors.any? %> <div id="error_explanation"> <h2><%= pluralize(@user.errors.count, "erreur") %> ont empêché denregistrer votre inscription</h2> <p>Merci de corriger ces champs :</p> <ul> <% @user.errors.full_messages.each do |msg| %> <li><%= msg %></li> <% end %> </ul> </div> <% end %> Ce partiel introduit plusieurs nouvelles constructions Rails et Ruby, incluant deux méthodes pour les objets de classe Array (Tableau). Ouvrons une session de console pour voir comment elles fonctionnent.
    • 293La première méthode est count (compter), qui retourne simplement le nombre déléments dans lobjet : $ rails console >> a = [1, 2, 3] => [1, 2, 3] >> a.count => 3Lautre nouvelle méthode est any?, lune des deux méthodes complémentaires : >> [].empty? => true >> [].any? => false >> a.empty? => false >> a.any? => trueNous voyons ici que la méthode empty? (vide ?), que nous avons vue initialement à la section 4.2.3 dans lecontexte des chaines de caractères, qui fonctionne aussi sur les tableaux, retourne true (vrai) si le tableau estvide et false (faux) dans le cas contraire. La méthode any? (quelque chose ?) est simplement lopposée de laméthode empty?, retournant true sil y a au moins un élément et false dans le cas contraire..Lautre nouvelle idée est lhelper de texte pluralize (mettre_au_pluriel). Elle nest pas accessible par laconsole, mais nous pouvons linclure explicitement par la module ActionView::Helpers::TextHelper :150 >> include ActionView::Helpers::TextHelper => Object >> pluralize(1, "error") => "1 error" >> pluralize(5, "error") => "5 errors"Nous voyons ici que pluralize prend un entier comme argument et retourne alors le nombre avec uneversion plurielle adéquate de son second argument. Sous cette méthode se trouve un puissant inflecteur qui saitcomment pluraliser un grand nombre de mots (qui incluent de nombreux pluriels irréguliers — en anglais pardéfaut.NdT) :
    • 294 >> pluralize(2, "woman") => "2 women" >> pluralize(3, "erratum") => "3 errata" Comme résultat, le code : <%= pluralize(@user.errors.count, "erreurs") %> … retourne "1 erreur" ou "2 erreurs" (etc.) en fonction du nombre derreurs. Notez que lextrait 8.9 inclut lid CSS error_explanation pour styliser les messages derreur (souvenez-vous que nous avons vu à la section 5.1.2 que CSS utilise le signe dièse # pour styliser les ids). De plus, sur les pages derreur, Rails encadre automatiquement les champs dun div de classe CSS field_with_errors. Ces labels nous permettent alors de styliser les messages derreur avec le code CSS montré dans lextrait 8.10. Comme résultat, en cas déchec de la soumission les messages derreur apparaissent comme dans lillustration 8.7. Comme les messages sont générés par les validations du modèle, ils changeront automatiquement si vous changez davis, disons, sur le format des adresses mail ou la longueur minimum pour les mots de passe. Extrait 8.10. CSS pour styliser les messages derreur. public/stylesheets/custom.css . . . .field_with_errors { margin-top: 10px; padding: 2px; background-color: red; display: table; } .field_with_errors label { color: #fff; } #error_explanation { width: 400px; border: 2px solid red; padding: 7px;
    • 295 padding-bottom: 12px; margin-bottom: 20px; background-color: #f0f0f0;}#error_explanation h2 { text-align: left; font-weight: bold; padding: 5px 5px 5px 15px; font-size: 12px; margin: -7px; background-color: #c00; color: #fff;}#error_explanation p { color: #333; margin-bottom: 0; padding: 5px;}#error_explanation ul li { font-size: 12px; list-style: square;}
    • 296 Illustration 8.7: Échec dinscription avec les messages derreur. 8.2.4 Filtrer les paramètres didentification Avant de sacheminer vers la réussite de linscription, il reste une chose à considérer. Vous avez peut-être noté que, même si nous nous sommes évertués à crypter le mot de passe au chapitre 7, ce mot de passe ainsi que sa confirmation apparaissent tous deux en « texte clair » (cleartext) dans linformation de débuggage. Ce nest pas un problème en soi — dans lextrait 6.23 nous avons vu que ces informations napparaissaient quen mode développement, donc les utilisateurs ne devraient pas les voir — mais ça peut conduire à un problème éventuel : les mots de passe peuvent aussi apparaitre de façon non cryptés dans le log file (le fichier journal) que Rails utilise pour enregistrer des informations sur lactivité de lapplication. En effet, dans les versions précédentes de Rails, le fichier journal de développement, dans ce cas, contiendrait des lignes comme celles montrées dans lextrait 8.11. Extrait 8.11. Le fichier journal de développement, avant Rails 3, avec des mots de passe en clair. log/development.log Parameters: {"commit"=>"Inscription", "action"=>"create", "authenticity_token"=>"K1HchFF8uYE8ZaQKz5DVG9vF2KGoXJu4JGp/VE3NMjA=", "controller"=>"users", "user"=>{"nom"=>"Foo Bar", "password_confirmation"=>"dude", "password"=>"dude", "email"=>"foo@invalid"}} Ce serait un trou de sécurité dramatique denregistrer les mots de passe non cryptés dans les fichiers journaux — si quiconque avait accès à ces fichiers, il pourrait obtenir les mots de passe de tous les utilisateurs du système
    • 297(bien sûr, ici linscription échoue, mais le problème serait exactement le même pour une soumissionréussie). Ce problème était tellement courant dans les applications Rails que Rails 3 implémente à présent unnouveau comportement par défaut : tous les attributs password (mot de passe) sont filtrés automatiquement,comme le montre lextrait 8.12. Nous voyons que la chaine "[FILTERED]" remplace le mot de passe et saconfirmation (en mode production, le fichier journal sera log/production.log, et le filtrage fonctionne de lamême manière). Extrait 8.12. Le journal de développement avec les mots de passe filtrés. log/development.log Parameters: {"commit"=>"Inscription", "action"=>"create", "authenticity_token"=>"K1HchFF8uYE8ZaQKz5DVG9vF2KGoXJu4JGp/VE3NMjA=", "controller"=>"users", "user"=>{"nom"=>"Foo Bar", "password_confirmation"=>"[FILTERED]", "password"=>"[FILTERED]", "email"=>"foo@invalid"}}Le filtrage du mot de passe lui-même est accompli via un réglage dans le fichier de configurationapplication.rb (extrait 8.13). Extrait 8.13. Filtrage des mots de passe par défaut. config/application.rb require File.expand_path(../boot, __FILE__) require rails/all # If you have a Gemfile, require the gems listed there, including any gems # youve limited to :test, :development, or :production. Bundler.require(:default, Rails.env) if defined?(Bundler) module SampleApp class Application < Rails::Application . . . # Configure sensitive parameters which will be filtered from the log file. config.filter_parameters += [:password] end end
    • 298 Si vous aviez à écrire une application Rails avec un paramètre à sécuriser qui porte une autre nom que password, vous aurez besoin de lajouter à la liste des paramètres filtrés. Par exemple, si vous incluez un code secret dans le processus dinscription en ajoutant une ligne comme : <div class="field"> <%= f.label :code_secret %><br /> <%= f.password_field :code_secret %> </div> … vous devrez alors ajouter :code_secret au fichier application.rb de cette façon : config.filter_parameters += [:password, :code_secret] 8.3 Réussite de linscription Ayant traité la soumission dun formulaire invalide, il est temps à présent dachever le formulaire dinscription en enregistrant réellement un nouvel utilisateur (valide) dans la base de données. Dabord, nous essayons de sauver un utilisateur ; si lenregistrement fonctionne, les informations de lutilisateur sont écrites dans la base de données automatiquement, et nous pouvons alors rediriger (redirect) le navigateur pour afficher le profil de lutilisateur (avec un sympathique message de bienvenue), comme le montre la maquette dans lillustration 8.8. Si ça rate, nous retournons simplement au comportement développé à la section 8.2. Illustration 8.8: Une maquette dune inscription réussie.
    • 2998.3.1 Tester la réussiteLes tests pour une inscription réussie suivent le même chemin que les tests de léchec de linscription delextrait 8.6. Jetons un coup dœil au résultat présenté dans lextrait 8.14. Extrait 8.14. Tests pour une inscription réussie. spec/controllers/users_controller_spec.rb require spec_helper describe UsersController do render_views . . . describe "POST create" do . . . describe "succès" do before(:each) do @attr = { :nom => "New User", :email => "user@example.com", :password => "foobar", :password_confirmation => "foobar" } end it "devrait créer un utilisateur" do lambda do post :create, :user => @attr end.should change(User, :count).by(1) end it "devrait rediriger vers la page daffichage de lutilisateur" do post :create, :user => @attr response.should redirect_to(user_path(assigns(:user))) end end end end
    • 300 Comme avec les tests de léchec de linscription (extrait 8.6), nous utilisons ici post :create pour atteindre laction create avec la requête HTML « POST ». Comme dans les tests dun échec de création de lextrait 8.6, le premier test « enroule » la création de lutilisateur dans un bloc lambda et utilise la méthode count pour vérifier que la base de données a changé de façon adéquate : it "devrait créer un utilisateur" do lambda do post :create, :user => @attr end.should change(User, :count).by(1) end Ici, au lieu du should_not change(User, :count) utilisé dans le cas dune création dutilisateur défectueuse, nous utilisons should change(User, :count).by(1), qui attend du bloc lambda quil change le compte User de 1. Le second test utilise la méthode assigns vue initialement dans lextrait 7.17 pour vérifier que laction create redirige le tout nouvel utilisateur vers sa page show : it "devrait rediriger vers la page daffichage de lutilisateur" do post :create, :user => @attr response.should redirect_to(user_path(assigns(:user))) end Cest le genre de redirection qui se produit dans presque toute réussite de soumission de formulaire sur le web, et avec la syntaxe assistée de RSpec vous navez rien besoin de savoir sur le code de réponse HTML sous- jacent.151 LURL elle-même est générée en utilisant la route nommée user_path présentée dans la Table 7.1. 8.3.2 Le formulaire dinscription final Pour obtenir la réussite de ces tests et ainsi achever un formulaire dinscription fonctionnel, remplissez la section ex-commentée de lextrait 8.7 avec une redirection, comme montré dans lextrait 8.15. Extrait 8.15. Laction utilisateur create avec une sauvegarde et une redirection. app/controllers/users_controller.rb class UsersController < ApplicationController . . . def create
    • 301 @user = User.new(params[:user]) if @user.save redirect_to @user else @titre = "Inscription" render new end end endNotez que nous pouvons omettre le user_path dans la redirection, en écrivant simplement redirect_to@user pour rediriger lutilisateur vers sa page daffichage (show ), une convention que nous avons vuprécédemment avec link_to dans lextrait 7.25. Cette syntaxe est joliment succincte, mais malheureusementRSpec ne la comprend pas, donc nous devons utiliser lexpression plus bavarde user_path(@user) dans cecas.8.3.3 Le message « flash »Avant de soumettre une registration valide dans le navigateur, nous allons ajouter quelque chose de trèscourant dans les applications web : un message qui apparait temporairement et disparait au rechargement de lapage (si ce nest pas clair, soyez patient : un exemple concret va être présenté rapidement). La manière de Railsdaccomplir cela consiste à utiliser une variable spéciale appelée le flash, qui opère comme mémoire flash danslaquelle il consigne temporairement ses données. La variable flash est en fait une table de hachage ; vouspouvez même vous souvenir de lexemple console de la section 4.3.3, où nous avions vu comment boucler surune table en utilisant une table stratégiquement appelée flash. Pour résumer, essayez cette session deconsole : $ rails console >> flash = { :success => "Ça marche !", :error => "Raté… :-(" } => {:success=>"Ça marche !", :error => "Raté… :-("} >> flash.each do |key, value| ?> puts "#{key}" ?> puts "#{value}" >> end success Ça marche ! error Raté… :-(
    • 302 Nous pouvons nous arranger pour afficher le contenu du flash sur le site en lincluant au layout de notre application, comme dans lextrait 8.16. Extrait 8.16. Ajout du contenu de la variable flash au layout du site. app/views/layouts/application.html.erb <!DOCTYPE html> <html> . . . <%= render layouts/header %> <section class="round"> <% flash.each do |key, value| %> <div class="flash <%= key %>"><%= value %></div> <% end %> <%= yield %> </section> . . . </html> Ce code sarrange pour insérer une balise div pour chaque élément du flash, avec une classe CSS indiquant le type du message. Par exemple, si flash[:success] = "Bienvenue dans lApplication Exemple !", alors le code : <% flash.each do |key, value| %> <div class="flash <%= key %>"><%= value %></div> <% end %> … produira le code HTML :152 <div class="flash success">Bienvenue dans lApplication Exemple !</div> La raison pour laquelle nous bouclons sur toutes les paires clé/valeur possible permet dinclure les autres types de messages flash ; par exemple, dans lextrait 9.8 nous verrons flash[:error] utilisé pour indiquer un échec didentification.153
    • 303Testons le bon message flash en nous assurant que le message adéquat apparait sous la clé :success(extrait 8.17). Extrait 8.17. Un test du message flash en cas de succès de linscription de lutilisateur. spec/controllers/users_controller_spec.rb require spec_helper describe UsersController do render_views . . . describe "POST create" do . . describe "success" do . . . it "devrait avoir un message de bienvenue" do post :create, :user => @attr flash[:success].should =~ /Bienvenue dans lApplication Exemple/i end end end endCela introduit lopération « égale-tilde » (« =~ ») pour comparer les chaines de caractère par expressionrégulière (nous avons déjà parlé des expressions régulières avec le email_regex de lextrait 6.17). Plutôt quede tester tout le message flash, nous testons simplement que le « Bienvenue dans lApplication Exemple » soitprésent (notez que nous ne testons pas encore lapparence du code HTML du message flash ; nous régleronscela en testant la balise div à la section 8.4.3.)Si vous avez déjà beaucoup programmé, vous devez être familier des expressions régulières, mais voilà unerapide session de console dans le cas où vous auriez besoin dune introduction : >> "foo bar" =~ /Foo/ # Par défaut, une Regex est sensible à la casse => nil
    • 304 >> "foo bar" =~ /foo/ => 0 Ici les valeurs de retour de la console peuvent sembler étranges : pour aucune correspondance, la comparaison régulière retourne nil ; pour une correspondance, elle retourne lindex (la position) dans la chaine où la correspondance commence.154 Habituellement, cependant, lindex exact nest pas très important, puisque la comparaison est le plus souvent utilisée dans un contexte booléen : en vous souvenant de la section 4.2.3, nil est false (faux) dans un contexte booléen et tout autre valeur, même 0, est true (vrai). Ainsi, nous pouvons écrire le code ainsi : >> success = "Bienvenue dans lApplication Exemple !" => "Bienvenue dans lApplication Exemple !" >> "Chaine trouvée !" if success =~ /bienvenue dans lapplication exemple/ => nil Il ny a aucune correspondance trouvée ici parce que les expressions régulières sont sensibles à la casse (minuscule/MAJUSCULE) par défaut, mais nous pouvons être plus permissifs dans la recherche en utilisant /.../i qui force une recherche non sensible à la casse : >> "Chaine trouvée !" if success =~ /bienvenue dans lapplication exemple/i => "Chaine trouvée !" Maintenant que nous comprenons comment fonctionne le test du message flash, nous pouvons obtenir quil réussisse en assignant flash[:success] dans laction create comme dans lextrait 8.18. Le message utilise une capitalisation différente de celle du test, mais le test réussit quand même grâce au i à la fin de lexpression régulière. De cette façon nous ne cassons pas le test si nous écrivons, par exemple, application exemple au lieu de Application Exemple. Extrait 8.18. Ajout dun message Flash lors de linscription de lutilisateur. app/controllers/users_controller.rb class UsersController < ApplicationController . . . def create @user = User.new(params[:user]) if @user.save flash[:success] = "Bienvenue dans lApplication Exemple!" redirect_to @user
    • 305 else @titre = "Inscription" render new end end end8.3.4 La première inscriptionNous pouvons voir le résultat de tout ce travail en inscrivant notre premier utilisateur (sous le nom « RailsTutorial » et ladresse mail « example@railstutorial.org »), qui affiche un messageconvivial au succès de linscription, comme montré dans lillustration 8.9 (la jolie stylisation verte pour la classesuccess vient du framework CSS Blueprint de la section 4.1.2). Ensuite, en rechargeant la page daffichage delutilisateur, le message Flash disparait comme promis (illustration 8.10). Illustration 8.9: Le résultat dune inscription réussie, avec le message Flash (version anglaise).
    • 306 Illustration 8.10: La page de profil sans le message Flash après le rechargement éventuel de la page (version anglaise). Nous pouvons maintenant interroger notre base de données juste pour nous assurer par deux fois que le nouvel utilisateur a bien été créé : $ rails console >> user = User.first => #<User id: 1, nom: "Rails Tutorial", email: "example@railstutorial.org", created_at: "2010-02-17 03:07:53", updated_at: "2010-02-17 03:07:53", encrypted_password: "48aa8f4444b71f3f713d87d051819b0d44cd89f4a963949f201...", salt: "f52924ba502d4f92a634d4f9647622ccce26205176cceca2adc..."> Cest un triomphe ! 8.4 Les tests dintégration RSpec En principe, à ce stade, nous en avons fini avec linscription de lutilisateur, mais vous avez peut-être noté que nous navons pas testé la structure du formulaire dinscription, ni testé que les soumissions fonctionnent vraiment. Bien sûr, nous avons testé ces choses en voyant les pages dans notre navigateur, mais le but ultime des tests automatisés est de sassurer quune fois les choses opérationnelles, elles continueront de le rester. Créer de tels tests est le but de cette section — et le résultat est plutôt sympathique.
    • 307Notre méthode de test devrait être de checker la structure HTML du formulaire (en utilisantrender_views et la méthode have_selector), et cest en effet une bonne manière de développer les vues ense laissant diriger par les tests (à cet effet, la section 8.6 propose un exercice concernant ce point). Mais jepréfère ne pas tester la structure HTML détaillée des vues — je ne vois aucune raison pour laquelle nousdevrions devoir savoir que Rails implémente la soumission de ladresse mail de lutilisateur en utilisantnom="user[email]", et en effet tout test de cette structure deviendrait obsolète si une version future de Railschangeait cette convention. Plus encore, ce serait bien davoir un test pour le processus entier dinscription : lavisite de la page dinscription, le remplissage du formulaire, le clic du bouton de soumission et pour sassurer(en cas de soumission valide) quun nouvel utilisateur a bien été créé dans la base de données (en mode test).Bien que ce ne soit pas la seule manière de procéder (voir le Box 8.1), ma solution de prédilection à cettequestion est dutiliser les tests RSpec dintégration, que nous avons initialement utilisés à la section 5.2.1 pourtester les routes personnalisées (telles que « /about » pour la page « À Propos »). Dans la section sus-nommée, nous navons vu quun petit exemple du pouvoir des tests dintégration ; en abordant cette section,nous allons voir à quel point ils peuvent être puissants. Box 8.1.Alternatives dintégrationComme nous lavons vu dans ce chapitre et le précédent, le Tutoriel Ruby on Rails utilise RSpec pour tous sestests, incluant les tests dintégration. Il existe cependant quelques alternatives tout aussi valables. Lune delleest proposée par Rails par défaut : Test::Unit. Il ny a aucune objection à ce que vous utilisiezTest::Unit pour dautres applications, mais jai choisi RSpec pour ce tutoriel et je préfère ne pas mixerRSpec et Test::Unit à lintérieur du même projet.Une autre alternative est Cucumber (Concombre), qui fonctionne bien avec RSpec et permet la définition enplain-texte de scénarios décrivant le comportement de lapplication. De nombreux programmeurs Railstrouvent Cucumber particulièrement pratique pour le travail avec le client ; puisque ces tests peuvent peuventêtre lus même par des utilisateurs non techniciens, les tests Cucumber, ou « scénarios », peuvent être partagésavec (et peuvent même parfois être écrits par) le client. Bien sûr, utiliser un framework de testing qui nest pasen pur Ruby a une contrepartie, et je trouve que les histoires en plain-texte peuvent être un peu bavardes etencom(com)brantes. Puisque nous navons pas dexigence de client dans le Tutoriel Rails, et puisque je préfèredéfinitivement une approche des tests en pur Ruby, nous adoptons les tests dintégration RSpec dans ce livre.Néanmoins, je vous suggère de jeter un œil au Tutoriel Cucumber pour voir sil pourrait vous plaire.8.4.1 Tests dintégration sur les stylesNous avons vu dans lextrait 5.13 que les tests dintégration RSpec supportent le « controller-test » (testcontrôleur) – telles que les constructions du type :
    • 308 get / response.should have_selector(title, :content => "Accueil") Ce nest pas le seul type de syntaxe supporté, cependant ; les tests dintégration RSpec supportent aussi une syntaxe hautement plus expressive en navigation web.155 Dans cette section, nous verrons comment utiliser cette syntaxe pour simuler le remplissage de formulaires en utilisant un code comme : visit signin_path fill_in "Nom", :with => "Exemple dUtilisateur" click_button 8.4.2 Un échec dinscription ne devrait pas créer un nouvel utilisateur Maintenant nous sommes prêts à faire un test dintégration pour linscription des utilisateurs. Comme nous lavons vu à la section 5.2.1, RSpec est fourni avec un générateur pour construire de tels specs dintégration ; dans le cas présent, nos tests dintégration contiendront des actions variées provoquées par les utilisateurs, donc nous appellerons ce test users (utilisateurs) : $ rails generate integration_test users invoke rspec create spec/requests/users_spec.rb Comme à la section 5.2.1, le générateur ajoute automatiquement un fichier de spec, appelé users_spec.rb.156 Nous commençons avec un échec dinscription. Une façon simple dobtenir un échec dinscription est de visiter lURL de linscription et de cliquer sur le bouton de soumission du formulaire sans remplir aucun champ, dont le résultat sera une page comme celle de lillustration 8.11. À léchec de la soumission, la réponse devrait rendre le template users/new. Si vous inspectez le code HTML en résultant, vous devriez voir quelque chose comme le balisage de lextrait 8.19. Cela signifie que nous pouvons tester la présence des messages derreur en cherchant une balise div avec un identifiant (id) "error_explanation". Un test pour ces étapes est montré dans lextrait 8.20.
    • 309Illustration 8.11: Le résultat de la visite de la page /signup et un simple clic sur le bouton « Inscription ». Extrait 8.19. Le div dexplication de lerreur de la page de lillustration 8.11. <div class="error_explanation" id="error_explanation"> <h2>5 erreurs ont empêché votre inscription</h2> <p>Problème avec ces champs :</p> <ul> <li>Nom cant be blank</li> <li>Email cant be blank</li> <li>Email is invalid</li> <li>Password cant be blank</li> <li>Password is too short (minimum is 6 characters)</li> </ul> </div> Extrait 8.20. Tester léchec de linscription. spec/requests/users_spec.rb require spec_helper describe "Users" do describe "une inscription" do describe "ratée" do
    • 310 it "ne devrait pas créer un nouvel utilisateur" do visit signup_path fill_in "Nom", :with => "" fill_in "eMail", :with => "" fill_in "Mot de passe", :with => "" fill_in "Confirmation mot de passe", :with => "" click_button response.should render_template(users/new) response.should have_selector("div#error_explanation") end end end end Ici, "div#error_explanation" est une inscription inspirée du style CSS pour : <div id="error_explanation">...</div> Notez laspect naturel du langage dans lextrait 8.20. Le seul souci est quil ne teste pas exactement ce que nous voulons : nous ne testons pas en réalité que léchec de la soumission échoue pour créer un nouvel utilisateur. Pour ce faire, nous avons besoin denrouler les étapes de test dans un unique package, et alors de vérifier quil ne modifie pas le nombre dutilisateurs. Comme nous le voyons dans lextrait 8.6 et lextrait 8.14, cela peut être accompli avec un bloc lambda. Dans ces cas-là, le bloc lambda contient une seule ligne, mais nous voyons dans lextrait 8.21 quil peut contenir des lignes multiples aussi facilement. Extrait 8.21. Tester léchec de linscription avec un bloc lambda. spec/requests/users_spec.rb require spec_helper describe "Users" do describe "Une inscription" do describe "ratée" do it "ne devrait pas créer un nouvel utilisateur" do lambda do visit signup_path
    • 311 fill_in "Nom", :with => "" fill_in "eMail", :with => "" fill_in "Mot de passe", :with => "" fill_in "Confirmation mot de passe", :with => "" click_button response.should render_template(users/new) response.should have_selector("div#error_explanation") end.should_not change(User, :count) end end end endComme dans lextrait 8.6, ce code utilise : should_not change(User, :count)… pour vérifier que le code à lintérieur du bloc lambda ne change pas la valeur de User.count(Table_des_utilisateurs.compter).Le test dintégration de lextrait 8.21 « intègre » ensemble les différents éléments de Rails, incluant les modèles,les vues, les contrôleurs, les routes et les helpers. Cela fournit une vérification de bout en bout de notremachinerie dinscription, au moins en ce qui concerne les échecs de soumission.8.4.3 Le succès dune inscription devrait créer un nouvel utilisateurNous en arrivons au test dintégration pour la réussite dune inscription. Dans ce cas, nous avons besoin deremplir les champs dinscription avec des données dutilisateur valides. Un fois fait, le résultat doit rendre lapage daffichage de lutilisateur avec une balise div contenant un message flash de réussite, et ça doit changer lenombre dutilisateurs en lincrémentant de 1 dans la base de données. Lextrait 8.22 montre comment réalisercela. Extrait 8.22. Tester la réussite de linscription. spec/requests/users_spec.rb require spec_helper describe "Users" do describe "Une inscription" do
    • 312 . describe "réussie" do it "devrait créer un nouvel utilisateurr" do lambda do visit signup_path fill_in "Nom", :with => "Example User" fill_in "eMail", :with => "user@example.com" fill_in "Mot de passe", :with => "foobar" fill_in "Confirmation mot de passe", :with => "foobar" click_button response.should have_selector("div.flash.success", :content => "Bienvenue") response.should render_template(users/show) end.should change(User, :count).by(1) end end end end En passant, bien que ce ne soit pas clair dans la documentation RSpec, nous pouvons utiliser lid CSS de la boite de texte au lieu du label, donc fill_in :user_nom fonctionne aussi157 (cest particulièrement bienvenue dans les formulaires nutilisant pas les labels). Jespère que vous êtes daccord que la syntaxe de navigation web est incroyablement naturelle et succincte (« fill_in » signifie « remplir » et « :with » signifie « :avec ». NdT). Par exemple, pour remplir un champ avec une valeur, nous utilisons simplement du code tel que : fill_in "Nom", :with => "Utilisateur exemple" fill_in "eMail", :with => "user@example.com" fill_in "Mot de passe", :with => "foobar" fill_in "Confirmation mot de passe", :with => "foobar" Ici, le premier argument de fill_in sont les valeurs de labels, cest-à-dire le texte exact que lutilisateur voit dans son navigateur ; il ny a pas besoin de savoir quoi que ce soit sur la structure HTML sous-jacente générée par lhelper form_for de Rails.
    • 313Pour finir, nous en arrivons au coup de grâce — tester que la réussite de linscription crée bien un nouvelutilisateur dans la base de données : it "devrait créer un nouvel utilisateur" do lambda do . . end.should change(User, :count).by(1)Comme dans lextrait 8.21, nous enroulons le code de la réussite dune inscription dans un bloc lambda. Dansce cas, au lieu de nous assurer que le nombre dutilisateurs ne change pas, nous vérifions quil sincrémente biende 1 suite à lenregistrement de lutilisateur créé dans la base de données de test. Le résultat est le suivant : $ rspec spec/requests/users_spec.rb .. Finished in 2.14 seconds 2 examples, 0 failuresAvec ça, nos tests dintégration de linscription sont achevés, et nous pouvons être confiant que si les utilisateursne sinscrivent pas à notre site, ça nest pas parce que le formulaire dinscription est défectueux.8.5 ConclusionLa possibilité pour les utilisateurs de sinscrire est une étape capitale de notre application. Bien que lapplicationexemple naccomplit encore rien dutile, nous avons posé ici les bases essentielles pour tout le futurdéveloppement. Dans les deux chapitres suivants, nous achèverons deux étapes tout aussi capitales : dabord, auchapitre 9 nous achèverons notre machinerie dauthentification en permettant aux utilisateurs de sidentifier etde se déconnecter de lapplication ; ensuite, au chapitre chapitre 10 nous permettrons à tous les utilisateursdactualiser les informations de leur compte et permettrons aux administrateurs de supprimer des utilisateurs,cela en complétant la suite complète des actions REST de la ressource Users de la Table 6.2.Comme dhabitude, si vous utilisez Git, vous devriez fusionner vos changements dans la branche maitresse : $ git add . $ git commit -m "Inscription utilisateur finie" $ git checkout master $ git merge signing-up
    • 314 8.6 Exercises 1. En sinspirant de lextrait 8.23, écrivez des tests pour vérifier la présence de chaque champ de saisie dans le formulaire dinscription (noubliez pas la ligne render_views, qui est essentielle pour que cela fonctionne). 2. Souvent, les formulaires dinscription effaceront les champs de mot de passe pour les soumissions défectueuses, comme le montre lillustration 8.12. Modifiez laction create du contrôleur Users pour répliquer ce comportement. Astuce : Ré-initialisez @user.password. 3. Le flash HTML de lextrait 8.16 est une combinaison particulièrement laide de HTML et de ERb. Vérifiez en jouant la suite de tests que le code plus propre de lextrait 8.24, qui utilise lhelper Rails content_tag, fonctionne aussi. Extrait 8.23. Un template pour tester tous les champs du formulaire dinscription. spec/controllers/users_controller_spec.rb require spec_helper describe UsersController do render_views . . describe "GET new" do . . it "devrait avoir un champ nom" do get :new response.should have_selector("input[nom=user[nom]][type=text]") end it "devrait avoir un champ email" it "devrait avoir un champ mot de passe" it "devrait avoir un champ confirmation du mot de passe" end . . end
    • 315Illustration 8.12: Un échec de la soumission du formulaire dinscription avec le champ du mot de passe effacé. Extrait 8.24. Le flash ERb dans le layout du site en utilisant content_tag. app/views/layouts/application.html.erb <!DOCTYPE html> <html> . . <section class="round"> <% flash.each do |key, value| %> <%= content_tag(:div, value, :class => "flash #{key}") %> <% end %> <%= yield %> </section> . . . </html>
    • 316 Chapitre 9 Connexion, déconnexion Maintenant que les nouveaux utilisateurs peuvent sinscrire sur notre site (chapitre 8), il est temps de donner à ces utilisateurs enregistrés la possibilité de se connecter (de sidentifier) et se déconnecter. Cela nous permettra dajouter des personnalisations du layout basées sur létat de linternaute visitant le site, utilisateur identifié ou simple visiteur. Par exemple, dans ce chapitre nous actualiserons lentête avec les liens didentification et de déconnexion et le lien pour rejoindre le profil ; au chapitre 11, nous utiliserons lidentité dun utilisateur connecté pour créer des micro-messages associés à cet utilisateur, et au chapitre 12 nous permettrons à lutilisateur de suivre les micro-messages dautres utilisateurs de lapplication. Avoir des utilisateurs connectés nous permettra aussi dimplémenter un modèle de sécurité restreignant laccès de certaines pages à des utilisateurs particuliers. Par exemple, comme nous le verrons au chapitre 10, seuls les visiteurs connectés seront en mesure daccéder à la page de modification des informations utilisateur. Le système didentification (de connexion) rendra aussi possible certains privilèges pour les utilisateurs administrateurs, comme la possibilité (chapitre 10) de détruire des utilisateurs de la base de données. Comme dans les chapitres précédents, nous ferons notre travail sur une branche sujet et fusionnerons les changements à la fin : $ git checkout -b sign-in-out 9.1 Les sessions Une session est une connexion semi-permanente entre deux ordinateurs, tels quun ordinateur client jouant un navigateur web et un serveur jouant Rails. Il existe plusieurs modèles de comportement de session sur le web : « oublier » la session à la fermeture du navigateur, utiliser une option « se souvenir de moi » pour des sessions persistantes, et se souvenir des sessions jusquà ce que lutilisateur se déconnecte explicitement.158 Nous opterons pour la dernière de ces options : quand un utilisateur sidentifie, nous nous souviendrons de son statut « pour toujours »,159 neffaçant sa session que lorsquil se déconnectera explicitement de lui-même. Il est pratique de modeler les sessions comme une ressource REST : nous aurons une page didentification pour les nouvelles sessions (new), lidentification créera une session (create), et la déconnexion la détruira (destroy). Nous aurons par conséquent besoin dun contrôleur Sessions avec les actions new (nouvelle), create (créer) et destroy (détruire). Contrairement au cas du contrôleur Users, qui utilise une base de données (via le modèle User) pour les données persistantes, le contrôleur Sessions utilisera un cookie, qui est un petit morceau de texte placé sur le navigateur de lutilisateur. Le plus gros du travail de lidentification consiste à construire cette machinerie dauthentification basée sur les cookies. Dans cette section et la suivante, nous allons nous préparer à ce travail en construisant un contrôleur Sessions, un formulaire didentification et les actions de contrôleur correspondantes (le plus gros ce travail est similaire à linscription de lutilisateur du
    • 317chapitre 8). Nous terminerons alors lidentification de lutilisateur avec le code nécessaire manipulant lescookies à la section 9.3.9.1.1 Contrôleur SessionsLes éléments de linscription et de lidentification correspondent aux actions REST particulières du contrôleurSessions : le formulaire didentification est traité par laction new (couverte par cette section), et plusprécisément lidentification est traitée en envoyant une requête POST à laction create (section 9.2 etsection 9.3), et la déconnexion est traitée en envoyant une requête DELETE à laction destroy (section 9.4)(rappelez-vous de lassociation des verbes HTTP avec les actions REST de la table 6.2.) Puisque nous savons quenous avons besoin dune action new, nous pouvons la créer en générant le contrôleur Session (comme pour lecontrôleur Users de lextrait 5.23):160 $ rails generate controller Sessions new $ rm -rf spec/views $ rm -rf spec/helpersMaintenant, comme avec le formulaire dinscription à la section 8.1, nous créons un nouveau fichier pour lespec du contrôleur Sessions et ajoutons une paire de tests pour laction new et la vue correspondante(extrait 9.1) (ce modèle devrait commencer à vous être familier maintenant). Extrait 9.1. Tests pour laction new et la vue de la session. spec/controllers/sessions_controller_spec.rb require spec_helper describe SessionsController do render_views describe "GET new" do it "devrait réussir" do get :new response.should be_success end it "devrait avoir le bon titre" do get :new response.should have_selector("titre", :content => "Sidentifier") end
    • 318 end end Pour faire réussir ces tests, nous avons dabord besoin dajouter une route pour laction new, et tant que nous y serons, nous créerons toutes les actions nécessaire au long de ce chapitre. Nous suivons de façon générale lexemple de lextrait 6.26, mais dans ce cas nous définissons seulement les actions particulières dont nous avons besoin, cest-à-dire new, create et destroy, et ajoutons aussi les routes nommées pour lidentification et la déconnexion (extrait 9.2). Extrait 9.2. Ajout dune ressource pour obtenir les actions RESTful pour les sessions. config/routes.rb SampleApp::Application.routes.draw do resources :users resources :sessions, :only => [:new, :create, :destroy] match /signup, :to => users#new match /signin, :to => sessions#new match /signout, :to => sessions#destroy . . . end Comme vous pouvez le voir, les méthodes resources (ressources) peuvent prendre une table doptions, qui dans ce cas possède une clé :only et une valeur égale à un tableau des actions à laquelle doit répondre le contrôleur Sessions. Les ressources définies dans lextrait 9.2 fournissent des URLs et des actions similaires à celles des utilisateurs (Table 6.2), comme montré dans la table 9.1. Requête HTTP URL Route nommée Action But GET /signin signin_path new page pour une nouvelle session (identification) POST /sessions sessions_path create crée une nouvelle session DELETE /signout signout_path destroy efface la session (déconnexion) Table 9.1: Routes RESTful fournies par les règles de sessions de lextrait 9.2.
    • 319Nous pouvons obtenir la réussite du second test de lextrait 9.1 en ajoutant la variable dinstance titreadéquate à laction new, comme dans lextrait 9.3 (qui définit aussi les actions create et destroy pourréférence future). Extrait 9.3. Ajout du titre pour la page didentification. app/controllers/sessions_controller.rb class SessionsController < ApplicationController def new @titre = "Sidentifier" end def create end def destroy end endAvec ça, les tests de lextrait 9.1 devraient réussir, et nous sommes prêts à construire le formulairedidentification.9.1.2 Formulaire didentificationLe formulaire didentification (ou, de façon équivalente, le formulaire de nouvelle session) est similaire enapparence au formulaire dinscription, à lexception prêt que nous navons que deux champs (email et mot depasse) au lieu de quatre. Une maquette est présentée dans lillustration 9.1.
    • 320 Illustration 9.1: Une maquette du formulaire didentification. Rappelez-vous, de lextrait 8.2, que le formulaire dinscription utilise lhelper form_for, prenant en argument la variable dinstance utilisateur @user : <%= form_for(@user) do |f| %> . . . <% end %> La différence principale avec le formulaire de nouvelle session est que nous navons pas de modèle Session, et ainsi pas de variable @session analogue à la variable @user. Cela signifie que, en construisant le formulaire de nouvelle session, nous devons donner à form_for légèrement plus dinformations . En particulier, alors que : form_for(@user) … permet à Rails de déduire que action du formulaire devrait être le POST de lURL /users, dans le cas des sessions nous avons besoin dindiquer le nom de la ressource tout comme lURL appropriée : form_for(:session, :url => sessions_path) Puisque nous authentifions les utilisateurs avec les adresses mail et les mots de passe, nous avons besoin dun champ pour chacun à lintérieur du formulaire ; le résultat apparait dans lextrait 9.4.
    • 321 Extrait 9.4. Code pour le formulaire didentification. app/views/sessions/new.html.erb <h1>Identification</h1> <%= form_for(:session, :url => sessions_path) do |f| %> <div class="field"> <%= f.label :email, "eMail" %><br /> <%= f.text_field :email %> </div> <div class="field"> <%= f.label :password, "Mot de passe" %><br /> <%= f.password_field :password %> </div> <div class="actions"> <%= f.submit "Sidentifier" %> </div> <% end %> <p>Pas encore inscrit ? <%= link_to "Sinscrire !", signup_path %></p>Avec le code de lextrait 9.4, le formulaire didentification apparait comme dans lillustration 9.2. Illustration 9.2: Le formulaire didentification (/sessions/new).
    • 322 Bien que nous allions bientôt perdre lhabitude de regarder le code HTML généré par Rails (et faire plutôt confiance aux helpers pour faire leur travail), pour le moment jetons-y un coup dœil (extrait 9.5). Extrait 9.5. HTML pour le formulaire didentification produit par lextrait 9.4. <form action="/sessions" method="post"> <div class="field"> <label for="session_email">eMail</label><br /> <input id="session_email" name="session[email]" size="30" type="text" /> </div> <div class="field"> <label for="session_password">Mot de passe</label><br /> <input id="session_password" name="session[password]" size="30" type="password" /> </div> <div class="actions"> <input id="session_submit" name="commit" type="submit" value="Sidentifier" /> </div> </form> En comparant lextrait 9.5 avec lextrait 8.5, vous devriez pouvoir deviner que soumettre ce formulaire produira une table params où params[:session][:email] et params[:session][:password] correspondent au champs email et mot de passe. Traiter cette soumission — et, en particulier, authentifier les utilisateurs en se basant sur lemail et le mot de passe soumis — est le projet des deux prochaines sections. 9.2 Échec de lidentification Comme dans le cas de la création dutilisateurs (signup), la première étape dans la création de sessions (signin) est de traiter les entrées invalides. Nous allons commencer par revoir ce qui se passe quand un formulaire est soumis, et nous arranger alors pour afficher un message derreur utile dans le cas de léchec de lidentification (comme présenté dans la maquette de lillustration 9.3). Enfin, nous poserons les bases dune identification réussie (section 9.3) en évaluant chaque soumission de lidentification en se basant sur la validité de sa combinaison email/mot de passe.
    • 323 Illustration 9.3: Une maquette de léchec de lidentification.9.2.1 Examen de la soumission du formulaireCommençons par définir une action create minimale pour le contrôleur Sessions (extrait 9.6), qui ne fait riendautre que rendre la vue new. Soumettre le formulaire /sessions/new avec des champs vierges puisrenvoyer le résultat vu dans lillustration 9.4. Extrait 9.6. Une version préliminaire de laction create de Sessions. app/controllers/sessions_controller.rb class SessionsController < ApplicationController . . . def create render new end . . . end
    • 324 Illustration 9.4: Léchec de lidentification initiale, avec create comme dans lextrait 9.6. Linspection attentive des informations de débuggage de lillustration 9.4 montre que, comme soulevé à la fin de la section 9.1.2, de la soumission résulte une table params contenant lemail et le mot de passe sous la clé :session : --- !map:ActiveSupport::HashWithIndifferentAccess commit: Sidentifier session: !ActiveSupport::HashWithIndifferentAccess password: "" email: "" authenticity_token: BlO65PA1oS5vqrv591dt9B22HGSWW0HbBtoHKbBKYDQ= action: create controller: sessions Comme dans le cas de linscription de lutilisateur (illustration 8.6) ces paramètres forment une table imbriquée comme celle que nous avons vue dans lextrait 4.5. En particulier, params contient une table imbriquée de la forme : { :session => { :password => "", :email => "" } } Cela signifie que : params[:session]
    • 325… est lui-même une table : { :password => "", :email => "" }Comme résultat : params[:session][:email]… est ladresse mail soumise et : params[:session][:password]… est le mot de passe soumis.En dautres termes, à lintérieur de laction create, la table params contient toutes les informations dont nousavons besoin pour authentifier les utilisateurs par lemail et le mot de passe. Ça nest pas le fait du hasard, maisnous avons déjà développé exactement la méthode nécessaire : User.authenticate à la section 7.2.4(extrait 7.12). En se souvenant que authenticate retourne nil pour une authentification invalide, notrestratégie pour lidentification des utilisateurs peut être résumée comme suit : def create user = User.authenticate(params[:session][:email], params[:session][:password]) if user.nil? # Crée un message derreur et rend le formulaire didentification. else # Authentifie lutilisateur et redirige vers la page daffichage. end end9.2.2 Échec de lidentification (test et code)Dans le but de traiter un échec didentification, dabord nous avons besoin de déterminer que cest un échec. Lestests suivent lexemple des tests analogues pour linscription de lutilisateur (extrait 8.6), comme vu danslextrait 9.7. Extrait 9.7. Tests pour un échec didentification. spec/controllers/sessions_controller_spec.rb require spec_helper
    • 326 describe SessionsController do render_views . . . describe "POST create" do describe "invalid signin" do before(:each) do @attr = { :email => "email@example.com", :password => "invalid" } end it "devrait re-rendre la page new" do post :create, :session => @attr response.should render_template(new) end it "devrait avoir le bon titre" do post :create, :session => @attr response.should have_selector("title", :content => "Sidentifier") end it "devait avoir un message flash.now" do post :create, :session => @attr flash.now[:error].should =~ /invalid/i end end end end Le code de lapplication nécessaire pour faire réussir ces tests est présenté dans lextrait 9.8. Comme promis à la section 9.2.1, nous extrayons ladresse mail soumise et le mot de passe de la table params, et les passons ensuite à la méthode User.authenticate. Si lutilisateur nest pas authentifié (cest-à-dire : si la méthode dauthentification renvoie nil), nous définissons le titre et rendons à nouveau le formulaire didentification.161 Nous traiterons lautre branche de la déclaration if-else à la section 9.3 ; pour le moment, nous allons juste laissé un commentaire descriptif.
    • 327 Extrait 9.8. Code pour une tentative ratée didentification. app/controllers/sessions_controller.rb class SessionsController < ApplicationController . . . def create user = User.authenticate(params[:session][:email], params[:session][:password]) if user.nil? flash.now[:error] = "Combinaison Email/Mot de passe invalide." @titre = "Sidentifier" render new else # Authentifie lutilisateur et redirige vers sa page daffichage. end end . . . endRappelez-vous de la section 8.4.2 : nous avons affiché les erreurs de linscription en utilisant les messagesderreur du modèle User. Puisque la session nest pas un modèle Active Record, cette stratégie ne fonctionnerapas ici, donc à la place nous déposons un message dans le flash (ou, plus exactement, dans flash.now ; voyezle Box 9.1). Grâce au message flash affiché dans le layout du site (extrait 8.16), le message flash[:error]sera automatiquement affiché ; grâce aux CSS Blueprint, il aura automatiquement une belle stylisation(illustration 9.5). Box 9.1.Flash . nowIl existe une subtile différence enetre flash et flash.now. La variable flash est conçue pour être utiliséeavant une redirection, et elle persiste sur la page résultante de la-dite requête — cest-à-dire quelle apparait unefois, disparait une fois, et disparait quand vous cliquez un autre lien. Malheureusement, cela signifie que si nousne redirigeons pas la page, et quau contraire nous ne faisons que la rendre (comme dans extrait 9.8), lemessage flash persiste pour les deux requêtes : il apparait sur la page rendue mais il attend toujours pour unerediction (cest-à-dire une seconde requête), et ainsi apparait une nouvelle fois si vous cliquez un lien.
    • 328 Pour éviter ce comportement bizarre, en rendant (render) plutôt quen redirigeant (redirect) nous utilisons flash.now plutôt que flash. Lobjet flash.now est spécialement conçu pour afficher les messages flash sur les pages rendues. Si vous vous demandez un jour pourquoi un message flash est affiché à un endroit que vous nattendiez pas, il y a de fortes chances quil faille mettre ce message dans flash.now plutôt que dans flash. Illustration 9.5: Une identification ratée (avec un message flash). 9.3 Réussite de lidentification Ayant traité un échec de lidentification, nous avons besoin maintenant, effectivement, de pouvoir authentifier un utilisateur. La maquette de lillustration 9.6 donne une idée de là où nous allons — la page de profil de lutilisateur, avec des liens de navigation.162 Même si cela nest pas évident du premier regard, parvenir à ce résultat requiert quelques-uns des défis de programmation Ruby les plus avancés, aussi, accrochez-vous jusquau bout et préparez-vous à soulever du poids (ou à en prendre à force de libérer votre frustration sur les paquets de chips. NdT). Heureusement, la première étape est facile — compléter laction create du contrôleur Sessions est un jeu denfant. Malheureusement, cest aussi une escroquerie.
    • 329Illustration 9.6: Une maquette du profil utilisateur après une identification réussie (avec des liens de navigation actualisés).9.3.1 Laction create achevéeRemplir laire non occupée pour le moment par le commentaire didentification (extrait 9.8) est simple : à laréussite de lidentification, nous identifions lutilisateur en utilisant la fonction sign_in et nous le re-dirigeonsvers sa page de profil (extrait 9.9). Nous voyons maintenant pourquoi cest une escroquerie : hélas, sign_innexiste pas pour le moment. Lécrire nous occupera pendant toute cette section. Extrait 9.9. Laction create du contrôleur Sessions achevée (mais pas encore fonctionnelle). app/controllers/sessions_controller.rb class SessionsController < ApplicationController . . . def create user = User.authenticate(params[:session][:email], params[:session][:password]) if user.nil? flash.now[:error] = "Combinaison Email/Mot de passe invalide." @titre = "Sidentifier" render new else sign_in user redirect_to user
    • 330 end end . . . end Même si la fonction sign_in fait défaut, nous pouvons quand même écrire les tests (extrait 9.10) (nous remplirons le corps du premier test à la section 9.3.3). Extrait 9.10. Ajouter des tests pour lidentification de lutilisateur (sera complet à la section 9.3.3). spec/controllers/sessions_controller_spec.rb describe SessionsController do . . . describe "POST create" do . . . describe "avec un email et un mot de passe valides" do before(:each) do @user = Factory(:user) @attr = { :email => @user.email, :password => @user.password } end it "devrait identifier lutilisateur" do post :create, :session => @attr # Remplir avec les tests pour lidentification de lutilisateur. end it "devrait rediriger vers la page daffichage de lutilisateur" do post :create, :session => @attr response.should redirect_to(user_path(@user)) end end end
    • 331 endCes tests ne réussissent pas encore, mais cest une bonne base.9.3.2 Se souvenir de moiNous sommes maintenant en mesure de commencer à implémenter notre modèle didentification, nommément,en se souvenant de létat « pour toujours » de lidentification de lutilisateur et en effaçant la session seulementquand lutilisateur se déconnecte explicitement de lui-même. Les fonctions didentification elles-mêmes finirontpar franchir la ligne du traditionnel Modèle-Vue-Contrôleur ; en particulier, plusieurs fonctions didentificationauront besoin dêtre accessibles dans les contrôleurs tout comme dans les vues. Vous vous souvenez sans doute,section 4.2.5, que Ruby fournit un module pratique pour emballer les fonctions ensemble et les inclure àplusieurs endroits, et cest ce que nous projetons pour les fonctions didentification. Nous pourrions faire untout nouveau module pour lauthentification, mais le contrôleur Sessions nous arrive déjà équipé dun module,nommément SessionsHelper. Plus encore, les helpers sont automatiquement inclus dans les vues Rails,donc tout ce que nous avons besoin de faire pour utiliser les fonctions de lhelper Sessions dans les contrôleursest dinclure le module dans le contrôleur Application (extrait 9.11). Extrait 9.11. Inclure le module helper Sessions dans le contrôleur Application. app/controllers/application_controller.rb class ApplicationController < ActionController::Base protect_from_forgery include SessionsHelper endPar défaut, tous les helpers sont accessibles dans les vues (views) mais pas dans les contrôleurs. Nous avonsbesoin des méthodes de lhelper Sessions aux deux endroits, donc nous devons linclure explicitement. Box 9.2.Sessions et cookiesParce que HTTP est un protacole sans état, les applications web requiérant lidentification des utilisateursimplémentent une façon de pister chaque parcours dutilisateur de page en page. Une technique pour maintenirlétat de lidentification de lutilisateur consiste à utiliser la traditionnelle session Rails (via la fonction spécialesessions) pour enregistrer un rappel symbolique (remember_token) égal à lidentifiant (id) delutilisateur : session[:remember_token] = user.idCette objet session rend lid de lutilisateur accessible de page en page en lenregistrant dans un cookie quiexpire à la fermeture du navigateur. Sur chaque page, lapplication a juste besoin dappeler :
    • 332 User.find_by_id(session[:remember_token]) … pour récupérer lutilisateur. Grâce à la façon dont Rails traite les sessions, ce processus est sécurisé ; si un utilisateur malicieux essaie de « parodier » lid dun utilisateur, Rails détectera une discordance en sappuyant sur le session id particulier généré pour chaque session. Pour nos choix de conception de lapplication, qui implique des sessions persistantes — cest-à-dire des états didentification qui durent même lorsque le navigateur est fermé — enregistrer lidentifiant de lutilisateur constitue un trou de sécurité. Dès que nous cassons le lien entre lid de session particulier et lid de lutilisateur enregistré, un utilisateur malveillant pourrrait sidentifier comme cet utilisateur avec un remember_token égal à lid de lutilisateur. Pour fixer ce défaut, nous générons un rappel symbolique unique, sécurisé, pour chaque utilisateur, basé sur lid et le salt (sel) de lutilisateur. Plus encore, un rappel symbolique permanent devrait aussi représenter un trou de sécurité — en inspectant les cookies du navigateur, un utilisateur malveillant pourrait trouver le rappel symbolique et lutiliser alors pour sidentifier depuis nimporte quel autre ordinateur, nimporte quand. Nous résolvons cela en ajoutant un timestamp (une signature de temps) au rappel symbolique, et ré-initialisons ce rappel chaque fois que lutilisateur sidentifie sur lapplication. Il en résulte une session persistante essentiellement imperméable à lattaque. Nous sommes maintenant prêts pour le premier élément didentification, la fonction sign_in elle-même. Notre méthode dauthentification consiste à placer un rappel symbolique (remember token) comme cookie sur le navigateur de lutilisateur (Box 9.2), et de lutiliser ensuite pour trouver lenregistrement de lutilisateur dans la base de données chaque fois que lutilisateur navigue de page en page (implémentée à la section 9.3.3). Le résultat (extrait 9.12) pousse deux choses dans la pile : la table cookies et current_user.163 Laissons-les maintenant sexprimer… Extrait 9.12. La fonction sign_in complète (mais pas encore fonctionnelle). app/helpers/sessions_helper.rb module SessionsHelper def sign_in(user) cookies.permanent.signed[:remember_token] = [user.id, user.salt] self.current_user = user end end Lextrait 9.12 introduit lutilitaire cookies fourni par Rails. Nous pouvons utiliser cookies comme si cétait une table ; chaque élément dans le cookie est lui-même une table de deux éléments, une value (une valeur) et
    • 333une date optionnelle expires. Par exemple, nous pourrions implémenter lidentification de lutilisateuren plaçant un cookie avec une valeur égale à lid de lutilisateur qui expire dans vingt ans : cookies[:remember_token] = { :value => user.id, :expires => 20.years.from_now.utc }(Ce code utilise lun des helpers de temps pratique de Rails, comme discuté dans la Box 9.3.) Nous pourrionsalors récupérer lutilisateur avec un code comme : User.find_by_id(cookies[:remember_token])Bien sûr, cookies nest pas vraiment une table, puisque renseigner un cookie, en fait, enregistre une petitepièce de texte sur le navigateur (comme vu dans lillustration 9.7), mais une part de la beauté de Rails est quilvous laisse oublier ce genre de détail et se concentre sur lécriture de lapplication. Illustration 9.7: Un rappel symbolique sécurisé.Malheureusement, utiliser lid de lutilisateur de cette manière nest pas très sûr, pour des raisons discutéesdans le Box 9.2 : un utilisateur malveillant pourrait simuler un cookie avec lid donnée, et ainsi permettre laccèsdu système à nimporte quel utilisateur. La solution traditionnelle avant Rails 3 était de créer un rappelsymbolique sécurisé associé au modèle User à utiliser à la place de lid de lutilisateur (voyez par exemple laversion Rails 2.3 du Tutoriel Rails). Cette façon de faire est devenue si courante que Rails 3 limplémentemaintenant pour nous en utilisant cookies.permanent.signed :
    • 334 cookies.permanent.signed[:remember_token] = [user.id, user.salt] Lassignement de la valeur du côté droit est un tableau consistant en un identifiant unique (cest-à-dire lid de lutilisateur) et une valeur sécurisée utilisée pour créer une signature digitale pour empêcher le genre dattaque décrite à la section 7.2. En particulier, puisque nous avons pris la peine de créer un salt sécurisé à la section 7.2.3, nous pouvons réutiliser cette valeur ici pour signer le rappel symbolique. Sous le capot, utiliser permanent fait que Rails règle lexpiration à 20.years.from_now, et signed rend le cookie sécurisé, de telle sorte que lid de lutilisateur nest jamais exposé dans le navigateur (nous verrons comment récupérer lutilisateur en utilisant le rappel symbolique à la section 9.3.3). Le code ci-dessus montre limportance dutiliser new_record? dans lextrait 7.10 pour sauver le sel seulement à la création de lutilisateur. Dans le cas contraire, ce salt changerait chaque fois que lutilisateur est enregistré, empêchant de récupérer la session de lutilisateur de la section 9.3.3. Box 9.3.Les cookies expirent 20.years.from_now (20.ans.plus_tard) Vous vous souvenez sans doute, de la section 4.4.2, que Ruby vous laisse ajouter des méthodes à nimporte quelle classe, même les classes intégrées. Dans la section mentionnée, nous avons ajouté une méthode palindrome? à la classe String (et découvert en résultat que "kayak" était un palindrome), et nous avons vu aussi comment Rails ajoutait une méthode blank? à la classe Object (Objet) (de telle sorte que "".blank?, " ".blank? et nil.blank? sont toutes true). Le code du cookie dans lextrait 9.12 (qui définit internalement un cookie qui expire 20.years.from_now) donne encore un autre exemple de cette pratique à travers lun des helpers de temps Rails, qui sont des méthodes ajoutées à la classe Fixnum (la classe de base pour les nombres) : $ rails console >> 1.year.from_now => Sun, 13 Mar 2011 03:38:55 UTC +00:00 >> 10.weeks.ago => Sat, 02 Jan 2010 03:39:14 UTC +00:00 Rails ajoute dautres helpers aussi : >> 1.kilobyte => 1024 >> 5.megabytes => 5242880
    • 335Elles sont utiles pour les validations de téléchargement, rendant plus facile de restreindre, disons, la tailledes images téléchargées à 5.megabytes.Bien quelle doive être utilisée avec précaution, la flexibilité qui autorise dajouter des méthodes aux classesintégrées permet des ajouts extraordinairement naturels à Ruby. En effet, toute lélégance de Rails découle enfin de compte de la malléabilité du langage sous-jacent Ruby.9.3.3 Utilisateur courantDans cette section, nous apprendrons comment obtenir et définir la session de lutilisateur courant. Regardonsà nouveau la fonction sign_in pour voir où nous en sommes : module SessionsHelper def sign_in(user) cookies.permanent.signed[:remember_token] = [user.id, user.salt] self.current_user = user end endConcentrons-nous maintenant sur la deuxième ligne :164 self.current_user = userLe but de cette ligne est de créer une variable current_user, accessible aussi bien dans les contrôleurs quedans les vues, ce qui permettra des constructions telles que : <%= current_user.nom %>… ou : redirect_to current_userLe but principal de cette section est de définir current_user.Pour décrire le comportement de la machinerie didentification restant à implémenter, nous allons dabordremplir le test pour lidentification de lutilisateur (extrait 9.13).
    • 336 Extrait 9.13. Remplir le test pour lidentification de lutilisateur. spec/controllers/sessions_controller_spec.rb describe SessionsController do . . . describe "POST create" do . . . describe "avec un email et un mot de passe valides" do before(:each) do @user = Factory(:user) @attr = { :email => @user.email, :password => @user.password } end it "devrait identifier lutilisateur" do post :create, :session => @attr controller.current_user.should == @user controller.should be_signed_in end it "devrait rediriger vers la page daffichage de lutilisateur" do post :create, :session => @attr response.should redirect_to(user_path(@user)) end end end end Le nouveau test utilise la variable controller (contrôleur) (accessible à lintérieur des tests Rails) pour vérifier que la variable current_user est réglée à lutilisateur identifié, et que lutilisateur est identifié : it "devrait identifier lutilisateur" do post :create, :session => @attr controller.current_user.should == @user controller.should be_signed_in
    • 337 endLa deuxième ligne peut être un peu déroutante à ce stade, mais vous pouvez deviner en vous fondant sur laconvention RSpec pour les méthodes booléenne que : controller.should be_signed_in… est équivalent à : controller.signed_in?.should be_trueCest une allusion au fait que nous devrons définit une méthode signed_in? qui retourne true (vrai) si unutilisateur est identifié et false (faux) dans le cas contraire. Plus encore, la méthode signed_in? seraattachée au contrôleur, pas à lutilisateur, ce qui explique pourquoi nous écrivons controller.signed_in?au lieu de current_user.signed_in? (si aucun utilisateur nest identifié, comment pourrions-nous appelésigned_in? sur lui ?).Pour commencer à écrire le code de current_user, notez que la ligne : self.current_user = user… est un assignement. Ruby possède une syntaxe spéciale pour définir de telle fonction dassignement, montrédans lextrait 9.14. Extrait 9.14. Définir lassignement de current_user. app/helpers/sessions_helper.rb module SessionsHelper def sign_in(user) . . . end def current_user=(user) @current_user = user end end
    • 338 Cela peut sembler déroutant, mais ça définit simplement une méthode current_user= expressément conçue pour traiter lassignement de current_user. Son premier argument est la partie droite de lassignement, dans ce cas lutilisateur qui doit être identifié. Le corps dune ligne de cette méthode définit juste une variable dinstance @current_user, en enregistrant effectivement lutilisateur pour un usage ultérieur. En Ruby ordinaire, nous pourrions définir une deuxième méthode, current_user, conçue pour retourner la valeur de @current_user (extrait 9.15). Extrait 9.15. Un définition tentante mais inutile de current_user. module SessionsHelper def sign_in(user) . . . end def current_user=(user) @current_user = user end def current_user @current_user # Inutile ! Nutilisez pas cette ligne. end end Si nous faisions ça, nous répliquerions en effet la fonctionnalité de attr_accessor, vue pour la première fois à la section 4.4.5 et utilisée pour créer un password (mot de passe) virtuel à la section 7.1.1.165 Le problème est que ça échoue complètement pour résoudre notre problème : avec le code de lextrait 9.15, létat de lidentification de lutilisateur serait oublié : dès que lutilisateur rejoindrait une autre page — poof ! — la session cesserait et lutilisateur serait automatiquement déconnecté. Pour éviter ce problème, nous pouvons trouver la session utilisateur correspondant au cookie créé par le code de lextrait 9.12, comme montré dans lextrait 9.16. Extrait 9.16. Trouver lutilisateur courant par remember_token. app/helpers/sessions_helper.rb module SessionsHelper .
    • 339 . . def current_user @current_user ||= user_from_remember_token end private def user_from_remember_token User.authenticate_with_salt(*remember_token) end def remember_token cookies.signed[:remember_token] || [nil, nil] end endCe code utilise plusieurs fonctionnalités plus avancées de Ruby, donc prenons un moment pour les examiner.Dabord, lextrait 9.16 utilise lopérateur dassignement courant mais pour le moment obscur ||= (« ou égal »)(Box 9.4). Son effet est de régler la variable dinstance à lutilisateur correspondant au rappel symbolique, maisseulement si @current_user nest pas défini.166 En dautres mots, la construction : @current_user ||= user_from_remember_token… appelle la méthode user_from_remember_token la première fois que current_user est appelé, maisretourne @current_user au cours des invocations suivantes sans appeler user_from_remember_token.167 Box 9.4.Quest-ce que cest que *$@! ||= ?La construction ||= est très « Ruby-ichienne » —cest-à-dire quelle est hautement caractéristique du langageRuby — et donc importante à apprendre si vous projetez de faire plus de programmation Ruby. Bien quaudébut elle puisse sembler mystérieuse, ou égal est facile à comprendre par analogie.Nous commençons par noter un idiome commun pour changer une variable déjà définie. De nombreuxprogrammes informatiques utilisent lincrémentation de variable, comme dans : x = x + 1
    • 340 La plupart des langages fournissent un raccourci sémantique pour cette opération ; en Ruby (et en C, C++, Perl, Python, Java, etc.), il apparait ainsi : x += 1 Des constructions analogues existent de la même façon pour dautres opérateurs : $ rails console >> x = 1 => 1 >> x += 1 => 2 >> x *= 3 => 6 >> x -= 7 => -1 Dans chaque cas, le modèle est que x = x O y et x O= y sont équivalents pour nimporte quel opérateur O. Une autre pattern courante de Ruby est dassigner une variable si elle est null (nil) mais de la laisser telle quelle dans le cas contraire. En vous souvenant de lopérateur ou || vu à la section 4.2.3, nous pouvons écrire cela comme suit : >> @user => nil >> @user = @user || "lutilisateur" => "lutilisateur" >> @user = @user || "un autre utilisateur" => "lutilisateur" Puisque nil est faux dans un contexte booléen, le premier assignement est nil || "lutilisateur", qui est évalué à "lutilisateur" ; de façon similaire, le second assignement est "lutilisateur" || "un autre utilisateur", qui est aussi évalué à "lutilisateur" —puisque les chaines de caractères sont true (vrai) dans un contexte booléen, la série dexpression || est interrompue dès quune première expression nest pas nulle (cette pratique dévaluer les expressions || de gauche à droite et de sarrêter à la première valeur vraie est connue sous le nom de évaluation court-circuit). En comparant les sessions de console pour une variété dopérateurs, nous voyons que @user = @user || value suit le modèle x = x O y avec || à la place de O, ce qui suggère la construction équivalente suivante :
    • 341 >> @user ||= "Lutilisateur" => "the user"Et voilà !Lextrait 9.16 utilise aussi lopérateur *, qui nous permet dutiliser un tableau à deux éléments comme argumentpour une méthode attendant deux variables, comme nous pouvons le voir dans la session de console : $ rails console >> def foo(bar, baz) ?> bar + baz ?> end => nil >> foo(1, 2) => 3 >> foo(*[1, 2]) => 3La raison pour laquelle cest nécessaire dans lextrait 9.16 est que cookies.signed[:remember_me]retourne un tableau de deux éléments — lid de lutilisateur et le salt — mais, (en suivant les conventions deRuby) nous voulons que la méthode authenticate_with_salt prenne deux arguments, donc elle peut êtreinvoquée avec : User.authenticate_with_salt(id, salt)(Il nexiste pas de raison fondamentale pour que authenticate_with_salt ne puisse prendre un tableaucomme argument, mais ce ne serait pas idiomatiquement correct en Ruby.)Enfin, dans la méthode dhelper remember_token définie par lextrait 9.16, nous utilisons lopérateur || pourretourner un tableau de valeurs nulles si cookies.signed[:remember_me] lui-même est nul : cookies.signed[:remember_token] || [nil, nil]La raison dêtre de ce code est que le support pour les tests des cookies didentification est encore jeune, et lavaleur nil pour le cookie occasionne des cassures de test fallacieux. Retourner plutôt [nil, nil] règle ceproblème.168
    • 342 Létape finale pour obtenir que le code de lextrait 9.16 fonctionne est de définir une méthode de classe authenticate_with_salt. Cette méthode, qui est analogue à la méthode originale authenticate définie dans lextrait 7.12, est montrée dans lextrait 9.17. Extrait 9.17. Ajout dune méthode authenticate_with_salt au modèle User. app/models/user.rb class User < ActiveRecord::Base . . . def self.authenticate(email, submitted_password) user = find_by_email(email) return nil if user.nil? return user if user.has_password?(submitted_password) end def self.authenticate_with_salt(id, cookie_salt) user = find_by_id(id) (user && user.salt == cookie_salt) ? user : nil end . . . end Ici, authenticate_with_salt commence par trouver lutilisateur par son id unique, et vérifie alors que le salt enregistré dans le cookie est bien celui de lutilisateur. Il est important de noter que cette implémentation de authenticate_with_salt est identique à la fonction du code suivant, qui est plus proche de la méthode authenticate : def self.authenticate_with_salt(id, cookie_salt) user = find_by_id(id) return nil if user.nil? return user if user.salt == cookie_salt end
    • 343Dans les deux cas, la méthode retourne lutilisateur si user est pas nil et si le sel de lutilisateurcorrespond au sel du cookie, et retourne nil dans le cas contraire. Dun autre côté, le code tel que : (user && user.salt == cookie_salt) ? user : nil… est courant en Ruby idiomatique correct, donc jai pensé que cétait une bonne idée de lintroduire. Ce codeutilise létrange mais utile ternary operator (opérateur ternaire) pour « compresser » une construction if-else en une seule ligne (Box 9.5). Box 9.5.10 types de gensIl y a 10 genres de personnes dans le monde : ceux qui aiment à lopérateur ternaire, ceux qui ne laiment pas, etceux qui ne le connaissent pas (si vous faites partie de la troisième catégorie, vous ny appartiendrez pluslongtemps).Quand vous faites beaucoup de programmation, vous apprenez rapidement que lun des blocs de contrôle desplus courants ressemble à : if boolean? # Si ça est vrai faire_une_chose else # sinon faire_autre_chose end # finRuby, comme la plupart des autres langages (incluant C/C++, Perl, PHP et Java), vous permettent de remplacerça part une expression plus compacte en utilisant lopérateur ternaire (appelé ainsi parce quil est constitué detrois parties) : boolean? ? faire_une_chose : faire_autre_choseVous pouvez aussi utiliser lopérateur ternaire pour remplacer lassignement : if boolean? var = foo else var = bar end… devient alors :
    • 344 var = boolean? ? foo : bar Lopérateur ternaire est courant en Ruby idiomatique, donc cest une bonne idée de chercher les opportunités de lutiliser. À ce point où nous en sommes, le test didentification réussit presque ; la seule chose restant à faire est de définir la méthode booléenne signed_in?. Heureusement, cest facile avec lutilisation de lopérateur « not » (pas) ! : un utilisateur est identifié si current_user nest pas nil (extrait 9.18). Extrait 9.18. La méthode dhelper signed_in?. app/helpers/sessions_helper.rb module SessionsHelper . . . def signed_in? !current_user.nil? end private . . . end Bien quelle soit déjà utile pour le test, nous allons revoir la méthode signed_in? pour un meilleur usage encore à la section 9.4.3 et encore au chapitre 10. Avec ça, tous les tests devraient réussir. 9.4 Déconnexion Comme discuté à la section 9.1, notre modèle dauthentification est de garder les utilisateurs identifiés jusquà ce quils se déconnectent explicitement. Dans cette section, nous allons ajouter les fonctionnalités nécessaires à la déconnexion. Une fois cela fait, nous ajouterons quelques tests dintégration pour mettre à lépreuve notre machinerie de déconnexion.
    • 3459.4.1 Détruire les sessionsJusquici, les actions du contrôleur Sessions ont suivi la convention REST en utilisant new pour une pagedidentification et create pour achever lidentification. Nous allons poursuivre sur cette voie en utilisant uneaction destroy pour effacer la sessions, cest-à-dire pour se déconnecter.Pour tester laction déconnexion, nous avons dabord besoin dun moyen de sidentifier dans le test. La façon laplus facile de faire ça est dutiliser lobjet controller vu à la section 9.3.3 et dutiliser lhelper sign_in pouridentifier lutilisateur donné. Pour pouvoir utiliser la fonction test_sign_in en résultant dans tous nos tests,nous devons la placer dans le fichier helper spec, comme vu dans lextrait 9.19.169 Extrait 9.19. Une fonction test_sign_in pour simuler lidentification de lutilisateur à lintérieur des tests. spec/spec_helper.rb . . . Rspec.configure do |config| . . . def test_sign_in(user) controller.sign_in(user) end endAprès avoir joué test_sign_in, current_user ne sera pas nil, donc signed_in? renverra true (vrai).Avec cet helper spec en main, le test pour la déconnexion est simple : sidentifier avec un utilisateur (dusine) etalors invoquer laction destroy et vérifier que lutilisateur se trouve déconnecté (extrait 9.20). Extrait 9.20. Un test de destruction de la session (déconnexion utilisateur). spec/controllers/sessions_controller_spec.rb describe SessionsController do . . . describe "DELETE destroy" do it "devrait déconnecter un utilisateur" do
    • 346 test_sign_in(Factory(:user)) delete :destroy controller.should_not be_signed_in response.should redirect_to(root_path) end end end Le seul élément nouveau ici est la méthode delete, qui répond à la requête HTTP DELETE (en analogie aux méthodes get et post vues dans les précédents tests), comme lexigent les conventions REST (Table 9.1). Comme avec lidentification de lutilisateur, qui est reliée à la fonction sign_in, la déconnexion de lutilisateur confie juste le dur boulot à la fonction sign_out (extrait 9.21). Extrait 9.21. Détruire une session (déconnexion utilisateur). app/controllers/sessions_controller.rb class SessionsController < ApplicationController . . . def destroy sign_out redirect_to root_path end end Comme avec les autres éléments dauthentification, nous placerons sign_out dans le module helper Sessions (extrait 9.22). Extrait 9.22. La méthode sign_out dans le module helper Sessions. app/helpers/sessions_helper.rb module SessionsHelper def sign_in(user) cookies.permanent.signed[:remember_token] = [user.id, user.salt] self.current_user = user end . .
    • 347 . def sign_out cookies.delete(:remember_token) self.current_user = nil end private . . . endComme vous pouvez le voir, la méthode sign_out effectivement annule la méthode sign_in en effaçant lerappel symbolique (remember token) et en réglant lutilisateur courant à nil.1709.4.2 Connexion à linscriptionEn principe, nous en avons fini avec lauthentification, mais telle que se présente lapplication actuellement, ilny a pas de liens pour les actions didentification et de déconnexion. Plus encore, les tout nouveaux utilisateursenregistrés peuvent être déroutés de ne pas être identifiés par défaut à linscription.Nous allons fixer ce second problème dabord, en commençant par tester quun nouvel utilisateur estautomatiquement identifié (extrait 9.23). Extrait 9.23. Tester quun nouvel utilisateur soit aussi identifié. spec/controllers/users_controller_spec.rb require spec_helper describe UsersController do render_views . . . describe "POST create" do . . . describe "success" do .
    • 348 . . it "devrait identifier lutilisateur" do post :create, :user => @attr controller.should be_signed_in end . . . end end end Avec la méthode sign_in de la section 9.3, faire que le test réussisse en identifiant un utilisateur est facile : ajoutez juste sign_in @user juste après avoir sauver lutilisateur dans la base de données (extrait 9.24). Extrait 9.24. Identifier lutilisateur après son inscription. app/controllers/users_controller.rb class UsersController < ApplicationController . . . def create @user = User.new(params[:user]) if @user.save sign_in @user flash[:success] = "Bienvenue dans lApplication Exemple !" redirect_to @user else @titre = "Sign up" render new end end 9.4.3 Changement des liens de la mise en page Nous en arrivons finalement à une application fonctionnelle pour tous nos travaux didentification et dinscription : nous allons changer les liens du layout selon létat de lidentification. En particulier, comme le montre la maquette de lillustration 9.6, nous allons faire en sorte que les liens changent suivant que
    • 349lutilisateur est identifié ou déconnecté, et nous allons ajouter aussi un lien vers la page de profil pour unutilisateur identifié.Nous commençons avec les deux tests dintégration : un pour vérifier que le lien "Sinscrire" est visiblepour un utilisateur non identifié, et un pour vérifier que le lien Déconnexion soit visible pour un utilisateuridentifié ; ces deux cas vérifient que le lien conduise à lURL appropriée. Nous placerons ces tests dans le testdes liens du layout que nous avons créé à la section 5.2.1 ; le résultat apparait dans lextrait 9.25. Extrait 9.25. Tests pour les liens de connexion/déconnexion dans le layout du site. spec/requests/layout_links_spec.rb describe "Liens du layout" do . . . describe "quand pas identifié" do it "doit avoir un lien de connexion" do visit root_path response.should have_selector("a", :href => signin_path, :content => "Sidentifier") end end describe "quand identifié" do before(:each) do @user = Factory(:user) visit signin_path fill_in :email, :with => @user.email fill_in "Mot de passe", :with => @user.password click_button end it "devrait avoir un lien de déconnxion" do visit root_path response.should have_selector("a", :href => signout_path, :content => "Déconnexion") end it "devrait avoir un lien vers le profil"
    • 350 end end Ici le bloc before(:each) identifie en visitant la page didentification et en soumettant une paire email/mot de passe valide.171 Nous faisons cela plutôt que dutiliser la fonction test_sign_in de lextrait 9.19 parce que test_sign_in ne fonctionne pas à lintérieur des tests dintégration pour certaines raisons (voir la section 9.6 pour un exercice pour construire une fonction integration_sign_in à lusage des tests dintégration). Le code de lapplication utilise une structure de branchement si-alors à lintérieur du code Ruby embarqué, en utilisant la méthode signed_in? définie dans lextrait 9.18 : <% if signed_in? %> <li><%= link_to "Déconnexion", signout_path, :method => :delete %></li> <% else %> <li><%= link_to "Sidentifier", signin_path %></li> <% end %&g