tutoriel ruby on rails : apprendre rails par l'exemple de michael hartl
DESCRIPTION
Mise en page avec pagination du tutoriel Ruby on Rails de Michael Hartl traduit en françaisTRANSCRIPT
1
Tutoriel Ruby on Rails Apprendre Rails par l'exemple
Michael Hartl
Contenu
chapitre 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 Qu'est-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 l'application .....................................................................................................................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 l'utilisateur.............................................................................................................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 l'application Démo ..................................................................................................76
2.4 Conclusion .....................................................................................................................................76
Chapitre 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 d'instance 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 à l'helper 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 d'origine .......................................................................................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 d'intégration ...........................................................................................................178
5.2.2 Routes Rails....................................................................................................................181
5.2.3 Routes nommées............................................................................................................184
5.3 Inscription de l'utilisateur : une première étape ..............................................................186
5.3.1 Contrôleur des utilisateurs (Users ) ............................................................................186
5.3.2 URL de l'inscription ......................................................................................................189
5.4 Conclusion .............................................................................................................................191
5.5 Exercises .................................................................................................................................192
Chapitre 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 l'utilisateur...................................................................................................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 d'unicité .......................................................................................................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 l'Active 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 d'authentification..................................................................................254
7.3 Meilleures vues d'utilisateurs..............................................................................................259
7.3.1 Tester la page d'affichage de l'utilisateur (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 d'inscription ......................................................................................................277
8.1.1 Utiliser form_for .........................................................................................................279
7
8.1.2 Le formulaire HTML .....................................................................................................282
8.2 Échec de l'inscription ...........................................................................................................285
8.2.1 Tester l'échec ..................................................................................................................286
8.2.2 Un formulaire fonctionnel ...........................................................................................288
8.2.3 Message d'erreur à l'inscription ..................................................................................291
8.2.4 Filtrer les paramètres d'identification........................................................................296
8.3 Réussite de l'inscription.......................................................................................................298
8.3.1 Tester la réussite ............................................................................................................299
8.3.2 Le formulaire d'inscription final .................................................................................300
8.3.3 Le message « flash »......................................................................................................301
8.3.4 La première inscription ................................................................................................305
8.4 Les tests d'intégration RSpec ..............................................................................................306
8.4.1 Tests d'intégration sur les styles ..................................................................................307
8.4.2 Un échec d'inscription ne devrait pas créer un nouvel utilisateur .........................308
8.4.3 Le succès d'une inscription devrait créer un nouvel utilisateur .............................311
8.5 Conclusion .............................................................................................................................313
8.6 Exercises ................................................................................................................................314
Chapitre 9 Connexion, déconnexion......................................................................................................316
9.1 Les sessions ............................................................................................................................316
9.1.1 Contrôleur Sessions........................................................................................................317
9.1.2 Formulaire d'identification...........................................................................................319
9.2 Échec de l'identification.......................................................................................................322
9.2.1 Examen de la soumission du formulaire ....................................................................323
9.2.2 Échec de l'identification (test et code)........................................................................325
9.3 Réussite de l'identification ..................................................................................................328
9.3.1 L'action 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 à l'inscription .............................................................................................347
9.4.3 Changement des liens de la mise en page ..................................................................348
9.4.4 Test d'intégration pour l'identification 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 L'identification requise de l'utilisateur .....................................................................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 L'action destroy (détruire) .....................................................................................395
10.5 Conclusion............................................................................................................................400
9
10.6 Exercices...............................................................................................................................401
chapitre 11 Micro-messages d'utilisateur...............................................................................................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 l'utilisateur ........................................................................418
11.2.2 Exemples de micro-messages .....................................................................................425
11.3 Manipuler les micro-messages ..........................................................................................428
11.3.1 Contrôle d'accè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 d'accueil ..............................................................................449
11.4 Conclusion ............................................................................................................................450
11.5 Exercices ...............................................................................................................................451
Chapitre 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 l'alimentation.......................................................................................................498
12.3.1 Motivation et stratégie.................................................................................................499
12.3.2 Une première implémentation de l'alimentation....................................................502
12.3.3 Champs d'application, sous-sélections et lambda ..................................................504
12.3.4 Le nouvel état de l'alimentation ................................................................................509
12.4 Conclusion............................................................................................................................510
12.4.1 Extensions de l'Application Exemple ........................................................................511
12.4.2 Guide vers d'autres 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 m'a
tellement recommandé ce livre Michael Hartl que je n'ai pu faire autrement que de le lire. C'est ainsi que le
Tutoriel Ruby on Rails m'a fait revenir à nouveau à Rails.
Bien qu'ayant parcouru de nombreux livres sur Rails, c'est ce tutoriel-là qui m'a véritablement « mis en
possession » de Rails. Tout est fait ici « à la manière de Rails » — une manière qui ne m'avait jamais semblé
naturelle avant que je ne lise ce livre. C'est aussi le seul ouvrage sur Rails qui met en place, d'un bout à l'autre,
un Développement Dirigé par les Tests (Test-Driven Development), une approche que je savais hautement
recommandée par les experts mais dont je n'avais jamais compris aussi bien la pertinence que dans ce livre.
11
Enfin, en incluant Git, GitHub et Heroku dans les exemples de la démonstration, l'auteur vous donne
vraiment le goût de ce qu'est le développement d'un projet dans la vie réelle. Et le exemples de code ne sont pas
en reste.
La narration linéaire adoptée par ce tutoriel est vraiment un bon format. Personnellement, j'ai étudié Le
Tutoriel Rails en trois longues journées, en faisant tous les exemples et les exercices proposés à la fin de chaque
chapitre. C'est en lisant ce livre du début à la fin, sans sauter la moindre partie, qu'on 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.
Remerciements
Ce Tutoriel Ruby on Rails doit beaucoup à mon livre précédent sur Rails, RailsSpace, et donc à mon co-auteur
Aurelius Prochazka. J'aimerais remercier Aure à la fois pour le travail qu'il a accompli sur ce précédent livre et
pour son soutien pour le présent ouvrage. J'aimerais aussi remercier Debra Williams Cauley, mon éditeur pour
les deux ouvrages ; aussi longtemps qu'elle jouera avec moi au baseball, je continuerai d'écrire des livres pour
elle.
J'aimerais remercier une longue liste de Rubyistes qui m'ont 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 qu'il est.
À propos de l'auteur
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 license
Le Tutoriel Ruby on Rails : apprendre Rails par l'exemple. Copyright © 2010 par Michael Hartl. Tout le code
source 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, à tout e personne obtenant
une copie de ce logiciel et la documentation ass ociée, pour faire des
modification dans le logiciel sans restriction e t sans limitation des
droits d’utiliser, copier, modifier, fusionner, publier, distribuer,
concéder sous licence, et / ou de vendre les cop ies du Logiciel, et à
autoriser les personnes auxquelles le Logiciel e st meublé de le faire,
sous réserve des conditions suivantes:
L’avis de copyright ci-dessus et cette autorisat ion 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 AUTEU RS 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 q ue vous
* conservez cette note, vous pouvez faire ce que v ous voulez
* de ce travail. Si nous nous rencontrons un jour, et que vous
* pensez que ce travail en vaut la peine, vous pou rrez me
* payer une bière en retour.
* ------------------------------------------------ ------------
*/
13
chapitre 1 De zéro au déploiement Bienvenue dans Le Tutoriel Ruby on Rails : Apprendre Rails par l'exemple. Le but de ce livre est d'être la
meilleure 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 connaissances
nécessaires pour développer et déployer votre propre application web. Vous serez également en mesure de tirer
profit 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 pleinement
d'actualité avec la dernière et meilleure version de Rails.1
Le Tutoriel Ruby on Rails suit essentiellement la même approche que mon livre précédent,2 enseignant le
développement web avec Rails en construisant un exemple d'application conséquente, depuis le tout départ.
Comme Derek Sivers le souligne dans l'avant-propos, ce livre est structuré en narration linéaire, destinée en
conséquence à être lue du début à la fin. Si vous avez l'habitude de survoler les livres techniques, suivre cette
approche linéaire peut vous demander quelques ajustements, mais je ne saurais trop vous recommander
d'essayer. 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 et
en préparant votre environnement de développement (section 1.2). Nous créerons alors notre première
application Rails, appelée (de façon assez pertinente ma foi) first_app (première_application). Le
Tutoriel Rails met l'accent 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 d'illustrer les fonctions élémentaires d'une
application Rails. De façon très rapide, nous construirons cette application démo (qui s'appellera
demo_app) en utilisant le système d'échaffaudage (« scaffolding ») (Box 1.1) pour générer
automatiquement le code ; comme ce code est aussi laid que complexe, le chapitre 2 se concentrera plutôt sur
l'interaction avec l'application 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 écrivant
cette fois tout le code à partir de (presque) rien. Nous développerons cette application exemple en utilisant le
Test Dirigé par le Développement (« test-driven development », TDD), amorcé au chapitre 3 en créant des
pages statiques puis en ajoutant un peu de contenu dynamique. Nous ferons un rapide détour au chapitre 4
pour en apprendre un peu plus sur le langage Ruby sous-jacent à Rails. Puis, du chapitre 5 au chapitre 10, nous
complèterons les fondations de l'application 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 d'enregistrement et
d'authentification. 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.
L'application exemple finale aura plus d'une 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, l'accent 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é d'un 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 d'adresse de quinze minutes en utilisant l'outil 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 s'appuyer sur l'approche par échaffaudage — elle est
plus rapide, plus facile, et plus séduisante. Mais la complexité et l'énorme quantité de code d'un échaffaudage
peut être rédhibitoire pour un développeur Rails débutant ; vous pouvez peut-être l'utiliser, mais vous serez
incapable de le comprendre. Suivre l'approche par échaffaudage risque de vous tourner vers un script
générateur de code virtuose au détriment d'une véritable connaissance de Rails.
Dans le Tutoriel Ruby on Rails, nous adopterons l'approche 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 l'Application 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. L'effet 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 n'importe quel type d'application 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 d'applications, depuis la start-up bagarreuse jusqu'aux énormes compagnies : Posterous,
UserVoice, 37signals, Shopify, Scribd, Twitter, Hulu, the Yellow Pages (les Pages Jaunes) — la liste des sites
utilisant Rails s'allongent de jour en jour. Il y a aussi de nombreux producteurs de développements web qui se
spécialisent en Rails, tels que ENTP, thoughtbot, Pivotal Labs ou Hashrocket, sans compter les innombrables
consultants indépendants, entraineurs et entrepreneurs.
Qu'est-ce qui rend Rails si fantastique ? Tout d'abord, Ruby on Rails est 100% en « open-source », accessible
sous la licence MIT License, e qui signifie qu'il ne coûte rien de le télécharger et de l'utiliser. 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 des
applications web. Il en résulte que de nombreuse tâches courantes de programmation web — telles que générer
du code HTML, faire des modèles de données ou rediriger les URLs — deviennent faciles avec Rails, et le code
de l'application produit est concis et lisible.
Rails s'adapte également avec rapidité aux développements les plus récents de la technologie internet et à la
conception par framework. Rails a par exemple été l'un des premiers frameworks à digérer et implémenter en
totalité l'architecture de style REST pour structurer les applications web (ce que nous apprendrons à faire au
cours de ce tutoriel). Et quand d'autres framewords développent avec succès de nouvelles technologies, le
créateur de Rails, David Heinemeier Hansson ainsi que le cœur de l'équipe Rails n'hésite jamais à incorporer à
Rails leurs nouvelles idées. Peut-être l'exemple le plus parlant est la fusion de Rails et de Merb, un framework
concurrent, de telle sorte que Rails bénéficie maintenant de la conception modulaire de Merb, API stable et aux
performances améliorées. Quiconque a assisté à la conférence donnée par le développeur de Merb et le noyau
dur de l'équipe Yehuda Katz n'a pu faire autrement que de noter à quel point ce fut une extrêmement bonne
idée de monter l'équipe de Merb à bord.
Enfin, Rails bénéficie d'une communauté exceptionnellement enthousiaste et diverse. Les résultats en sont des
centaines de contributeurs open-source, des conférences largement suivies, un nombre considérable de plugins
et des gems (solutions clé-en-main pour des problèmes spécifiques comme la pagination ou le téléchargement
d'images), une riche variété de blogs d'information, et une corne d'abandonce débordant de forums de
discussions et de canaux de discussion IRC. Le grand nombre de développeurs Rails rend également plus facile
la résolution des erreurs inévitables de toute l'application : l'algorithme « Googlelisez votre message d'erreur »
renvoie pratiquement toujours vers le message pertinent d'un blog ou un sujet de discussion dans un forum.
1.1.1 Commentaires pour la grande variété de lecteurs
Le 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 d'utiliser 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 d'un 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 n'est pas destiné à expliquer cette magie — principalement parce que la plupart des développeurs
d'applications Rails n'ont 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 n'avez 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 n'avez même jamais utilisé d'éditeur de texte, ce ne sera pas facile pour vous d'avancer, mais avec un peu de détermination vous devriez pouvoir passer à travers. Si, d'un autre côté, votre fichier .emacs est tellement complexe qu'il 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 l'expé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 s'il 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. D'un autre côté, de nombreux développeurs Rails
débutants sont excités à l'idé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 n'est 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 à l'aise.
Vous pouvez aussi vous contenter d'un goût de ce qu'est Ruby en suivant un court tutoriel en ligne, tels que ceux
qu'on peut trouver sur les sites http://www.ruby-lang.org/ ou http://rubylearning.com/.
Une autre question classique concerne l'utilisation des tests dès le départ. Comme cela est indiqué dans l'introduction, 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 l'inclusion 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, n'hésitez pas à les passer. Bien que j'ai inclus seulement le matériel que je considère essentiel au développement d'un
17
niveau professionnel d'une application Rails, seul le cœur du code de l'application est strictement nécessaire dans une première lecture.
Aux programmeurs non expérimentés (non-concepteurs) : ce Tutoriel Rails n'exige aucun autre
background qu'une connaissance générale de l'ordinateur, donc si vous avez une expérience limitée de la
programmation ce livre est une bonne façon de commencer. Gardez à l'esprit que c'est seulement la première
étape d'un long voyage ; le développement web possède de nombreux aspects différents, parmi lesquels le
HTML/CSS, JavaScript, les bases de données (et SQL), les contrôles de version et le déploiement. Ce livre
contient 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 web
vous donnent une longueur d'avance, puisque vous connaissez certainement déjà le HTML et les CSS. Après
avoir fini ce livre, vous serez dans une excellente position pour travailler sur des projets Rails existants et
pourrez probablement en amorcer un de votre propre chef. Vous pouvez trouver difficile tout ce qui concerne la
programmation, mais le langage Ruby est exceptionnellement accessible aux débutants, tout spécialement ceux
qui possèdent une fibre artistique.
Après avoir achevé la lecture du Tutoriel Ruby on Rails, je recommande aux nouveaux programmeurs la lecture
de Beginning Ruby de Peter Cooper, qui partage la même philosophie pédagogique que ce Tutoriel Rails. Je
recommande aussi The Ruby Way de Hal Fulton. Enfin, pour acquérir une compréhension plus profonde de
Rails 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, c'est peut-être que vous n'êtes pas tout à fait prêts encore à développer des applications web. Dans ce cas, je vous suggère d'apprendre les bases du HTML et des CSS et de recommencer ensuite seulement la lecture de ce tutoriel (malheureusement, je n'ai 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 l'exemple d'une application plus petite qu'une application web au sens fort du terme.
Aux programmeurs expérimentés découvrant le développement web : votre expérience signifie que
vous comprenez probablement déjà les concepts de classes, de méthodes, de structures de données, etc., ce qui
constitue un sérieux avantage. Soyez tout de même avertis, dans le cas où votre background est le C/C++ ou le
Java, que vous pourriez trouver que le langage Ruby ressemble un peu à un canard boiteux, et sa prise en main
pourrait vous prendre du temps ; familiarisez-vous juste avec lui, et tout devrait aller pour le mieux (Ruby vous
autorise même à mettre des points-virgule à la fin des lignes s'ils vous manquent trop !). Ce Tutoriel Rails
couvre toutes les idées spécifiques au développement web dont vous avez besoin, donc ne vous inquiétez pas si
vous ne distinguez pas pour le moment une requête PUT d'une requête POST.
Aux développeurs web expérimentés découvrant Rails : vous avez une sérieuse longueur d'avance, tout
spécialement si vous avez utilisé un langage dynamique tel que le PHP ou (encore mieux) le langage Python. Les
bases 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
l'architecture 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 : l'ensemble 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 d'autres 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 d'autres choses, les techniques employées ici sont peut-être plus d'actualité 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 m'ont exprimé leur surprise quant à la quantité de choses qu'ils avaient tirés de ce livre, et vous pourriez être ravis d'aborder 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 d'où vous partiez, vous serez en mesure d'appréhender les ressources
Rails plus avancées. Voilà celle que je recommande particulièrement :
• Railscasts : d'excellents screencats gratuits sur Rails ;
• PeepCode, Pragmatic.tv, EnvyCasts : d'excellent screencasters payants ;
• Rails Guides : bonnes références sur Rails, d'actualité 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 l'introduction, j'aimerais m'arrê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, n'est qu'un framework. Donc la vraie
question aurait dû être « Est-ce qu'un 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 d'application 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 s'expliquent la plupart du temps d'elles-mêmes ; dans cette section, je mentionne
celles qui ne le pourraient pas.
19
Les é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).5
De nombreux exemples de ce livre utilisent la « ligne de commande ». Pour la simplicité, tous les exemples en
ligne de commande utilisent l'invite classique Unix (un dollar), comme suit :
$ echo "bonjour le monde"
bonjour le monde
Les utilisateurs Windows doivent savoir que leur système utilisera l'invite analogue « > » :
C:\Sites>echo Salut le monde
Salut le monde
Sur les systèmes d'exploitation Unix, certaines commandes devront être exécutées avec sudo , qui signifie
« substitute user do » (L'utilisateur de substition fait…). Par défaut, une commande exécutée avec sudo est
jouée comme par un utilisateur-administrateur, qui a accès à des fichiers et des dossiers que les utilisateurs
normaux ne peuvent pas toucher. Pour indiquer que l'utilisation de sudo dépendra de votre système, elle se
présentera entre crochets quand elle peut être (ou ne pas être) nécessaire sur votre propre système, comme
dans l'exemple de la section 1.2.2:
$ [ sudo ] ruby setup.rb
La plupart des systèmes d'exploitation Unix/Linux/Os X requièrent la commande sudo par défaut, sauf si vous
utilisez le Ruby Version Manager comme cela est suggéré dans la section 1.2.2.3. Notez que vous ne devez pas
utiliser les crochets ; vous devez utiliser soit…
$ sudo ruby setup.rb
soit…
$ ruby setup.rb
… en fonction de votre système d'exploitation.
Rails est fourni avec un grand nombre de commandes qui peuvent être jouées en ligne de commande. Par
exemple, dans la section 1.2.5 nous lancerons un serveur web local en mode développement comme suit :
$ rails server
20
Comme pour l'invite de la commande en ligne, ce Tutoriel Rails utilise la convention Unix pour les séparateurs de
dossiers (c'est-à-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, l'adresse du dossier analogue devrait être :
C:\Sites\sample_app
Le dossier racine pour toute application donnée est connue comme la racine Rails (Rails root), et désormais
tous les chemins d'accès seront relatifs à ce dossier (notez bien que la « racine Rails » n'est 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 , c'est-à-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/rout es.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 d'exploitation, la sortie que vous obtiendrez ne coïncidera pas toujours avec ce qui sera
montré dans le texte, mais il n'y a aucune raison de s'en pré-occuper. De surcroît, certaines commandes
pourrons produire des erreurs en fonction de votre système ; plutôt que d'essayer la tâche Sisyphéenne de
documenter tous ces types d'erreurs dans ce tutoriel, je délèguerai cette tâche à l'algorithme « Google le
message d'erreur », qui est, parmi d'autres choses, une bonne pratique dans le développement grandeur nature
de logiciel.
1.2 Debout et au boulot
Le temps est venu d'aborder l'environnement de développement Ruby on Rails et notre première application. Il
y a quelques casse-têtes ici, spécialement si vous n'avez 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
21
concerne pas en particulier ; tout développeur doit en passer par là (et souvent plus d'une fois), mais soyez
assuré que les efforts seront richement récompensés.
1.2.1 Les environnements de développement
En prenant en compte la grande variété des personnalisations possibles, il existe au moins autant
d'environnements de développement que de programmeurs Rails, mais il se dégage tout de même deux
méthodes principales : les environnements à base d'éditeur de texte/ligne de commande, et les environnements
de développement intégrés (IDEs). Considérons d'abord le second.
IDEs
Les IDEs Rails ne manquent pas, incluant RadRails, RubyMine, et 3rd Rail. Tous sont « cross-platform »
(utilisables sur n'importe quel système. NdT), et j'ai entendu dire du bien de quelques uns d'entre eux. Je vous
encourage à les essayer s'ils fonctionnent pour vous, mais j'ai une confession à vous faire : je n'ai jamais trouvé
un IDE qui me permette de satisfaire tous mes besoins en matière de développement Rails — et pour certains
projets je n'ai pas même été capable de les faire fonctionner.
Éditeurs de texte et ligne de commande
Que devons-nous utiliser alors pour remplacer ces IDE tout-en-un ? Je parierai que la majorité des
développeurs Rails ont opté pour la même solution que moi : utiliser un éditeur de texte pour éditer le texte, et
une ligne de commande pour exécuter des commandes (Illustration 1.1). La combinaison que vous utiliserez
dépendra de vos goûts et de votre plate-forme :
• Macintosh OS X : comme beaucoup de développeurs Rails, j'ai une préférence pour TextMate.
D'autres options incluent Emacs et MacVim (chargé par la commande mvim), l'excellente version
Macintosh de Vim.6 J'utilise iTerm comme terminal de ligne de commande ; d'autres affectionnent
l'application native « Terminal ».
• Linux : vos options d'édition sont à la base les mêmes que pour les OS X, à l'exception 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 d'utiliser l'invite 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-using
Rails 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 qu'on 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 d'utiliser l'add-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 n'importe quelle page. Pour ceux qui n'utilisent pas Firefox, Firebug Lite
fonctionne avec la plupart des autres navigateurs, et Safari comme Chrome ont de façon native un outil
d'inspection des éléments accessible en cliquant-droit sur n'importe quelle partie de la page. Quel que soit le
navigateur utilisé, l'expérience démontre que le temps passé à apprendre à se servir d'un outil d'inspection 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
d'apprentissage 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 d'une 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.
23
1.2.2 Ruby, RubyGems, Rails, et Git
Maintenant, il est temps d'installer Ruby et Rails. La source canonique et actualisée est la page de
téléchargement de Ruby on Rails. Vous pouvez y aller dès à présent ; les parties de ce livre peuvent être lues
avec profit sans être connecté à par celle-ci. Car je vais injecter certains commentaires personnels sur cette
étape.
Installeur Rails (Windows)
L'installation 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 — l'installation de Rails et des logiciels
afférents sur Windows est maintenant incroyablement facilitée. Si vous êtes sous Windows, allez au Rails
Installer (http://railsinstaller.org/) et téléchargez l'exécutable Rails Installer. Double-cliquez l'exécutable et
suivez les instructions d'installation 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 l'installation terminée, vous pouvez directement passer à la création de la première application à la
section 1.2.3.
Installation de Git
Le plus gros de l'écosystème Rails repose d'une manière ou d'une autre sur un système de contrôle de version
appelé Git (couvert plus en détail dans la section 1.3). Puisque son usage est omniprésent, vous devriez installer
Git même à ce point de départ ; je vous suggère de suivre les instructions d'installation propres à votre plate-
forme que vous trouverez à cette adresse : Installing Git section of Pro Git.
Installer Ruby
L'étape suivante consiste à installer Ruby. Il est possible que votre système le possède déjà ; essayez d'exécuter
la 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 mieux
avec 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 Ruby
peut relever du challenge. Vous devrez compter alors sur les instructions les plus actualisées qu'on trouve en
24
ligne. Ce qui suit est une série d'étapes que j'ai exécutées pour le fonctionnement sur mon système (Macintosh
OS X), mais vous aurez peut-être à chercher à droite et à gauche pour qu'elles fonctionnent sur le vôtre.
Dans le cadre de l'installation de Ruby, si vous utilisez OS X ou Linux je recommande fortement l'installation de
Ruby en utilisant le Ruby Version Manager (RVM), qui vous permet d'installer 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 l'utilisation 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 l'installation 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 à l'installation 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, j'ai découvert que j'avais besoin du « patchlevel »
numéro 174 :
$ rvm install 1.8.7-p174
Quand de telles choses surviennent, c'est toujours très frustrant (Oh que oui ! NdT), mais au moins, vous saurez
que ça n'arrive 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
d'elle :
25
$ rvm --create 1.8.7-p174@rails2tutorial
$ rvm --create use 1.9.2@rails3tutorial
La première commande crée le gemset rails2tutorial associé à Ruby 1.8.7-p174, tandis que la seconde
commande crée le gemset rails3tutorial associé à Ruby 1.9.2 et l'utilise (via la commande use ) en même
temps. RVM supporte une large variété de commandes pour manipuler les gemsets ; voyez la documentation à
l'adresse 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 pouvons
définir comme suit :
$ rvm --default use 1.9.2@rails3tutorial
Cela 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 devraient
vous permettre de retrouver toutes vos facultés :
$ rvm --help
$ rvm gemset --help
Installation de RubyGems
RubyGems est un gestionnaire de packages pour les projets Ruby, et il y a légion d'excellentes librairies (Rails
inclus) accessibles comme packages Ruby, ou comme gems. L'installation de RubyGems devrait être facile une
fois Ruby installé. En fait, si vous avez installé RVM, vous avez déjà RubyGems, puisque RVM l'inclut
automatiquement :
$ which gem
/Users/mhartl/.rvm/rubies/ruby-head/bin/gem
Si vous ne l'avez pas encore, vous devez télécharger RubyGems, l'extraire, puis aller dans le dossier rubygems
pour lancer le programme d'installation :
$ [ sudo ] ruby setup.rb
Si 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 l'installation.
Installer Rails
Une fois RubyGems installé, l'installation de Rails 3.0 devrait être un jeu d'enfant :
$ [ 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 l'application 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_controlle r.rb
create app/helpers/application_helper.rb
create app/views/layouts/application.html.er b
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 l'un 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 quelqu'un d'autre. 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
app/ Cœur du code de l'application (app), incluant des modèles (models), des vues (views), des contrôleurs (controllers), et des « assistants » (helpers)
28
config/ Configuration de l'application
db/ Fichiers pour manipuler la base de données
doc/ Documentation pour l'application
lib/ Modules libraries
log/ Journaux (logs) de l'application
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 l'application (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 l'application
Rakefile Tâches utiles accessibles par le biais de la commande rake
Gemfile Les Gems requis par l'application
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 d'un dossier Rails.
1.2.4 Bundler
Après avoir créé une nouvelle application Rails, on utilise Bundler pour installer et inclure les gems utiles à
l'application. Cela consiste à ouvrir le fichier Gemfile avec votre éditeur de texte favori :
$ cd first_app/
$ mate Gemfile
Le résultat devrait ressembler à l'illustration 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/rail s.git'
gem 'sqlite3-ruby' , :require => 'sqlite3'
# Utilser unicorn comme serveur web
# gem 'unicorn'
# Déployer l'application 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'
# end
La plupart de ces lignes sont commentés par le symbole dièse (#) ; elles sont là pour vous montrer certains des
gems les plus communs et donner des exemples de la syntaxe Bundler. Pour le moment, nous n'aurons besoin
d'aucun gem autres que ceux par défaut : Rails lui-même, et le gem pour l'interface Ruby pour la base de
données SQLite.
Même si vous spécifiez un numéro de version pour la commande gem, Bundler installera toujours
automatiquement la dernière version. Malheureusement, les actualisations des gems causent souvent des
ruptures mineures mais potentiellement perturbantes, donc dans ce tutoriel nous ajouterons en général un
numéro de version explicite connu, comme le montre l'illustration 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 l'extrait 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 d'autre packages à ce point du tutoriel :10
$ sudo apt-get install libxslt-dev libxml2-dev libsq lite3-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 à l'exé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 server
Ce code nous dit que l'application est lancée sur le port 300014 à l'adresse 0.0.0.0 . Cette adresse dit à
l'ordinateur d'écouter toutes les adresses IP accessibles et configurées sur la machine spécifiques ; en
particulier, nous pouvons voir l'application en utilisant l'adresse spéciale 127.0.0.1 , connue aussi sous la
forme localhost . Nous pouvons voir le résultat en visitant l'adresse http://localhost:3000/
de l'illustration 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 application's
environment ». Le résultat est montré dans l'illustration 1.4.16
32
Illustration 1.4: La page par défaut (http://localhost:3000/ ) avec l'environnement de
l'application.
Bien sûr, nous n'avons pas besoin de la page par défaut de Rails très longtemps, mais il est sympa de voir qu'elle
fonctionne pour le moment. Nous détruirons la page par défaut et la remplacerons par une page d'accueil
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 d'avoir un aperçu de haut-niveau de comment fonctionne les
applications Rails (Illustration 1.5). Vous pouvez avoir noté que la structure standard d'une application Rails
(Illustration 1.2) possède un dossier appelé app/ contenant trois sous-dossiers : models (Modèles), views
(Vues), and controllers (Contrôleurs). C'est l'indice 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
33
représente un élément du site (tel qu'un utilisateur) et est en charge de la communication avec la base de
données. Après l'invocation du modèle, le contrôleur produit alors une vue et retourne la page web achevée au
navigateur sous forme de code HTML.
Illustration 1.5: Une représentation schématique de l'architecture 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 ferons
fréquemment référence. Addionnellement, la section 2.2.2 contient une discussion plus détaillée sur le MVC
dans le contexte de l'Application démo. Pour finir, l'Application 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 la
section 6.1, et nous verrons comment les trois fonctionnent ensemble dans la section 6.3.2.
1.3 Contrôle de version avec Git
Maintenant que nous avons une application Rails fraiche et fonctionnelle, nous allons prendre le temps pour
une étape qui, bien que techniquement optionnelle, est considérée par de nombreux développeurs Rails comme
pratiquement essentielle, à savoir placer le code source de votre application sous contrôle de version. Les
systèmes de contrôle de version nous permettent de procéder à des modifications du code de notre projet, de
collaborer plus aisément avec d'autres développeurs, et de revenir en arrière en cas d'erreur (comme la
suppression accidentelle de fichiers). Savoir utiliser un système de contrôle de version est une compétence que
doit acquérir tout développeur.
34
Il y a beaucoup d'options 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 l'effleurer 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 c'est 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 d'installer Git si vous n'avez pas suivi les étapes de la section 1.2.2.2 (comme
indiqué dans la section, cela implique d'avoir 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 n'avez à les faire qu'une fois par ordinateur :
$ git config --global user.name "Votre Nom"
$ git config --global user.email [email protected] om
J'aime 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 n'ont pas
configuré ce co , mais dans la vie réelle j'utilise presque toujours git co pour contrôler la sortie d'un 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 d'utiliser 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.
35
Initialisation des réglages du repository
Nous allons maintenant effectuer quelques opérations nécessaires chaque fois que vous créez un nouveau
repository — un nouveau « système de contrôle », ou un dossier, où vous allez déposer vos versions de
l'application . NdT — (ce qui n'arrive qu'une fois dans ce livre, mais qui est susceptible de se reproduire un
jour). Commencez par naviguer dans le dossier racine de la première application et initialisez un nouveau
repository :
$ 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 ne
voulons pas pister. Par exemple, Rails crée des journaux (logs) pour enregistrer le comportement de
l'application ; ces fichiers changent fréquemment, et nous ne voulons pas que notre système de contrôle de
version les actualise en permanence. Git possède un mécanisme simple pour ignorer de tels fichiers : ajoutez
simplement 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 par
défaut dans le dossier racine de Rails, comme le montre l'illustration 1.4.
Illustration 1.4. Le fichier par défaut .gitignore créé par la commande rails .
.bundle
db/*.sqlite3
log/*.log
tmp/**/*
L'illustration 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 dossier
log/ , nous utilisons le code log/*.log pour ignorer tous les fichiers d'extension .log ). La plupart de ces
fichiers ignorés changent fréquemment et automatiquement, donc les inclure dans le contrôle de version est
gênant ; plus encore, lors d'une collaboration, ils peuvent entrainer des conflits frustrants et inutiles.
Le fichier .gitignore de l'extrait 1.4 est suffisant pour ce tutoriel, mais en fonction de votre système vous
pourrez trouver le code de l'extrait 1.5 plus adapté.18 Ce fichier .gitignore augmenté est défini pour ignorer
les fichiers de documentation Rails, les fichiers swap Vim et Emacs, (et pour les utilisateurs OS X) les fichiers de
dossier .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
l'extrait 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, j'ai 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 que
vous avez défini dans la section 1.3.1 et vous demandera alors d'entrer le message.
Il est important de noter que les dépôts Git sont locaux, enregistrés seulement sur la machine où se fait le
dépôt. Ce comportement est différent du système de contrôle de version populaire appelé Subversion, dans
lequel le dépôt fait nécessairement des changements dans le repository à distance. Git, lui, divise le dépôt de
style 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 <[email protected]>
Date: Thu Oct 15 11:36:21 2009 -0700
Dépôt initial
Pour quitter le git log , vous avez peut-être à taper q.
1.3.3 Qu'est-ce que Git peut faire de bien pour vous ?
Au point où nous en sommes, l'utilité de mettre votre code source sous contrôle de version n'est peut-être pas
encore clair, donc laissez-moi vous donner juste un exemple (nous en verrons de nombreux autres au cours des
chapitres 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ù l'on 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_cont roller.rb
#
no changes added to commit (use "git add" and/or "g it commit -a")
Nous voyons qu'un certain nombre de fichiers ont été détruits, mais les changements se trouvent seulement
dans l'« arbre courant » (working tree) ; ils n'ont 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 !
39
1.3.4 GitHub
Maintenant que vous avez déposé votre projet sous version de contrôle avec Git, il est temps de « pusher » votre
code vers GitHub, un site social de code optimisé pour accueillir et partager les repositories Git. Déposer une
copie de votre repository Git sur GitHub vise deux buts : c'est une sauvegarde complète de votre code (qui
inclus l'historique 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 d'une large variété de projets Ruby on Rails (GitHub
possède un taux d'adoption é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 n'en possédez pas encore un
(vous pouvez lire d'abord les instrutions sur les clés SSH). Après vous être inscrit, vous verrez une page
similaire à celle de l'illustration 1.6. Cliquez le lien create a repository (Créer un repository) et remplissez le
formulaire comme dans l'illustration 1.7. Après avoir soumis le formulaire, « pushez » votre première
application (c'est-à-dire envoyer votre code sur le repository distant) comme suit :
$ git remote add origin [email protected]:<username>/fi rst_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 d'utilisateur réel. Par exemple, la commande que j'ai jouée pour l'utilisateur railstutorial était :
$ git remote add origin [email protected]: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 d'autres petites choses utiles
(Illustration 1.8).
41
Illustration 1.8: Une page repository GitHub.
1.3.5 Branch, edit, commit, merge
Si vous avez suivi les étapes de la section 1.3.4, vous avez peut-être noté que GitHub affiche automatiquement le
contenu du fichier README sur la page principale du repository. Dans notre cas, puisque le projet est une
application Rails générée en utilisant la commande rails , le fichier README est celui qui vient avec Rails
(Illustration 1.9). Il n'est pas très utile, donc dans cette section nous créerons notre première édition en
modifiant ce fichier README pour décrire notre projet plutôt que le frameword Rails. Chemin faisant, nous
verrons 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 d'utiliser avec Git.
Illustration 1.9: Le fichier README initial de notre projet sur GitHub.
Branch
Git est incroyablement bon pour faire des branches, qui sont en fait des copies du repository sur lesquelles nous
pouvons effectuer des changements (peut-être expérimentaux) sans modifier les fichiers parents. Dans la
plupart des cas, le repository parent est la branche master, et nous pouvons créer une nouvelle branche sujet en
utilisant la commande checkout avec le drapeau -b :
$ git checkout -b modify-README
Switched to a new branch 'modify-README'
$ git branch
master
* modify-README
Ici, la seconde commande, git branch , liste simplement toutes les branches locales, et l'astérisque « * »
identifie la branche sur laquelle nous nous trouvons actuellement. Notez que git checkout -b modify-
README d'une part crée une nouvelle branche et d'autre part bascule vers cette branche, comme l'indique
42
l'astérisque devant la branche modify-README (si vous avez défini l'alias 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 m'embêterais pas avec une nouvelle branche,
mais il n'est 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. J'aime utiliser le langage
Markdown pour ça, et si vous utilisez l'extension de fichier .markdown alors GitHub mettra automatiquement
en forme l'affichage 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 l'extrait 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
C'est la première application pour le
[*Tutoriel Ruby on Rails : Apprendre Rails par l'exemple*](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 comme
raccourci pour le (très commun) cas d'un dépôt de toutes les modifications des fichiers existants (ou des fichiers
créé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.markdown
Soyez prudent en utilisant de façon inappropriée le drapeau -a ; si vous avez ajouté de nouveaux fichiers au
projet depuis le dernier dépôt, vous devez toujours le préciser à Git en utilisant d'abord la commande git add .
Merge
Maintenant que nous avons achevé nos changements, nous sommes prêts à « merger » (fusionner) le résultat
dans 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 d'avant 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 d'abandonner les changements de la branche sujet, dans ce
cas avec la commande git branch -D :
# Pour l'illustration 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 n'avons 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 d'indiquer 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 an ything.
(Traduction :
fatal : la branche maitresse ne piste rien en ce mo ment.)
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éploiement
Même à ce stade peu avancé, nous allons déjà déployer en production notre application Rails même
rudimentaire. Cette étape est optionnelle, mais déployer assez tôt et souvent nous permet de « capturer » le
plus rapidement possible les problèmes de déploiement dans le cycle de développement. L'alternative —
déployer l'application seulement après un effort laborieux enfermé dans l'environnement de développement —
conduit souvent à de terribles maux de tête d'intégration quand le temps du lancement est arrivé.24
Déployer une application Rails était difficile, mais l'écosystème de déploiement Rails a rapidement mûri ces
dernières années et il existe maintenant plusieurs grandes options. Celles-ci incluent les hôtes partagés ou les
serveurs virtuels privés comme Phusion Passenger (un module pour les web serveurs Apache et Nginx25), des
compagnies de déploiement « tous-services » telles que Engine Yard ou Rails Machine et des clouds Engine
Yard Cloud ou Heroku.
Mon option de déploiement Rails favorite est Heroku, qui est une plate-forme hôte construite spécialement
pour déployer les applications web Rails et autres applications Ruby.26 Heroku facile incroyablement le
déploiement des applications Rails — pourvu que votre code source soit sous contrôle de version Git (c'est
encore une autre raison de suivre les étapes d'utilisation de Git de la section 1.3 si vous ne l'avez 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 d'un 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 n'en
avez pas déjà une, et indiquer ensuite à Heroku votre clé publique pour utiliser Git pour « pusher » le repository
de l'Application exemple vers les serveurs :
$ heroku keys:add
Enfin, utilisez la commande heroku pour créer une place sur les serveurs Heroku pour l'application exemple
(Extrait 1.7).
Extrait 1.7. Créer une nouvelle application sur Heroku.
$ heroku create
Created http://severe-fire-61.heroku.com/ | git@her oku.com:severe-fire-61.git
Git remote heroku added
Oui, c'est ça. La commande heroku command crée un nouveau sous-domaine juste pour notre application,
accessible pour un affichage immédiat. Il n'y 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 l'application vers Heroku, la première étape consiste à utiliser Git pour « pusher » l'application
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 d'actualiser votre Gemfile avec le code de l'extrait 1.8, qui
empêche Heorku d'essayer de charger le gem sqlite3-ruby ).
47
Extrait 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 => :development
1.4.3 Déploiement Heroku, seconde étape
Il n'y a pas de seconde étape ! Nous l'avons déjà accomplie (Illustration 1.11). Pour voir la toute nouvelle
application déployée, vous pouvez visiter l'adresse que vous voyez quand vous jouez la commande heroku
create (p.e. extrait 1.7, mais avec l'adresse de votre propre application, pas la mienne).27 Vous pouvez aussi
utiliser un argument avec la commande heroku qui provoque le lancement automatique du navigateur avec la
bonne 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 votre
application (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
N'utilisez pas ce nom ; je l'utilise déjà moi-même ! En fait, vous ne devriez peut-être pas vous soucier de cette
étape tout de suite ; utiliser l'adresse 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 l'application 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 l'adresse (en passant, comme aperçu de l'incroyable « compacticité » de Ruby, voici le code que j'ai
utilisé pour générer aléatoirement ce sous-domaine :
( 'a' . . 'z' ) . to_a . shuffle [0. . 7]. join
49
Joli, non ?)
En plus de supporter les sous-domaines, Heroku supporte aussi les domaines personnalisés (en fait, le Site du
tutoriel Ruby on Rails se trouve sur Heroku ; si vous lisez ce livre online, vous êtes justement en train de
regarder le site hébergé par Heroku !). Consultez la documentation Heroku pour en savoir plus sur les domains
personnalisés et les autres sujets Heroku.
1.5 Conclusion
Nous avons parcouru un long chemin dans ce chapitre : installation, développement, réglage de
l'environnement, contrôle de version et déploiement. Si vous voulez partager vos progrès, sentez-vous libre
d'envoyer un tweet ou une notification Facebook avec quelque chose comme :
J'apprends 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. L'objectif est d'obtenir une vue d'ensemble de la programmation Ruby on Rails (et du
développement web en général) en générant rapidement une application par l'utilisation 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 n'y a rien de mieux que la
construction par échaffaudage. L'application Démo résultant de cette construction nous permettra d'interagir
avec elle par le biais des URLs, en nous donnant un aperçu de la structure type d'une application Rails, incluant
un premier exemple de l'architecture REST préconisée par Rails.
Comme avec l'Application Exemple que nous développerons par la suite, l'application 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 : l'Application Exemple
complète développera une application similaire depuis le tout départ au chapitre 3, et je fournirai d'abondantes
références concernant tous les points abordés. Dans le même temps, soyez patient et gardez la foi — l'objectif de
ce tutoriel est de vous conduire au-delà de la superficialité de l'approche par échaffaudage pour acquérir une
compréhension profonde de Rails.
2.1 Planifier l'application
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 l'application 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 l'extrait 2.1.
Extrait 2.1. Un fichier Gemfile pour l'application 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 install
Pour 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 l'application Démo sur GitHub.
Vous pouvez aussi, optionnellement, créer un nouveau repository (Illustration 2.1) et le « pusher » vers
GitHub :
$ git remote add origin [email protected]:<username>/de mo_app.git
$ git push origin master
Nous sommes prêts à présent à construire l'application elle-même. La première étape typique quand on
fabrique une application web est de créer un modèle de données, qui est une représentation de la structure
nécessaire à notre application. Dans notre cas, l'application Démo sera un micro-blog, avec uniquement des
utilisateurs et de courts messages. Nous commencerons donc par le modèle de données pour les utilisateurs de
l'application (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 qu'il y a de formulaires d'inscription ; 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 d'utilisateur (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 l'illustration 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 d'utilisateur) de l'auteur du message. Le résultat apparait dans l'illustration 2.3.
Illustration 2.3: Le modèle de données pour les micro-messages.
53
Nous verrons à la section 2.3.3 (et plus profondément au chapitre 11) comment cet attribut user_id nous
permet d'exprimer de façon succinte le fait qu'un 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ême
temps qu'une 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 être
créés, consultés, actualisés et supprimés sur le web via le protocole HTTP (le traducteur tient à préciser qu'il
s'inscrit en faux contre le fait de « penser les utilisateurs comme des objets » :-).NdT).
Comme promis dans l'introduction, notre ressource utilisateurs sera créée par un programme de génération
d'échaffaudage, présent de façon standard dans chaque projet Rails. L'argument de la commande scaffold
(échaffaudage) est la version singulier du nom de la ressource (dans ce cas : User ), auquel on peut ajouter en
paramètres optionnels les attributs du modèle de données :31
$ rails generate scaffold User nom:string email:stri ng
invoke active_record
create db/migrate/20100615004000_create_us ers.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 l'illustration 2.2 (notez qu'il n'y a nul besoin d'inclure un paramètre pour
l'attribut id : il est créé automatiquement par Rails.32)
Pour pouvoir utiliser l'application Démo, nous avons d'abord 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
L'application Démo devrait maintenant être accessible à l'adresse http://localhost:3000/ de
votre navigateur.
Box 2.1.Rake
Dans la tradition Unix, l'utilitaire make joue un rôle important pour construire des programmes exécutable à
partir de leur code source ; plus d'un hacker s'est 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 d'autres ; 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 n'avez 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 d'entre
elles.
2.2.1 Un tour de l'utilisateur
Rejoindre l'url racine http://localhost:3000/ affichera la même page Rails par défaut
présentée dans l'illustration 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 l'utilisateur d'id 1
/users/new new Page pour créer un nouvel utilisateur
/users/1/edit edit Page d'édition de l'utilisateur d'id 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 n'y 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 l'illustration 2.5 (au chapitre 8, cette page deviendra
la page d'inscription).
Illustration 2.5: La page de nouvel utilisateur (/users/new ).
57
Nous pouvons créer un nouvel utilisateur en entrant les valeurs du nom et de l'email dans les champs de
texte correspondants et en cliquant ensuite sur le bouton de création (Create User). Le résultat est la page
d'utilisateur show (afficher) de l'illustration 2.6 la présente (le message vert de bienvenue est obtenu en
utilisant la messagerie flash qui sera abordée dans la section 8.3.3). Notez que l'URL est à présent
/users/1 ; comme vous pouvez vous en douter, le nombre 1 est l'identifiant de l'utilisateur (l'attribut id )
de l'illustration 2.2. À la section 7.3, cette page deviendra la page du profil de l'utilisateur.
Illustration 2.6: La page affichant l'utilisateur (/users/1 — version anglaise).
Pour modifier les informations de l'utilisateur, nous nous rendons sur la page edit (modifier)
(Illustration 2.7). En modifiant les informations de l'utilisateur et en cliquant sur le bouton d'actualisation
(Update User), nous changerons les informations dans l'application Démo (Illustration 2.8) (comme nous le
verrons en détail en abordant le chapitre 6, les données de l'utilisateur sont stockées dans la base de données).
Nous ajouterons les fonctionnalités edit/update à l'Application Exemple dans la section 10.1.
58
Illustration 2.7: La page d'édition de l'utilisateur (/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
d'informations (Il peut être utile de cliquer d'abord le lien « Back » pour revenir à la liste des utilisateurs.
NdT) ; l'index (la liste) en résultant est présenté dans l'illustration 2.9. La section 10.3 permettra de polisser
cette page d'index affichant tous les utilisateurs.
59
Illustration 2.9: La page d'index des utilisateurs (/users ) avec un autre utilisateur.
Ayant vu comment créer, afficher et éditer les utilisateurs, nous en arrivons maintenant à voir comment les
détruire (Illustration 2.10). Vous devriez pouvoir vérifier qu'un clic sur le lien mis en exergue dans
l'illustration 2.10 détruit le second utilisateur, réduisant la page d'index à un seul utilisateur (si cela ne
fonctionne pas, assurez-vous que JavaScript est bien activé dans votre navigateur ; Rails utilise JavaScript pour
exécuter la requête de destruction d'un utilisateur). La section 10.4 ajoute la suppression de l'utilisateur à
l'Application Exemple, en prenant soin de restreindre l'usage de cette fonctionnalité aux seuls administrateurs
du 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 d'un « hit » typique de navigateur — une visite à la page
d'index des utilisateurs, à l'adresse /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 l'URL /users ;
61
2. Rails route (dirige) /users vers une action index dans le contrôleur Users
(Utilisateurs) ;
3. L'action 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.34
Nous commençons par une requête provenant du navigateur — par exemple, après avoir tapé une URL dans la
barre d'adresse du navigateur ou avoir cliqué un lien (étape 1 de l'illustration 2.11). Cette requête atteint le
routeur Rails (étape 2), qui la dispatche vers l'action de contrôleur adéquate basée sur l'URL (et, comme nous
le verrons dans le Box 3.1, le type de requête). Le code pour créer la carte des URLs utilisateur vers les actions
de contrôleur de la ressource Users est présenté dans l'illustration 2.2 ;35 ce code définit effectivement la table
des 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
.
.
.
end
Les pages du tour de la section 2.2.1 correspondent aux actions dans le controller des utilisateurs
, qui est une collection d'actions correspondantes ; le contrôleur généré par l'écheffaudage est présenté
schématiquement dans l'extrait 2.3. Remarquez la notation class UsersController <
ApplicationController ; c'est un exemple de classe Ruby avec héritage (nous discuterons brièvement des
hé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 qu'il y a plus d'actions 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 qu'elles 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 l'implémentation de
l'architecture REST en Rails (Box 2.2). Notez, dans la table 2.2, qu'il y a certains chevauchements dans les
URLs ; par exemple, les actions utilisateur show (afficher) et update (actualiser) correspondent toutes deux à
l'URL /users/1 . La distinction entre l'une et l'autre 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 Propos
GET /users index page listant tous les utilisateurs
GET /users/1 show page affichat l'utilisateur d'id 1
GET /users/new new page pour créer un nouvel utilisateur
POST /users create créer un nouvel utilisateur
GET /users/1/edit edit page pour éditer l'utilisateur d'id 1
PUT /users/1 update actualiser l'utilisateur d'id 1
DELETE /users/1 destroy détruire l'utilisateur d'id 1
Table 2.2: Routages RESTful fournit par la ressource Users dans l'extrait 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 d'allusions à
« REST », acronyme de REpresentational State Transfer (NdT. Qui pourrait se traduire par « Représentation de
l'état de transfert »). REST est un style d'architecture 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 l'application
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 d'application Rails, le style RESTful vous aide à faire des choix quant à quel contrôleur
ou quelles actions écrire : vous structurez simplement l'application 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, puisqu'elles 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 », d'une
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 l'action index vue dans l'extrait 2.4.36
Extrait 2.4. L'action utilisateur index simplifiée pour l'application 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 l''extrait 2.5 ;
bien que plutôt ordinaire, il arrive équipé d'un grand nombre de fonctionnalités grâce à l'hé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
l'extrait 2.5 se débrouille pour que User.all retourne tous les utilisateurs.
Extrait 2.5. Le modèle Utilisateur (User) pour l'application Démo.
app/models/user.rb
class User < ActiveRecord :: Base
end
65
Une fois la variable @users renseignée, le contrôleur appelle la vue (view) (étape 6), vue de l'extrait 2.6.
Les variables commençant par le caractère @, appelées variables d'instance (instance variables), sont
automatiquement accessibles dans la vue ; dans ce cas précis, la vue index.html.erb de l'extrait 2.6 boucle
itérativement sur la liste @users et retourne une ligne HTML à chaque itération.37
Extrait 2.6. La vue pour l'index 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 qui
va l'afficher (étape 8).
66
2.2.3 Faiblesse de cette ressource utilisateur
Bien qu'excellent pour donner un aperçu général de Rails, la ressource Utilisateurs de l'échaffaudage souffre
d'un 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 d'authentification. Nous n'avons aucune notion d'identification ou de déconnexion, et aucun
moyen de prévenir un utilisateur quelconque d'exécuter une opération quelconque ;
• Pas de tests. Techniquement, ça n'est 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, l'authentification ou autres besoins personnalisés ;
• Pas de mise en page. Il n'y a pas de charte graphique ;
• Pas de compréhension réelle. Si vous comprenez le code de l'échaffaudage, alors vous n'avez
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 s'apparente l'une à l'autre 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 d'applications plus solides que
l'exemple 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 l'illustration 2.3:38
$ rails generate scaffold Micropost content:string u ser_id:integer
invoke active_record
create db/migrate/20100615004429_create_mi croposts.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_controll er.rb
invoke erb
create app/views/microposts
create app/views/microposts/index.html.e rb
create app/views/microposts/edit.html.er b
create app/views/microposts/show.html.er b
create app/views/microposts/new.html.erb
create app/views/microposts/_form.html.e rb
invoke test_unit
create test/functional/microposts_contro ller_test.rb
invoke helper
create app/helpers/microposts_helper.rb
invoke test_unit
create test/unit/helpers/microposts_he lper_test.rb
invoke stylesheets
identical public/stylesheets/scaffold.css
Pour actualiser notre base de données avec le nouveau modèle de données, il est nécessaire de demander une
nouvelle 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éé des
utilisateurs 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 montre
l'extrait 2.7.39 Comme pour les utilisateurs, la règle des routages resources :microposts dirige les URLs
des 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 d'id 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 d'id 1
PUT /microposts/1 update Actualiser le micro-message d'id 1
DELETE /microposts/1 destroy Détruire le micro-message d'id 1
Table 2.3: Routes RESTful fournies par la ressource Microposts de l'extrait 2.7.
La contrôleur Microposts lui-même apparait dans sa forme schématique dans l'extrait 2.8. Notez que, hormis le
MicropostsController à la place du UsersController , l'extrait 2.8 est identique au code de l'extrait 2.3.
C'est une conséquence directe de l'architecture 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
.
.
.
end
end
70
Pour faire quelques micro-messages, nous entrons les informations dans la page de création des micro-messages,
/microposts/new de l'illustration 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 l'un au moins possède un
user_id de 1 pour correspondre à l'identifiant du premier utilisateur créé dans la section 2.2.1. Le résultat
devrait s'apparenter à celui de l'illustration 2.13.
Illustration 2.13 : La page d'index des micro-messages (/microposts).
71
2.3.2 Appliquer le micro aux micro-messages
Tout 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-messages
d'au plus 140 caractères (à la Twitter), nous utilisons une validation de longueur (length). À ce stade, vous
devriez ouvrir le fichier app/models/micropost.rb dans votre éditeur de texte ou votre IDE et le remplir
avec le contenu de l'extrait 2.9 (l'utilisation de validates dans l'extrait 2.9 est caractéristique de la version 3
de Rails ; si vous avez travaillé précédemment avec Rails 2.3, vous devriez comparer cet usage à l'utilisation de
l'ancien 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 }
end
Le code de l'extrait 2.9 peut sembler plutôt mystérieux — nous couvrirons la question des validations plus
profondément à la section 6.2 — mais ses effets sont significatifs si nous nous rendons à la page de création des
micro-messages et que nous entrons un messages de plus de 140 caractères. Comme nous pouvons le voir dans
l'illustration 2.14, Rails renvoie un message d'erreur indiquant que le contenu du message est trop long (nous
en apprendrons plus à propos des messages d'erreur à la section 8.2.3.)
Illustration 2.14 : Message d'erreur à l'échec de la création d'un message.
72
2.3.3 Un utilisateur possède plusieurs micro-messages
L'une 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 à l'extrait 2.10 et l'extrait 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 l'illustration 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 : L'association entre les micro-messages et les utilisateurs.
Au chapitre 11 et au chapitre 12, nous utiliserons l'association entre utilisateurs et micro-messages pour afficher
tous les micro-messages d'un 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 d'abord la console avec
73
rails console en ligne de commande, puis récupérons le premier utilisateur dans la base de données en
tapant 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: "mich [email protected]",
created_at: "2010-04-03 02:01:31", updated_at: "201 0-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:3 7: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
(J'ajoute la dernière ligne pour montrer comment sortir de la console Rails, et sur la plupart des systèmes vous
pouvez taper Ctrl-d pour obtenir le même résultat). Ici nous avons obtenu les micro-messages de l'utilisateur en
utilisant le code first_user.microposts : grâce à ce code, Active Record retourne automatiquement tous
les micro-messages dont le user_id est égal à l'identifiant du first_user (1, dans ce cas précis). Nous en
apprendrons plus à propos des facilités des associations avec Active Record au chapitre 11 et au chapitre 12.
2.3.4 Hiérarchie des héritages
Nous terminons notre discussion sur l'application Démo par une brève description des hiérarchies des classes
contrôleur (controller) et modèle (model) dans Rails. Cette discussion n'aura vraiment de sens pour vous que si
vous possédez une expérience de la programmation orientée objet (OOP) ; si vous n'avez pas étudié l'OOP,
sentez-vous libre de sauter cette section. En particulier, si vous n'êtes pas familier des classes (en discussion à la
section 4.4), je vous suggère de revenir ultérieurement à cette section.
Nous commençons par l'héritage de structure pour les modèles. En comparant l'extrait 2.12 et l'extrait 2.13,
vous voyez que le modèle User (Utilisateur) tout comme le modèle Micropost (Micro-message) hérite de
ActiveRecord::Base (via le signe <), qui est la classe de base pour les modèles fournis par ActiveRecord ; un
diagramme résumant cette relation est présenté par l'illustration 2.16. C'est en héritant de
ActiveRecord::Base que nos modèles d'objet acquièrent la faculté de communiquer avec la base de
donné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 d'hé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 l'extrait 2.14 avec
l'extrait 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 l'extrait 2.16, nous voyons que
ApplicationController lui-même hérite de ActionController::Base ; c'est 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 l'illustration 2.17.
Extrait 2.14. La classe-objet UsersController , avec l'hé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 d'héritage pour les contrôleurs Users (Utilisateurs) et Microposts (Micro-
messages).
Comme pour l'héritage du modèle, en héritant en fin de compte de ActionController::Base , le contrôleur
Users tout comme le contrôleur Microposts héritent d'un grand nombre de fonctionnalités, telles que la
capacité de manipuler les objets du modèle, de filtrer les requêtes HTTP entrantes, et de rendre les vues en code
HTML. Puisque tous les contrôleurs Rails héritent de ApplicationController , les règles définies dans le
contrôleur de l'application sont automatiquement appliquées à toutes les actions à l'intérieur de l'application.
Par exemple, nous verrons dans la section 8.2.4 comment une règle définie dans le contrôleur de l'application
76
nous permet de filtrer tous les mots de passe de tous les fichiers, évitant ainsi l'éventualité d'un sérieux trou de
sécurité.
2.3.5 Déployer l'application Démo
Avec l'achè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 l'application demo"
$ git push
Vous pouvez également déployer l'application 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 l'extrait 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 d'en haut » d'une application Rails. L'application 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 l'architecture 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 d'accueil ou l'aide) ;
• Pas de mot de passe utilisateur ;
• Pas d'avatars utilisateurs ;
• Pas d'identification ;
• Pas de sécurité ;
• Pas d'association automatique entre utilisateur et micro-messages ;
• Pas de notion de suivi de message (du côté auteur comme lecteur) ;
• Pas d'alimentation de micro-messages ;
• Pas de « Développement Dirigé par le Test » ;
• Pas de réelle compréhension…
La suite de ce tutoriel s'appuie sur les points forts pour continuer de développer et se consacre à éliminer
systématiquement tous les points faibles.
78
Chapitre 3 Pages statiques courantes Dans ce chapitre, nous commencerons à développer l'Application 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 d'authentification 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, l'option -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 n'exé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 d'exploitation). D'un autre côté,
pour l'Application 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 l'extrait 3.1 (note : si vous préférez
installer tous les gems nécessaire à l'Application Exemple, vous devriez utiliser le code de l'extrait 10.42 dès à
présent).
Extrait 3.1. Un Gemfile pour l'Application 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'
end
Ceci inclut rspec-rails en mode développement, de telle sorte que nous avons accès aux génératers
spécifiques à RSpec, et il inclut également rspec en mode de test dans le but de jouer les tests (nous
incluons aussi un gem pour Webrat, un utilitaire de test qui a l'habitude d'être installé automatiquement en tant
que dépendance mais a besoin ici d'être inclus explicitement). Pour installer et inclure les gems RSpec, nous
utilisons bundle install comme d'habitude :
$ [sudo] bundle install
Pour demander à Rails d'utiliser RSpec plutôt que Test::Unit (qui est utilisé par défaut), nous avons
besoin d'installer les fichiers nécessaires à RSpec. Cela peut être accompli avec rails generate :
$ rails generate rspec:install
Ceci 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 d'actualier le fichier README (situé dans le dossier racine de
l'application) pour être plus utile et descriptif, comme montré dans l'extrait 3.2.
Extrait 3.2. Un fichier README amélioré pour l'Application Exemple.
# Tutoriel Ruby on Rails : Application Exemple
80
C'est l' Application Exemple pour le
[*Tutoriel Ruby on Rails : Apprendre Rails par l'exemple*](http://railstutorial.org/)
par [Michael Hartl](http://michaelhartl.com/).
Changez ce fichier pour utiliser l'extension 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 l'Application Exemple sur GitHub.
Puisque nous utiliserons cette Application Exemple jusqu'à la fin de ce livre, c'est une bonne idée de faire un
repository sur GitHub (illustration 3.1) et de la « pusher » :
$ git remote add origin [email protected]:<username>/sa mple_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 l'Application 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û d'un côté à l'incorporation des exercices du livre et d'un
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 l'application 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 l'extrait 1.8 pour des solutions possibles.) À
mesure que vous avancez dans le livre, je vous recommande de « pusher » et de déployer l'application
régulièrement avec :
$ git push
$ git push heroku
Cela posé, nous sommes prêts à développer l'Application Exemple.
3.1 Pages statiques
Rails propose deux principales manières de créer des pages web statiques. Primo, Rails peut utiliser des pages
vraiment 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 au
navigateur.
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, nous
ajouterons même un dossier de notre cru).
3.1.1 Vraies pages statiques
Commençons avec les vraies pages statiques. Rappelez-vous, de la section 1.2.5, que toute application Rails se
présente comme une application fonctionnelle minimale générée par le script rails , avec une page d'accueil à
l'adresse http://localhost:3000/ (illustration 1.3).
82
Illustration 3.2: Le fichier public/index.html .
Pour savoir d'où 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, c'est 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 n'avez même pas à indiquer le nom du fichier dans l'URL, puisque le nom
index.html est le nom de fichier par défaut. Vous pouvez l'inclure si vous voulez, cependant ; l'adresse :
http://localhost:3000/
… et l'adresse :
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 !</p>
</body>
</html>
Nous pouvons voir dans l'extrait 3.3 la structure caractéristique d'un document HTML : un type de document
(document type), ou doctype, qui est une déclaration aux navigateurs de la version du code HTML que nous
employons (dans ce cas, HTML5) ;46 une section head , dans le cas présent contenant le texte « Greeting » à
l'intérieur d'une balise title ; et une section body (corps), dans le cas présennt avec le texte « Salut le
monde& ! » à l'intérieur d'une balise paragraphe p (l'indentation du texte est optionnelle — HTML n'est pas
sensible aux espaces « blanches », et ignore aussi bien les tabulations que les espaces — mais cela rend la
structure du document plus lisible) Comme attendu, en visitant l'adresse
http://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 l'illustration 3.3 correspond au contenu de
la balise title , c'est-à-dire « Salutations ».
84
Illustration 3.3: Notre vrai fichier HTML statique (http://localhost:3000/hello.html ).
Puisque ce fichier n'a valeur que de démonstration, nous n'avons pas vraiment envie qu'il fasse partie de notre
applicaton, donc il est peut-être mieux de le détruire une fois l'euphorie 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 l'effacer aussi,
éventuellement : nous ne désirons pas que la racine de notre application soit la page par défaut de Rails
montrée dans l'illustration 1.3. Nous verrons dans la section 5.2 comment changer l'adresse
http://localhost:3000/ pour qu'elle 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 n'est 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 d'actions Rails, qui sont une façon plus puissante de définir des URLs que
les fichiers statiques.47 Les actions Rails sont regroupées à l'intérieur de contrôleurs (controllers) (le « C » dans
le MVC de la section 1.2.6), qui contient des jeux d'actions 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 l'architecture REST (à partir du chapitre 6) ; par essence, un contrôleur est le
conteneur d'un groupe de pages web (peut-être dynamiques).
Pour commencer, rappelez-vous, section 1.3.5, que lorsque nous utilisons Git, c'est une bonne pratique
d'exé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, c'est le nom du contrôleur. Puisque nous créons ce contrôleur pour traiter les pages statiques
(courantes), nous l'appellerons simplement le contrôleurs Pages, et planifierons de faire des actions pour la
page d'accueil, la page de contact et la page « À Propos ». Le script generate prend une liste optionnelle
d'actions, 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_s pec.rb
create spec/views/pages
create spec/views/pages/home.html.erb_spec .rb
create spec/views/pages/contact.html.erb_s pec.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ération
de contrôleur crée automatiquement des fichiers de test RSpec dans le dossier spec/ ).
Ici, j'ai intentionnellement « oublié » la page À Propos pour que nous puissions voir comment l'ajouter « à la
main » à la section 3.2.
La génération de pages de contrôleurs de l'extrait 3.4 actualise automatiquement le fichier de routage, nommé
config/routes.rb , que Rails utilise pour trouver la correspondance entre URLs et pages web. C'est notre
première rencontre avec le dossier config , donc il peut être utile d'y jeter un coup d'œil (illustration 3.4). Le
dossier config est l'endroit où Rails rassemble les fichiers nécessaires à la configuration de l'application —
d'où bien sûr son nom.
86
Illustration 3.4: Contenu du dossier config de l'Application 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
d'eux, comme on peut le voir dans l'extrait 3.5.
Extrait 3.5. Le routage pour l'action home et l'action 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 l'URL /pages/home pour la page d'accueil (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 à l'intérieur du contrôleur Pages nous
atteignons automatiquement une page à l'adresse /pages/home . Pour voir le résultat, « tuez » le serveur
avec Ctrl-C, jouez la commande rails server , et naviguez alors à l'adresse /pages/home
(illustration 3.5).
87
Illustration 3.5: La vue de l'accueil brute (/pages/home ) générée par Rails.
Box 3.1.GET, et caetera
Le Hypertext Transfer Protocol (HTTP) définit quatre opérations de base, correspondant aux quatre verbes
GET (obtenir), POST (poster), PUT (déposer) et DELETE (effacer). Cela fait référence aux opérations
entre 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 qu'en développant des
applications web sur un ordinateur local, le client et le serveur sont la même machine physique, mais en général
ils sont différents). L'accent mis sur les verbes HTTP est typique des frameworks (à commencer par Rails)
influencés par l'architecture REST, que nous avons abordée brièvement au chapitre 2 et que nous apprendrons
mieux connaitre au chapitre 8.
GET est l'opé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, votre
navigateur soumet une requête GET. La requête POST est l'opération suivante la plus courante ; c'est la
requête envoyée par votre navigateur quand vous soumettez un formulaire. Dans les applications Rails, les
requê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 vous
soumettez un formulaire pour la création d'un 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 sont moins courantes que GET et POST puisque les navigateurs sont incapables de les envoyer nativement, mais
certains frameworks web (comme Ruby on Rails) ont des façons intelligentes de simuler ces demandes avec les
navigateurs.
88
Pour comprendre d'où 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 l'extrait 3.6 (vous pouvez noter que, contrairement
aux contrôleurs Users et Microposts de l'application Démo du chapitre 2, le contrôleur Pages ne respecte pas les
conventions REST).
Extrait 3.6. Le contrôleur Pages contruit par l'extrait 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 d'organiser 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 l'hé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 qu'elle hérite de la classe ApplicationController le
comportement de ses méthodes est spécifique à Rails : en visitant l'URL /pages/home , Rails consulte le
contrôleur Pages et exécute le code de l'action home, et rend alors la vue (le caractère « V » de MVC de la
section 1.2.6) correspondant à l'action. Dans le cas présent, l'action home est vide, donc tous les appels à
/pages/home rendent simplement la vue. Donc, à quoi ressemble une vue, et où la trouvons-nous ?
89
Si vous jetez un nouveau coup d'œil à la sortie de l'extrait 3.4, vous devriez être en mesure de deviner la
correspondance entre les actions et les vues : une action comme home possède une vue correspondante appelée
home.html.erb . Nous apprendrons à la section 3.3 ce que la partie .erb signifie ; la partie .html ne devrait
pas vous surprendre puisqu'elle ressemble à HTML (extrait 3.7).
Extrait 3.7. La vue générée pour la page d'accueil (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 l'action 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 (à l'intérieur de la balise h1) et un
paragraphe (la balise p) contenant le chemin d'accès au fichier correspondant. Nous allons ajouter quelque (très
lé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 HTML
des fichiers de la section 3.1.1 et la méthode controller/action des pages délivrées sont indistinctes : tout ce que
voit le navigateur est de l'HTML.
Dans la suite de ce chapitre, nous ajouterons d'abord l'action about que nous avons « oubliée » à la
section 3.1.2, un petit peu de contenu dynamique et nous effecturons les premiers pas vers la stylisation des
pages grâce aux CSS. Avant de poursuivre, si vous utilisez Git, c'est une bonne idée d'ajouter dès maintenant les
fichiers du contrôleur Pages au repository :
$ git add .
$ git commit -m "Ajout d'un controleur Pages"
3.2 Nos premiers tests
Si vous demandez à cinq développeurs Rails comment tester une partie quelconque de code, vous obtiendrez
quinze réponses différentes — mais ils seront tous d'accord sur le fait que vous devrez écrire ces tests. C'est dans
cet esprit que nous approcherons le testing de notre Application Exemple, en écrivant de solides tests sans se
préoccuper trop pour le moment de leur perfection. Ne prenez pas les tests du Tutoriel Rails pour parole
d'évangile ; ils s'appuient sur le style que j'ai développé au cours de mes propres travaux ainsi qu'à la lecture du
90
code d'autres développeurs. À mesure que vous étofferez votre expérience en tant que développeur Rails, il n'y 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 l'Application Exemple, nous ferons aussi
le choix de plus en plus fréquent d'écrire ces tests avant d'écrire le code de l'application — une approche connue
sous le nom « Développement Dirigée par le Test » (test-driven development, ou TDD).48 Notre exemple
spécifique sera d'ajouter une page « À Propos » à notre exemple de site. Heureusement, ajouter cette page
supplémentaire n'est 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 d'idées nouvelles.
Tout d'abord, tester l'existence d'une page peut paraitre superflu, mais l'expérience montre que ça ne l'est pas.
Tellement de choses peuvent mal tourner quand on développe un logiciel qu'avoir 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
d'introduire 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 d'une 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é d'un programme (appelé
rspec ) pour vérifier le comportement attendu. Conçu pour tester n'importe 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 d'accord.49
Si vous avez suivi les étapes de l'introduction, 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 s'appuyant 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).
91
L'installation de Autotest est optionnelle, et sa configuration peut être un peu délicate, mais si vous
parvenez à le faire fonctionner sur votre système, je suis certain que vous le trouverez tout comme moi très
utile. Pour installer Autotest, installez les gems autotest et autotest-rails-pure 50 comme
suit :
$ [ sudo ] gem install autotest -v 4.4.6
$ [ sudo ] gem install autotest-rails-pure -v 4.1.2
La prochaine étape dépend de votre plateforme. Je passerai par les étapes pour le système OS X, puisque c'est
celui que j'utilise, et donnerai ensuite les références vers les posts de blog qui parlent d'Autotest sur Linus et
Windows. Sur OS X, vous devrez installer Growl (si vous ne l'avez pas déjà) et installer alors les gems
autotest-fsevent et autotest-growl :51
$ [ sudo ] gem install autotest-fsevent -v 0.2.4
$ [ sudo ] gem install autotest-growl -v 0.2.9
Si FSEvent ne s'installe 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 Rails
racine et remplissez-le avec le contenu de l'extrait 3.9 (ou de l'extrait 3.10 si l'extrait 3.9 renvoie une erreur sur
votre 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 l'Application Exemple seulement ; si vous voulez partager
cette configuration Autotest avec les autres projets Rails ou Ruby, vous devriez crér un fichier .autotest
plutô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 à l'adresse Automate
Everything, qui installe sur Linux un système semblable aux notifications Growl sur OS X. Les utilisateurs
Windows devront essayer d'installer 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 d'abord un test échec : dans notre cas,
une pièce de code qui exprime l'idée qu'il devrait y avoir une page « À Propos » (about ). Nous lançons alors le
test, dans notre cas en ajoutant l'action about et la vue correspondante. La raison typique pour laquelle nous
ne faisons pas l'inverse — l'implémentation d'abord, puis le test — permet de nous assurer que nous testons
vraiment la fonctionnalité que nous ajoutons. Avant d'utiliser 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 s'assurant 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 n'est 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 l'application, juste pour sentir à
quoi la solution pourrait ressembler (dans le langage de l''Extreme Programming (XP), cette étape d'exploration
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 d'un 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, d'en changer la forme (en éliminant les répétitions
par exemple) sans en changer la fonction.
Nous n'avons 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 l'extrait 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 j'ai
93
toujours trouvée soit fragile soit redondante, notre première étape va consister à les effacer. Si vous utilisez
Git, vous pouvez le faire comme suit :
$ git rm -r spec/views
$ git rm -r spec/helpers
Dans le cas contraire, supprimez-les directement :
$ rm -rf spec/views
$ rm -rf spec/helpers
Nous 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. C'est 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 n'avez 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 l'anglais, et si vous suivez les
exemples du script généré et les autres exemples de ce tutoriel, vous le maitriserez rapidement.
L''extrait 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 l'action home. C'est juste une
description, et ça peut être ce que vous voulez ; RSpec s'en fiche, mais vous ou d'autres 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 d'accueil, cela devrait
réussir. Comme avec la première ligne, ce qui vient à l'intérieur des guillemets n'intéresse pas RSpec, ça n'est
pertinent qu'aux lecteurs humains. La troisième ligne, get ’home’ , est la première ligne qui véritablement
accomplit quelque chose. À l'intérieur de RSpec, cette ligne soumet vraiment une requête GET ; en d'autres
termes, elle agit comme un navigateur et pointe vers la page, dans ce cas /pages/home (il sait
automatiquement qu'il doit toucher le contrôleur Pages puisque c'est un test de ce contrôleur Pages ; il sait qu'il
doit atteindre la page d'accueil 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 qu'un client (tel qu'un 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
95
cela directement avec, par exemple, www.google.com (où les drapeaux --head empêchent
curl 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 . A
contrario, 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 test
RSpec, 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 le
faire.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 l'invite de commande Windows), vous pourriez avoir un
message d'erreur à ce stade indiquant des difficultés à trouver le programme rspec :
C:\Sites\sample_app>rspec spec/
'rspec' is not recognized as an internal or externa l command,
operable program or batch file.
Dans ce cas, vous aurez besoin d'exécuter rspec par le Bundler en utilisant la commande bundle exec :
C:\Sites\sample_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 d'avoir 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 d'idées.)
97
En jouant rspec spec/ , rspec est un programme fourni par RSpec, tandis que spec/ est le dossier où
vous voulez que rspec s'exécute. Vous pouvez également jouer seulement les specs dans un sous-dossier
particulier. Par exemple, la commande suivante joue uniquement ls specs des contrôleurs :
$ rspec spec/controllers/
....
Finished in 0.07502 seconds
2 examples, 0 failures
Vous pouvez aussi jouer seulement un fichier :
$ rspec spec/controllers/pages_controller_spec.rb
....
Finished in 0.07253 seconds
2 examples, 0 failures
Le résultat de ces trois commandes est le même puisque le spec du contrôleur Pages est actuellement notre seul
fichier de test. Au cours de la suite de ce livre, je ne montrerai pas toujours la sortie du testing, mais vous devrez
jouer rspec spec/ (ou une de ses variantes) régulièrement en poursuivant — ou, même mieux, utiliser
Autotest 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 command
autotest :
$ autotest
Si cela ne fonctionne pas, essayez :
$ bundle exec autotest
Si vous utilisez un Mac avec les notifications Growl activées, vous devriez pouvoir répliquer mes réglages
montrés dans l'illustration 3.6. Avec Autotest jouant en arrière-tâche et les notifications Growl vous disant l'état
de 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 d'une suite de tests peuvent être
considérables. Cela s'explique par le fait que chaque fois que RSpec joue les tests, il doit recharger tout
l'environnement Rails. Le serveur de test Spork56 a pour but de régler ce problème. Spork charge une seule fois
l'environnement, 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 c'est un sujet de niveau plutôt avancé. Si vous vous
sentez dépassé, n'hé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 l'Application 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'
end
Ensuite, installez-le :
$ bundle install
Amorcer la configuration Spork :
$ spork --bootstrap
Maintenant nous devons éditer le fichier de configuration RSpec, spec/spec_helper.rb , pour que
l'environnement soit chargé dans le bloc prefork, pour n'avoir à le charger qu'une seule fois (extrait 3.13).
Extrait 3.13. Ajout du chargement de l'environnement dans le bloc Spork.prefork .
spec/spec_helper.rb
Note : utilisez seulement ce code si vous utilisez Spork. Si vous essayez d'utiliser l''extrait 3.13 sans Spork, la
suite 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 s pork.
ENV [ "RAILS_ENV" ] ||= 'test'
require File . expand_path( "../../config/environment" , __FILE__)
require 'rspec/rails'
# Requires supporting files with custom matchers an d 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, uncom ment 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 you're not using ActiveRecord, or you'd prefer not to run each of your
# examples within a transaction, comment the follow ing 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
101
Ici la suite de tests prend plus de 7 secondes pour être exécutée même si les tests effectifs jouent en moins
d'une dizaine de secondes. Pour accélérer cela, nous pouvons ouvrir une fenêtre terminal dédiée aux tests, nous
rendre 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 l'option --drb 57 et vérifier que le temps de chargement de l'environnement 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.171s
Comme attendu, les temps ont été considérablement réduits.
Pour lancer RSpec et Spork avec Autotest, nous avons besoin de configurer RSpec pour utiliser l'option --drb par défaut, ce que nous pouvons faire en l'ajoutant au fichier de configuration .rspec dans le dossier
racine de Rails (extrait 3.14).
Extrait 3.14. Ajout de l'option --drb au fichier .rspec .
-- colour
-- drb
Avec ce fichier .rspec actualisé, la suite de tests devrait fonctionner aussi vite que précédemment, même sans
l'option 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 n'a de sens que pour l'illustration ; 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 qu'ils 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 l'extrait 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
end
Notez que nous avons ajouté une ligne pour dire à RSpec de rendre les vues à l'intérieur des tests du contrôleur.
En d'autres termes, par défaut, RSpec teste juste les actions à l'intérieur d'un test d'un contrôleur de test ; si
vous voulez en plus qu'il 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 d'atteindre (get ) l'action about , et indique que la réponse en résultant doit être un
succès. À dessein, cela échoue (avec un message d'erreur rouge), comme le montre l'illustration 3.7 (rspec
spec/ ) et l'illustration 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 n'invitera 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.
C'est Rouge. Maintenant obtenons le Vert.
Vert
Rappelez-vous, de la section 3.1.2, que nous pouvons générer une page statique avec Rails en créant une action
et la vue correspondante avec le nom de la page. Dans notre cas, la page « À Propos » aura d'abord besoin d'une
action appelée about dans le contrôleur Pages. Ayant écrit un test d'échec, nous pouvons maintenant être sûr
qu'en le faisant réussir, nous avons bien créé une page about qui fonctionne.
En suivant les modèles fournis par home et contact dans l'extrait 3.6, commençons d'abord par ajouter
l'action about dans le contrôleur Pages (extrait 3.16).
Extrait 3.16. Le contrôleur Pages avec l'ajout de l'action about .
app/controllers/pages_controller.rb
class PagesController < ApplicationController
def home
end
def contact
end
106
def about
end
end
Ensuite, nous allons ajouté l'action 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 n'est jamais une mauvaise idée de jeter un œil à la page dans un navigtaeur pour s'assurer que les
tests 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 qu'il devient rapidement laid,
bouffi et plein de répétitions. L'ordinateur s'en 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é d'introduire
des bogues en cours de restructuration.
Notre Application Exemple est un peu trop petite pour la restructurer maintenant, mais l'odeur de code sale
s'infiltre par chaque fissure, donc nous n'aurons 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 qu'il 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 l'extrait 3.16, de l'extrait 3.17 et de l'extrait 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 l'extrait 3.3, en incluant des titres qui changent pour chacune des pages. C'est
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 l'Application Exemple.
À la fin de cette section, nos trois pages statiques auront des titres de la forme « Simple App du Tutoriel Ruby
on Rails | Accueil », où la dernière partie de titre variera en fonction de la page (Table 3.1). Nous construirons
les tests d'après l'extrait 3.15, en ajoutant des tests sur le titre suivant le modèle de l'extrait 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" )
end
Ce code utilise la méthode have_selector à l'intérieur de RSpec ; la documentation sur have_selector est
étonnament éparse, mais ce qu'elle fait, c'est de comparer le contenu d'un élément HTML (d'un « selector »)
avec un contenu donné. En d'autres termes, le code :
response . should have_selector( "title" ,
:content => "Simple App du Tutoriel Ruby on Rails | Accueil" )
… s'assure que le contenu entre les balises <title></title> est bien "Simple App du Tutoriel
Ruby on Rails | Accueil" .58 Il est utile de mentionner que le contenu n'a pas besoin d'être exact ; toute
sous-chaine fonctionne aussi, donc le code :
response . should have_selector( "title" , :content => " | Accueil" )
… fonctionnera tout aussi bien.59
Notez que dans l'extrait 3.19 j'ai coupé le texte à l'intérieur du have_selector en deux lignes ; cela nous
apprend quelque chose d'important à propos de la syntaxe Ruby : Ruby ne se soucie pas des retours à la ligne.60
La raison pour laquelle j'ai choisi de couper le code en deux est que je préfère garder des lignes de code
inférieures à 80 signes pour une question de lisibilité.61 À l'heure actuelle, je trouve le formatage de ce code
plutôt laid ; la section 3.5 comprend un exercice de restructuration qui lui refera une beauté.62
Ajouter les nouveaux tests pour chacune de nos trois pages statiques en suivant le modèle de l'extrait 3.19 nous
fournit 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
end
Notez que la ligne render_views introduite dans l'extrait 3.15 est nécessaire pour que le test des titres
fonctionne.
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 titre
Nous allons maintenant faire en sorte que nos tests de titre réussissent, et en même temps ajouter une structure
HTML valide. Commençons avec la page d'accueil (extrait 3.21), en utilisant le même squelette HTML basique
que la page « hello » de l'extrait 3.3.
Note : avec Rails 3, le générateur de contrôleur crée un fichier de mise en forme (layout), dont nous
expliquerons 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 d'accueil avec la structure HTML complète.
app/views/pages/home.html.erb
<!DOCTYPE html>
<html>
<head>
<title>Simple App du Tutoriel Ruby on Rails | A ccueil</title>
</head>
<body>
112
<h1>Simple App</h1>
<p>
Ceci est la page d'accueil de l' Application Exemple du
<a href="http://railstutorial.org/">Ruby on R ails Tutorial</a>.
</p>
</body>
</html>
L''extrait 3.21 utilise le titre testé dans l'extrait 3.20 :
<title>Simple App du Tutoriel Ruby on Rails | Accue il</title>
Les tests pour la page d'accueil 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 l'extrait 3.22 et de
l'extrait 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 | C ontact</title>
</head>
<body>
<h1>Contact</h1>
<p>
Contact Ruby on Rails Tutorial à propos de l' Application Exemple à
<a href="http://railstutorial.org/feedback">f eedback 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 Tutori el 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 o n Rails</a>. Ceci
est l' Application 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 d'une balise ancre) :
<a href="http://railstutorial.org/">Tutoriel Ruby o n Rails</a>
Vous pouvez voir les résultats dans l'illustration 3.11.
Illustration 3.11: Une page accueil minimale pour l'Application Exemple (/pages/home ).
114
3.3.3 Variables d'instance 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 ;
• L'entiè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 à l'inté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 signe
arobase, @, dans le nom @titre indique que c'est une variable d'instance. Les variables d'instance ont un sens
plus général en Ruby (voir la section 4.2.3), mais en Rails leur rôle est principalement de lier les actions et les
vues : toute variable d'instance définie dans l'action home est automatiquement accessible dans la vue
home.html.erb , et aisni de suite pour toutes les paires action/vue.63
Nous pouvons voir comment cela fonctionne en remplaçant le titre littéral « Accueil » par le contenu de la
variable @titre dans la vue home.html.erb (extrait 3.25).
Extrait 3.25. La Vue pour la page d'accueil 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>
C'est la page d'accueil de l' Application Exemple du
<a href="http://railstutorial.org/">Tutoriel Ruby on Rails</a>.
</p>
</body>
</html>
L''extrait 3.25 est notre premier exemple de Ruby embarqué, également appelé ERb (vous savez maintenant
pourquoi les fichiers des vues HTML possèdent l'extension .html.erb ). ERb est le principal mécanisme Rails
pour inclure du contenu dynamique dans les pages web.64 Le code :
<%= @titre %>
… indique par l'utilisation de <%= ... %> que Rails devra insérer le contenu de la variable @titre , quel
qu'il puisse être. Quand nous visitons la page /pages/home , Rails exécute le corps de l'action home, qui
procè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 qu'auparavant, à 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 qu'ils
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 l' Application Exemple à la
<a href="http://railstutorial.org/feedback">p age 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 Tutori el Ruby on Rails</a>
est un projet pour faire un livre des screnncas ts pour apprendre le
développement web avec
<a href="http://rubyonrails.org/">Ruby on Rails </a>. C'est
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 layouts
Maintenant que nous avons remplacé la partie variable des titres de la page avec une variable d'instance 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 d'autres mots, toutes nos pages sont identiques en structure, incluant même le titre (à cause du code Ruby
embarqué), 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), et
que le contenu du body soit inséré sur cette page de base ? Vraiment, ce serait bien, et Rails nous y convie
gentiment en utilisant un fichier spécial appelé application.html.erb , qui doit résider dans le dossier
layouts . Pour capturer le squelette de la structure, créez le fichier application.html.erb et copiez-y le
contenu de l'extrait 3.28.
Extrait 3.28. Le layout du site de l'Application 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 l'insertion 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 l'expression 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 c'est d'être
certain qu'avec ce layout, la visite de la page /pages/home convertira le contenu du fichier
home.html.erb en HTML et l'insèrera alors à la place du <%= yield %> .
Maintenant que nous avons un layout pour le site, nous avons aussi saisi l'opportunité d'ajouter une
fonctionnalité de sécurité pour chaque page. L'extrait 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
d'attaque 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 d'introduire 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>
C'est la page d'accueil de l' Application 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 l' Application 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 app rendre le développement web
avec <a href="http://rubyonrails.org/">Ruby on Ra ils</a>. C'est l' Application 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
— c'est-à-dire que nous les avons restructurées avec succès — mais elles contiennent beaucoup moins de
duplications de code. Et, comme requis, les tests réussissent toujours.
3.4 Conclusion
Vu de l'extérieur, ce chapitre n'a pas accompli grand chose : nous l'avons commencé avec des pages statiques, et
nous le finissons avec… les pages le plus souvent statiques. Mais les apparences sont trompeuses : en
développant en termes de Contrôleurs Rails, d'Actions Rails et de Vues Rails, nous sommes maintenant en
mesure d'ajouter n'importe quel contenu dynamique à notre site. Voir comment cela joue exactement est la
tâche que se propose la suite de ce tutoriel.
Avant de poursuivre, prenez le temps de « commettre » vos changements et les fusionner avec la branche
maitresse. Dans la section 3.1.2, nous avons créé une branche Git pour le développement des pages statiques. Si
vous n'avez pas fait de dépôts à mesure que nous avancions, commencez par en faire un en indiquant que nous
avons atteint un point d'arrê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 la
section 1.3.5 :
120
$ git checkout master
$ git merge static-pages
Une fois que vous atteignez un point d'arrêt comme celui-ci, c'est 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 l'application actualisée sur Heroku :
$ rspec spec/
$ git push heroku
Notez que dans les deux cas j'ai joué rspec spec/ , juste pour m'assurer que tous les tests réussissaient
toujours. Faire jouer les tests avant de « pusher » du code ou de déployer l'application est une habitude à
cultiver.
3.5 Exercises
1. Créez une page d'aide pour l'Application Exemple. Écrivez d'abord un test pour vérifier l'existence
d'une page à l'URL /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 l'extrait 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 l'extrait 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 d'instance, nous sommes maintenant en mesure d'aligner :content avec le premier
caractère à l'intérieur de chaque parenthèse ouverte. C'est ma convention préférée pour formater du
code découpé en plusieurs lignes.
Extrait 3.32. Code pour la page d'aide proposée.
app/views/pages/help.html.erb
<h1>Aide</h1>
121
<p>
Obtenez de l'aide sur le Tutoriel Ruby on Rails s ur la
<a href="http://railstutorial.org/help">page d'ai de du Tutoriel Rails</a>.
Pour obtenir de l'aide 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.rb
require 'spec_helper'
describe 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
123
chapitre 4 Rails au goût Ruby En s'appuyant sur les exemples du chapitre 3, ce chapitre explore quelques éléments Ruby importants pour
Rails. Ruby est un vaste langage, mais heureusement le sous-ensemble nécessaire pour être productif en tant
que développeur Rails est relativement limité. Plus encore, ce sous-ensemble est différent de l'approche
habituelle de l'apprentissage de Ruby, ce pourquoi, si votre but est de construire des applications web
dynamiques, je recommande d'apprendre Rails d'abord, 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 donne
de 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-Grounded
Rubyist 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, j'y reviendrai fréquemment au cours des prochains chapitres.
4.1 Motivation
Comme nous l'avons vu dans le dernier chapitre, il est possible de développer le squelette d'une application
Rails, et même de commencer à la tester, sans aucune connaissance du langage Ruby. Nous avons fait cela en
nous 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 un
certain nombre d'ajouts au site qui vont nous permettre de nous confronter à nos limites en langage Ruby.
4.1.1 Un « helper » pour le titre
Quand nous avons vu la dernière fois notre application, nous avions juste actualisé les pages statiques
classiques 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 l'application 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 % >
… s'appuie 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 ? C'est une bonne convention graphique
d'avoir 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 l'une des actions, en l'absence de la variable @titre le
titre s'affiche comme suit :
Simple App du Tutoriel Ruby on Rails |
En d'autres 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 n'est 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
end
Ce code peut sembler très simple aux yeux d'un développeur Rails expérimenté, mais il est en réalité plein de
nouvelles 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 ce
chapitre.
Maintenant que nous avons un helper, nous pouvons l'utiliser 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 l'extrait 4.3. Notez en particulier le basculement de la variable d'instance @titre
vers la méthode de l'helper titre (sans le signe arobase, @). En utilisant Autotest ou rspec spec/ , vous
devriez pouvoir vérifier que les tests du chapitre 3 réussissent toujours.
Extrait 4.3. Le layout du site de l'application 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 :
l'inclusion 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-<vers ion 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, puisqu'il change à chaque
actualisation de Blueprint. Enfin, assurez-vous de ne pas taper :
$ cp -r ~/Downloads/joshuaclayton-blueprint-css-<vers ion 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 l'application 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 l'helper Rails intégré stylesheet_link_tag , détaillé dans l'API Rails.67 La première ligne
stylesheet_link_tag inclut la feuille de style blueprint/screen.css pour l'écran (par exemple le
moniteur de votre ordinateur), et la seconde inclut blueprint/print.css pour l'impression (l'helper ajoute
automatiquement l'extension .css au nom de fichier s'il est absent, donc je l'ai omis pour la brièveté). Comme
pour l'helper de titre, pour un développeur Rails expérimenté ces lignes semblent vraiment simples, mais elles
contiennent au moins quatre nouvelles idées : les méthodes Rails intégrées, l'invocation de méthode avec
l'omission des parenthèses, les symboles et les tableaux hash. Ce chapitre couvre aussi ces nouvelles idées (nous
verrons le code HTML produit par ces feuilles de styles dans l'extrait 4.6 de la section 4.3.4).
En passant, notons que les nouvelles feuilles de styles ne changent pas l'aspect de notre site, c'est seulement un
bon début (illustration 4.1). Nous travaillerons sur cette fondation au chapitre 5.68
128
Illustration 4.1: La page d'accueil 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 à
l'environnement 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 l'un des trois environnements
séparés définis par Rails (les deux autres environnements sont l'environnement de test et l'environnement 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 d'apprentissage, et vous devrez toujours vous sentir libre de l'explorer — 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 l'API 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 l'entrée de l'API 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
l'auteur 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.
D'ordinaire, vous n'incluez pas de commentaires dans les sessions de console, mais dans le but de cet
apprentissage, j'en incluerai dans ce qui suit, comme cela :
$ rails console
>> 17 + 42 # Addition d'entiers
=> 59
Si vous poursuivez cette section en tapant ou en copiant-collant les commandes dans votre console, vous pouvez
bien sûr omettre ces commentaires ; la console les ignorera de toute façon.
4.2.2 Chaines de caractères
la chaine de caractères (Strings) est probablement la structure de données la plus importante des applications
web, puisque les pages web consistent en fin de compte en des chaines de texte envoyées par le serveur aux
navigateurs. Commençons à explorer ces chaines de caractères avec la console, cette fois en commençant avec
rails c (si vous avez déjà quitté votre console. NdT), qui est un raccourci pour la commande rails
console :
$ 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 de
l'évaluation de chaque ligne, qui dans le cas d'une chaine littérale est juste la chaine elle-même.
Nous pouvons aussi concaténer des chaines avec l'opérateur + :
>> "foo" + "bar" # Concaténation de chaines
=> "foobar"
Ici, l'évaluation de "foo" plus "bar" donne la chaine "foobar" .71
Une autre façon de contruire des chaines de caractère se fait via l'interpolation 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 l'avons interpolée à l'inté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} " # L'interpolation é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 : l'expression 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é).
L'utilisation 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 l igne)
foo=> nil
131
>> print "foo \n " # = puts "foo"
foo
=> nil
Chaines de caractères « apostrophées »
Tous les exemples précédents utilisaient les chaines entre guillemets, mais Ruby supporte aussi les chaines
entre 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 à l'intérieur d'une chaine
apostrophe simple :
>> '#{foo} bar' # Les chaines apostrophe empêchent l'interpolation
=> "\#{foo} bar"
Notez comment la console retourne des valeurs en utilisant des chaines entre guillemets (tel que « # » ci-
dessus), ce qui demande d'utiliser le caractère spécial d'échappement (backslash, « \ »).
Si les chaines entre guillemets peuvent faire tout ce que font les chaines entre apostrophes, et peuvent
interpoler, quelle est l'utilité des chaines apostrophiées ? Elles sont souvent utiles parce qu'elles sont vraiment
litté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 vous
voulez qu'une 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ère
d'échappement lui-même ; à l'intérieur d'une chaine entre guillemets, un échappement littéral doit être
représenté par deux échappements. Pour un court exemple comme celui-là, il n'y a pas un gain énorme, mais s'il
y a beaucoup de choses à échapper ça peut être d'une 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 n'a 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 d'exemples.
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 d'interrogation à la fin de la méthode empty? . C'est 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 n'est pas vide"
>> end
=> "La chaine n'est 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 "L'une des chaines est vide" if x . empty? || y . empty?
"L'une des chaines est vide"
=> nil
>> puts "x n'est pas vide" if ! x. empty?
"x n'est 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 aussi
aux méthodes. On en trouve un exemple avec la méthode to_s qui peut dans l'absolu convertir tout objet en
chaine de caractères :
>> nil . to_s
=> ""
Cela apparait certainement comme une chaine vide, comme nous pouvons le vérifier en chainant les messages
que nous passons à nil :
>> nil . empty?
NoMethodError: You have a nil object when you didn' t expect it!
You might have expected an instance of Array.
The error occurred while evaluating nil.empty?
(
Traduction du message d'erreur :
ErreurDAbsenceDeMethode: Vous avez un objet null in attendu !
Vous attendiez peut-être une instance de tableau (A rray)
L'erreur est survenue en évaluant l'expression nil. empty?
)
>> nil . to_s . empty? # Chainage du message
=> true
Nous voyons ici que l'objet 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 à l'extrait 4.2, vous verrez que l'helper de titre (titre ) teste pour voir si la variable @titre
est nil en utilisant la méthode nil? . C'est le signe qu'il y a quelque chose de spécial à propos des variables
d'instance (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 n'avons pas défini la variable titre .
NameError: undefined local variable or method `titr e'
>> @titre # Une variable d'instance dans la console
=> nil
>> puts "Il n'y a pas de telle variable d'instance." if @titre . nil?
Il n'y a pas de telle variable d'instance.
=> 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 d'instance qui n'existe pas ; au lieu de ça, les variables d'instance
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 "" .
135
Le dernier exemple montre aussi un usage alternatif du mot-clé if : Ruby vous permet d'écrire des blocs
qui ne sont évalués que si l'expression suivant le if est vraie. Il existe un mot-clé complémentaire, unless
(sauf), qui fonctionne de la même façon, mais à l'opposé :
>> string = "foobar"
>> puts "La chaine ' #{string} ' n'est pas vide." unless string . empty?
La chaine string 'foobar' n'est pas vide.
=> nil
Il est important de noter que l'objet nil est spécial, dans le sens où c'est le seul objet Ruby qui est faux dans un
contexte booléen, à part bien sûr l'objet false lui-même :
>> if nil
>> true
>> else
>> false # nil est faux
>> end
=> false
En particulier, tous les autres objets Ruby sont true, même 0 :
>> if 0
>> true # 0 (et toute autre valeur que nil et false) est vr ai
>> else
>> false
>> end
=> true
4.2.4 Définition des méthodes
La console nous permet de définir des méthodes de la même façon que nous le faisions avec l'action home de
l'extrait 3.6 ou l'helper de titre de l'extrait 4.2 (définir des méthodes dans la console est un peu lourd, et
d'ordinaire on utilise plutôt un fichier, mais c'est pratique ici pour les besoins de la démonstration). Par
exemple, définissons une fonction string_message qui prend un seul argument et retourne un message en
fonction de la nullité ou non de l'argument :
>> def string_message(string)
>> if string . empty?
>> "C'est une chaine vide !"
136
>> else
>> "La chaine n'est pas vide."
>> end
>> end
=> nil
>> puts string_message( "" )
C'est une chaine vide !
>> puts string_message( "foobar" )
La chaine n'est pas vide.
Notez que les fonctions Ruby ont un retour implicite, ce qui signifie qu'elles retournent la valeur de la dernière
expression évaluée — dans ce cas, l'un des deux messages, en fonction du fait que l'argument 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 "C'est une chaine vide !" if string . empty?
>> return "La chaine n'est 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 n'est 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 à l'helper titre
Nous sommes maintenant en mesure de comprendre l'helper titre de l'extrait 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
end
Ces éléments — fonction, définition, assignement de variable, tests booléens, contrôle de flux et extrapolation
de chaine — se combinent pour créer une méthode d'helper compacte à utiliser dans notre layout. L'élément
final 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, mais
dans notre cas Rails gère l'inclusion automatiquement pour nous. Le résultat est que la méthode titre est
automagiquement (sic) accessible à toutes nos vues.
4.3 Autres structures de données
Bien que les applications web produisent en dernier lieu des chaines de caractères, la fabrication de ces chaines
peut requérir l'utilisation de tout autant d'autres structures de données. Dans cette section, nous allons étudier
quelques structures de données Ruby importante pour la conception d'applications Rails.
4.3.1 Tableaux (Arrays) et rangs (ranges)
Un tableau (array) est juste une liste d'éléments dans un ordre particulier. Nous n'avons pas encore abordé les
tableaux dans ce Tutoriel Rails, mais les comprendre offre de bonnes bases pour comprendre les tables de
hachage (hashes) (section 4.3.3) et les aspects de la modélisation des données de Rails (tels que l'association
has_many vue à la section 2.3.3 et couverte plus largement à la section 11.1.2).
Jusqu'ici nous avons passé beaucoup de temps à comprendre les chaines de caractères, et il existe une façon
naturelle 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 en
la scindant selon les espaces blancs (whitespace), mais vous pouvez scinder tout aussi bien selon n'importe
quelle 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 l'index 0, le deuxième
l'index 1, et ainsi de suite :
>> a = [42 , 8, 17]
=> [42, 8, 17]
>> a[0] # Ruby utilise les crochets pour accéder aux élémen ts.
=> 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 l'accès à certains éléments particuliers :75
>> a # Pour se rappeler ce qu'est '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 l'opé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
=> false
En addition à length (aperçu dans la première ligne ci-dessus), les tableaux répondent à une foule d'autres
mé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 l'opé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 aux
tableaux dans d'autres langages, les tableaux Ruby peuvent contenir des éléments de types différents (dans ce
cas, 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 à l'aide 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 d'ajouter des
parenthèses pour lui appliquer la méthode.
Les Rangs sont utiles pour tirer des éléments d'un 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..5
Ce code appelle la méthode each (chaque) sur le rang (1..5) et lui passe le bloc { |i| puts 2 * i } . La
barre verticale autour du nom de la variable dans |i| est la syntaxe Ruby pour une variable de bloc, et c'est à la
méthode de savoir ce qu'elle doit faire avec le bloc , dans ce cas précis, la méthode each du rang peut traiter le
bloc avec une simple variable locale, que nous avons appelée i , et elle exécute simplement le bloc pour chaque
valeur de ce rang (elle exécute puts 2 * i avec i = 1 — premier terme du rang —, elle exécute puts 2 * i
avec i = 2 , etc. jusqu'à i = 5 — dernier élément du rang —).
Les accolades sont une façon d'indiquer un bloc, mais il existe une autre façon :
>> ( 1. . 5) . each do | i |
?> puts 2 * i
>> end
2
4
6
8
10
=> 1..5
Les blocs peuvent comprendre plus d'une ligne, et en comprennent souvent plus d'une. Dans le Tutoriel Rails
nous suivrons la convention courante d'utiliser les accolades seulement pour les blocs d'une courte ligne et la
syntaxe 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 j'ai utilisé number au lieu de i juste pour souligner que n'importe quel nom de variable peut convenir.
Sans avoir de substantielles connaissances en programmation, il n'y pas de raccourci pour comprendre les
blocs ; vous avez juste besoin d'en rencontrer beaucoup, et à la longue vous vous y ferez.76 Heureusement, les
humains sont assez bons pour faire des généralisations à partir d'exemples concrets ; en voilà d'autres, 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 d'un tableau ou d'un rang.
En passant, nous sommes maintenant en mesure de comprendre la ligne de Ruby que j'avais 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 l'alphabet
=> ["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 symboles
Les 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 cette
raison). Au lieu de ça, les indices d'une table de hachage, ou keys (clés), peuvent être presque n'importe quel
objet. 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" ] # L'accè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 d'accolades
sans clé-valeurs — c'est-à-dire {} — est une table vide. Il est important de noter que les accolades pour les
tables de hachage n'ont rien à voir avec les accolades pour les blocs (oui, ça peut être source de confusion). Bien
que les tables de hachage ressemblent aux tableaux, une différence importante est que les tables ne garantissent
pas, en règle générale, de garder leurs éléments dans un ordre particulier.77 Si l'ordre importe, utilisez un
tableau.
Plutôt que de définir les tables de hachage item après item en utilisant les crochets, il est facile d'utiliser leur
représentation littérale :
144
>> user = { "first_name" => "Michael" , "last_name" => "Hartl" }
=> {"last_name"=>"Hartl", "first_name"=>"Michael"}
Ici j'ai 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
l'aspect des extra espaces, et la convention a pris).
Jusqu'ici nous avons utilisé des chaines comme clé de hachage, mais en Rails il est bien plus courant d'utiliser
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:Sy mbol
(Traduction :
ErreurAucuneMethode : méthode `split' indéfinie pou r le symbole :nom)
>> "foobar" . reverse
=> "raboof"
>> :foobar . reverse
NoMethodError: undefined method `reverse' for :foob ar:Symbol
(Traduction :
ErreurAucuneMethode : méthode `reverse' indéfinie p our 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 => "[email protected]" }
=> {:nom=>"Michael Hartl", :email=>"michael@example .com"}
>> personne [ :nom ] # Accès à la valeur correspondant à :nom.
=> "Michael Hartl"
>> personne [ :password ] # Accès à la valeur d'une clé indéfinie.
=> nil
145
Nous voyons ici, dans le dernier exemple, que la valeur pour une clé indéfinie est simplement la valeur
nil (nul).
Les valeurs d'une table de hachage peuvent virtuellement être n'importe quoi, même d'autres tables de hachage,
comme on peut le voir dans l'extrait 4.5.
Extrait 4.5. Tables imbriquées.
>> params = {} # Défini une table appelée 'params' ('paramètres').
=> {}
>> params [ :user ] = { :nom => "Michael Hartl" , :email => "[email protected]" }
=> {:nom=>"Michael Hartl", :email=>"mhartl@example. com"}
>> params
=> {:user=>{:nom=>"Michael Hartl", :email=>"mhartl@ example.com"}}
>> params [ :user ][ :email ]
=> "[email protected]"
Ces sortes de tables de table, ou tables imbriquées, sont intensivement utilisées par Rails, comme nous le
verrons 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 les
tables de hachage en prend deux, une variable pour la clé et une variable pour la valeur. Ainsi, la méthode each
pour les tables de hachage fait son itération à travers la table une paire clé-valeur à la fois.
L'exemple précédent utilise la méthode utile inspect , qui retourne une chaine avec une représentation
littérale de l'objet qui l'appelle :
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 qu'il 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 l'extrait 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 à l'appel de la fonc tion.
stylesheet_link_tag( 'blueprint/screen' , :media => 'screen' )
stylesheet_link_tag 'blueprint/screen' , :media => 'screen'
147
Secondo, l'argument :media ressemble à une table de hachage, mais où sont les accolades ? Quand une
table de hachage est le dernier argument d'une fonction, ses accolades sont optionnelles ; ces deux lignes sont
donc équivalentes :
# Accolades optionnelles sur l'argument final.
stylesheet_link_tag 'blueprint/screen' , { :media => 'screen' }
stylesheet_link_tag 'blueprint/screen' , :media => 'screen'
Donc, 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 le
chemin d'accès (relatif) au fichier de la feuille de styles CSS, et une table de hachage, indiquant le type de média
concerné (’screen’ pour l'écran de l'ordinateur 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 page
dans 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 l'inclusion d'une CSS.
<link href="/stylesheets/blueprint/screen.css" medi a="screen" rel="stylesheet"
type="text/css" />
<link href="/stylesheets/blueprint/print.css" media ="print" rel="stylesheet"
type="text/css" />
4.4 Classes Ruby
Nous avons dit auparavant que tout dans Ruby était objet, et dans cette section nous serons amenés à définir les
nôtres. Ruby, comme beaucoup de langages orienté-objet, utilise les classes pour organiser les méthodes ; ces
classes 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 Constructeurs
Nous avons vu beaucoup d'exemples d'utilisation des classes pour instancier des objets, mais nous devons
encore le faire de façon explicite. Par exemple, nous avons instancié une chaine de caractère en utilisant les
guillemets, 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 d'utiliser un constructeur littéral, nous pouvons utiliser le contructeur nommé équivalent, ce qui
implique d'appeler 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
C'est équivalent au constructeur littéral, mais c'est 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 d'accès à la valeur d'une clé :foo inexistante.
=> nil
>> h = Hash . new( 0) # pour que les clés inexistantes retournent 0 plutô t que nil.
=> {}
>> h[ :foo ]
149
=> 0
4.4.2 Héritage de classe
Quand on apprend les classes, il est utile de trouver la hiérarchie de classe en utilisant la méthode
superclass :
>> 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
=> nil
Dans le diagramme de cette héritage hiérarchique dans lillustration 4.2, nous voyons que la superclasse de
String (Chaine) est la classe Object et la superclasse de la classe Object est la classe BasicObject , mais
BasicObject ne possède pas de superclasse. Ce modèle est vrai pour chaque objet Ruby : remontez la
hiérarchie des classes assez loin, et chaque classe Ruby héritera en fin de compte de la classe BasicObject ,
qui n'a pas de superclasse elle-même. C'est l'explication 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 n'y 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 un
palindrome (s'il s'épelle de la même façon à l'endroit et à l'envers) :
150
>> class Mot
>> def palindrome?(string)
>> string == string . reverse
>> end
>> end
=> nil
Nous pouvons l'utiliser 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 ; c'est à dessein. C'est étrange de créer une nouvelle
classe juste pour créer une méthode qui prend une chaine de caractère comme argument. Puisqu'un 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 l'extrait 4.7 (vous devriez quitter la console et la relancer pour effacer l'ancienne 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 d'héritage Ruby (discuté brièvement à la section 3.1.2), qui s'assure 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.
=> 5
Puisque la classe Mot hérite de la classe String , nous pouvons utiliser la console pour voir la hiérarchie des
classes explicitement :
>> s. class
=> Mot
>> s. class . superclass
=> String
>> s. class . superclass . superclass
=> Object
Cette hiérarchie est présentée dans l'illustration 4.3.
Illustration 4.3: La hiérarchie de l'héritage pour une classe (non-intégré) Mot de l'extrait 4.7.
Dans l'extrait 4.7, notez que vérifier que le mot est son propre inverse implique d'avoir accès au mot à l'intérieur
de la classe Mot . Ruby nous permet de le faire en utilisant le mot-clé self : à l'intérieur de la classe Mot , self
est l'objet 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 d'origine
Bien que l'héritage soit une idée puissante, dans le cas des palindromes il peut être plus naturel d'ajouter la
méthode palindrome? à la classe String elle-même, ce qui, entre autres choses, nous permettrait d'appeler
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éfi nie 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 d'y 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 d'ajouter 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 d'un 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?
=> true
Nous voyons qu'une chaine constituée d'espaces n'est pas vide (empty), mais elle est vierge (blank). Notez aussi
que nil est vierge ; puisque nil n'est pas une chaine de caractères, c'est 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 l'avons vu au
début de cette section) un Objet . Nous verrons d'autres exemples d'addition de Rails aux classes Ruby à la
section 9.3.2.
4.4.4 Une classe contrôleur
Tous ces propos sur les classes et l'héritage peut avoir ravivé quelque souvenir, puisque nous les avons déjà vu
auparavant, 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
end
Vous êtes maintenant en mesure d'apprécier, au moins vaguement, ce que ce code signifie : PagesController
est une classe qui hérite de la classe ApplicationController , et arrive toute équipée des méthodes home
(accueil), contact , et about (à propos), chacune d'elle définissant une variable d'instance @titre (titre).
Puisque chaque session de console Rails charge l'environnement local Rails, nous pouvons même créer un
contrô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 l'illustration 4.4.
Illustration 4.4: La hiérarchir d'héritage du contrôleur Pages.
Nous pouvons même appeler les actions du contrôleur à l'intérieur de la console, tout comme des méthodes :
155
>> controller . home
=> "Accueil"
Cette valeur de retour "Accueil" vient de l'assignement @titre = "Accueil" dans l'action home.
Mais attendez — les actions n'ont pas de valeurs de retour, du moins pas celles qui nous intéressent. Pour ce qui
est de l'action home, comme nous l'avons vu au chapitre 3, sa fonction est de rendre une page web. Et je suis
certain de ne pas me souvenir avoir jamais appelé PagesController.new quelque part. Que se passe-t-il
donc ?…
Ce qui se passe, c'est que Rails est écrit en Ruby, mais que Rails n'est pas Ruby. Certaines classes Rails sont
utilisées comme des objets Ruby ordinaires, mais d'autres sont conçues pour les capacités magiques de Rails.
Rails est sui generis, et devrait être étudié et compris indépendamment de Ruby. C'est pourquoi, si votre intérêt
principal est de programmer des applications web, je vous recommande d'étudier Rails d'abord, puis
d'apprendre Ruby avant de revenir à Rails.
4.4.5 La classe utilisateur
Nous terminons notre tour de Ruby avec une classe de notre propre cru, une classe utilisateur User qui anticipe
le modèle utilisateur qui sera utilisé au chapitre 6.
Jusqu'ici nous avons entré les définitions des classes à la console, mais cela devient rapidement fatigant ; nous
allons plutôt créer un fichier example_user.rb dans notre dossier racine Rails et allons le remplir avec le
code de l'extrait 4.8 (souvenez-vous de la section 1.1.3 : la racine de Rails est le dossier racine de votre
application ; par exemple, la racine Rails de ma propre application exemple est
/Users/mhartl/rails_projects/sample_app ).
Extrait 4.8. Code pour un exemple d'utilisateur.
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 d'un coup ici, donc reprenons ce code pas à pas. La première ligne :
attr_accessor :nom , :email
… crée un accesseur d'attribut (attribute accessor) pour le nom de l'utilisateur et son adresse email. Cela crée
les méthodes «getter» (« obteneur ») et «setter» (« assigneur ») qui nous permettent comme leur nom
l'indique d'obtenir (get) et d'assigner (set) les variables d'instance @nom et @email .
La première méthode, initialize , est spéciale en Ruby : c'est 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 s'il n'y 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 l'adresse email en utilisant
l'interpolation de chaine (section 4.2.2) :
def formatted_email
" #{@nom} < #{@email} >"
end
Lançons la console, appelons (require ) le code de l'exemple d'utilisateur et essayons notre classe User ainsi
définie :
>> require './example_user' # C'est 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 d'utilisateur" # Assigne un nom non nul
=> "Exemple d'utilisateur"
>> example . email = "[email protected]" # et une adresse email non nulle
=> "[email protected]"
>> example . formatted_email
=> "Exemple d'utilisateur <[email protected]>"
Ici le ’.’ signifie en Unix « dossier courant », et ’./example_user’ dit à Ruby de chercher un fichier
d'exemple d'utilisateur dans ce dossier courant. Le code suivant crée un exemple d'utilisateur vide et définit le
nom et l'adresse mail en assignant directement les attributs respectifs (l'assignement « depuis l'extérieur » est
rendu possible par la ligne attr_accessor de l'extrait 4.8). Quand nous écrivons :
example . name = "Exemple d'utilisateur"
… Ruby assigne (set) la valeur Exemple d'utilisateur à la variable @nom (de même pour l'attribut email ),
que nous utilisons alors pour la méthode formatted_email .
Souvenez-vous d'après la section 4.3.4 que nous pouvons omettre les accolades pour une table de hachage en
argument final d'une fonction, nous pouvons donc créer une autre utilisateur avec des valeurs prédéfinies en
passant une table de hachage à la méthode initialize :
>> user = User . new( :nom => "Michael Hartl" , :email => "[email protected]" )
=> #<User:0x225167c @email="[email protected]", @n om="Michael Hartl">
>> user . formatted_email
=> "Michael Hartl <[email protected]>"
Nous verrons en abordant le chapitre 8 qu'initialiser des objets en utilisant un attribut de type table de hachage
est courant pour les applications Rails.
4.5 Exercises
1. En utilisant l'extrait 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 d'une chaine de
caractère donnée.
2. En vous appuyant sur l'extrait 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 l'API Ruby et consultez la méthode de hachage (Hash) merge
(fusionner).
Extrait 4.9. Squelette d'une fonction chaine shuffle .
>> def string_shuffle(s)
>> s . split( '' ) . ?.?
>> end
=> nil
>> string_shuffle( "foobar" )
Extrait 4.10. Squelette d'une méthode shuffle attachée à la classe String .
>> class String
>> def shuffle
>> self . split( '' ) . ?.?
>> end
>> end
=> nil
>> "foobar" . shuffle
159
Chapitre 5 Poursuivre la mise en page Dans 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 de
notre 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 d'intégration. Nous terminerons en passant une étape importante :
permettre à nos utilisateurs de s'inscrire sur notre site.
5.1 Ajout de structure
Ce Tutoriel Rails est un livre sur le développement web, pas sur le design web, mais ce serait un peu déprimant
de travailler sur un site qui ressemblerait à une pouillerie, donc dans cette section nous allons structurer un peu
notre mise en page et lui donner un minimum de style avec les CSS. Nous donnerons aussi du style à notre
code, pour ainsi dire, en utilisant les partiels (partials) pour mieux organiser le layout lorsqu'il deviendra trop
fouilli.
Quand on construit des applications web, il est souvent utile d'avoir le plus tôt possible un aperçu général de
l'interface de l'utilisateur. Tout au long de la suite de ce libre, j'ajouterai donc des maquettes (appelées aussi
wireframes dans le contexte du web), qui sont des croquis de ce à quoi pourra ressembler l'application.84 Dans
ce chapitre, nous développerons principalement les pages statiques introduites à la section 3.1, en incluant un
logo du site, une entête de navigation et un pied de page. Une maquette pour la plus importante de ces pages, la
page d'accueil, est présentée dans l'illustration 5.1 (vous pouvez voir le résultat final dans l'illustration 5.8. Vous
noterez qu'elle 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, puisqu'une maquette n'a pas vocation à être exacte).
160
Illustration 5.1: Une maquette (anglaise) de la page Accueil de l'application exemple.
Comme d'habitude, 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 c'est 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 l'extrait 4.3, nous venions
juste d'ajouter les feuilles de styles Blueprint en utilisant l'helper Rail stylesheet_link_tag . Il est temps
d'ajouter 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 l'extrait 5.1 ; les explications des différentes parties suit l'extrait. Si vous préférez ne
pas remettre à plus tard votre plaisir, vous pouvez voir le résultat dans l'illustration 5.2 (note : je vous l'accorde,
ça n'est 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/html 5.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 à la
section 3.1, Rails 3 utilise HTML5 par défaut (comme le doctype <!DOCTYPE html> l'indique) ; puisque le
standard HTML5 est récent, certains navigateurs (spécialement Internet Explorer) ne le supportent pas encore
complè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/tr unk/html5.js"></script>
<![endif]-->
La syntaxe quelque peu bizarre :
<!--[if lt IE 9]>
… n'inclut les lignes entre les balises <!-- et --> que si la version d'Internet Explorer (IE) est inférieure à la
version 9 (if lt IE 9 ). La syntaxe bizarre [if lt IE 9] n'est pas une partie de Rails ; C'est en réalité un
commentaire conditionnel supporté par les navigateurs IE pour ce genre de situation. C'est une bonne chose,
aussi, parce que cela signifie que nous pouvons inclure la feuille de style supplémentaire seulement pour les
versions d'IE 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 l'extrait 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 n'existe 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 n'existe 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 d'information). Viennent ensuite les éléments header et section ; le header
(entête) contient le logo de l'application (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>
163
La balise div , en HTML, est une division générique ; elle sert simplement à part diviser le document en
parties distinctes. Dans le vieux style HTML, les tables div sont utilisés pour presque toutes les divisions, mais
HTML5 ajoute les éléments header , nav et section pour les divisions communes à beaucoup d'applications.
On peut assigner des classes (class ) et identifiant (id ) à tous les éléments HTML, dont les divs et les
nouveaux é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 être
utilisé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 d'options, dans ce cas
définissant les attribuets alt et class de la balise image en utilisant les symboles :alt et :class . Pour
rendre cela plus clair, regardons ce que cette balise produit en HTML :86
<img alt="Application Exemple" class="round" src="/ images/logo.png" />
L'attribut alt définit ce qui sera affiché si l'image 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 d'options comme celle-ci, nous offrant la
flexibilité d'ajouter des options HTML arbitraire sans jamais quitter Rails). Vous pouvez voir le résultat dans
l'illustration 5.2 ; nous ajouterons l'image du logo à la fin de cette section.
Le deuxième élément à l'intérieur de l'header 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 d'items 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 l'helper Rails link_to pour créer des liens (que vous avons créés directement avec la balise
ancre a à la section 3.3.2) ; le premier argument est le texte du lien et le second est l'URL du lien. Nous
définirons les URLs avec des noms de route à la section 5.2.3, mais pour le moment nous utilisons l'URL 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
d'accueil. En anticipant l'ajout d'utilisateurs à notre site au chapitre 8, ajoutons un lien d'inscription à la vue
home.html.erb (extrait 5.2).
Extrait 5.2. Une page d'accueil avec un lien d'inscription.
app/views/pages/home.html.erb
<h1>Application Exemple</h1>
<p>
C'est la page d'accuile pour l'application exempl e du
<a href="http://railstutorial.org/">Tutoriel Ruby on Rails</a>.
</p>
<%= link_to "S'inscrire !" , '#' , :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">S'inscrire !</a>
Notez encore le thème désormais récurrent de la table d'options, 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">
C'est pratique pour le cas courant d'un élément avec deux styles différents.
165
Nous 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éments
HTML sensibles aux id et class , ce qui nous met en bonne position pour mettre notre site en forme grâce aux
CSS.
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 l'application exemple à l'adresse :
http://railstutorial.org/images/sample_app/logo.png
Déposez le logo dans le dossier public/images pour que Rails puisse le trouver. Le résultat apparait dans
l'illustration 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, c'est-à-dire qu'ils ont une
signification en anglais qui transcende la structure de la page. Par exemple, au lieu d'avoir à é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 l'extrait 5.3 (il n'y a que quelques règles dans l'extrait 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, c'est-à-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 l'illustration 5.4. Il y a beaucoup de CSS ici, mais il a une forme
consistante. 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 l'image (img ) à l'inté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 qu'un 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 de
l'extrait 5.4. Le résultat apparait dans l'illustration 5.5 (dans certains des exemples de code du livre, incluant
l'extrait 5.4, j'utilise trois points alignés verticalement pour indiquer du code omis. Quand vous reproduisez ce
code à 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 à l'intérieuur d'une balise nav , nav ul li stylise une balise li à l'intérieur
d'une balise ul à l'intérieur d'une 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 d'inscription un peu moins évident (bien que nous
n'en ayons rien à faire pour notre application exemple, sur un site réel il est naturellement assez important de
rendre le lien d'inscription très proéminent — et pourquoi donc ?… NdT). L'extrait 5.5 montre le code CSS pour
rendre gros, vert et cliquable le lien d'inscription (pour qu'un clic n'importe où dans la boite puisse suivre ce
lien).
Extrait 5.5. CSS pour rendre le bouton d'inscription 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 d'habitude, « commentez » une ligne et rechargez la page si vous voulez
voir à quoi elles sert. Le résultat final est un lien d'inscription difficile à rater (illustration 5.6).
Illustration 5.6: La page Accueil (/pages/home ) avec un bouton d'inscription 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 l'extrait 5.6,
pour obtenir le résultat de l'illustration 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 d'obtenir 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.
173
5.1.3 Partiels
Bien que le layout de l'extrait 5.1 remplisse son role, il devient un peu encombré de code ; il y a plusieurs lignes
d'inclusion de fichiers CSS et même plus de lignes d'header pour ce qui n'est logiquement que deux idées. Nous
pouvons ranger ces sections en utilisant la facilité de Rails qui s'appelle les partiels (partials). Jetons d'abord un
œil à l'aspect 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 l'entê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 l'extrait 5.7, nous avons remplacé les lignes de feuilles de styles par un simple appel à un helper Rails
appellé render :
<%= render 'layouts/stylesheets' %>
L'effet de cette ligne est de trouver un fichier appelé app/views/layouts/_stylesheets.html.erb ,
d'évaluer son contenu et d'en insérer le contenu dans la vue.91 (Rappelez-vous que <%= ... %> est la
syntaxe du « Ruby embarqué » nécessaire pour évaluer une expression Ruby et insérer le résultat à l'intérieur
du template.) Remarquez le tiret bas au début du nom du fichier _stylesheets.html.erb ; ce tiret bas est la
convention universelle pour nommer les partiels, et parmi d'autres choses rend possible d'identifier d'un coup
d'œ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, c'est juste les quatre ligne incluse de l'extrait 5.1 ; le résultat apparait dans l'extrait 5.8.
(Techniquement, le HTML shiv inclut Javascript, pas CSS. En d'autres 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/tr unk/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 l'entête dans un partiel montré dans l'extrait 5.9 et
l'insérer dans le layout avec un autre appel à render :
<%= render 'layouts/header' %>
Extrait 5.9. Un partiel avec l'entê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 l'entête.
Dès à présent vous devez pouvoir devnier que nous l'appelerons _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/">T utoriel Rails</a></li>
</ul>
</nav>
</footer>
Comme pour l'entê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 est
nouvelle en HTML5.)
Nous pouvons rendre le partial du pied de page dans le layout en suivant le même modèle que les partiels des
feuilles de styles et de l'entê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 l'illustration 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 l'entête. Cette convention d'avoir 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 page
Maintenant 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 n'est pas la façon de faire Rails. Primo, ce serait bien si l'URL pour la page « À Propos » était
/about plutôt que /pages/about ; plus encore, Rails utilise conventionnellement des routes
nommées qui permet d'utiliser 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 n'avons qu'à changer
la 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 URLs
et les routes. Nous les implémenterons toutes sauf la dernière d'ici la fin de ce chapitre (la dernière sera
implé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 d'inté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 d'inté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 n'est qu'un début.
Nous commençons par générer un test d'intégration pour les liens du layout de l'application 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 d'inté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 d'inté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
end
Dans cette section, nous voulons tester des URLs comme « / » et « /about », mais nous ne pouvons pas
obtenir (get ) ces URLs à l'intérieur du test simple de contrôleur — les tests de contrôleur connaissent
seulement les URLs définis pour ce contrôleur précis. En contraste, les test d'intégration ne sont pas limités par
de telles restrictions, puisqu'ils sont désignés comme tests intégrés pour l'application complète et ainsi peuvent
obtenir (get ) la page qu'ils veulent.
En suivant le modèle du spec du contrôleur Pages, nous pouvons écrire un spec d'intégration pour chacune des
pages de la table 5.1 que nous avons déjà créées, c'est-à-dire les pages « Accueil », « À Propos », « Contact » et
« Aide ». Pour être sûr que la bonne page (c'est-à-dire la bonne vue) soit rendue dans chaque cas, nous allons
tester la validité du titre en utilisant have_selector . Les définitions du test apparaissent dans l'extrait 5.13.
Extrait 5.13. Test d'inté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 n'avez pas de page d'aide, il serait temps d'en ajouter une (si vous avez résolu les exercices
de la section 3.5 du chapitre 3, vous en avez déjà une). D'abord, ajoutez l'action help au contrôleur Pages
(extrait 5.14). Ensuite, créez la vue correspondante (extrait 5.15).
Extrait 5.14. Ajout de l'action help (aide) au contrôleur Pages.
app/controllers/pages_controller.rb
class PagesController < ApplicationController
.
.
.
def help
@title = "Aide"
end
end
Extrait 5.15. Ajout d'une vue pour la page d'aide.
app/views/pages/help.html.erb
<h1>Aide</h1>
<p>
Obtenez de l'aide sur le Tutoriel Ruby on Rails T utorial sur la
<a href="http://railstutorial.org/help">page d'ai de du Tutoriel Rails</a>.
Pour obtenir de l'aide sur cette application exem ple, 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 qu'il ne
joue pas le test d'intégration. C'est à dessein, puisque les tests d'inté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 d'inté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 d'intégration avec Autotest sur Ubuntu Linux.
Autotest . add_hook :initialize do | autotest |
autotest . add_mapping(%r%^spec/(requests)/.*rb$%) do | filename, _ |
filename
end
end
Ne vous souciez pas de où vient ce code ; je ne connais pas non plus l'API Autotest. Pour ce cas précis, j'ai lancé
Google sur des termes de recherche tels que “rspec autotest integration” et j'ai trouvé ce code, et lorsque je l'ai
introduit dans mon fichier .autotest , ça a fonctionné.
5.2.2 Routes Rails
Maintenant 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 vous
jetez un coup d'œil au fichier par défaut des routes, vous verrez que c'est plutôt le bazar, mais c'est un bazar
utile — plein d'exemples de routages à décommenter. Je vous suggère de le lire, et je vous suggère aussi de jeter
un œil à l'article des Guides Rails “Rails Routing from the outside in” pour un traitement plus en profondeur
des routes. Pour le moment, cependant, nous en resterons là avec les exemples de l'extrait 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'
.
.
.
end
L'extrait 5.18 contient des routes personnalisées pour les pages contact , about (à propos), et help (aide) ;
nous prendrons soin de la page d'accueil elle-même dans extrait 5.20 (puisque nous utiliserons des routes
182
personnalisées dans l'extrait 5.18 exclusivement à partir de maintenant, nous avons saisi l'opportunité de
supprimer les routes du contrôleur Pages (get "pages/home" , etc.) vues dans l'extrait 3.17).
Si vous lisez attentivement le code de l'extrait 5.18, vous pouvez probablement imaginer ce qu'il fait ; par
exemple, nous pouvons voir que :
match '/about' , :to => 'pages#about'
… trouve le ’/about’ et le route vers l'action 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 n'est
pas évident, c'est 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 à l'URL 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 d'habitude, utilisez Autotest ou rspec spec/ pour vérifier). Il ne reste plus que le
test pour la page d'accueil.
Pour établir la route vers la page d'accueil, nous pourrions utiliser un code tel que :
match '/' , :to => 'pages#home'
Ça n'est pas nécessaire, pourtant ; Rails possède des instructions spéciales pour l'URL 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 "r oot"
183
# just remember to delete public/index.html.
# root :to => "welcome#index"
.
.
.
end
En utilisant l'extrait 5.19 comme modèle, nous arrivons à l'extrait 5.20 pour router l'URL « / » vers la page
d'accueil.
Extrait 5.20. Ajout d'une 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'
.
.
.
end
Ce code dirige l'URL racine « / » vers /pages/home , et fournit aussi l'URL aux helpers comme suit :
root_path => '/'
root_url => 'http://localhost:3000/'
Nous devrions aussi tenir compte du commentaire de l'extrait 5.19 et effacer public/index.html pour
empêcher Rails de retourner la page par défaut (illustration 1.3) quand nous visitons « / ». Vous pouvez bien
entendu simplement détruire le fichier, mais si vous utilisez Git pour le contrôle de version il existe une façon
de dire à Git qu'une destruction a été faite en même temps qu'il 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 qu'elles 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 l'entête, _header.html.erb (extrait 5.21), qui contient des liens
vers les pages d'accueil et d'aide. Pendant que nous y serons, nous suivrons une convention web courante et
lierons le logo également à la page d'accueil.
Extrait 5.21. Partiel de l'entê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 "S'identifier" , '#' %></li>
</ul>
</nav>
</header>
185
Nous n'aurons pas de route nommée pour le lien “Inscription” jusqu'au chapitre 9, donc nous le laisserons
à ’#’ pour le moment. Notez que ce code définit la variable locale logo pour la balise de l'image logo, et
ensuite la lie à la ligne suivante :
<% logo = image_tag( "logo.png" , :alt => "Application exemple" , :class => "round" ) %>
<%= link_to logo, root_path %>
C'est un peu plus propre que de tout rassembler dans une seule ligne. C'est spécialement important de noter
que le ERb pour l'assignement de variable ne contient pas de signe égal (=) ; c'est seulement <% ... %> ,
parce que nous ne voulons pas que cette ligne soit insérée dans le template (utiliser une variable locale de cette
manière est une façon de faire mais ça n'est pas la seule. Une façon encore plus propre consiste à définir un
helper logo ; voyez la section 5.5.)
L'autre endroit où on peut trouver des liens, c'est le partiel du pied de page, _footer.html.erb , qui contient
des 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/">R ails 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 n'ayons pas testé, en fait, la présence des liens dans le
layout, nos tests échoueront si les routes ne sont pas définies. Vous pouvez le vérifier en commentant les routes
de l'extrait 5.18 et en jouant votre suite de tests. Pour une méthode de testing qui s'assure en fait que le lien
conduise au bon endroit, voyez la section 5.5.
186
Illustration 5.9: La page À Propos à /about .
5.3 Inscription de l'utilisateur : 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 d'inscription, ce qui signifiera de créer un second contrôleur. C'est un premier pas important vers la
possibilité pour les utilisateurs de s'enregistrer 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 d'en 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, c'est-à-dire un
contrôleur avec la souche d'une page d'inscription pour les nouveaux utilisateurs. En suivant les conventions de
l'architecture REST préconisée par Rails, nous appellerons l'action 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_s pec.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.rb
Comme avec le contrôleur Pages, cela génère vue et specs helper dont nous n'avons pas besoin, donc
détruisons-les :
$ rm -rf spec/views
$ rm -rf spec/helpers
Le générateur de contrôleur construit deux choses : un contrôleur Users et un test par défaut, qui vérifie que
l'action new réponde bien à la requête GET (Box 3.1) ; le code est présenté dans l'extrait 5.24. Il devrait vous
sembler familier ; il suit exactement la même forme que le spec du contrôleur Pages vue précédemment à la
section 3.3.1 (extrait 3.20).
Extrait 5.24. Tester la page d'inscription.
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
end
Par construction, le contrôleur Users possède déjà sa propre action new et le template new.html.erb pour
faire réussir ce test (extrait 5.25) (pour voir la page, à l'adresse /users/new , vous devez peut-être
relancer le serveur).
Extrait 5.25. Action pour la page d'inscription du nouvel utilisateur.
188
app/controllers/users_controller.rb
class UsersController < ApplicationController
def new
end
end
Pour respecter l'esprit 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 d'ajouter render_views comme nous l'avons 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 d'inscription.
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 puisqu'il teste la vue associée à
l'action et doit donc la rendre pour pouvoir la tester.
189
Bien sûr, c'est à dessein que ce test, pour le moment, doit échouer (Rouge). Pour avoir un titre
personnalisé, nous avons besoin de créer une variable d'instance @titre comme à la section 3.3.3. Nous
pouvons ainsi obtenir le Vert avec le code de l'extrait 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
end
5.3.2 URL de l'inscription
Avec le code de la section 5.3.1, nous avons déjà une page fonctionnelle pour les nouveaux utilisateurs à
l'adresse /users/new , mais vous vous souvenez que d'après la Table 5.1 nous voulons que l'URL soit
plutôt /signup . Comme à la section 5.2, nous allons d'abord écrire un test (extrait 5.28).
Extrait 5.28. Simple test d'intégration pour le lien d'inscription utilisateur.
spec/requests/layout_links_spec.rb
require 'spec_helper'
describe "LayoutLinks" do
.
.
.
it "devrait avoir une page d'inscription à '/signup'" do
get '/signup'
response . should have_selector( 'title' , :content => "Inscription" )
end
end
Notez que c'est le même fichier que celui utilisé pour les autres liens du layout, même si la page d'inscription est
dans un contrôleur différent. Avoir la capacité d'atteindre des pages dans divers contrôleurs est l'un des
nombreux avantages de l'utilisation des tests d'intégration.
La dernière étape consiste à faire une route nommée pour les inscriptions. Nous suivrons les exemples de
l'extrait 5.18 et ajouterons une règle match ’/signup’ pour l'URL de l'inscription (extrait 5.29).
190
Extrait 5.29. Une route pour la page d'inscription.
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 à l'extrait 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 d'inscription de l'extrait 5.28 devrait réussir. Tout ce que qui nous reste à faire est d'ajouter le
propre lien au bouton sur la page d'accueil. Comme avec les autres routes, match ’/signup’ nous donne la
route nommée gives signup_path , que nous allons utiliser dans l'extrait 5.30.
Extrait 5.30. Lier le bouton à la page d'inscription.
app/views/pages/home.html.erb
<h1>Application exemple</h1>
<p>
C'est la page d'accueil pour l'application exempl e du
<a href="http://railstutorial.org/">Tutoriel Ruby on Rails</a>.
</p>
<%= link_to "S'inscrire !" , signup_path, :class => "signup_button round" %>
191
Cela fait, nous en avons fini avec les liens et les routes nommées, au moins jusqu'à ce que nous ajoutions
la route pour s'identifier (se connecter) (chapitre 9). La page d'inscription d'un nouvel utilisateur résultant de ce
travail (à l'URL /signup ) est présentée dans l'illustration 5.10.
Illustration 5.10: La nouvelle page d'inscription à l'adresse /signup (version anglaise).
5.4 Conclusion
Dans ce chapitre, nous avons arrangé la mise en forme de notre application et avons peaufiné le routage. La
suite du livre se consacrera à donner du contenu à cette Application Exemple : d'abord, en ajoutant des
utilisateurs qui peuvent s'inscrire, s'identifier et se déconnecter ; ensuite, en ajoutant des micro-messages
d'utilisateur ; et, enfin, en ajoutant des relations de suivi entre ces utilisateurs.
Si vous vous servez de Git pour le contrôle de vos versions, n'oubliez pas de « commettre » votre dépôt
(commit) et de fusionner votre branche actuelle avec la branche maitresse (et, juste par paranoïa, jouez vos
tests 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 l'extrait 5.21 par une méthode helper du même nom, pour que
le nouveau partiel ressemble à l'extrait 5.31. Utilisez le code de l'extrait 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 d'implémenter ces
tests est d'utiliser visit et click_link (cliquer le lien) à l'intérieur du test d'intégration RSpec.
Remplissez le code de l'extrait 5.33 pour vérifier que tous les liens du layout sont proprement définis.
Extrait 5.31. Partiel d'entête avec un helper logo de l'extrait 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 l'helper logo .
app/helpers/application_helper.rb
module ApplicationAideer
def logo
# Fill in.
end
# Retourne un titre basé sur la page.
193
def titre
.
.
.
end
end
Extrait 5.33. Un test pour les liens sur le layout.
spec/requests/layout_links_spec.rb
require 'spec_helper'
describe "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 "S'inscrire !"
response . should # fill in
end
end
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 l'inscription 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 qu'une 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 s'inscrire au chapitre 8. Une fois que
l'Application Exemple pourra créer de nouveaux utilisateurs, nous leur offrirons la possibilité de s'identifier (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
d'identification (login) et d'authentification. Comme vous le savez peut-être, il existe une variété de solutions
d'authentifications 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 d'authentification
En pratique, toutes les applications web, de nos jours, requièrent un système d'identification et
d'authentification. Ça n'est donc pas étonnant que la plupart des frameworks web contiennent pléthore
d'options pour implémenter de tels systèmes, Rails ne faisant pas exception à la règle. Les exemples de
systèmes d'authentification et d'authentification 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 qu'en composer une nous-mêmes ?
Il y a plusieurs raisons à ça. D'abord, il n'existe pas de réponse standard d'authentification 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 d'authentification d'un 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 d'authentification sont par ailleurs des défis de programmation riches d'enseignement ; composer
notre propre solution signifie que nous avons l'opportunité de considérer chacun des aspects l'un après l'autre,
nous conduisant à une compréhension bien plus profonde tant de l'authentification 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 d'utiliser un système d'authentification
195
clé-en-main pour votre application, vous serez en mesure d'une part de le comprendre et d'autre part de
l'adapter à vos besoins spécifiques.
En parallèle de notre modélisation des données, nous développerons une page web pour afficher les
utilisateurs, ce qui nous servira de première étape vers l'implémentation de l'architecture REST pour les
utilisateurs (discutée brièvement à la section 2.2.2). Mais nous n'irons pas profondément dans ce chapitre,
notre but final pour les pages de profil d'utilisateur étant de montrer l'avatar (le gravatar) de l'utilisateur, ses
données basiques et une liste de micro-messages, comme le montre la maquette de l'illustration 6.1.93
(l'illustration 6.1 contient notre premier exemple de texte lorem ipsum, texte qui connait une histoire fascinante
que vous devriez lire un jour). Dans ce chapitre, nous coucherons les fondations essentielles de notre page
d'affichage de l'utilisateur, et nous entrerons dans ses détails au chapitre 7.
Illustration 6.1: Une maquette approximative de la page de profil de l'utilisateur.
Comme d'habitude, si vous utilisez le contrôle de version Git, il est temps de faire une nouvelle branche pour la
modélisation des utilisateurs :
$ git checkout master
$ git checkout -b modeling-users
(La première ligne ici sert uniquement à s'assurer que vous partiez bien de la branche maitresse, pour que la
branche-sujet modeling-users parte bien de cette branche master . Vous pouvez ne pas jouer
cette 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 d'inscription au site, il ne serait pas
très judicieux d'accepter les informations d'inscription tout de suite puisque nous n'avons pour le moment
aucun endroit où les stoker. Ainsi, la première étape dans l'inscription 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) (c'est 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 L'effet 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, jusqu'au point où nous n'avons 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
d'une classe User (Utilisateur), des objets utilisateur possédant les attributs nom et email . Cette classe a servi
d'exemple 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 l'utilisateur 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 l'extrait 4.8, nous avons réalisé cela avec la
méthode Ruby attr_accessor :
class User
attr_accessor :nom , :email
.
.
.
end
197
En utilisant Rails pour modéliser les utilisateurs nous n'avons pas besoin d'identifier explicitement les
attributs. Comme noté brièvement plus haut, pour consigner les données, Rails utilise la base de données
relationnelle par défaut, qui consiste en des tables composées de rangées de données, où chaque rangée
possède des colonnes pour chaque attribut de la donnée. Par exemple, pour consigner des utilisateurs avec des
noms et des emails, nous créerons une table users (utilisateurs) avec les colonnes nom et email dont chaque
rangée correspondra à un utilisateur particulier. En nommant les colonnes de cette façon, nous laissons Active
Record deviner par lui-même les attributs de l'objet utilisateur.
Voyons comment il fonctionne (si cette discussion vous semble trop abstraite, soyez patient ; les exemples en
ligne de commande qui commencent à la section 6.1.3 et les copies d'écran du navigateur de base de données de
l'illustration 6.3 et de l'illustration 6.8 devraient rendre les choses plus claires). À la section 5.3.1, vous vous
souvenez (extrait 5.23) que nous avons créé un contrôleur pour les utilisateurs (avec une action new), en
utilisant la commande :
$ rails generate controller Users new
Il existe une commande analogue pour faire un modèle : generate model ; l'extrait 6.1 montre la commande
pour 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 sont
singulier : un contrôleur Users mais un modèle User.) En passant les paramètres optionnels nom:string et
email: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, c'est-à-dire chaine de caractère). Comparez cela avec l'inclusion des noms d'actions
dans l'extrait 3.4 et l'extrait 5.23.
L'un des résultats de la commande generate de l'extrait 6.1 est un nouveau fichier appelé une migration. Les
migrations fournissent un moyen d'altérer la structure de la base de données de façon incrémentielle, de telle
sorte que notre modèle de données peut adapter progressivement les changements requis au cours du
développement. Dans le cas du modèle User, la migration est créée automatiquement par le script de génération
de modèle ; il crée une table users (utilisateurs) avec deux colonnes, nom et email , comme le montre
l'extrait 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 (l'utilisation de self
— soi-même — dans self.up l'identifie comme une méthode de classe. Ça n'a pas d'importance 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 »). À l'intérieur du bloc, la méthode create_table utilise l'objet 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 s'enregistrent
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 l'illustration 6.2.
199
Illustration 6.2: Le modèle de données des utilisateurs produit par l'extrait 6.2.
Nous pouvons lancer la migration, opération connue sous le nom anglais « migration up » en utilisant la
commande 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 au
chapitre 2.) La première fois que db:migrate est lancé, un fichier db/development.sqlite3 est créé (ou
actualisé s'il existe déjà. NdT), qui est une base de données SQLite101. Nous pouvons voir la structure de la base
de données en utilisant l'excellent Navigateur de données SQLite pour ouvrir le fichier
db/development.sqlite3 (illustration 6.3) ; comparez-le avec le diagramme de l'illustration 6.2. Vous
pouvez noter qu'il y a une colonne dans l'illustration 6.3 qui n'a pas été prise en compte par la migration : la
colonne 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. Qu'en est-il, donc, du code self.down (soi-même.en-bas ) ? Comme vous pouvez le deviner, down
migre vers le bas (c'est-à-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 l'argument db:rollback :
$ rake db:rollback
C'est souvent utile quand vous réalisez qu'il 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 n'est 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 l'extrait 6.1 générait un fichier de migration
(extrait 6.2), et nous avons vu dans l'illustration 6.3 les résultats d'opé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 . L'extrait 6.1 a créé aussi le modèle lui-même ; la suite de cette section est dédiée à l'explicitation
de ce modèle.
201
Nous commençons par regarder le code du modèle User, qui se trouve dans le fichier user.rb à
l'inté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
end
Si vous vous souvenez de la section 4.4.2, la syntaxe class User < ActiveRecord::Base signifie que la
classe User hérite de ActiveRecord::Base , de telle sorte que le modèle User possède automatiquement les
fonctionnalités de la classe ActiveRecord::Base . Bien entendu, la connaissance de cet héritage ne sert à rien
si nous ne savons pas ce que ActiveRecord::Base contient. Nous allons justement en avoir un avant-goût
dans un moment. Mais avant de continuer, deux tâches sont à accomplir.
Annotation du modèle
Bien que cela ne soit pas strictement nécessaire, vous pourriez trouver utile d'annoter vos modèles Rails en
utilisant 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.) Nous
l'installons 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 l'extrait 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 ke y
# nom :string(255)
# email :string(255)
# created_at :datetime
# updated_at :datetime
#
class User < ActiveRecord :: Base
end
Je trouve qu'avoir 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, c'est-à-dire quels attributs peuvent être modifiés par des utilisateurs
extérieurs (tels que les utilisateurs soumettant des requêtes à l'aide de leur navigateur internet). Nous réalisons
cela avec la méthode attr_accessible (extrait 6.6). Nous verrons au chapitre 10 qu'utiliser
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 d'applications Rails.
203
Extrait 6.6. Rendre les attributs nom et email accessibles.
app/models/user.rb
class User < ActiveRecord :: Base
attr_accessible :nom , :email
end
6.1.3 Créer des objets utilisateur
Nous avons accompli un beau boulot préparatoire, et il est temps à présent d'en tirer profit et d'en apprendre
plus sur Active Record en jouant avec notre nouveau modèle User créé. Comme au chapitre 4, notre outil de
choix est la console Rails. Puisque nous ne voulons pas (encore) faire des changements dans notre base de
donné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 e xit
>>
Comme l'indique le message « Any modifications you make will be rolled back on exit » (Toute modification
que vous ferez sera oubliée en quittant le bac à sable. NdT ), en commençant dans un bac à sable, la console
annulera 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), qui
enregistre les états de bas niveau de SQL renvoyés par Active Record, comme montrés à illustration 6.4. La
façon d'obtenir cette sortie par une commande en ligne Unix est de tail er le journal :
$ tail -f log/development.log
Le drapeau -f assure que tail affichera les lines additionnelles comme elles sont écrites. Je recommande de
garder 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 n'avions accès qu'après avoir appelé le fichier de l'utilisateur exemple de l'extrait 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 l'environnement 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 d'un objet utilisateur, qui affiche les mêmes attributs que dans
l'illustration 6.3 et l'extrait 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 l'exemple de la classe User pour qu'elle utilise une table
d'initialisation pour définir les attributs de l'objet ; 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 => "[email protected]" )
=> #<User id: nil, nom: "Michael Hartl", email: "mh [email protected]",
created_at: nil, updated_at: nil>
Ici nous voyons que les attributs nom et email ont été définis comme voulu.
205
Si vous suivez le journal de développement, vous avez peut-être noté qu'aucune nouvelle ligne n'a été
affichée. Ceci parce qu'appeler User.new n'affecte pas la base de données ; cela crée simplement un nouvel
objet Ruby en mémoire. Pour sauver l'objet utilisateur dans la base de données, nous appelons la méthode
save de la variable d'instance user :
>> user . save
=> true
La 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 que
vous sauvez, vous devez voir une ligne du journal de développeent avec une commande SQL pour INSERT
INTO "users" (INSÉRER DANS "users"). Grâce aux nombreuses méthodes fournies par Active Record, nous
n'aurons jamais besoin de requête SQL dans ce livre, et j'omettrai 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 les
colonnes magiques created_at et updated_at . Voyons si notre save y a changé quelque chose :
>> user
=> #<User id: 1, nom: "Michael Hartl", email: "mhar [email protected]",
created_at: "2010-01-05 00:57:46", updated_at: "201 0-01-05 00:57:46">
Nous voyons qu'à l'attribut id a été assigné la valeur 1, et que les colonnes magiques se sont vues assigner le
temps et la date courants.102 Pour le moment, les timestamps de création et de modification sont identiques ;
nous verrons qu'ils 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 la
classe User) permettent un accès à leurs attributs en utilisant la notation par point :103
>> user . nom
=> "Michael Hartl"
>> user . email
=> "[email protected]"
>> 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 l'avons 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 => "[email protected]" )
=> #<User id: 2, nom: "A Nother", email: "another@e xample.org", created_at:
"2010-01-05 01:05:24", updated_at: "2010-01-05 01:0 5:24">
>> foo = User . create( :nom => "Foo" , :email => "[email protected]" )
=> #<User id: 3, nom: "Foo", email: "[email protected]", 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 l'objet User lui-même, que nous
pouvons optionnellement assigner à une variable (comme la variable foo dans la seconde commande ci-
dessus).
L'inverse de la méthode create (créer) est la méthode destroy (détruire) :
>> foo . destroy
=> #<User id: 3, nom: "Foo", email: "[email protected]", created_at: "2010-01-05
01:05:42", updated_at: "2010-01-05 01:05:42">
Bizarrement, destroy , comme create , retourne l'objet en question, bien que je ne puisse me souvenir d'avoir
jamais utiliser le retour d'une méthode destroy . Encore plus bizarrement peut-être, l'objet pourtant détruit
par destroy existe toujours en mémoire :
>> foo
=> #<User id: 3, nom: "Foo", email: "[email protected]", created_at: "2010-01-05
01:05:42", updated_at: "2010-01-05 01:05:42">
Comment pouvons-nous être sûrs, alors, d'avoir 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 d'apprendre à 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 l'utilisateur existant :
207
>> User . find( 1)
=> #<User id: 1, nom: "Michael Hartl", email: "mhar [email protected]",
created_at: "2010-01-05 00:57:46", updated_at: "201 0-01-05 00:57:46">
Ici nous avons passé l'identifiant de l'utilisateur à User.find ; Active Record retourne l'utilisateur
correspondant à cet attribut id .
Voyons si l'utilisateur avec un identifiant de 3 existe toujours dans la base de données :
>> User . find( 3)
ActiveRecord::RecordNotFound: Couldn't find User wi th ID=3
Puisque nous avons détruit notre troisième utilisateur à la section 6.1.3, Active Record ne peut pas le retrouver
dans la base de données. Ou plus exactement, find a provoqué une exception, qui est une manière d'indiquer
un évènement exceptionnel dans l'exécution d'un programme — dans ce cas, un id Active Record inexistant, ce
qui a conduit find à provoquer une exception ActiveRecord::RecordNotFound .104
En addition au find générique, Active Record nous permet aussi de retrouver des utilisateurs par des valeurs
d'attributs spécifiques :
>> User . find_by_email( "[email protected]" )
=> #<User id: 1, nom: "Michael Hartl", email: "mhar [email protected]",
created_at: "2010-01-05 00:57:46", updated_at: "201 0-01-05 00:57:46">
Puisque nous utiliserons l'adresse email comme nom d'utilisateur (usernames), cette façon de récupérer des
objets enregistrés sera utile pour permettre aux utilisateurs de s'identifier à notre site (chapitre 8).105
Nous allons terminer avec deux des manières classiques de retrouver des utilisateurs. D'abord, il y a la méthode
first (premier) :
>> User . first
=> #<User id: 1, nom: "Michael Hartl", email: "mhar [email protected]",
created_at: "2010-01-05 00:57:46", updated_at: "201 0-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: "mha [email protected]",
208
created_at: "2010-01-05 00:57:46", updated_at: "201 0-01-05 00:57:46">,
#<User id: 2, nom: "A Nother", email: "another@exam ple.org", created_at:
"2010-01-05 01:05:24", updated_at: "2010-01-05 01:0 5:24">]
all , qui signifie « tous » en anglais, retourne comme on peut s'y 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. D'abord,
nous pouvons assigner les attributs individuellement, comme nous l'avons fait à la section 4.4.5 :
>> user # Pour l'afficher à titre de rappel
=> #<User id: 1, nom: "Michael Hartl", email: "mhar [email protected]",
created_at: "2010-01-05 00:57:46", updated_at: "201 0-01-05 00:57:46">
>> user . email = "[email protected]"
=> "[email protected]"
>> 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 qu'il arrive en cas d'oubli de sauvegarde en utilisant reload , qui recharge l'objet en fonction des
informations contenues dans la base de données :
>> user . email
=> "[email protected]"
>> user . email = "[email protected]"
=> "[email protected]"
>> user . reload . email
=> "[email protected]"
Maintenant que nous avons actualisé l'utilisateur, 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"
209
La seconde façon d'actualiser les attributs est d'utiliser la méthode update_attributes :
>> user . update_attributes( :nom => "The Dude" , :email => "[email protected]" )
=> true
>> user . nom
=> "The Dude"
>> user . email
=> "[email protected]"
La méthode update_attributes accepte une table d'attributs, et en cas de succès accomplit d'un coup
l'actualisation et la sauvegarde (en retournant true pour indiquer que la sauvegarde a pu se faire). Il est
important de noter que, une fois que vous avez défini l'accessibilité de certains attributs avec
attr_accessible (section 6.1.2.2), seuls ces attributs peuvent être modifiés en utilisant la méthode
update_attributes . Si, au cours de vos développements, vous trouvez tout à coup que votre modèle
commence mystérieusement à refuser d'actualiser certaines colonnes, assurez-vous que ces colonnes soient
bien incluses dans l'appel à attr_accessible .
6.2 Validations de l'utilisateur
Le modèle User que nous avons créé à la section 6.1 possède maintenant des attributs nom et email qui
fonctionnent, mais ils sont complètement « génériques » : n'importe quelle chaine de caractères (même
l'espace) est en ce moment valide dans tous les cas. Et pourtant, les noms et les adresses email sont plus
spécifiques que ça. Par exemple, le nom ne devrait pas être vierge, et l'email devrait être conforme au format
caractéristique des adresses email. Plus encore, puisque nous allons utiliser l'adresse comme unique nom
d'utilisateur (username) quand un nouvel utilisateur s'inscrira, nous ne devrions pas lui permettre d'entrer une
adresse déjà contenue dans la base de données.
En bref, nous ne devrions pas permettre que nom et email soit n'importe quelle chaine de caractères ; nous
devrions forcer certaines contraintes sur ces valeurs. Active Record nous permet d'imposer de telles contraintes
en utilisant les validations. Dans cette section, nous couvrions plusieurs des cas les plus courants, en validant la
présence (presence), la longueur (length), le format et l'unicité (uniqueness). Nous ajouterons à la section 7.1.1
une validation finale courante, la confirmation de l'adresse. Et nous verrons à la section 8.2 comment les
validations nous renvoient des messages d'erreur utiles quand l'utilisateur soumet des données qui les violent.
Comme pour les autres fonctionnalités de notre application exemple, nous allons ajouter les validations au
modèle User en suivant toujours le « Développement Dirigé par les Tests ». Puisque nous avons changé le
modèle de données, c'est une bonne idée de préparer le test de la base de données avant de poursuivre :
$ rake db:test:prepare
210
Cela s'assure 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 l'attribut nom attribute. Bien que la première étape en
TDD consiste à écrire un test échouant (section 3.2.2), nous n'en savons pas encore assez dans ce cas à propos
des validations pour écrire le test approprié, donc nous allons d'abord écrire la validation, en utilisant la console
pour la comprendre. Ensuite nous commenterons la validation pour l'exclure, é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 j'ai 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
d'exclusion par commentaire — que j'appellerai « ex-commenter ». NdT — est utile, aussi, quand on vient au
secours d'une application dont le code est déjà écrit mais qu'elle ne possède — quelle horreur ! — aucun test).
La façon de valider la présence d'un attribut nom est d'utiliser la méthode validates avec l'argument
:presence => true , comme montré dans l'extrait 6.7. L'argument :presence => true est une table
d'options à un élément ; souvenez-vous, section 4.3.4, que les accolades sont optionnelles lorsque les tableaux
sont les arguments finaux d'une méthode (comme noté à la section 5.1.1, l'utilisation de tables d'options est un
théme récurrent en Rails).
Extrait 6.7. Valider la présence d'un 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, l'utilisation de validates est caractéristique de Rails 3 (en
Rails 2.3, nous aurions dû plutôt écrire validates_presence_of :nom ).
L'extrait 6.7 peut sembler quelque peu magique, mais validates est juste une méthode, telle que l'est aussi
attr_accessible . Une formulation équivalente de l'extrait 6.7 en utilisant les parenthèses serait :
class User < ActiveRecord :: Base
attr_accessible( :nom , :email )
validates( :nom , :presence => true)
211
end
Utilisons la console pour voir les effets de l'ajout de la validation sur notre modèle User :107
$ rails console --sandbox
>> user = User . new( :nom => "" , :email => "[email protected]" )
>> user . save
=> false
>> user . valid?
=> false
Ici, user.save retourne false (faux), indiquant un sauvegarde défectueuse. Dans la commande finale, nous
utilisons la méthode valid? , qui retourne false (false) quand l'objet rate une validation ou plus, et true
(vrai) quand toutes les validations réussissent (souvenez-nou, section 4.2.3, que Ruby utilise le point
d'interrogation pour signifier les méthodes booléennes). Dans ce cas, nous n'avons qu'une seule validation,
donc nous savons laquelle échoue, mais il peut toujours être utile de vérifier en utilisant l'objet errors
(erreurs) généré par l'échec :
>> user . errors . full_messages
=> ["Name can't be blank"]
(Le message d'erreur contient un indice qui révèle que Rails valide la présence d'un attribut en utilisant la
méthode blank? , ce que nous avons vu à la fin de la section 4.4.3.)
Voilà pour les tests échouant. Pour s'assurer que notre test naissant échouera, « ex-commentons » la validation
(extrait 6.8).
Extrait 6.8. Ex-commenter une validation pour s'assurer que le test échoue.
app/models/user.rb
class User < ActiveRecord :: Base
attr_accessible :nom , :email
# validates :nom, :presence => true
end
Comme dans le cas de la génération de contrôleur (par exemple l'extrait 5.23), la commande de génération de
modèle dans l'extrait 6.1 produit une spec initiale pour tester les utilisateurs, mais dans ce cas elle est
pratiquement 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/mode ls/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
l'extrait 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 => "[email protected]" }
end
it "devrait créer une nouvelle instance dotée des attr ibuts valides" do
User . create!(@attr)
end
it "devrait exiger un nom"
end
213
Nous avons vu require et describe auparavant, le plus récemment dans l'extrait 5.28. La ligne
suivante est un bloc before(:each) ; ceci a été couvert brièvement par un exercice (extrait 3.33), et tout ce
qu'il fait est de jouer le code à l'intérieur du bloc avant chaque exemple — dant ce cas en définissant l'instance
de variable @attr comme tableau d'initialisation.
Le premier exemple est juste un « check de santé » vérifiant que le modèle User fonctionne. Il utilise
User.create! (lire « create bang », « créer bang » ), qui fonctionne comme la méthode create vue à la
section 6.1.3 à la différence près qu'elle provoque une exception (une erreur)
ActiveRecord::RecordInvalid si la création échoue (similairement à l'exception
ActiveRecord::RecordNotFound que nous avons pu voir à la section 6.1.4). Aussi longtemps que les
attributs sont valides, elle ne provoque aucune erreur, et le test réussira.
La ligne finale est le test pour la présence de l'attribut nom — ou plutôt, il devrait être le test effectif, s'il y avait
quelque chose dans cet attribut. Au lieu de ça, le test est juste une esquisse, mais une esquisse utile : c'est une
spec de mise en attente, qui est un moyen d'écrire une description du comportement de l'application sans se
soucier encore de son implémentation. L'extrait 6.9 montre un exemple spec de mise en attente utilisant un
appel explicite à la méthode pending (mise en attente) ; dans ce cas, puisque nous avons inclus seulement la
partie it de l'exemple…
it "devrait exiger un nom"
… RSpec déduit l'existence d'une spec de mise en attente.
Les specs de mise en attente sont bien traités par les programmes pour jouer des specs, comme vu pour
Autotest dans l'illustration 6.5, et la sortie de rspec spec/ est utile de la même façon. Les specs de mise en
attente sont utiles en tant qu'« emplacements réservés » pour des tests que nous savons avoir à écrire mais que
nous 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 d'une manière de créer une table
d'attributs 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 => "[email protected]" }
=> {:nom => "Example User", :email => "user@example .com"}
>> @attr . merge( :nom => "" )
=> {:nom => "", :email => "[email protected]"}
Avec merge (fusionne) en main, nous sommes prêts à faire une nouvelle spec (utilisant une astuce que
j'expliquerai dans un instant) comme le montre l'extrait 6.11.
Extrait 6.11. Un test échouant pour la validation de l'attribut nom.
spec/models/user_spec.rb
describe User do
before( :each ) do
@attr = { :nom => "Example User" , :email => "[email protected]" }
end
215
.
.
.
it "exige un nom" do
bad_guy = User . new(@attr . merge( :nom => "" ))
bad_guy . should_not be_valid
end
end
Ici nous utilisons merge pour créer un nouvel utilisateur appelée bad_guy (mauvais_garcon) avec un nom
vierge. La deuxième ligne utilise alors la méthode RSpec should_not (ne_devrait_pas) pour vérifier que
l'utilisateur en résultant n'est pas valide. L'astuce à laquelle je faisais allusion ci-dessus fait référence à
be_valid (être_valide) : nous avons appris plus haut dans cette section qu'un objet utilisateur (user)
répondait à la méthode booléenne valid? . RSpec adopte une convention utile qui nous permettait de tester
n'importe quelle méthode booléenne en supprimant le point d'interrogation et en préfixant la méthode avec
be_ (être). En d'autres mots…
bad_guy . should_not be_valid
… est équivalent à :
bad_guy . valid? . should_not == true
Puisque ça sonne plus proche du langage naturel (au moins pour les anglophones. NdT), écrire should_not
be_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 fichier
user_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 (c'est-à-dire que de l'extrait 6.8 nous allons revenir à l'extrait 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 l'adresse email. Le test (extrait 6.12) est analogue à celui
pour l'attribut nom.
Extrait 6.12. Un test de présence de l'attribut email .
spec/models/user_spec.rb
describe User do
before( :each ) do
@attr = { :nom => "Example User" , :email => "[email protected]" }
end
.
.
.
it "exige une adresse email" do
no_email_user = User . new(@attr . merge( :email => "" ))
no_email_user . should_not be_valid
end
end
L'implémentation est aussi virtuellement la même, comme vu dans l'extrait 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 d'exiger un nom (et une adresse mail) pour chaque utilisateur,
mais nous devons aller plus loin : les noms d'utilisateur seront affichés sur le site exemple, donc nous devons
introduire 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 n'y a pas de science exacte pour choisir une longueur maximum ; nous allons
déclarer que 50 caractères sont une limite maximale acceptable, ce qui signifie que les noms de 51 caractères
seront 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 => "[email protected]" }
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
end
De façon pratique, nous avons utilisé la « multiplication de chaine » dans l'extrait 6.14 pour construire une
chaine de 51 caractères. Nous pouvons voir comment cela fonctionne en utilisant la console :
>> s = "a" * 51
=> "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaa"
>> s. length
=> 51
218
Le test de l'extrait 6.14 devrait échouer. Pour le voir réussir, nous avons besoin de connaitre l'argument de
validation pour contraindre la longueur, :length , qui s'utilise avec le paramètre :maximum pour placer la
limite supérieure (extrait 6.15).
Extrait 6.15. Ajout d'une validation de longueur pour l'attribut 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
l'email..
6.2.3 Validation de format
Nos validations pour l'attribut nom forcent seulement des contraintes minimales — seuls les noms non vierges
et inférieurs à 51 caractères seront acceptés — mais bien sûr l'attribut email doit satisfaire des exigences plus
strictes. Jusqu'ici nous n'avons rejeté que l'adresse vierge ; dans cette section, nous exigerons des adresses
email qu'elles soient conformes au modèle habituel [email protected] .
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
d'adresses valides et d'adresses 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[[email protected] [email protected] first.last@fo o.jp]
=> ["[email protected]", "[email protected]", "first. [email protected]"]
>> adresses . each do | address |
?> puts address
>> end
219
Ici, 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 basique
d'adresses (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 => "[email protected]" }
end
.
.
.
it "devrait accepter une adresse email valide" do
adresses = %w[[email protected] [email protected] first.last@fo o.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
end
Comme noté ci-dessus, c'est loin d'être exhaustif, mais nous vérifions les formes d'adresse email valides les plus
courantes [email protected] , [email protected] (capitales, tiret plat et domaines composés), et
[email protected] (le nom d'utilisateur standard de l'entreprise first.last , avec un domaine de deux
lettres jp ), en plus de quelques adresses de forme invalide.
220
Le code de l'application 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 l'argument :format pour la méthode
validates (extrait 6.17).
Extrait 6.17. Valider le format de l'email avec une expression régulière.
app/models/user.rb
class User < ActiveRecord :: Base
attr_accessible :nom , :email
email_regex = /\A[\w+\-.]+@[a-z\d\-.]+\.[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-z\d\-.]+\.[a-z]+\z/i
.
.
.
validates :email , :presence => true,
:format => { :with => email_regex }
… s'assure que seules les adresses mail qui correspondent au pattern de l'expression régulière seront
considérées comme valides.
Mais d'où 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, j'ai 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 d'expression 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 qu'un 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-z\d\-.]+\.[a-z]+\z/i Pattern complet
/ marque le début de l'expression
\A cherche le début de la chaine
[\w+\-.]+ au moins un caractère de mot, le signe « + », le signe « - » ou le point
@ arobase
[a-z\d\-.]+ au moins une lettre, un chiffre, un tiret ou un point
\. un point
[a-z]+ au moins une lettre
\z cherche la fin de la chaine
/ marque la fin de l'expression régulière
i insensible à la casse
Table 6.1: Détail de l'expression régulière pour les emails de l'extrait 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 l'extrait 6.17 est bien, peut-être même
meilleure que l'expression officielle.110
222
Illustration 6.6: L'impressionnant éditeur d'expressions 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 l'email
sont ici à titre de test de cohérence sur email_regex ). Cela signifie qu'il ne reste qu'une seule contrainte à
ajouter : forcer l'adresse email à être unique.
6.2.4 Validation d'unicité
Pour forcer l'unicité des adresses email (de telle sorte que nous puissons les utiliser comme username — nom
d'utilisateur), nous allons utiliser l'option :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 d'habitude, 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 d'unicité nous
avons besoin en fait de déposer un enregistrement dans la base de données.111 Le (premier) test de duplication
d'email apparait dans l'extrait 6.18.
Extrait 6.18. Un test pour le rejet de la duplication d'une adresse mail.
spec/models/user_spec.rb
describe User do
before( :each ) do
@attr = { :nom => "Example User" , :email => "[email protected]" }
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 d'essayer de faire un autre utilisateur qui possèderait la même
adresse email (nous utilisons la méthode « bruyante » create! , d'abord vue à l'extrait 6.10, de telle sorte
223
qu'elle provoquera une exception, une erreur, si quelque chose ne tournait pas rond. En utilisant create ,
sans le « bang » ! , nous risquerions d'obtenir une erreur silencieuse dans nos tests, une source potentielle de
bogue insaisissable). Nous pouvons obtenir que ce test réussisse avec le code de l'extrait 6.19.112
Extrait 6.19. Valider l'unicité de l'adresse mail.
app/models/user.rb
class User < ActiveRecord :: Base
.
.
.
validates :email , :presence => true,
:format => { :with => email_regex },
:uniqueness => true
end
Nous n'en avons pas fini, cependant. Les adresses mails sont sensibles à la casse — [email protected] se rend au
même endroit que [email protected] ou [email protected] — donc nos validations doivent aussi couvrir ce cas. Nous
testons cela avec le code de l'extrait 6.20.
Extrait 6.20. Un test pour le rejet d'une adresse mail existente, insensible à la casse.
spec/models/user_spec.rb
describe User do
before( :each ) do
@attr = { :nom => "Example User" , :email => "[email protected]" }
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 => "[email protected]" }
=> {:nom => "Example User", :email => "user@example .com"}
>> upcased_email = @attr [ :email ]. upcase
=> "[email protected]"
>> 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 c'est un
test d'échec, mais nous voulons qu'il retourne false (faux). Heureusement, :uniqueness (unicité) accepte
une option, :case_sensitive , justement dans ce but (extrait 6.21).
Extrait 6.21. Valider l'unicité de l'adresse 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 (l'unicité ) devrait être true (vraie). À ce point, notre application fait (quelque peu) en sorte
que l'adresse mail soit unique, et notre suite de tests devrait réussir.
L'avertissement d'unicité
Il y a juste un petit problème, le piège auquel je faisais allusion ci-dessus :
Utiliser validates :uniqueness ne garantit en rien l'unicité.
225
« Oh ?… Mais qu'est-ce qui peut bien mal tourner ?… » Cela :
1. Alice s'inscrit à l'application exemple, avec l'adresse mail [email protected] ;
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, l'utilisateur de la requête 1 (Alice) est enregistré, l'utilisateur de
la requête 2 (ALice) est aussi enregistré ;
4. Résultat : deux enregistrements d'utilisateur avec la même adresse email, malgré la validation
d'unicité.
Si la séquence ci-dessus ne vous semble pas plausible, croyez-moi, elle l'est. Cela survient sur n'importe quel
site Rails connaissant un trafic important.113 Heureusement, la solution est simple à implémenter ; nous avons
juste besoin de forcer l'unicité au niveau de la base de données elle-même. Notre méthode consiste à créer un
index de base de données sur la colonne email , et ensuite d'exiger que cet index soit unique.
L'index email représente une actualisation à nos impératifs du modèle de données, ce qui (comme discuté à la
section 6.1.1) est traité par Rails en utilisant les migrations. Nous avons vu à la section 6.1.1 que générer le
modèle User créait automatiquement une nouvelle migration (extrait 6.2) ; dans le cas présent, nous ajoutons
de la structure à un modèle existant, donc nous avons besoin de créer une migration directement en utilisant le
générateur migration :
$ rails generate migration add_email_uniqueness_inde x
Contrairement à la migration pour les utilisateurs, la migration pour l'unicité de l'adresse mail n'est pas pré-
définie, donc nous avons besoin de remplir son contenu par le code de l'extrait 6.22.114
Extrait 6.22. La migration pour forcer l'unicité de l'adresse mail.
db/migrate/<timestamp>_add_email_uniqueness_index.r b
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 l'index sur la colonne email de la table
users . L'index par lui-même ne contraint pas l'unicité, mais c'est l'option :unique => true qui le fait.
L'étape finale est de migrer la base de données :
$ rake db:migrate
Maintenant, le scénario d'Alice fonctionnera parfaitement : la base de données sauvera l'enregistrement basé
sur la première requête, et rejettera la seconde pour violation de la contrainte d'unicité de l'adresse mail (une
erreur apparaitra dans le journal Rails, mais elle ne provoque aucun dommage. Vous pouvez bien entendu saisir
l'exception ActiveRecord::StatementInvalid qui est provoquée — voyez Insoshi pour un exemple —
mais dans ce tutoriel nous ne nous occuperons pas de cette étape). L'ajout de l'index sur l'attribut email
accomplit un second but, auquel j'ai 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 l'explique 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, l'attribut email créé par la migration dans
l'extrait 6.2. Quand nous permettrons aux utilisateurs de s'identifier à l'application exemple, en abordant le
chapitre 8, nous aurons besoin de trouver l'enregistrement de l'utilisateur correspondant à l'adresse email
soumise ; malheureusement, s'appuyant 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 d'utilisateur 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 d'utilisateurs, c'est une très
vilaine chose.
Placer un index sur la colonne de l'email fixe ce problème. Pour comprendre un index de base de données, on
peut prendre l'analogie d'un index de livre. Dans un livre, pour trouver toutes les occurences d'un mot donné,
disons « foobar », vous aurez à consulter chaque page, une par une, pour trouver chaque « foobar ». Avec un
index de livre, d'un autre côté, vous pouvez juste chercher « foobar » dans l'index 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 n'en avons pas tout à fait fini avec le modèle utilisateur de base — nous avons encore besoin d'ajouter des
mots de passe, tâche qui sera accomplie au chapitre 7 — mais nous en avons fait suffisamment pour faire une
227
page minimale pour pouvoir afficher les informations de l'utilisateur. Cela permettra une introduction en
douceur à l'organisation de type REST des actions utilisateur de notre site. Puisque c'est juste une
démonstration grossière pour le moment, il n'y aura pas de tests dans cette section ; nous les ajouterons quand
nous étofferons la vue de l'utilisateur à la section 7.3.
6.3.1 Débuggage et environnement Rails
À titre de préparation de l'ajout de pages dynamique dans notre Application Exemple, il est temps à présent
d'ajouter des informations de débuggage au layout de notre site (extrait 6.23). Cela affiche des informations
utiles sur chaque page en utilisant la méthode intégrée debug et la variable params (que nous étudierons plus
en détail à la section 6.3.2), comme le montre l'illustration 6.7.
Extrait 6.23. Ajout d'information 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 à l'utilisateur de l'application déployée
online, nous utilisons :
if Rails . env . development?
… pour les restreindre à l'environnement de développement. Bien que nous ayons déjà abordé l'évidence des
environnements Rails précédemment (tout récemment à la section 6.1.3), c'est la première fois que cela nous
importe vraiment.
228
Illustration 6.7: La page d'accueil de l'application exemple (/ ) avec des informations de débuggage en bas de
page.
Rails arrive équipé de trois environnements : test , development , et production .115 L'environnement par
défaut de la console est l'environnement 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 l'environnement
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 n'est probablement pas bon non plus, donc il est
mieux de restreindre l'affichage des débuggage à l'environnement de développement seulement).
229
Quand vous avez besoin de jouer la console dans un environnement différent (pour débugguer un test par
exemple), vous pouvez passer l'environnement 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 l'environnement par défaut du server Rails local,
mais vous pouvez aussi le faire jouer dans un environnement différent :
$ rails server --environment production
Si 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 l'environnement,
quand ce n'est pas l'environnement par défaut, de trois façons mutuellement incompatibles, c'est pourquoi j'ai
pris le soin de les montrer les trois.)
En passant, si vous avez déployé votre application exemple sur Heroku, vous pouvez voir son environnement en
utilisant la commande heroku qui fournit sa propre console à distance :
$ heroku console
Ruby console for yourapp.heroku.com
>> Rails . env
=> "production"
>> Rails . env . production?
=> true
Naturellement, puisque Heroku est une plateforme pour les sites en mode production, il joue chaque
application dans cette environnement production.
6.3.2 Modèle User, Vue, Contrôleur
Afin de faire une page pour voir un utilisateur, nous allons utiliser le modèle User pour créer un utilisateur dans
la 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 d'autres termes, pour la première
fois dans ce tutoriel, nous allons voir en un seul endroit les trois éléments de l'architecture 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 l'enregistrement dans la base de données :
$ rails console
Loading development environment (Rails 3.0.7)
>> User . create!( :nom => "Michael Hartl" , :email => "[email protected]" )
=> #<User id: 1, nom: "Michael Hartl", email: "mhar [email protected]",
created_at: "2010-01-07 23:05:14", updated_at: "201 0-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 l'accent sur le fait que c'est juste une démonstration
(extrait 6.24). Nous utilisons le fichier standard de Rails pour afficher un utilisateur,
231
app/views/users/show.html.erb ; contrairement à la vue new.html.erb , que nous avons créée
avec le générateur de l'extrait 5.23, le fichier show.html.erb n'existe pas encore, donc vous devez le créer à la
main.
Extrait 6.24. Une vue brute pour afficher les informations de l'utilisateur.
app/views/users/show.html.erb
<%= @user. nom %>, <%= @user . email %>
Cette vue utilise du code Ruby embarqué pour afficher le nom et l'adresse mail de l'utilisateur, en assumant
l'existence d'une variable d'instance appelée @user. Bien sûr, la page finale d'affichage de l'utilisateur sera très
différente, et n'affichera pas publiquement l'adresse email.
Enfin, nous ajouterons l'action show au contrôleur Users (correspondant à la vue show.html.erb ) avec le
code de l'extrait 6.25, qui définit la variable d'instance @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 = "S'inscrire"
end
end
Ici nous avons donné un peu plus de nous-mêmes en utilisant le paramètre (params ), objet standard de Rails
pour récupérer l'identifiant (id ) de l'utilisateur. Quand nous ferons la requête appropriée au contrôleur User,
params[:id] sera l'id utilisateur 1, donc l'effet 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 l'action show soient maintenant toutes deux définies, nous n'avons toujours pas de moyen de
voir la page elle-même. Cela nécessite de définir la règle adéquate dans le fichier de routage de Rails, comme
nous allons le voir dans la section suivante.
232
6.3.3 Une ressource Users
Nous méthode pour afficher la page d'affichage de l'utilisateur va suivre les conventions de l'architecture 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 l'expert 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
l'accent 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. Qu'est-ce que ça signifie dans le contexte des utilisateurs — auxquels nous
penserons en tant que « ressource Users » ? Cela signifie que nous devrions voir l'utilisateur avec un identifiant
de 1 en envoyant une requête GET à l'URL /users/1 . Ici, l'action 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 l'action show action.
Illustration 6.9: L'effet initial de l'URL /users/1 .
Malheureusement, l'URL /users/1 ne fonctionne pas encore, à cause d'une erreur de routage
(illustration 6.9). Nous pouvons faire fonctionner l'URL utilisateur de style REST en ajoutant users
(utilisateurs) comme ressource au fichier config/routes.rb , comme vu dans l'extrait 6.26.
233
Extrait 6.26. Ajout d'une ressource Users au fichier de routage.
config/routes.rb
SampleApp :: Application . routes . draw do
resources :users
match '/signup' , :to => 'users#new'
.
.
.
end
Après avoir ajouté les routes pour la ressource Users, l'URL /users/1 fonctionne parfaitement
(illustration 6.10).
Illustration 6.10: La page d'affichage de l'utilisateur à l'URL /users/1 après avoir ajouté une ressource
Users.
Vous avez peut-être noté que l'extrait 6.26 a effacé la ligne :
get "users/new"
… vue auparavant dans l'extrait 5.29. C'est parce que la ligne de ressource ajoutée dans l'extrait 6.26 n'ajoute
pas 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 l'utilisateur d'id 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 l'utilisateur d'id 1
PUT /users/1 update user_path(1) actualiser l'utilisateur d'id 1
DELETE /users/1 destroy user_path(1) détruire l'utilisateur d'id 1
Table 6.2: Routes RESTful fournies par la ressource Users dans l'extrait 6.26.
Les params en débuggage (debug )
Avant de quitter la page d'affichage de l'utilisateur, nous allons prendre un moment pour examiner les
informations de débuggage produite par l'extrait 6.23. Si vous regardez attentivement l'illustration 6.10, vous
verrez qu'elle fournit des informations utiles à propos de la page rendue :118
--- !map:ActiveSupport::HashWithIndifferentAccess
action: show
id: "1"
controller: users
C'est 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 l'occasion d'utiliser params[:controller] ou params[:action] , utiliser params[:id] pour
extraire l'identifiant de l'URL est un idiome Rails très courant. En particulier, nous utilisons le code :
235
User . find(params [ :id ] )
… dans l'extrait 6.25 pour trouver l'utilisateur d'id 1 (la méthode find sait comment convertir la chaine de
caractères "1" en entier 1).
L'information debug fournit souvent des retours utiles au cours du développement des applications Rails et je
vous suggère d'adopter l'habitude de le consulter chaque fois que votre application se comporte de façon
inattendue.
6.4 Conclusion
Ce chapitre est la première moitié du processus en deux temps de la création d'un modèle utilisateur (User)
fonctionnel. Nos utilisateurs ont maintenant des attributs nom et email , chacun paré de validations
contraignant leur valeur. Nous avons aussi amorcé une page d'affichage de l'utilisateur et une ressource
utilisateur basée sur l'architecture REST. Au chapitre 7, nous achèverons le processus en ajoutant des mots de
passe et des vues de l'utilisateur plus utiles.
Si vous utilisez Git, il serait bien de commettre un nouveau dépôt si vous ne l'avez pas fait depuis un moment :
$ git add .
$ git commit -m "Finished first cut of the User model"
6.5 Exercises
1. Lisez l'API Rails à l'entrée pour ActiveRecord::Base pour avoir une idée de ses capacités.
2. Étudiez l'entré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
chapitre 7 Modéliser et afficher les utilisateurs, partie II Au chapitre 6, nous avons créé la première itération d'un modèle User (Utilisateur) pour représenter les
utilisateurs de notre application, mais le job a été à moitié accompli. Virtuellement n'importe quel site avec des
utilisateurs, incluant le nôtre, a besoin d'un système d'authentification, mais pour le moment n'importe quel
utilisateur qui peut s'inscrire au site possède un nom et une adresse mail, et nous n'avons aucun moyen de
vérifier leur identité. Dans ce chapitre, nous allons ajouter l'attribut mot de passe (password) nécessaire à une
inscription initiale de l'utilisateur (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) d'Active 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 d'utilisateur afficheront les noms et
les photos du profil (comme indiqué dans la maquette de l'illustration 7.1), et ils seront bien testés avec des
« utilisateurs d'usine ».
Avant d'avancer, ré-initialisons la base de données avec rake db:reset , ce qui va retirer tous les vieux
exemples d'utilisateurs des sessions précédentes :
$ rake db:reset
Illustration 7.1: Une maquette du profil utilisateur fait à la section 7.3.
237
7.1 Mots de passe peu sécurisés
Faire des mots de passe robustes requiert beaucoup de machinerie, donc nous allons diviser le processus en
deux principales étapes. Dans cette section, nous allons faire un attribut mot_de_passe et ajouter des
validations. 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 ce
problème en cryptant les mots de passe avant de les sauver, protégeant ainsi notre site des attaques toujours
possibles.
7.1.1 Validations du mot de passe
Quand bien même nous n'avons 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 d'avoir des tests pour valider la
présence, la longueur et la confirmation des mots de passe. C'est notre plus gros bloc de tests jusqu'ici, voyez si
vous pouvez le lire tout d'un tenant. Si vous restez coincé, ça vous aidera sans doute de revoir les validations
analogues de la section 6.2 ou sauter directement au code de l'application de l'extrait 7.2.
Dans le but de minimiser les typos dans les mots de passe, quand on fera la page d'inscription de l'utilisateur au
chapitre 8 nous adopterons la convention courante que les utilisateurs confirment leur mot de passe. Pour
commencer, revoyons la table d'attributs vue dans l'extrait 6.20:
describe User do
before( :each ) do
@attr = { :nom => "Utilisateur exemple" , :email => "[email protected]" }
end
.
.
.
end
Pour écrire des tests pour les mots de passe, nous aurons besoin d'ajouter deux nouveaux attributs à la table
@attr , password (mot de passe) et password_confirmation (motdepasse_confirmation). Comme vous
pouvez certainement le deviner, l'attribut password_confirmation sera utilisé pour l'étape de confirmation
du mot de passe.
Écrivons des tests pour la présence du mot de passe et sa confirmation, avec des tests confirmant que le mot de
passe a une longueur valide (réduite à quelque chose arbitrairement fixée entre 6 et 40 caractères). Le résultat
apparait dans l'extrait 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 => "[email protected]" ,
:password => "foobar" ,
:password_confirmation => "foobar"
}
end
it "devrait créer une nouvelle instance avec des attri buts 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 qu i 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
end
Notez sans l'extrait 7.1 comme nous collectons d'abord un set d'attributs utilisateur valides dans @attr . Si pour
quelque raison que ce soit ces attributs ne sont pas valides — comme ce devrait être le cas, par exemple, si nous
n'avons pas implémenté les confirmations proprement — alors la première étape :
it "devrait créer une nouvelle instance avec des attri buts valides" do
User . create!(@attr)
end
… rencontrera une erreur. Les tests suivants vérifieront alors chaque validation à son tour, en utilisant la même
technique @attr.merge introduite la première fois dans l'extrait 6.11.
Voyons maintenant le code de l'application, qui contient une astuce. En fait, elle contient deux astuces. D'abord,
vous pouvez vous attendre, à ce point, à ce qu'on joue une migration pour ajouter un attribut password à notre
modèle User, comme nous l'avions fait pour les attributs nom et email dans l'extrait 6.1. Mais ce n'est pas le
cas : nous n'allons enregistrer qu'un mot de passe crypté dans la base de données ; pour le mot de passe, nous
allons introduire la notion d'attribut virtuel (c'est-à-dire un attribut qui ne correspond pas à une colonne de la
base de données) en utilisant la méthode attr_accessor , comme nous l'avons fait avec la méthode
attr_accessible pour les attributs nom et email pour l'exemple d'utilisateur à la section 4.4.5. L'attribut
password ne sera jamais écrit dans la base de données, mais n'existera qu'en mémoire pour permettre l'étape
de confirmation du mot de passe (implémentée ci-dessous) et l'étape de cryptage (implémentée à la
section 7.1.2 et section 7.2).
La seconde astuce est que nous n'allons pas introduire un attribut password_confirmation , pas même un
virtuel. À 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 confirmant
dans le même temps qu'il correspond exactement à l'attribut password .
240
Ainsi préparé à comprendre l'implémentation, jetons un coup d'œil au code lui-même (extrait 7.2).
Extrait 7.2. Validation pour l'attribut password .
app/models/user.rb
class User < ActiveRecord :: Base
attr_accessor :password
attr_accessible :nom , :email , :password , :password_confirmation
.
.
.
# Crée automatique l'attribut virtuel 'password_con firmation'.
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 d'inscription au chapitre 8, nous avons besoin d'ajouter 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 d'un :password (comme, par
exemple dans l'extrait 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 l'extrait 6.15 nous contraignons l'attribut nom à avoir moins de 50 caractères en utilisant
l'option :maximum :
validates :nom , :presence => true,
:length => { :maximum => 50 }
Pour la validation de la longueur du mot de passe, nous avons plutôt utilisé l'option :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 n'enregistrons le mot de passe de l'utilisateur nulle part ; puisque
nous avons décidé d'utiliser un mot de passe virtuel, plutôt que de l'enregistrer dans la base de données, il
241
n'existe qu'en mémoire. Comment pouvons-nous utiliser ce mot de passe pour l'identification ? La
solution 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 enregistrerons
dans la base de données à l'inscription de l'utilisateur (chapitre 8) et que nous récupérerons plus tard pour
l'identification de l'utilisateur (chapitre 9).
Planifions l'enregistrement du mot de passe crypté en utilisant un attribut encrypted_password dans notre
modèle User (Utilisateur). Nous discuterons des détails de l'implémentation à la section 7.2, mais nous pouvons
commencer 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 et
retourne true (vrai) si l'objet répond à la méthode ou l'attribut donné et false (faux) dans le cas contraire :
$ rails console --sandbox
>> user = User . new
>> user . respond_to?( :password )
=> true
>> user . respond_to?( :encrypted_password )
=> false
Nous pouvons tester l'existence d'un attribut encrypted_password avec le code de l'extrait 7.3, qui utilise
l'helper de méthode RSpec respond_to .
Extrait 7.3. Tester l'existence d'un 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 d'appeler 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 l'utilisateur 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 d'une migration pour ajouter l'attribut
encrypted_password à la table users (utilisateurs) :
$ rails generate migration add_password_to_users enc rypted_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 d'attribut que nous voulons créer (comparez cela avec la génération originale de la table users de
l'extrait 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 d'information à Rails pour construire entièrement la migration pour nous, comme le montre l'extrait 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 l'illustration 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 à l'attribut encrypted_password (assurez-vous de fermer toutes les
consoles Rails lancées dans le « bac à sable » (sandbox) ; la sandbox verrouille la base de données et interdit les
migrations).
$ rake db:migrate
$ rake db:test:prepare
Bien sûr, nous pouvons jouer toute la suite de tests avec rspec spec/ , mais il est pratique parfois de jouer
juste 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 failures
7.1.3 Fonction de rappel dans l'Active Record
Maintenant que notre modèle User possède un attribut pour consigner le mot de passe, nous devons nous
arranger pour générer et sauver le mot de passe crypté quand Active Record enregistre l'utilisateur dans la base
de données. Nous allons réaliser cela en utilisant une technique appelée une fonction de rappel (callback), qui
est une méthode invoquée à un point particulier de la vie de l'objet Active Record. Dans le cas présent, nous
utiliserons la fonction de rappel before_save pour créer encrypted_password juste avant que l'utilisateur
ne soit enregistré.121
Nous commençons avec un test pour l'attribut mot de passe crypté. Puisque nous avons différé les détails de
l'implémentation — et, en particulier, la méthode de cryptage — à la section 7.2, dans cette section nous allons
244
seulement nous assurer que l'attribut encrypted_password d'un utilisateur enregistré n'est 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 l'extrait 6.11), rendant le test de l'extrait 7.5.
Extrait 7.5. Tester que l'attribut encrypted_password n'est 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? n'est 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 d'enregistrer la donnée. Le résultat est présenté dans l'extrait 7.6.
Extrait 7.6. Fonction de rappel before_save pour créer l'attribut 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
end
Ici la fonction de rappel encrypt_password délègue en fait le cryptage à une méthode encrypt ; comme
signalé dans le commentaire, c'est seulement une implémentation provisoire — construit ainsi, l'extrait 7.6 met
simplement le mot de passe crypté à la valeur du mot de passe non crypté, ce qui n'est pas notre but. Mais ça
suffit pour faire réussir notre test, et nous ferons que la méthode encrypt procède réellement au cryptage à la
section 7.2.
Avant d'essayer de comprendre l'implémentation, notez d'abord que les méthodes de cryptage sont placées
après le mot-clé private ; à l'intérieur d'une classe Ruby, toutes les méthodes définies après le mot-clé
private (privé) sont utilisées de façon interne par l'objet et ne sont pas destinées à l'utilisation publique.122 À
titre d'exemple, nous pouvons tester l'objet User dans la console :
>> user = User . new
>> user . encrypt_password
NoMethodError: Attempt to call private method
(Traduction :
ErreurPasDeMethode : Tentative d'appel d'une méthod e privée)
Ici Ruby provoque une exception (une erreur) NoMethodError (erreur d'absence de méthode) en signalant
par 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 n'est pas strictement
nécessaire, mais c'est une bonne pratique de les rendre privées sauf si elles sont nécessaires à l'interface
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
C'est 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
l'attribut 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 à l'objet lui-même, ce qui pour le modèle User est simplement l'utilisateur
lui-même). L'utilisation 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 n'est pas du tout ce que nous voulons
(elle n'existerait 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 n'y a pas de variable password en vue. Dans la console, nous devrions accéder à
l'attribut mot de passe (password) au travers d'un objet utilisateur :
>> user = User . new( :password => "foobar" )
>> user . password
=> "foobar"
À l'intérieur de la classe User, l'objet utilisateur étant self , nous pourrions écrire :
def encrypt_password
self . encrypted_password = encrypt( self . password)
end
247
… en analogie avec l'exemple en console, remplacez simplement user par self . Mais le self est ici
optionnel (dans un membre d'expression droit), donc pour la brièveté nous pouvons écrire simplement :
def encrypt_password
self . encrypted_password = encrypt(password)
end
… comme dans l'extrait 7.6 ci-dessus (bien sûr, comme nous l'avons noté, le self n'est pas optionnel quand
nous assignons la valeur d'un attribut (dans le membre gauche d'une expression), donc nous devons écrire
self.encrypted_password dans ce cas).
7.2 Mots de passe sécurisés
Avec le code de la section 7.1, en principe nous avons fini : bien que le mot de passe « crypté » soit le même que
le mot de passe non crypté, la possibilité d'enregistrer un mot de passe crypté dans la base de données nous
fournit les fondations nécessaires pour l'identification et l'authentification de l'utilisateur.124 Les normes de ce
Tutoriel Rails doivent être beaucoup plus strictes, cependant : tout développeur web digne de ce nom doit
savoir implémenter un système de mot de passe avec un hachage sécurisé à sens unique (secure one-way
hashing). Dans cette section, nous construirons le matériel puisé de la section 7.1 pour implémenter un tel
systè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 soigneusement
rangé dans la région private du modèle User, ce qui représente une difficulté particulière pour les tests. Nous
aurions besoin d'une espèce d'interface publique que nous pourrions exposer au reste de l'application. Un des
aspects utiles du « Développement Dirigé par les Tests » est que, en agissant comme un client pour coder notre
application, les tests nous motivent à concevoir une interface utilisable dès le départ.
L'authentification des utilisateurs implique de pouvoir comparer la version cryptée d'un mot de passe soumis
avec la version cryptée du mot de passe de l'utilisateur en question. Cela signifie que nous avons besoin de
dé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 l'interface pour la machinerie de cryptage.125 La
méthode has_password? testera si l'utilisateur possède le même mot de passe que celui soumis lors de
l'inscription (sera écrit au chapitre 9) ; un squelette de méthode pour has_password? est présenté dans
l'extrait 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 l'extrait 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ïnciden t" do
@user . has_password?(@attr [ :password ] ) . should be_true
249
end
it "doit retourner false si les mots de passe divergen t" do
@user . has_password?( "invalide" ) . should be_false
end
end
end
end
À la section 7.2.3, nous compléterons l'implémentation de la méthode has_password? (et obtiendrons que le
test réussisse dans le processus). Mais d'abord nous avons besoin d'en apprendre un peu plus sur la
sécurisation des mots de passe.
7.2.2 Un peu de théorie sur la sécurisation des mots de passe
L'idée de base d'un mot de passe crypté est simple : plutôt que d'enregistrer un mot de passe « en clair » dans la
base de données (donc tel quel), nous enregistrons une chaine de caractères générée en utilisant une fonction
de hachage cryptographique (cryptographic hash function), qui est par essence irréversible, de telle sorte que
même un hacker en possession de la version cryptée du mot de passe sera incapable d'en déduire l'original.
Pour vérifier qu'un mot de passe soumis coïncide avec le mot de passe de l'utilisateur, nous cryptons d'abord la
chaine de caractères soumise puis nous la comparons au mot de passe crypté enregistré. Rejoignons une session
de 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)
=> "2bb80d537b1da3e38bd30361aa855686bde0eacd7162fef 6a25fe97bf527a25b"
>> 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 n'est pas utile de savoir exactement comment fonctionnent ces fonctions ; ce qui est
important pour notre propos est qu'elles sont à sens unique : il n'y a aucune façon informatisable de découvrir
que :
2bb80d537b1da3e38bd30361aa855686bde0eacd7162fef6a25 fe97bf527a25b
… 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 = "2bb80d537b1da3e38bd30361aa855686bde0eacd7162fef6a2 5fe97bf527a25b"
>> 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 s'assurer l'unicité (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 s'ils 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
=> "d1a3eb8c9aab32ec19cfda810d2ab351873b5dca4e16e7f 57b3c1932113314c8"
>> encrypted_password = secure_hash( " #{salt} -- #{password} " )
=> "69a98a49b7fd103058639be84fb88c19c998c8ad3639cfc 5deb458018561c847"
Dans les dernières lignes, nous avons haché le sel avec le mot de passe, pour retourner un mot de passe crypté
qu'il est virtuellement impossible de cracker (pour la clarté, les arguments des fonctions de hachage sont
souvent 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 à l'implémentation. Nous allons
aller de l'avant pour voir où nous allons. Chaque objet utilisateur connait son propre mot de passe crypté, donc
pour 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)
end
Puisque nous cryptons le mot de passe soumis en utilisant le même sel que celui utilisé pour le mot de passe
original, cette fonction retournera vrai si et seulement si le mot de passe soumis correspond.
Puisque comparer le mot de passe d'un utilisateur avec un mot de passe soumis implique de crypter le mot de
passe soumis avec le sel, nous avons besoin d'enregistrer ce sel quelque part, donc la première étape va
consister à ajouter une colonne salt à la table users :
$ rails generate migration add_salt_to_users salt:st ring
Comme 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 l'attribut et son type (« String » ici, chaine de
caractè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 d'habitude :
$ rake db:migrate
$ rake db:test:prepare
Le résultat est une base de données avec le modèle de données correspondant à l'illustration 7.3.
Illustration 7.3: Le modèle User avec un sel ajouté.
Nous sommes enfin prêts pour l'implémentation complète. Quand nous avons vu la dernière fois la fonction
encrypt (extrait 7.6), elle ne faisait rien d'autre que retourner la chaine de caractère qu'elle recevait en
argument. Dans l'idé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
end
Ce code contient les deux mêmes subtilités que celles mentionnées à la section 7.1.3, nommément,
l'assignement d'un attribut Active Record à l'aide de self à la ligne :
self . salt = make_salt if new_record?
… et l'omission du mot-clé self dans le membre droit de ligne de code de la méthode encrypt :
def encrypt(string)
secure_hash( " #{salt} -- #{string} " )
end
Puisque nous sommes à l'intérieur de la classe, Ruby sait que salt se réfère à l'attribut salt de l'utilisateur.
Il est important de noter aussi l'utilisation de la méthode booléenne de l'Active Record new_record? , qui
retourne true (vrai) si l'objet n'a pas encore été enregistré dans la base de données. Puisque le sel est un
identifiant unique pour chaque utilisateur, nous ne voulons pas qu'il change chaque fois que l'utilisateur 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 l'utilisateur129 (cette subtilité n'importe pas pour le moment, mais elle
importera quand nous implémenterons une fonctionnalité « se souvenir de moi » à l'identification à la
section 9.3.2).
À ce stade, les tests de l'extrait 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 divergen t"
.
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
d'interrogation « ? » de la méthode "has_password?" :
$ rspec spec/models/user_spec.rb -e "Méthode has_password\?"
Run filtered using {:full_description=>/(?-mix:Méth ode has_password\?)/}
..
2 examples, 0 failures
L'échappement (« \ ») avant le point d'interrogation assure que l'interpréteur d'expressions 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 d'authentification
Avoir une méthode has_password? pour chaque utilisateur est une bonne chose, mais en elle-même elle n'est
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.
255
Nous pouvons avoir une idée de ce fonctionnement à l'aide de la console. D'abord, nous créons un
utilisateur, et ensuite nous récupérons cet utilisateur par l'adresse-mail pour vérifier qu'il a un mot de passe
donné :130
$ rails console --sandbox
>> User . create!( :nom => "Michael Hartl" , :email => "[email protected]" ,
?> :password => "foobar" , :password_confirmation => "foobar" )
>> user = User . find_by_email( "[email protected]" )
>> user . has_password?( "foobar" )
=> true
En utilisant ces idées, écrivons une méthode qui retournera un utilisateur identifié si les mots de passe
correspondent, et nul (nil ) dans le cas contraire. Nous devrions pouvoir utiliser la méthode de classe
authenticate 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 de
User.authenticate . Il y a trois choses à vérifier : authenticate (1) devrait retourner nil quand la
combinaison email/mot de passe est invalide ou (2) quand aucun utilisateur n'existe avec l'adresse mail fournie,
et (3) devrait retourner l'objet utilisateur lui-même en cas de succès. Avec ces informations, nous pouvons
écrire les tests de authenticate comme dans l'extrait 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 d'inéquation entre em ail/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( "[email protected]" , @attr [ :password ] )
nonexistent_user . should be_nil
end
it "devrait retourner l'utilisateur si email/mot de pa sse correspondent" do
matching_user = User . authenticate(@attr [ :email ] , @attr [ :password ] )
matching_user . should == @user
end
end
end
end
Nous sommes prêts maintenant pour l'implé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 d'une classe User. En dehors de la
classe, elles sont invoquées en utilisant le nom de la classe, comme dans User.find , mais à l'intérieur de la
classe nous pouvons omettre le nom de la classe.
Box 7.1. Qu'est-ce que self ?
Nous avons déjà expliqué que self est « l'objet lui-même », mais ce que ça signifie dépend du contexte. À
l'intérieur d'une méthode ordinaire, self se réfère à une instance de la classe, c'est-à-dire à l'objet lui-même.
Par exemple, dans l'extrait 7.10, self est un utilisateur (user) :
def encrypt_password
self.salt = make_salt if new_record?
self.encrypted_password = encrypt(password)
end
À l'intérieur de la méthode encrypt_password , self est l'objet utilisateur, donc self.salt est identique à
user.salt en dehors de la méthode :
257
$ rails console
>> user = User.first
>> user.salt
=> "d3b9af261c502947fbf32f78cb8179b16e62eabacf059 451efee404328b2f537"
D'un autre côté, l'extrait 7.12 montre la définition de authenticate , qui utilise self pour définir une
méthode de classe ; ici, self est la classe User elle-même :
def self.authenticate(email, submitted_password)
.
.
.
end
Parce ce qu'elle est définie dans une classe User , authenticate est invoquée directement sur le nom de
classe User :
>> user = User.authenticate('example@railstutoria l.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 classe
authenticate montrée dans l'extrait 7.12. D'abord, nous pourrions indiquer la classe User explicitement par
son nom :
def User.authenticate(email, submitted_password)
.
end
(Certaines personnes peuvent trouver cette syntaxe plus claire, mais elle n'est pas aussi correcte,
idiomatiquement parlant.) Ensuite, nous pourrions utiliser le code suivant qui, franchement, m'explose le
cerveau :
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 d'autres 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 d'utiliser le mot-clé self dans la définition de la méthode (ce
self n'est pas le même que le self montré dans l'extrait 7.10 ; voyez le Box 7.1.) L'extrait 7.12 montre cette
construction dans le contexte de la méthode authenticate . Notez l'appel à find_by_email , dans laquelle
nous omettons le nom explicite de classe User puisque cette méthode est déjà à l'inté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 l'implé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 d'implémenter cette méthode.
259
7.3 Meilleures vues d'utilisateurs
Maintenant que le modèle User est effectivement achevé,131 nous sommes en mesure d'ajouter un exemple
d'utilisateur à la base de données de développement et de faire une page show (montrer, afficher) pour afficher
certaines des informations de l'utilisateur. Chemin faisant, nous ajouterons quelques tests au spec du
contrô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 d'affichage de l'utilisateur suivront cet exemple, mais nous devons comprendre que
contrairement aux tests de l'action new les tests de l'action show exigeront l'utilisation d'instances du modèle
User. 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
end
7.3.1 Tester la page d'affichage de l'utilisateur (avec factories)
Les tests pour le contrôleur User auront besoin des objets instances du modèle User, avec de préférence des
valeurs pré-définies (pour ne pas avoir à les rentrer toutes « à la main »). Par exemple, comme vu à
l'extrait 7.14, l'action show du contrôleur Users a besoin d'une instance de la classe User, donc les tests de cette
action vont exiger que nous créions d'une manière ou d'une autre une variable @user. Nous allons accomplir
260
cela avec un utilisateur d'usine, qui est une façon pratique de définir un objet utilisateur et de l'insérer à
l'intérieur de notre base de données de test.132
Extrait 7.14. L'action utilisateur show de l'extrait 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 d'usine générées par Factory Girl (Fille d'Usine),133, un gem Ruby produit par les
gens valeureux de thoughtbot. Comme pour les autres gems Ruby, vous pouvez l'installer en ajoutant une ligne
à votre Gemfile utilisé par Bundler (extrait 7.15) (puisque Factory Girl est nécessaire seulement pour les tests,
nous l'incluons 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 d'habitude :
$ bundle install
Nous sommes maintenant prêts à créer le fichier spec/factories.rb et à définir un utilisateur d'usine,
comme le montre l'extrait 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 "[email protected]"
user . password "foobar"
user . password_confirmation "foobar"
end
Avec la définition de l'extrait 7.16, nous pouvons créer un User d'usine dans les tests comme cela :
@user = Factory( :user )
Comme l'indique le commentaire de la première ligne de l'extrait 7.16, en utilisant le symbole :user nous nous
assurons que Factory Girl devinera que nous voulons utiliser un modèle User, donc dans ce cas @user simulera
une instance de la classe User .
Pour utiliser notre nouvel Utilisateur d'usine 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 d'affichage et vérifier la réussite (tout
comme nous l'avons fait avec la page new de l'extrait 7.13), tout en vérifiant aussi que l'action show récupère
l'utilisateur correct de la base de données. Le résultat est présenté dans l'extrait 7.17 (si vous utilisez Spork, vous
avez peut-être à le redémarrer pour obtenir la réussite de ces tests).
Extrait 7.17. Un test pour get ting (obtenir ) la page utilisateur show, avec un utilisateur d'usine.
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 l'utilisation d'une usine, la réelle nouveauté ici est l'utilisation 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 qu'a la variable d'instance correspondante (ici @user
— :user => @user) dans l'action concernée, ici l'action show du contrôleur User . En d'autres termes, dans
l'extrait 7.17 le code :
assigns( :user )
… retourne la valeur de la variable d'instance :
@user
… dans l'action 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 l'action correspond à l'instance @user créée
par Factory Girl. Il est important de noter que tous les programmeurs Rails n'utilisent pas assigns dans ce
contexte, lui préférant parfois la technique appelée le stubbing (Box 7.2).
Box 7.2.Stub er ou ne pas Stub er ?
Le code de l'extrait 7.17 s'appuie sur une méthode User.find dans l'action du contrôleur pour récupérer le
bon utilisateur de la base de données de test. Une autre manière d'atteindre le même résultat consiste à utiliser
une technique appelée stubbing, à l'aide de la méthode RSpec stub! :
before(:each)
263
@user = Factory(:user)
User.stub!(:find, @user.id).and_return(@user)
end
Ce code s'assure que tout appel à User.find avec l'id donné retournera @user. Puisque c'est juste ce que
nous avons dans le code de l'application (extrait 7.14), le stub fera que RSpec va intercepter l'appel à
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 qu'elle
sépare les tests des controleurs de la couche modèle. En vérité, la version Rails 2.3 de ce livre utilise stubs, aux
côté des techniques très proches de message expectations. Après avoir acquis plus d'expérience avec les stubs et
les expectations, et spécialement après avoir répondu à beaucoup de questions de lecteurs du Tutoriel de la
version Rails 2.3 déroutés par ces problèmes, j'en suis arrivé à la conclusion que le stubbing et les techniques
afférentes ne valaient pas tous ces troubles.
Déterminer quand stuber les choses est difficile, et les messages expectations sont incroyablement subtils et
sujets à erreurs (voir par exemple le Box 8.1 dans le Tutoriel Rails 2.3 Tutorial). On fait souvent l'objection
suivante : « Mais maintenant les tests de contrôleur se connectent à la base de données de test ! ». Ce à quoi je
réponds maintenant par : « Et alors ? » Dans mon expérience ça n'a jamais compté. Je ne vois aucune
impérieuse raison de ne pas atteindre la couche modèle dans les tests contrôleur, spécialement quand ça
conduit à des tests plus simples. Si vous êtes intéressé par l'apprentissage du stubbing et des techniques de
message expectation, je recommande la lecture du Tutoriel Ruby on Rails 2.3. Sinon, je suggère de ne pas trop
se soucier d'une séparation complète entre couche modèle et couche contrôleur dans les tests Rails. Bien que les
tests contrôleur dans la suite de ce livre se connecteront à la base de données, à un niveau conceptuel, la partie
du 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 base
de données, et pour la suite de tests de l'application exemple complète de ce Tutoriel Rails, ils se connecteront
— en environ deux dixièmes de seconde.
Il reste deux autres détails dans l'extrait 7.17 qu'il convient de souligner. D'abord, dans l'appel 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 l'extrait 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 l'attribut id de l'utilisateur @user, est l'objet 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 l'objet user vers l'id
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 l'extrait 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 d'affichage de l'utilisateur en ajoutant une entête
avec le nom de l'utilisateur et une image de profil. C'est une de ces situations où il est très difficile de prévoir les
choses, et donc difficile d'utiliser le « Développement Dirigé par les Tests », et souvent en construisant les vues
j'expé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 l'utilisateur et une balise img de
classe gravatar (nous expliquerons dans un instant le sens de cette seconde partie).
Pour voir une page d'affichage de l'utilisateur fonctionner dans le navigateur, nous aurons besoin de créer un
exemple d'utilisateur 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 => "[email protected]" ,
?> :password => "foobar" , :password_confirmation => "foobar" )
Les tests de cette section sont similaires aux tests de la page new vus dans l'extrait 5.26. En particulier, nous
utilisons la méthode have_selector pour vérifier le titre et le contenu de la balise h1 , comme le montre
l'extrait 7.18.
Extrait 7.18. Tests pour la page d'affichage de l'utilisateur.
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 l'utilisateur" 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
l'utilisateur. Le troisième exemple introduit un nouvel élément par le code h1>img , qui fait que la balise img
est à l'inté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 l'helper titre
(section 4.1.1), dans ce cas en l'assignant à la valeur du nom de l'utilisateur (extrait 7.19).
Extrait 7.19. Un titre pour la page d'affichage de l'utilisateur.
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é à l'intérieur de l'application par l'helper titre défini dans
l'extrait 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 d'injecter un programme JavaScript
malveillant en utilisant <script> dans son nom, l'échappement HTML automatique le convertirait en
<script> , le rendant complètement inoffensif.
Voyons maintenant les autres tests. Créer un h1 à partir du nom de l'utilisateur (échappé) est facile
(extrait 7.20).
Extrait 7.20. La page de profil avec un nom d'utilisateur.
app/views/users/show.html.erb
<h1>
<%= @user . nom %>
</h1>
267
Faire réussir le test de img est plus astucieux. La première étape consiste à installer le gem
gravatar_image_tag pour traiter chaque Gravatar d'utilisateur,138 qui est un « avatar reconnu
globalement ».139 Comme d'habitude, 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 install
Vous devrez peut-être aussi redémarrer votre serveur web ici pour charger le nouveau gem Gravatar
proprement.
Les gravatars sont une façon pratique d'inclure des images de profil utilisateur sans avoir à gérer la difficulté
des téléchargement d'images, leur redimensionnement et leur sauvegarde.140 Chaque gravatar est associé à une
adresse email, donc le gem Gravatar est fourni avec une méthode d'helper appelée gravatar_image_tag qui
prend une adresse mail comme argument :
<%= gravatar_image_tag '[email protected]' %>
Pour le moment, nous utilisons cela directement dans notre vue affichant l'utilisateur, comme le montre
l'extrait 7.22 (nous construirons une méthode helper dans un moment). Le résultat apparait dans
l'illustration 7.4, qui montre notre exemple avec l'image gravatar par défaut.
Extrait 7.22. La vue d'affichage de l'utilisateur 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 d'affichage de l'utilisateur /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 => "[email protected]" ,
?> :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é l'adresse mail de
l'utilisateur, en la changeant en [email protected] . Comme vous pouvez le voir dans
l'illustration 7.5, ce changement produit l'affichage d'un nouveau gravatar : le logo du tutoriel Rails. Ce qui se
passe, c'est que Gravatar travaille en associant image et adresse email ; puisque [email protected] est une
adresse invalide (le domaine example.com est réservé aux exemples), il n'y a pas de gravatar associé à cette
adresse. Mais à mon compte Gravatar j'ai associé l'adresse [email protected] au logo du tutoriel
Rails, donc en actualisant l'utilisateur exemple avec l'adresse mail, le gravatar change automatiquement.
269
Illustration 7.5: La page d'affichage de l'utilisateur /users/1 avec le gravatar du Tutoriel Rails.
Un helper Gravatar
À ce stade, le gravatar s'affiche proprement, mais l'exemple final de l'extrait 7.18 ne réussit toujours pas. C'est
parce que la classe CSS "gravatar" , que nous voulons utiliser pour styliser les gravatars en CSS, n'est pas
encore spécifier dans la balise img du gravatar. Pour faire réussir le test, nous devons ajouter l'option adéquate
à la méthode gravatar_image_tag :
<%= gravatar_image_tag @user . email, :class => "gravatar" %>
D'un autre côté, puisque nous anticipons le fait que les gravatars apparaitront à différents endroits de notre
application, il serait répétitif de mettre la classe chaque fois à la main. Ce serait mieux de faire une méthode
d'helper 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 on
Rails »), que nous avons résolue avec l'helper titre dans l'helper de l'application (extrait 4.2). La solution ici
est la même ; puisque les gravatars sont naturellement associés aux utilisateurs, nous allons définir une
méthode gravatar_for (gravatar_pour) dans le helper Users (le choix d'utiliser le helper Users plutôt que le
helper 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
L'helper gravatar_for devrait prendre un objet user et passer alors quelques options par défaut à l'helper
gravatar_image_tag . L'implémentation apparait dans l'extrait 7.23.
Extrait 7.23. Définir une méthode d'helper 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 l'appel à gravatar_image_tag passe la version minuscule de l'adresse mail (en
utilisant la méthode downcase ).141 Ensuite la première option (deuxième argument) de
gravatar_image_tag assigne le nom de l'utilisateur à l'attribut 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 d'options 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 d'affichage de l'utilisateur avec le code de l'extrait 7.24, la page
d'affichage de l'utilisateur apparaitra comme dans l'illustration 7.6. Et puisque l'helper gravatar_for assigne
à la balise img la classe CSS "gravatar" , les tests de l'extrait 7.18 devraient maintenant réussir.
Extrait 7.24. Actualiser le template de l'affichage de l'utilisateur en utilisant gravatar_for .
app/views/users/show.html.erb
<h1>
<%= gravatar_for @user %>
<%= @user . nom %>
271
</h1>
Illustration 7.6: La page d'affichage de l'utilisateur avec gravatar_for .
7.3.3 Une barre utilisateur latérale
Même si nos tests réussissent à présent, et que la page d'affichage de l'utilisateur s'est considérablement
améliorée, il est encore possible de la polisser un petit peu plus. Dans l'extrait 7.25, nous avons une balise
table avec un rangée tr et deux cellules td .144
Extrait 7.25. Ajout d'une barre latérale pour la vue show de l'utilisateur.
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 l'utilisateur et
l'URL. Notez aussi l'utilisation de user_path pour créer un lien cliquable qui permet aux utilisateurs de
partager facilement l'URL de leur profil. C'est seulement le première d'un 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 d'accès) de l'utilisateur, dans ce cas /users/1 .
Le code lié :
user_url(@user)
… retourne l'URL 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
l'extrait 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.
273
Notez que dans :
<%= link_to user_path(@user), @user %>
… user_path(@user) est le lien texte, tandis que l'adresse est juste @user. Dans le contexte d'un link_to ,
Rails convertit @user en l'URL appropriée ; en d'autres termes, le code ci-dessus est équivalent au code :
<%= link_to user_path(@user), user_path(@user) %>
N'importe laquelle de ces formulations fonctionne bien, mais, comme dans l'idiome :id => @user de
l'extrait 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 d'affichage avec le code CSS
montré dans l'extrait 7.26. La page en résultant est montrée dans l'illustration 7.7.
Extrait 7.26. CSS pour styliser la page d'affichage de l'utilisateur, 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 d'affichage de l'utilisateur /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 s'identifier 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
l'utilisateur, donc après l'identification les utilisateurs auront un endroit où aller.
7.4.1 Dépôt Git
Avant de poursuivre, nous devrions interrompre la boucle ouverte dans l'introduction 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
D'abord, vérifiez que vous êtes bien sur la branche modeling-users :
$ git branch
master
275
* modeling-users
Comme indiqué à la section 1.3.5.1, l'astérisque ici indique la branche courante, donc nous sommes vraiment
prêts à déposer et à fusionner :146
$ git add .
$ git commit -m "Modele utilisateur avec mot de passe et page profi l"
$ git checkout master
$ git merge modeling-users
7.4.2 déploiement Heroku
Si vous avez déployé votre application exemple sur Heroku, vous pouvez la « pusher » maintenant :
$ git push heroku
Migrez alors la base de données sur le serveur à distance en utilisant la commande heroku :
$ heroku rake db:migrate
Maintenant, si vous voulez créer un exemple d'utilisateur sur Heroku, vous pouvez utiliser la console Heroku :
$ heroku console
>> User . create!( :nom => "Utilisateur exemple" , :email => "[email protected]" ,
?> :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 qu'ils sont correct en jouant votre suite de tests.
2. L'exemple 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 l'helper gravatar gravatar_for fonctionne si votre modèle User
utilisait email_address au lieu de email pour représenter l'adresse 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 l'opérateur ternaire.
def self . authenticate(email, submitted_password)
user = find_by_email(email)
user && user . has_password?(submitted_password) ? user : nil
end
277
chapitre 8 Inscription Maintenant que nous avons un modèle User fonctionnel, il est temps d'ajouter quelques capacités dont ne peut
pas se passer un site web : laisser ses utilisateurs s'inscrire — et ainsi tenir la promesse faite à la section 5.3,
« Inscription de l'utilisateur : une première étape ». Nous allons utiliser un formulaire HTML pour soumettre
les informations d'inscription de l'utilisateur à notre application à la section 8.1, qui serviront à créer un nouvel
utilisateur et à sauver ses attributs dans la base de données à la section 8.3. Comme d'habitude, nous allons
écrire des tests à mesure que nous développons, et à la section 8.4 nous utiliserons le support RSpec pour la
syntaxe de la navigation web pour écrire des tests d'intégration succincts et expressifs.
Puisque nous allons créer un nouvel utilisateur dans ce chapitre, il est peut-être nécessaire de supprimer tous
les utilisateurs créés à la console dans la base de données (par exemple ceux de la section 7.3.2), de telle sorte
que vos résultats correspondent à ceux montrés dans ce tutoriel. Vous pouvez faire cela comme suit :
$ rake db:reset
Si vous suivez votre développement en utilisant le contrôle de version, faites une branche sujet comme
d'habitude :
$ git checkout master
$ git checkout -b signing-up
8.1 Formulaire d'inscription
Souvenez-vous d'après la section 5.3.1 que nous avons déjà des tests pour la page des nouveaux utilisateurs
(signup page), vu à l'origine dans l'extrait 5.26 et reproduits dans l'extrait 8.1 (comme promis à la section 7.3.1,
nous sommes passés de get ’new’ à get :new parce que c'est ce que mes doigts veulent taper). De plus,
nous avons vu dans l'illustration 5.10 (et à nouveau dans l'illustration 8.1) que cette page d'inscription est pour
le moment vierge : donc tout à fait inapte à inscrire de nouveaux utilisateurs. Le but de cette section sera de
commencer à changer ce triste état des choses en produisant la maquette de formulaire d'inscription de
l'illustration 8.2.
Extrait 8.1. Les tests pour la page d'inscription des utilisateurs (vu d'abord dans l'extrait 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 d'inscription /signup .
279
Illustration 8.2: Une maquette de la page d'inscription.
8.1.1 Utiliser form_for
L'élément HTML nécessaire pour soumettre des informations à un site à distane est un formulaire, ce qui
suggère qu'une bonne première étape vers la registration des utilisateurs est de faire un formulaire susceptible
de recueillir leurs informations d'inscription. Nous pouvons accomplir cela en Rails avec la méthode helper
form_for (formulaire_pour) ; le résultat apparait dans l'extrait 8.2 (les lecteurs familiers de Rails 2.x doivent
noter que form_for utilise maintenant la syntaxe ERb « pourcent-égal » pour insérer le contenu ; c'est-à-dire
qu'où Rails 2.x utilisait <% form_for ... %> , Rails 3 utilise maintenant <%= form_for ... %> ).
Extrait 8.2. Un formulaire pour l'inscription 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 ». À l'intérieur de l'helper
form_for , f est un objet qui représente un formulaire ; comme c'est souvent le cas avec les helpers Rails, nous
n'avons pas besoin de connaitre tous les détails de l'implémentation, mais ce que nous avons besoin de
connaitre est ce que l'objet f fait : quand appelé avec une méthode correspondant à un élément formulaire
HTML — tel qu'un 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 l'objet @user. En d'autres 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 l'attribut nom d'un 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 n'avons pas
défini la variable d'instance @user — comme toute variable d'instance 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
d'inscription échoue. Pour obtenir qu'elle s'affiche et rendre notre formulaire, nous devons définir une variable
@user dans l'action du contrôleur correspondant à new.html.erb , c'est-à-dire l'action new dans le contrôleur
Users. L'helper form_for attend que @user soit un objet User, et puisque nous créons un nouvel utilisateur
nous utiliserons simplement User.new , comme dans l'extrait 8.3.
Extrait 8.3. Ajout d'une variable d'instance @user à l'action new.
app/controllers/users_controller.rb
281
class UsersController < ApplicationController
.
.
.
def new
@user = User . new
@titre = "Inscription"
end
end
Avec la variable @user ainsi définie, les tests devraient réussir à nouveau,147 et maintenant le formulaire (avec
un peu de stylisation qui vient de l'extrait 8.4) apparait comme dans l'illustration 8.3.
Extrait 8.4. Une très mince quantité de CSS pour le formulaire d'inscription.
public/stylesheets/custom.css
.
.
.
div.field , div.actions {
margin-bottom : 10px;
}
Illustration 8.3: Le formulaire d'inscription /signup pour de nouveaux utilisateurs.
282
8.1.2 Le formulaire HTML
Comme indiqué par illustration 8.3, la page d'inscription est maintenant rendue proprement, indiquant que le
code form_for dans l'extrait 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 l'extrait 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 l'illustration 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+Jwl xLScMQ=" />
</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">Confirm ation</label><br />
<input id="user_password_confirmation" nom="use r[password_confirmation]"
size="30" type="password" />
</div>
<div class="actions">
<input id="user_submit" nom="commit" type="subm it" value="Inscription" />
</div>
</form>
283
Nous allons commencer avec la structure interne. En comparant l'extrait 8.2 avec l'extrait 8.5, nous
voyons 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" ty pe="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]" si ze="30" type="password" />
</div>
Comme vu dans l'illustration 8.4, les champs de saisie textuels (type="text" ) affichent simplement leur
contenu, tandis que les champs mot de passe (type="password" ) masquent leur contenu pour des questions
de sécurité, comme le montre l'illustration 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 l'attribut 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 d'initialisation (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 l'objet @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">
285
Ici les attributs class et id ne sont pas très utiles ; ce qui est important c'est action="/users" et
method="post" . Ensemble, ils constituent les instructions pour prendre en compte une requête HTML
POST à l'URL « /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+Jwl xLScMQ=" />
</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 son
fonctionnement et de son importance. Heureusement, Rails prend soin du problème pour nous, et la balise
input est hidden (cachée) donc vous n'avez pas à vous en soucier plus que ça ; mais comme elle saute aux yeux
en consultant le code HTML source, je voulais au moins vous en parler.
8.2 Échec de l'inscription
Bien que nous ayons brièvement examiné le code HTML du formulaire de l'illustration 8.3 (vu dans
l'extrait 8.5), il sera encore plus compréhensible dans le contexte d'un échec d'inscription. Dans cette section,
nous allons nous arranger pour soumettre un formulaire d'inscription invalide qui devra redonner la page
d'inscription (cf. la maquette de l'illustration 8.5).
Illustration 8.5: Une maquette pour l'échec de la page d'inscription.
286
8.2.1 Tester l'échec
Souvenez-vous de la section 6.3.3 qu'ajouter resources :users au fichier routes.rb (extrait 6.26)
permettait de s'assurer automatiquement que notre application Rails répondait aux URLs RESTful de la
Table 6.2. En particulier, cela assurait qu'une requête POST pour /users était traitée par l'action create .
Notre stratégie pour l'action create (créer) est d'utiliser la soumission du formulaire pour fabriquer un nouvel
objet utilisateur à l'aide de User.new , en essayant (et échouant) de sauver cet utilisateur, et alors de retourner
la page d'inscription pour une possible re-soumission. Notre tâche est d'écrire les tests pour cette action, et
d'ajouter ensuite create au contrôleur Users pour le faire réussir.
Commençons par revisiter le code du formulaire d'inscription :
<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 l'URL /users . En analogie
à la méthode get , qui émet une requête GET à l'intérieur des tests, nous utilisons la méthode post pour
émettre une requête POST pour l'action create . Comme nous allons le voir brièvement, create reçoit une
table de hachage correspondant au type d'objet qui doit être créé ; puisque c'est un test pour l'échec de
l'inscription, nous allons juste passer la table @attr avec une entrée vierge, comme montré dans l'extrait 8.6.
Cela revient à visiter la page d'inscription et à cliquer sur le bouton de soumission « S'inscrire » sans avoir
rempli aucun des champs de saisie du formulaire.
Extrait 8.6. Tests pour l'échec de l'inscription de l'utilisateur.
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 d'utilisateur" 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
end
Les deux tests finaux sont relativement simples : nous nous assurons que le titre est correct, et puis nous
vérifions qu'une inscription défectueuse renvoie bien à nouveau la page d'inscription d'un nouvel utilisateur (en
utilisant 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 d'utilisateur" do
lambda do
post :create , :user => @attr
end . should_not change(User, :count )
end
… est censé vérifier que l'échec de l'action create ne crée pas d'utilisateur dans la base de données. Pour ce
faire, il introduit deux éléments nouveaux. D'abord, nous utilisons la méthode RSpec change pour retourner le
changement du nombre d'utilisateurs 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 d'enregistrements 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 d'enrouler l'étape post :create dans un « package » en utilisant la construction
Ruby appelée un lambda 148 (une fonction anonyme), qui nous permet de vérifier qu'elle 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 l'extrait 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é d'introduire 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 d'inscription (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 d'enregistrement.
else
@titre = "Inscription"
289
render 'new'
end
end
end
La meilleure façon de comprendre le fonctionnement du code de l'extrait 8.7 est de soumettre le formulaire
avec des données d'inscription invalides ; le résultat apparait dans l'illustration 8.6.
Illustration 8.6: Échec d'inscription 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 à la
table params dans les informations de débuggage en bas de la page de l'illustration 8.6 :
--- !map:ActiveSupport::HashWithIndifferentAccess
commit: Inscription
authenticity_token: rB82sI7Qw5J9J1UMILG/VQL411vH5pu R+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 d'une URL comme « /users/1 », la valeur de params[:id] est l'id de l'utilisateur
correspondant (1 dans ce exemple). Dans le cas d'un POSTing du formulaire d'inscription, 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. L'information 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 l'attribut 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 l'utilisateur — en
fait, exactement, les attributs nécessaires comme argument pour User.new , comme nous l'avons initialement
vu à la section 4.4.5 et revu dans l'extrait 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" )
C'est 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 l'inscription — comme nous le
verrons à la section 8.3, une fois que @user est défini proprement, appeler @user.save est tout ce qu'il y a à
faire pour achever l'enregistrement — mais cela a des conséquences même dans l'échec d'inscription considéré
ici. Notez dans l'illustration 8.6 que les champs sont pré-remplis avec les données de la soumission défectueuse.
C'est parce que form_for remplit automatiquement les champs avec les attributs de l'objet @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 l'attribut value de la balise input vaut "Foo" , et c'est ce qui apparait dans le champ de texte.
8.2.3 Message d'erreur à l'inscription
Bien que ce ne soit pas strictement nécessaire, il est utile de retourner à l'utilisateur des messages d'erreur en
cas d'échec pour lui indiquer les problèmes à régler, qui permettront de réussir son inscription. Rails fournit de
tels messages en se basant sur les validations du modèle User. Par exemple, essayons de sauver un utilisateur
avec 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 (min imum is 6 characters)"]
Ici, l'objet errors.full_messages (que nous avons vu brièvement à la section 6.2.1) contient un tableau de
messages d'erreurs.
292
Comme dans la console ci-dessus, l'échec de l'enregistrement de l'extrait 8.7 génère une liste de messages
d'erreurs associée à l'objet @user. Pour afficher les messages dans le navigateur, nous allons rendre un partiel
de messages d'erreur sur la page new (extrait 8.8).149
Extrait 8.8. Code pour afficher les messages d'erreur sur le formulaire d'inscription.
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 l'extrait 8.9.
Extrait 8.9. Un partiel pour afficher les messages d'erreur à 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é d'enregistrer 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.
293
La première méthode est count (compter), qui retourne simplement le nombre d'éléments dans l'objet :
$ rails console
>> a = [1 , 2, 3]
=> [1, 2, 3]
>> a. count
=> 3
L'autre nouvelle méthode est any? , l'une des deux méthodes complémentaires :
>> []. empty?
=> true
>> []. any?
=> false
>> a. empty?
=> false
>> a. any?
=> true
Nous voyons ici que la méthode empty? (vide ?), que nous avons vue initialement à la section 4.2.3 dans le
contexte des chaines de caractères, qui fonctionne aussi sur les tableaux, retourne true (vrai) si le tableau est
vide et false (faux) dans le cas contraire. La méthode any? (quelque chose ?) est simplement l'opposée de la
méthode empty? , retournant true s'il y a au moins un élément et false dans le cas contraire..
L'autre nouvelle idée est l'helper de texte pluralize (mettre_au_pluriel). Elle n'est pas accessible par la
console, mais nous pouvons l'inclure 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 une
version plurielle adéquate de son second argument. Sous cette méthode se trouve un puissant inflecteur qui sait
comment pluraliser un grand nombre de mots (qui incluent de nombreux pluriels irréguliers — en anglais par
dé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 d'erreurs.
Notez que l'extrait 8.9 inclut l'id CSS error_explanation pour styliser les messages d'erreur (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
d'erreur, Rails encadre automatiquement les champs d'un div de classe CSS field_with_errors . Ces labels
nous permettent alors de styliser les messages d'erreur avec le code CSS montré dans l'extrait 8.10. Comme
résultat, en cas d'échec de la soumission les messages d'erreur apparaissent comme dans l'illustration 8.7.
Comme les messages sont générés par les validations du modèle, ils changeront automatiquement si vous
changez d'avis, disons, sur le format des adresses mail ou la longueur minimum pour les mots de passe.
Extrait 8.10. CSS pour styliser les messages d'erreur.
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 d'inscription avec les messages d'erreur.
8.2.4 Filtrer les paramètres d'identification
Avant de s'acheminer vers la réussite de l'inscription, 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 l'information de débuggage. Ce n'est pas
un problème en soi — dans l'extrait 6.23 nous avons vu que ces informations n'apparaissaient qu'en 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 l'activité de l'application. 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
l'extrait 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 d'enregistrer 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 l'inscription échoue, mais le problème serait exactement le même pour une soumission
réussie). Ce problème était tellement courant dans les applications Rails que Rails 3 implémente à présent un
nouveau comportement par défaut : tous les attributs password (mot de passe) sont filtrés automatiquement,
comme le montre l'extrait 8.12. Nous voyons que la chaine "[FILTERED]" remplace le mot de passe et sa
confirmation (en mode production, le fichier journal sera log/production.log , et le filtrage fonctionne de la
mê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 configuration
application.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 th ere, including any gems
# you've limited to :test, :development, or :produc tion.
Bundler . require( :default , Rails . env) if defined?(Bundler)
module SampleApp
class Application < Rails :: Application
.
.
.
# Configure sensitive parameters which will be filt ered 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 l'ajouter à la liste des paramètres filtrés. Par exemple, si vous incluez un code
secret dans le processus d'inscription 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 l'inscription
Ayant traité la soumission d'un formulaire invalide, il est temps à présent d'achever le formulaire d'inscription
en enregistrant réellement un nouvel utilisateur (valide) dans la base de données. D'abord, nous essayons de
sauver un utilisateur ; si l'enregistrement fonctionne, les informations de l'utilisateur sont écrites dans la base
de données automatiquement, et nous pouvons alors rediriger (redirect ) le navigateur pour afficher le profil
de l'utilisateur (avec un sympathique message de bienvenue), comme le montre la maquette dans
l'illustration 8.8. Si ça rate, nous retournons simplement au comportement développé à la section 8.2.
Illustration 8.8: Une maquette d'une inscription réussie.
299
8.3.1 Tester la réussite
Les tests pour une inscription réussie suivent le même chemin que les tests de l'échec de l'inscription de
l'extrait 8.6. Jetons un coup d'œil au résultat présenté dans l'extrait 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 => "[email protected]" ,
: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 d'affichage de l'ut ilisateur" 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 l'inscription (extrait 8.6), nous utilisons ici post :create pour atteindre
l'action create avec la requête HTML « POST ». Comme dans les tests d'un échec de création de l'extrait 8.6,
le premier test « enroule » la création de l'utilisateur 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 d'une création d'utilisateur
défectueuse, nous utilisons should change(User, :count).by(1) , qui attend du bloc lambda qu'il
change le compte User de 1.
Le second test utilise la méthode assigns vue initialement dans l'extrait 7.17 pour vérifier que l'action create
redirige le tout nouvel utilisateur vers sa page show :
it "devrait rediriger vers la page d'affichage de l'ut ilisateur" do
post :create , :user => @attr
response . should redirect_to(user_path(assigns( :user )))
end
C'est 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 n'avez rien besoin de savoir sur le code de réponse HTML sous-
jacent.151 L'URL 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 d'inscription final
Pour obtenir la réussite de ces tests et ainsi achever un formulaire d'inscription fonctionnel, remplissez la
section ex-commentée de l'extrait 8.7 avec une redirection, comme montré dans l'extrait 8.15.
Extrait 8.15. L'action 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
end
Notez que nous pouvons omettre le user_path dans la redirection, en écrivant simplement redirect_to
@user pour rediriger l'utilisateur vers sa page d'affichage (show ), une convention que nous avons vu
précédemment avec link_to dans l'extrait 7.25. Cette syntaxe est joliment succincte, mais malheureusement
RSpec ne la comprend pas, donc nous devons utiliser l'expression plus bavarde user_path(@user) dans ce
cas.
8.3.3 Le message « flash »
Avant de soumettre une registration valide dans le navigateur, nous allons ajouter quelque chose de très
courant dans les applications web : un message qui apparait temporairement et disparait au rechargement de la
page (si ce n'est pas clair, soyez patient : un exemple concret va être présenté rapidement). La manière de Rails
d'accomplir cela consiste à utiliser une variable spéciale appelée le flash, qui opère comme mémoire flash dans
laquelle il consigne temporairement ses données. La variable flash est en fait une table de hachage ; vous
pouvez même vous souvenir de l'exemple console de la section 4.3.3, où nous avions vu comment boucler sur
une table en utilisant une table stratégiquement appelée flash . Pour résumer, essayez cette session de
console :
$ 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 l'incluant au layout de notre
application, comme dans l'extrait 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 s'arrange 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 l'Application
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 l'Applica tion Exemple !</div>
La raison pour laquelle nous bouclons sur toutes les paires clé/valeur possible permet d'inclure les autres types
de messages flash ; par exemple, dans l'extrait 9.8 nous verrons flash[:error] utilisé pour indiquer un
échec d'identification.153
303
Testons 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 l'inscription de l'utilisateur.
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 l'Application Exemple/i
end
end
end
end
Cela introduit l'opération « égale-tilde » (« =~ ») pour comparer les chaines de caractère par expression
régulière (nous avons déjà parlé des expressions régulières avec le email_regex de l'extrait 6.17). Plutôt que
de tester tout le message flash, nous testons simplement que le « Bienvenue dans l'Application Exemple » soit
présent (notez que nous ne testons pas encore l'apparence du code HTML du message flash ; nous réglerons
cela 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à une
rapide session de console dans le cas où vous auriez besoin d'une 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 l'index (la position) dans la chaine où la
correspondance commence.154 Habituellement, cependant, l'index exact n'est 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 l'Application Exemple !"
=> "Bienvenue dans l'Application Exemple !"
>> "Chaine trouvée !" if success =~ /bienvenue dans l'application exemple/
=> nil
Il n'y 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 l'application exemple/i
=> "Chaine trouvée !"
Maintenant que nous comprenons comment fonctionne le test du message flash, nous pouvons obtenir qu'il
réussisse en assignant flash[:success] dans l'action create comme dans l'extrait 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 l'expression
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 d'un message Flash lors de l'inscription de l'utilisateur.
app/controllers/users_controller.rb
class UsersController < ApplicationController
.
.
.
def create
@user = User . new(params [ :user ] )
if @user . save
flash [ :success ] = "Bienvenue dans l'Application Exemple!"
redirect_to @user
305
else
@titre = "Inscription"
render 'new'
end
end
end
8.3.4 La première inscription
Nous pouvons voir le résultat de tout ce travail en inscrivant notre premier utilisateur (sous le nom « Rails
Tutorial » et l'adresse mail « [email protected] »), qui affiche un message
convivial au succès de l'inscription, comme montré dans l'illustration 8.9 (la jolie stylisation verte pour la classe
success vient du framework CSS Blueprint de la section 4.1.2). Ensuite, en rechargeant la page d'affichage de
l'utilisateur, le message Flash disparait comme promis (illustration 8.10).
Illustration 8.9: Le résultat d'une 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: "exa [email protected]",
created_at: "2010-02-17 03:07:53", updated_at: "201 0-02-17 03:07:53",
encrypted_password: "48aa8f4444b71f3f713d87d051819b0d44cd89f4a963949f20 1...",
salt: "f52924ba502d4f92a634d4f9647622ccce26205176cc eca2adc...">
C'est un triomphe !
8.4 Les tests d'intégration RSpec
En principe, à ce stade, nous en avons fini avec l'inscription de l'utilisateur, mais vous avez peut-être noté que
nous n'avons pas testé la structure du formulaire d'inscription, 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 s'assurer qu'une 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.
307
Notre méthode de test devrait être de checker la structure HTML du formulaire (en utilisant
render_views et la méthode have_selector ), et c'est en effet une bonne manière de développer les vues en
se laissant diriger par les tests (à cet effet, la section 8.6 propose un exercice concernant ce point). Mais je
préfère ne pas tester la structure HTML détaillée des vues — je ne vois aucune raison pour laquelle nous
devrions devoir savoir que Rails implémente la soumission de l'adresse mail de l'utilisateur en utilisant
nom="user[email]" , et en effet tout test de cette structure deviendrait obsolète si une version future de Rails
changeait cette convention. Plus encore, ce serait bien d'avoir un test pour le processus entier d'inscription : la
visite de la page d'inscription, le remplissage du formulaire, le clic du bouton de soumission et pour s'assurer
(en cas de soumission valide) qu'un 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 à cette
question est d'utiliser les tests RSpec d'intégration, que nous avons initialement utilisés à la section 5.2.1 pour
tester les routes personnalisées (telles que « /about » pour la page « À Propos »). Dans la section sus-
nommée, nous n'avons vu qu'un petit exemple du pouvoir des tests d'intégration ; en abordant cette section,
nous allons voir à quel point ils peuvent être puissants.
Box 8.1.Alternatives d'intégration
Comme nous l'avons vu dans ce chapitre et le précédent, le Tutoriel Ruby on Rails utilise RSpec pour tous ses
tests, incluant les tests d'intégration. Il existe cependant quelques alternatives tout aussi valables. L'une d'elle
est proposée par Rails par défaut : Test::Unit . Il n'y a aucune objection à ce que vous utilisiez
Test::Unit pour d'autres applications, mais j'ai choisi RSpec pour ce tutoriel et je préfère ne pas mixer
RSpec et Test::Unit à l'intérieur du même projet.
Une autre alternative est Cucumber (Concombre), qui fonctionne bien avec RSpec et permet la définition en
plain-texte de scénarios décrivant le comportement de l'application. De nombreux programmeurs Rails
trouvent 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és
avec (et peuvent même parfois être écrits par) le client. Bien sûr, utiliser un framework de testing qui n'est pas
en pur Ruby a une contrepartie, et je trouve que les histoires en plain-texte peuvent être un peu bavardes et
encom(com)brantes. Puisque nous n'avons pas d'exigence de client dans le Tutoriel Rails, et puisque je préfère
définitivement une approche des tests en pur Ruby, nous adoptons les tests d'intégration RSpec dans ce livre.
Néanmoins, je vous suggère de jeter un œil au Tutoriel Cucumber pour voir s'il pourrait vous plaire.
8.4.1 Tests d'intégration sur les styles
Nous avons vu dans l'extrait 5.13 que les tests d'intégration RSpec supportent le « controller-test » (test
contrôleur) – telles que les constructions du type :
308
get '/'
response . should have_selector( 'title' , :content => "Accueil" )
Ce n'est pas le seul type de syntaxe supporté, cependant ; les tests d'inté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 d'Utilisateur"
click_button
8.4.2 Un échec d'inscription ne devrait pas créer un nouvel utilisateur
Maintenant nous sommes prêts à faire un test d'intégration pour l'inscription des utilisateurs. Comme nous
l'avons vu à la section 5.2.1, RSpec est fourni avec un générateur pour construire de tels specs d'intégration ;
dans le cas présent, nos tests d'inté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 d'inscription. Une façon simple d'obtenir un échec d'inscription est de visiter
l'URL de l'inscription et de cliquer sur le bouton de soumission du formulaire sans remplir aucun champ, dont
le résultat sera une page comme celle de l'illustration 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 l'extrait 8.19. Cela signifie que nous pouvons tester la présence des messages d'erreur en
cherchant une balise div avec un identifiant (id ) "error_explanation" . Un test pour ces étapes est montré
dans l'extrait 8.20.
309
Illustration 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 d'explication de l'erreur de la page de l'illustration 8.11.
<div class="error_explanation" id="error_explanatio n">
<h2>5 erreurs ont empêché votre inscription</h2>
<p>Problème avec ces champs :</p>
<ul>
<li>Nom can't be blank</li>
<li>Email can't be blank</li>
<li>Email is invalid</li>
<li>Password can't be blank</li>
<li>Password is too short (minimum is 6 charact ers)</li>
</ul>
</div>
Extrait 8.20. Tester l'échec de l'inscription.
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 l'aspect naturel du langage dans l'extrait 8.20. Le seul souci est qu'il 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 d'enrouler les étapes de test dans un unique package, et alors de vérifier qu'il
ne modifie pas le nombre d'utilisateurs. Comme nous le voyons dans l'extrait 8.6 et l'extrait 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
l'extrait 8.21 qu'il peut contenir des lignes multiples aussi facilement.
Extrait 8.21. Tester l'échec de l'inscription 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
end
Comme dans l'extrait 8.6, ce code utilise :
should_not change(User, :count )
… pour vérifier que le code à l'intérieur du bloc lambda ne change pas la valeur de User.count
(Table_des_utilisateurs.compter).
Le test d'intégration de l'extrait 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 notre
machinerie d'inscription, au moins en ce qui concerne les échecs de soumission.
8.4.3 Le succès d'une inscription devrait créer un nouvel utilisateur
Nous en arrivons au test d'intégration pour la réussite d'une inscription. Dans ce cas, nous avons besoin de
remplir les champs d'inscription avec des données d'utilisateur valides. Un fois fait, le résultat doit rendre la
page d'affichage de l'utilisateur avec une balise div contenant un message flash de réussite, et ça doit changer le
nombre d'utilisateurs en l'incrémentant de 1 dans la base de données. L'extrait 8.22 montre comment réaliser
cela.
Extrait 8.22. Tester la réussite de l'inscription.
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 => "[email protected]"
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 l'id CSS de la boite
de texte au lieu du label, donc fill_in :user_nom fonctionne aussi157 (c'est particulièrement bienvenue dans
les formulaires n'utilisant pas les labels).
J'espère que vous êtes d'accord 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 => "[email protected]"
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, c'est-à-dire le texte exact que l'utilisateur voit
dans son navigateur ; il n'y a pas besoin de savoir quoi que ce soit sur la structure HTML sous-jacente générée
par l'helper form_for de Rails.
313
Pour finir, nous en arrivons au coup de grâce — tester que la réussite de l'inscription crée bien un nouvel
utilisateur 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 l'extrait 8.21, nous enroulons le code de la réussite d'une inscription dans un bloc lambda . Dans
ce cas, au lieu de nous assurer que le nombre d'utilisateurs ne change pas, nous vérifions qu'il s'incrémente bien
de 1 suite à l'enregistrement de l'utilisateur 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 failures
Avec ça, nos tests d'intégration de l'inscription sont achevés, et nous pouvons être confiant que si les utilisateurs
ne s'inscrivent pas à notre site, ça n'est pas parce que le formulaire d'inscription est défectueux.
8.5 Conclusion
La possibilité pour les utilisateurs de s'inscrire est une étape capitale de notre application. Bien que l'application
exemple n'accomplit encore rien d'utile, nous avons posé ici les bases essentielles pour tout le futur
développement. Dans les deux chapitres suivants, nous achèverons deux étapes tout aussi capitales : d'abord, au
chapitre 9 nous achèverons notre machinerie d'authentification en permettant aux utilisateurs de s'identifier et
de se déconnecter de l'application ; ensuite, au chapitre chapitre 10 nous permettrons à tous les utilisateurs
d'actualiser 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 d'habitude, 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 s'inspirant de l'extrait 8.23, écrivez des tests pour vérifier la présence de chaque champ de saisie
dans le formulaire d'inscription (n'oubliez pas la ligne render_views , qui est essentielle pour que
cela fonctionne).
2. Souvent, les formulaires d'inscription effaceront les champs de mot de passe pour les soumissions
défectueuses, comme le montre l'illustration 8.12. Modifiez l'action create du contrôleur Users
pour répliquer ce comportement. Astuce : Ré-initialisez @user.password .
3. Le flash HTML de l'extrait 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 l'extrait 8.24, qui utilise l'helper Rails
content_tag , fonctionne aussi.
Extrait 8.23. Un template pour tester tous les champs du formulaire d'inscription.
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 pass e"
end
.
.
end
315
Illustration 8.12: Un échec de la soumission du formulaire d'inscription 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 s'inscrire sur notre site (chapitre 8), il est temps de donner à
ces utilisateurs enregistrés la possibilité de se connecter (de s'identifier) et se déconnecter. Cela nous permettra
d'ajouter des personnalisations du layout basées sur l'état de l'internaute visitant le site, utilisateur identifié ou
simple visiteur. Par exemple, dans ce chapitre nous actualiserons l'entête avec les liens d'identification et de
déconnexion et le lien pour rejoindre le profil ; au chapitre 11, nous utiliserons l'identité d'un utilisateur
connecté pour créer des micro-messages associés à cet utilisateur, et au chapitre 12 nous permettrons à
l'utilisateur de suivre les micro-messages d'autres utilisateurs de l'application.
Avoir des utilisateurs connectés nous permettra aussi d'implémenter un modèle de sécurité restreignant l'accès
de certaines pages à des utilisateurs particuliers. Par exemple, comme nous le verrons au chapitre 10, seuls les
visiteurs connectés seront en mesure d'accéder à la page de modification des informations utilisateur. Le
système d'identification (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 qu'un 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 l'utilisateur se déconnecte explicitement.158 Nous
opterons pour la dernière de ces options : quand un utilisateur s'identifie, nous nous souviendrons de son statut
« pour toujours »,159 n'effaçant sa session que lorsqu'il se déconnectera explicitement de lui-même.
Il est pratique de modeler les sessions comme une ressource REST : nous aurons une page d'identification pour
les nouvelles sessions (new), l'identification créera une session (create ), et la déconnexion la détruira
(destroy ). Nous aurons par conséquent besoin d'un 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 l'utilisateur. Le plus gros du travail de l'identification
consiste à construire cette machinerie d'authentification 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 d'identification et
les actions de contrôleur correspondantes (le plus gros ce travail est similaire à l'inscription de l'utilisateur du
317
chapitre 8). Nous terminerons alors l'identification de l'utilisateur avec le code nécessaire manipulant les
cookies à la section 9.3.
9.1.1 Contrôleur Sessions
Les éléments de l'inscription et de l'identification correspondent aux actions REST particulières du contrôleur
Sessions : le formulaire d'identification est traité par l'action new (couverte par cette section), et plus
précisément l'identification est traitée en envoyant une requête POST à l'action create (section 9.2 et
section 9.3), et la déconnexion est traitée en envoyant une requête DELETE à l'action destroy (section 9.4)
(rappelez-vous de l'association des verbes HTTP avec les actions REST de la table 6.2.) Puisque nous savons que
nous avons besoin d'une action new, nous pouvons la créer en générant le contrôleur Session (comme pour le
contrôleur Users de l'extrait 5.23):160
$ rails generate controller Sessions new
$ rm -rf spec/views
$ rm -rf spec/helpers
Maintenant, comme avec le formulaire d'inscription à la section 8.1, nous créons un nouveau fichier pour le
spec du contrôleur Sessions et ajoutons une paire de tests pour l'action new et la vue correspondante
(extrait 9.1) (ce modèle devrait commencer à vous être familier maintenant).
Extrait 9.1. Tests pour l'action 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 => "S'identifier" )
end
318
end
end
Pour faire réussir ces tests, nous avons d'abord besoin d'ajouter une route pour l'action 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
l'exemple de l'extrait 6.26, mais dans ce cas nous définissons seulement les actions particulières dont nous
avons besoin, c'est-à-dire new, create et destroy , et ajoutons aussi les routes nommées pour l'identification
et la déconnexion (extrait 9.2).
Extrait 9.2. Ajout d'une 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 d'options, 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 l'extrait 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 l'extrait 9.2.
319
Nous pouvons obtenir la réussite du second test de l'extrait 9.1 en ajoutant la variable d'instance titre
adéquate à l'action new, comme dans l'extrait 9.3 (qui définit aussi les actions create et destroy pour
référence future).
Extrait 9.3. Ajout du titre pour la page d'identification.
app/controllers/sessions_controller.rb
class SessionsController < ApplicationController
def new
@titre = "S'identifier"
end
def create
end
def destroy
end
end
Avec ça, les tests de l'extrait 9.1 devraient réussir, et nous sommes prêts à construire le formulaire
d'identification.
9.1.2 Formulaire d'identification
Le formulaire d'identification (ou, de façon équivalente, le formulaire de nouvelle session) est similaire en
apparence au formulaire d'inscription, à l'exception prêt que nous n'avons que deux champs (email et mot de
passe) au lieu de quatre. Une maquette est présentée dans l'illustration 9.1.
320
Illustration 9.1: Une maquette du formulaire d'identification.
Rappelez-vous, de l'extrait 8.2, que le formulaire d'inscription utilise l'helper form_for , prenant en argument
la variable d'instance utilisateur @user :
<%= form_for(@user) do | f | %>
.
.
.
<% end %>
La différence principale avec le formulaire de nouvelle session est que nous n'avons 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 d'informations . En particulier, alors
que :
form_for(@user)
… permet à Rails de déduire que action du formulaire devrait être le POST de l'URL /users , dans le cas
des sessions nous avons besoin d'indiquer le nom de la ressource tout comme l'URL 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 d'un
champ pour chacun à l'intérieur du formulaire ; le résultat apparait dans l'extrait 9.4.
321
Extrait 9.4. Code pour le formulaire d'identification.
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 "S'identifier" %>
</div>
<% end %>
<p>Pas encore inscrit ? <%= link_to "S'inscrire !" , signup_path %></p>
Avec le code de l'extrait 9.4, le formulaire d'identification apparait comme dans l'illustration 9.2.
Illustration 9.2: Le formulaire d'identification (/sessions/new).
322
Bien que nous allions bientôt perdre l'habitude 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 d'identification produit par l'extrait 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</lab el><br />
<input id="session_password" name="session[pass word]" size="30"
type="password" />
</div>
<div class="actions">
<input id="session_submit" name="commit" type=" submit" value="S'identifier" />
</div>
</form>
En comparant l'extrait 9.5 avec l'extrait 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 l'email et le mot de passe soumis — est le projet des deux prochaines sections.
9.2 Échec de l'identification
Comme dans le cas de la création d'utilisateurs (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 d'erreur utile dans le cas de l'échec de l'identification
(comme présenté dans la maquette de l'illustration 9.3). Enfin, nous poserons les bases d'une identification
réussie (section 9.3) en évaluant chaque soumission de l'identification en se basant sur la validité de sa
combinaison email/mot de passe.
323
Illustration 9.3: Une maquette de l'échec de l'identification.
9.2.1 Examen de la soumission du formulaire
Commençons par définir une action create minimale pour le contrôleur Sessions (extrait 9.6), qui ne fait rien
d'autre que rendre la vue new. Soumettre le formulaire /sessions/new avec des champs vierges puis
renvoyer le résultat vu dans l'illustration 9.4.
Extrait 9.6. Une version préliminaire de l'action create de Sessions.
app/controllers/sessions_controller.rb
class SessionsController < ApplicationController
.
.
.
def create
render 'new'
end
.
.
.
end
324
Illustration 9.4: L'échec de l'identification initiale, avec create comme dans l'extrait 9.6.
L'inspection attentive des informations de débuggage de l'illustration 9.4 montre que, comme soulevé à la fin de
la section 9.1.2, de la soumission résulte une table params contenant l'email et le mot de passe sous la clé
:session :
--- !map:ActiveSupport::HashWithIndifferentAccess
commit: S'identifier
session: !ActiveSupport::HashWithIndifferentAccess
password: ""
email: ""
authenticity_token: BlO65PA1oS5vqrv591dt9B22HGSWW0H bBtoHKbBKYDQ=
action: create
controller: sessions
Comme dans le cas de l'inscription de l'utilisateur (illustration 8.6) ces paramètres forment une table imbriquée
comme celle que nous avons vue dans l'extrait 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 l'adresse mail soumise et :
params [ :session ][ :password ]
… est le mot de passe soumis.
En d'autres termes, à l'intérieur de l'action create , la table params contient toutes les informations dont nous
avons besoin pour authentifier les utilisateurs par l'email et le mot de passe. Ça n'est pas le fait du hasard, mais
nous 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, notre
stratégie pour l'identification 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 d'erreur et rend le formulaire d' identification.
else
# Authentifie l'utilisateur et redirige vers la pag e d'affichage.
end
end
9.2.2 Échec de l'identification (test et code)
Dans le but de traiter un échec d'identification, d'abord nous avons besoin de déterminer que c'est un échec. Les
tests suivent l'exemple des tests analogues pour l'inscription de l'utilisateur (extrait 8.6), comme vu dans
l'extrait 9.7.
Extrait 9.7. Tests pour un échec d'identification.
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 protected]" , :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 => "S'identifier" )
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 l'application nécessaire pour faire réussir ces tests est présenté dans l'extrait 9.8. Comme promis à la
section 9.2.1, nous extrayons l'adresse mail soumise et le mot de passe de la table params , et les passons
ensuite à la méthode User.authenticate . Si l'utilisateur n'est pas authentifié (c'est-à-dire : si la méthode
d'authentification renvoie nil ), nous définissons le titre et rendons à nouveau le formulaire d'identification.161
Nous traiterons l'autre 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 d'identification.
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 = "S'identifier"
render 'new'
else
# Authentifie l'utilisateur et redirige vers sa pag e d'affichage.
end
end
.
.
.
end
Rappelez-vous de la section 8.4.2 : nous avons affiché les erreurs de l'inscription en utilisant les messages
d'erreur du modèle User. Puisque la session n'est pas un modèle Active Record, cette stratégie ne fonctionnera
pas ici, donc à la place nous déposons un message dans le flash (ou, plus exactement, dans flash.now ; voyez
le 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 . now
Il existe une subtile différence enetre flash et flash.now . La variable flash est conçue pour être utilisée
avant une redirection, et elle persiste sur la page résultante de la-dite requête — c'est-à-dire qu'elle apparait une
fois, disparait une fois, et disparait quand vous cliquez un autre lien. Malheureusement, cela signifie que si nous
ne redirigeons pas la page, et qu'au contraire nous ne faisons que la rendre (comme dans extrait 9.8), le
message flash persiste pour les deux requêtes : il apparait sur la page rendue mais il attend toujours pour une
rediction (c'est-à-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 qu'en redirigeant (redirect ) nous utilisons
flash.now plutôt que flash . L'objet 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
n'attendiez pas, il y a de fortes chances qu'il 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 l'identification
Ayant traité un échec de l'identification, nous avons besoin maintenant, effectivement, de pouvoir authentifier
un utilisateur. La maquette de l'illustration 9.6 donne une idée de là où nous allons — la page de profil de
l'utilisateur, avec des liens de navigation.162 Même si cela n'est 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
jusqu'au 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 l'action create du contrôleur
Sessions est un jeu d'enfant. Malheureusement, c'est aussi une escroquerie.
329
Illustration 9.6: Une maquette du profil utilisateur après une identification réussie (avec des liens de navigation
actualisés).
9.3.1 L'action create achevée
Remplir l'aire non occupée pour le moment par le commentaire d'identification (extrait 9.8) est simple : à la
réussite de l'identification, nous identifions l'utilisateur en utilisant la fonction sign_in et nous le re-dirigeons
vers sa page de profil (extrait 9.9). Nous voyons maintenant pourquoi c'est une escroquerie : hélas, sign_in
n'existe pas pour le moment. L'écrire nous occupera pendant toute cette section.
Extrait 9.9. L'action 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 = "S'identifier"
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 l'identification de l'utilisateur (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 l'utilisateur" do
post :create , :session => @attr
# Remplir avec les tests pour l'identification de l 'utilisateur.
end
it "devrait rediriger vers la page d'affichage de l'ut ilisateur" do
post :create , :session => @attr
response . should redirect_to(user_path(@user))
end
end
end
331
end
Ces tests ne réussissent pas encore, mais c'est une bonne base.
9.3.2 Se souvenir de moi
Nous sommes maintenant en mesure de commencer à implémenter notre modèle d'identification, nommément,
en se souvenant de l'état « pour toujours » de l'identification de l'utilisateur et en effaçant la session seulement
quand l'utilisateur se déconnecte explicitement de lui-même. Les fonctions d'identification elles-mêmes finiront
par franchir la ligne du traditionnel Modèle-Vue-Contrôleur ; en particulier, plusieurs fonctions d'identification
auront 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 c'est ce que nous projetons pour les fonctions d'identification. Nous pourrions faire un
tout nouveau module pour l'authentification, mais le contrôleur Sessions nous arrive déjà équipé d'un 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 l'helper Sessions dans les contrôleurs
est d'inclure 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
end
Par défaut, tous les helpers sont accessibles dans les vues (views ) mais pas dans les contrôleurs. Nous avons
besoin des méthodes de l'helper Sessions aux deux endroits, donc nous devons l'inclure explicitement.
Box 9.2.Sessions et cookies
Parce que HTTP est un protacole sans état, les applications web requiérant l'identification des utilisateurs
implémentent une façon de pister chaque parcours d'utilisateur de page en page. Une technique pour maintenir
l'état de l'identification de l'utilisateur consiste à utiliser la traditionnelle session Rails (via la fonction spéciale
sessions ) pour enregistrer un rappel symbolique (remember_token ) égal à l'identifiant (id ) de
l'utilisateur :
session[:remember_token] = user.id
Cette objet session rend l'id de l'utilisateur accessible de page en page en l'enregistrant dans un cookie qui
expire à la fermeture du navigateur. Sur chaque page, l'application a juste besoin d'appeler :
332
User.find_by_id(session[:remember_token])
… pour récupérer l'utilisateur. Grâce à la façon dont Rails traite les sessions, ce processus est sécurisé ; si un
utilisateur malicieux essaie de « parodier » l'id d'un utilisateur, Rails détectera une discordance en s'appuyant
sur le session id particulier généré pour chaque session.
Pour nos choix de conception de l'application, qui implique des sessions persistantes — c'est-à-dire des états
d'identification qui durent même lorsque le navigateur est fermé — enregistrer l'identifiant de l'utilisateur
constitue un trou de sécurité. Dès que nous cassons le lien entre l'id de session particulier et l'id de l'utilisateur
enregistré, un utilisateur malveillant pourrrait s'identifier comme cet utilisateur avec un remember_token
égal à l'id de l'utilisateur. Pour fixer ce défaut, nous générons un rappel symbolique unique, sécurisé, pour
chaque utilisateur, basé sur l'id et le salt (sel) de l'utilisateur. 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 l'utiliser alors pour s'identifier depuis n'importe quel autre
ordinateur, n'importe quand. Nous résolvons cela en ajoutant un timestamp (une signature de temps) au
rappel symbolique, et ré-initialisons ce rappel chaque fois que l'utilisateur s'identifie sur l'application. Il en
résulte une session persistante essentiellement imperméable à l'attaque.
Nous sommes maintenant prêts pour le premier élément d'identification, la fonction sign_in elle-même.
Notre méthode d'authentification consiste à placer un rappel symbolique (remember token) comme cookie sur
le navigateur de l'utilisateur (Box 9.2), et de l'utiliser ensuite pour trouver l'enregistrement de l'utilisateur dans
la base de données chaque fois que l'utilisateur 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 s'exprimer…
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
L'extrait 9.12 introduit l'utilitaire 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
333
une date optionnelle expires . Par exemple, nous pourrions implémenter l'identification de l'utilisateur
en plaçant un cookie avec une valeur égale à l'id de l'utilisateur qui expire dans vingt ans :
cookies [ :remember_token ] = { :value => user . id,
:expires => 20. years . from_now . utc }
(Ce code utilise l'un des helpers de temps pratique de Rails, comme discuté dans la Box 9.3.) Nous pourrions
alors récupérer l'utilisateur avec un code comme :
User . find_by_id(cookies [ :remember_token ] )
Bien sûr, cookies n'est pas vraiment une table, puisque renseigner un cookie , en fait, enregistre une petite
pièce de texte sur le navigateur (comme vu dans l'illustration 9.7), mais une part de la beauté de Rails est qu'il
vous laisse oublier ce genre de détail et se concentre sur l'écriture de l'application.
Illustration 9.7: Un rappel symbolique sécurisé.
Malheureusement, utiliser l'id de l'utilisateur de cette manière n'est pas très sûr, pour des raisons discutées
dans le Box 9.2 : un utilisateur malveillant pourrait simuler un cookie avec l'id donnée, et ainsi permettre l'accès
du système à n'importe quel utilisateur. La solution traditionnelle avant Rails 3 était de créer un rappel
symbolique sécurisé associé au modèle User à utiliser à la place de l'id de l'utilisateur (voyez par exemple la
version Rails 2.3 du Tutoriel Rails). Cette façon de faire est devenue si courante que Rails 3 l'implémente
maintenant pour nous en utilisant cookies.permanent.signed :
334
cookies . permanent . signed [ :remember_token ] = [ user . id, user . salt ]
L'assignement de la valeur du côté droit est un tableau consistant en un identifiant unique (c'est-à-dire l'id de
l'utilisateur) et une valeur sécurisée utilisée pour créer une signature digitale pour empêcher le genre d'attaque
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 l'expiration à 20.years.from_now , et signed rend le cookie sécurisé, de
telle sorte que l'id de l'utilisateur n'est jamais exposé dans le navigateur (nous verrons comment récupérer
l'utilisateur en utilisant le rappel symbolique à la section 9.3.3).
Le code ci-dessus montre l'importance d'utiliser new_record? dans l'extrait 7.10 pour sauver le sel seulement
à la création de l'utilisateur. Dans le cas contraire, ce salt changerait chaque fois que l'utilisateur est
enregistré, empêchant de récupérer la session de l'utilisateur 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 à n'importe
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 l'extrait 9.12 (qui définit
internalement un cookie qui expire 20.years.from_now ) donne encore un autre exemple de cette pratique à
travers l'un 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 d'autres helpers aussi :
>> 1.kilobyte
=> 1024
>> 5.megabytes
=> 5242880
335
Elles sont utiles pour les validations de téléchargement, rendant plus facile de restreindre, disons, la taille
des images téléchargées à 5.megabytes .
Bien qu'elle doive être utilisée avec précaution, la flexibilité qui autorise d'ajouter des méthodes aux classes
intégrées permet des ajouts extraordinairement naturels à Ruby. En effet, toute l'élégance de Rails découle en
fin de compte de la malléabilité du langage sous-jacent Ruby.
9.3.3 Utilisateur courant
Dans cette section, nous apprendrons comment obtenir et définir la session de l'utilisateur 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
end
Concentrons-nous maintenant sur la deuxième ligne :164
self . current_user = user
Le but de cette ligne est de créer une variable current_user , accessible aussi bien dans les contrôleurs que
dans les vues, ce qui permettra des constructions telles que :
<%= current_user . nom %>
… ou :
redirect_to current_user
Le but principal de cette section est de définir current_user .
Pour décrire le comportement de la machinerie d'identification restant à implémenter, nous allons d'abord
remplir le test pour l'identification de l'utilisateur (extrait 9.13).
336
Extrait 9.13. Remplir le test pour l'identification de l'utilisateur.
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 l'utilisateur" do
post :create , :session => @attr
controller . current_user . should == @user
controller . should be_signed_in
end
it "devrait rediriger vers la page d'affichage de l'ut ilisateur" 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 à l'intérieur des tests Rails) pour
vérifier que la variable current_user est réglée à l'utilisateur identifié, et que l'utilisateur est identifié :
it "devrait identifier l'utilisateur" do
post :create , :session => @attr
controller . current_user . should == @user
controller . should be_signed_in
337
end
La deuxième ligne peut être un peu déroutante à ce stade, mais vous pouvez deviner en vous fondant sur la
convention RSpec pour les méthodes booléenne que :
controller . should be_signed_in
… est équivalent à :
controller . signed_in? . should be_true
C'est une allusion au fait que nous devrons définit une méthode signed_in? qui retourne true (vrai) si un
utilisateur est identifié et false (faux) dans le cas contraire. Plus encore, la méthode signed_in? sera
attachée au contrôleur, pas à l'utilisateur, ce qui explique pourquoi nous écrivons controller.signed_in?
au lieu de current_user.signed_in? (si aucun utilisateur n'est 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 d'assignement, montré
dans l'extrait 9.14.
Extrait 9.14. Définir l'assignement 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 l'assignement de current_user . Son premier argument est la partie droite de l'assignement, dans
ce cas l'utilisateur qui doit être identifié. Le corps d'une ligne de cette méthode définit juste une variable
d'instance @current_user , en enregistrant effectivement l'utilisateur 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 ! N'utilisez 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 l'extrait 9.15, l'état de
l'identification de l'utilisateur serait oublié : dès que l'utilisateur rejoindrait une autre page — poof ! — la session
cesserait et l'utilisateur serait automatiquement déconnecté.
Pour éviter ce problème, nous pouvons trouver la session utilisateur correspondant au cookie créé par le code
de l'extrait 9.12, comme montré dans l'extrait 9.16.
Extrait 9.16. Trouver l'utilisateur 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
end
Ce code utilise plusieurs fonctionnalités plus avancées de Ruby, donc prenons un moment pour les examiner.
D'abord, l'extrait 9.16 utilise l'opérateur d'assignement courant mais pour le moment obscur ||= (« ou égal »)
(Box 9.4). Son effet est de régler la variable d'instance à l'utilisateur correspondant au rappel symbolique, mais
seulement si @current_user n'est pas défini.166 En d'autres 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é, mais
retourne @current_user au cours des invocations suivantes sans appeler user_from_remember_token .167
Box 9.4.Qu'est-ce que c'est que *$@! ||= ?
La construction ||= est très « Ruby-ichienne » —c'est-à-dire qu'elle est hautement caractéristique du langage
Ruby — et donc importante à apprendre si vous projetez de faire plus de programmation Ruby. Bien qu'au
dé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 nombreux
programmes informatiques utilisent l'incré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 d'autres 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 n'importe quel opérateur O.
Une autre pattern courante de Ruby est d'assigner une variable si elle est null (nil ) mais de la laisser telle
quelle dans le cas contraire. En vous souvenant de l'opérateur ou || vu à la section 4.2.3, nous pouvons écrire
cela comme suit :
>> @user
=> nil
>> @user = @user || "l'utilisateur"
=> "l'utilisateur"
>> @user = @user || "un autre utilisateur"
=> "l'utilisateur"
Puisque nil est faux dans un contexte booléen, le premier assignement est nil || "l'utilisateur" , qui
est évalué à "l'utilisateur" ; de façon similaire, le second assignement est "l'utilisateur" || "un
autre utilisateur" , qui est aussi évalué à "l'utilisateur" —puisque les chaines de caractères sont
true (vrai) dans un contexte booléen, la série d'expression || est interrompue dès qu'une première expression
n'est pas nulle (cette pratique d'évaluer les expressions || de gauche à droite et de s'arrê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é d'opé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 ||= "L'utilisateur"
=> "the user"
Et voilà !
L'extrait 9.16 utilise aussi l'opérateur * , qui nous permet d'utiliser un tableau à deux éléments comme argument
pour 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] )
=> 3
La raison pour laquelle c'est nécessaire dans l'extrait 9.16 est que cookies.signed[:remember_me]
retourne un tableau de deux éléments — l'id de l'utilisateur et le salt — mais, (en suivant les conventions de
Ruby) nous voulons que la méthode authenticate_with_salt prenne deux arguments, donc elle peut être
invoquée avec :
User . authenticate_with_salt( id , salt)
(Il n'existe pas de raison fondamentale pour que authenticate_with_salt ne puisse prendre un tableau
comme argument, mais ce ne serait pas idiomatiquement correct en Ruby.)
Enfin, dans la méthode d'helper remember_token définie par l'extrait 9.16, nous utilisons l'opérateur || pour
retourner 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 d'identification est encore jeune, et la
valeur nil pour le cookie occasionne des cassures de test fallacieux. Retourner plutôt [nil, nil] règle ce
problème.168
342
L'étape finale pour obtenir que le code de l'extrait 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 l'extrait 7.12, est montrée dans l'extrait 9.17.
Extrait 9.17. Ajout d'une 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 l'utilisateur par son id unique, et vérifie alors que le
salt enregistré dans le cookie est bien celui de l'utilisateur.
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
343
Dans les deux cas, la méthode retourne l'utilisateur si user est pas nil et si le sel de l'utilisateur
correspond au sel du cookie, et retourne nil dans le cas contraire. D'un autre côté, le code tel que :
(user && user . salt == cookie_salt) ? user : nil
… est courant en Ruby idiomatique correct, donc j'ai pensé que c'était une bonne idée de l'introduire. Ce code
utilise 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 gens
Il y a 10 genres de personnes dans le monde : ceux qui aiment à l'opérateur ternaire, ceux qui ne l'aiment pas, et
ceux qui ne le connaissent pas (si vous faites partie de la troisième catégorie, vous n'y appartiendrez plus
longtemps).
Quand vous faites beaucoup de programmation, vous apprenez rapidement que l'un des blocs de contrôle des
plus courants ressemble à :
if boolean? # Si ça est vrai
faire_une_chose
else # sinon
faire_autre_chose
end # fin
Ruby, 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 l'opérateur ternaire (appelé ainsi parce qu'il est constitué de
trois parties) :
boolean? ? faire_une_chose : faire_autre_chose
Vous pouvez aussi utiliser l'opérateur ternaire pour remplacer l'assignement :
if boolean?
var = foo
else
var = bar
end
… devient alors :
344
var = boolean? ? foo : bar
L'opérateur ternaire est courant en Ruby idiomatique, donc c'est une bonne idée de chercher les opportunités
de l'utiliser.
À ce point où nous en sommes, le test d'identification réussit presque ; la seule chose restant à faire est de
définir la méthode booléenne signed_in? . Heureusement, c'est facile avec l'utilisation de l'opérateur « not »
(pas) ! : un utilisateur est identifié si current_user n'est pas nil (extrait 9.18).
Extrait 9.18. La méthode d'helper signed_in? .
app/helpers/sessions_helper.rb
module SessionsHelper
.
.
.
def signed_in?
! current_user . nil?
end
private
.
.
.
end
Bien qu'elle 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 d'authentification est de garder les utilisateurs identifiés jusqu'à
ce qu'ils 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 d'intégration pour mettre à l'épreuve notre
machinerie de déconnexion.
345
9.4.1 Détruire les sessions
Jusqu'ici, les actions du contrôleur Sessions ont suivi la convention REST en utilisant new pour une page
d'identification et create pour achever l'identification. Nous allons poursuivre sur cette voie en utilisant une
action destroy pour effacer la sessions, c'est-à-dire pour se déconnecter.
Pour tester l'action déconnexion, nous avons d'abord besoin d'un moyen de s'identifier dans le test. La façon la
plus facile de faire ça est d'utiliser l'objet controller vu à la section 9.3.3 et d'utiliser l'helper sign_in pour
identifier l'utilisateur 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 l'extrait 9.19.169
Extrait 9.19. Une fonction test_sign_in pour simuler l'identification de l'utilisateur à l'intérieur des tests.
spec/spec_helper.rb
.
.
.
Rspec . configure do | config |
.
.
.
def test_sign_in(user)
controller . sign_in(user)
end
end
Aprè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 : s'identifier avec un utilisateur (d'usine) et
alors invoquer l'action destroy et vérifier que l'utilisateur 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 l'exigent les conventions REST (Table 9.1).
Comme avec l'identification de l'utilisateur, qui est reliée à la fonction sign_in , la déconnexion de l'utilisateur
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 d'authentification, 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
.
.
.
end
Comme vous pouvez le voir, la méthode sign_out effectivement annule la méthode sign_in en effaçant le
rappel symbolique (remember token ) et en réglant l'utilisateur courant à nil .170
9.4.2 Connexion à l'inscription
En principe, nous en avons fini avec l'authentification, mais telle que se présente l'application actuellement, il
n'y a pas de liens pour les actions d'identification et de déconnexion. Plus encore, les tout nouveaux utilisateurs
enregistrés peuvent être déroutés de ne pas être identifiés par défaut à l'inscription.
Nous allons fixer ce second problème d'abord, en commençant par tester qu'un nouvel utilisateur est
automatiquement identifié (extrait 9.23).
Extrait 9.23. Tester qu'un 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 l'utilisateur" 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 l'utilisateur dans la base de données (extrait 9.24).
Extrait 9.24. Identifier l'utilisateur 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 l'Application 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 d'identification et
d'inscription : nous allons changer les liens du layout selon l'état de l'identification. En particulier, comme le
montre la maquette de l'illustration 9.6, nous allons faire en sorte que les liens changent suivant que
349
l'utilisateur est identifié ou déconnecté, et nous allons ajouter aussi un lien vers la page de profil pour un
utilisateur identifié.
Nous commençons avec les deux tests d'intégration : un pour vérifier que le lien "S'inscrire" est visible
pour un utilisateur non identifié, et un pour vérifier que le lien Déconnexion soit visible pour un utilisateur
identifié ; ces deux cas vérifient que le lien conduise à l'URL appropriée. Nous placerons ces tests dans le test
des liens du layout que nous avons créé à la section 5.2.1 ; le résultat apparait dans l'extrait 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 => "S'identifier" )
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 d'identification et en soumettant une paire email/mot
de passe valide.171 Nous faisons cela plutôt que d'utiliser la fonction test_sign_in de l'extrait 9.19 parce que
test_sign_in ne fonctionne pas à l'intérieur des tests d'intégration pour certaines raisons (voir la section 9.6
pour un exercice pour construire une fonction integration_sign_in à l'usage des tests d'intégration).
Le code de l'application utilise une structure de branchement si-alors à l'intérieur du code Ruby embarqué, en
utilisant la méthode signed_in? définie dans l'extrait 9.18 :
<% if signed_in? %>
<li><%= link_to "Déconnexion" , signout_path, :method => :delete %></li>
<% else %>
<li><%= link_to "S'identifier" , signin_path %></li>
<% end %>
Remarquez que le lien de déconnexion passe un argument table indiquant qu'il devrait soumettre avec une
requête HTTP DELETE.172 Avec ce fragment ajouté, l'entête complète du partiel apparait comme dans
l'extrait 9.26.
Extrait 9.26. Changer les liens du layout links pour l'utilisateur identifié.
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>
<% if signed_in? %>
<li><%= link_to "Déconnexion" , signout_path, :method => :delete %></li>
<% else %>
<li><%= link_to "S'identifier" , signin_path %></li>
<% end %>
</ul>
</nav>
</header>
351
Dans l'extrait 9.26 nous avons utilisé l'helper logo des exercices du chapitre 5 (section 5.5) ; dans le cas
où vous n'auriez pas fait cet exercice, la réponse apparait dans l'extrait 9.27.
Extrait 9.27. Un helper pour le logo du site.
app/helpers/application_helper.rb
module ApplicationHelper
.
.
.
def logo
image_tag( "logo.png" , :alt => "Application Exemple" , :class => "round" )
end
end
Enfin, ajoutons le lien vers le profil. Le test (extrait 9.28) et le code de l'application (extrait 9.29) sont tous deux
extrêmement simples. Remarquez que l'URL du lien vers le profil est simplement current_user ,173 qui est
notre première utilisation de cette méthode utile (ça ne sera pas la dernière).
Extrait 9.28. Un test pour le lien du profil.
spec/requests/layout_links_spec.rb
describe "Liens du layout" do
.
.
.
describe "quand identifié" do
.
.
.
it "devrait avoir un lien vers le profil" do
visit root_path
response . should have_selector( "a" , :href => user_path(@user),
:content => "Profil" )
end
end
end
Extrait 9.29. Ajout du lien vers le profil.
app/views/layouts/_header.html.erb
<header>
352
<%= link_to logo, root_path %>
<nav class="round">
<ul>
<li><%= link_to "Accueil" , root_path %></li>
<% if signed_in? %>
<li><%= link_to "Profil" , current_user %></li>
<% end %>
<li><%= link_to "Aide" , help_path %></li>
<% if signed_in? %>
<li><%= link_to "Déconnexion" , signout_path, :method => :delete %></li>
<% else %>
<li><%= link_to "S'identifier" , signin_path %></li>
<% end %>
</ul>
</nav>
</header>
Avec le code de cette section, un utilisateur identifié voit maintenant les deux liens de déconnexion et de profil,
comme voulu (illustration 9.8).
Illustration 9.8: Un utilisateur identifié avec les liens déconnexion et profil (version anglaise).
353
9.4.4 Test d'intégration pour l'identification et la déconnexion
Pierre angulaire de notre dur labeur sur l'authentification, nous allons finir avec les tests d'intégration pour
l'identification et la déconnexion (placé dans le fichier users_spec.rb pour le côté pratique). Le testing
d'intégration RSpec est suffisamment expressif pour que l'extrait 9.30 ne demande qu'une toute petite
explication ; j'aime spécialement l'utilisation de click_link "Déconnexion" , qui ne fait pas que simuler le
clic sur le lien de déconnexion dans le navigateur mais produit également une erreur si ce lien n'existe pas —
testant ainsi l'URL, la route nommée, le texte du lien et le changement des liens dans le laxyout en une seule
ligne. Si ça n'est pas un test d'integration, je ne sais pas ce que c'est !
Extrait 9.30. Un test d'intégration pour l'identification et la déconnxion.
spec/requests/users_spec.rb
require 'spec_helper'
describe "Users" do
describe "signup" do
.
.
.
end
describe "identification/déconnexion" do
describe "l'échec" do
it "ne devrait pas identifier l'utilisateur" do
visit signin_path
fill_in "eMail" , :with => ""
fill_in "Mot de passe" , :with => ""
click_button
response . should have_selector( "div.flash.error" , :content => "Invalid" )
end
end
describe "le succès" do
it "devrait identifier un utilisateur puis le déconnec ter" do
user = Factory( :user )
visit signin_path
354
fill_in "eMail" , :with => user . email
fill_in "Mot de passe" , :with => user . password
click_button
controller . should be_signed_in
click_link "Déconnexion"
controller . should_not be_signed_in
end
end
end
end
9.5 Conclusion
Nous avons couvert un grand nombre de points dans ce chapitre, transformant notre application prometteuse
mais informe en site capable d'accomplir une pleine suite de registration et d'identification. Tout ce qu'il reste à
faire pour achever l'authentification est de restreindre l'accès aux pages en s'appuyant sur l'état de
l'identification et l'identité de l'utilisateur. Nous accomplirons ces tâches tout en donnant à l'utilisateur la
possibilité de modifier ses informations et en donnant aux administrateurs la faculté de supprimer des
utilisateurs.
Mais avant de poursuivre, fusionnons nos changements dans la branche maitresse de notre repository :
$ git add .
$ git commit -m "Fini avec l'identification et la deconnexion"
$ git checkout master
$ git merge sign-in-out
9.6 Exercises
Le deuxième et troisième exercices sont plus difficiles que d'habitude. Les résoudre nécessitera des recherches
externes (par exemple la consultation de l'API Rails et des recherches Google), mais ils peuvent être sautés sans
perte de la continuité du livre.
1. Plusieurs des spécifications d'intégration utilisent le même code pour identifier un utilisateur.
Remplacez ce code par une fonction integration_sign_in dans l'extrait 9.31 et vérifiez que les
tests continuent de réussir.
355
2. Utilisez la session plutôt que les cookies de telle sorte que les utilisateurs puissent être
automatiquement déconnectés quand ils ferment leur navigateur..174 Astuce : faites une recherche
Google sur les termes « Rails session ».
3. (avancé) Certains sites utilisent une connexion sécurisée (HTTPS) pour leurs pages d'identification.
Cherchez en ligne pour apprendre comment utiliser HTTPS en Rails, et sécurisez ensuite les actions
new et create du contrôleur Sessions. Challenge supplémentaire : écrivez des tests pour les
fonctionnalités HTTPS (note : je vous suggère de faire cet exercice seulement en mode
développement, ce qui ne nécessitera pas d'obtenir un certificat SSL ou de régler la machinerie de
cryptage SSL. En réalité, déployer un site « SSL-enabled » est beaucoup plus difficile).
Extrait 9.31. Une fonction pour identifier les utilisateurs à l'intérieur des tests d'intégration.
spec/spec_helper.rb
.
.
.
Rspec . configure do | config |
.
.
.
def test_sign_in(user)
controller . sign_in(user)
end
def integration_sign_in(user)
visit signin_path
fill_in "eMail" , :with => user . email
fill_in "Mot de passe" , :with => user . password
click_button
end
end
356
Chapitre 10Actualiser, afficher et supprimer des utilisateurs Dans ce chapitre, nous allons compléter les actions REST de la ressource Users (« utilisateurs ») (table 6.2) en
ajoutant les action edit (« éditer »), update (« actualiser »), index (« lister »), et destroy (« détruire »).
Nous commencerons par donner la possibilité aux utilisateurs d'actualiser leur profil, ce qui donnera l'occasion
de produire un modèle sécurisé (rendu possible par le code d'authentification dans le chapitre 9). Puis nous
ferons un listing de tous les utilisateurs (nécessitant l'identification de l'utilisateur) ce qui nous amènera à
introduire la notion de « données exemple » et de pagination. Enfin, nous ajouterons la possibilité de
supprimer des utilisateurs en les retirant de la base de données. Puisque nous ne pouvons permettre à
n'importe quel utilisateur d'avoir de tels pouvoirs, nous profiterons de l'occasion pour créer la classe privilégiée
d'utilisateur-administrateur (admins).
Avant de nous mettre au travail, commençons par initier une nouvelle branche développement : updating-
users :
$ git checkout -b updating-users
10.1 Actualisation
La façon d'éditer les informations de l'utilisateur est très proche de celle pour les créer (chapitre 8). Au lieu
d'une action new rendant une vue pour des nouveaux utilisateurs, nous avons une action edit rendant une vue
pour éditer les utilisateurs ; à la place d'une action create répondant à une requête POST, nous avons une action update répondant à une requête PUT (Box 3.1). La différence notoire est que, entendu que n'importe
qui peut s'inscrire, seul l'utilisateur courant doit être en mesure d'actualiser ses informations. Cela signifie que
nous devons renforcer le contrôle d'accès afin que seuls l'utilisateur autorisé puisse accomplir les actions
d'édition et d'actualisation ; le mécanisme d'authentification du chapitre 9 va nous permettre d'utiliser un
before filter (un filtre « passe-avant ») pour remplir cette fonction.
10.1.1 Formulaire d'édition
Nous commençons avec des tests pour le formulaire d'édition, dont la maquette apparait dans
l'illustration 10.1.175 Les deux sont analogues aux tests que nous avons vus pour la page de nouvel utilisateur
(extrait 8.1), qui vérifie la réponse appropriée et le titre ; le troisième test s'assure qu'il y ait un lien pour éditer
l'image Gravatar de l'utilisateur (section 7.3.2). Si vous fouinez dans le site de Gravatar, vous verrez que la page
pour ajouter ou modifier l'image est (ce qui peut sembler quelque peu étrange) à l'adresse
http://gravatar.com/emails , donc nous testons la page edit page pour un lien possédant
cette URL.176 Le résultat est montré dans extrait 10.1.
357
Illustration 10.1: Maquette de la page d'édition de l'utilisateur (version anglaise).
Extrait 10.1. Tests pour l'action edit de l'utilisateur.
spec/controllers/users_controller_spec.rb
require 'spec_helper'
describe UsersController do
render_views
.
.
.
describe "GET 'edit'" do
before( :each ) do
@user = Factory( :user )
test_sign_in(@user)
end
it "devrait réussir" do
get :edit , :id => @user
response . should be_success
end
it "devrait avoir le bon titre" do
get :edit , :id => @user
358
response . should have_selector( "title" , :content => "Édition profil" )
end
it "devrait avoir un lien pour changer l'image Gravata r" do
get :edit , :id => @user
gravatar_url = "http://gravatar.com/emails"
response . should have_selector( "a" , :href => gravatar_url,
:content => "changer" )
end
end
end
Ici, nous nous sommes assurés d'utiliser la méthode test_sign_in(@user) pour s'identifier en tant
qu'utilisateur, anticipant par là-même l'accès protégé à la page d'édition (section 10.2). Dans le cas contraire,
ces tests seraient invalides dès que nous implémenterions notre code d'authentification.
Notez, en observant la table 6.2 que l'URL correct pour la page d'édition d'un utilisateur est
/users/1/edit (qui suppose que l'identifiant — id — de l'utilisateur est 1). Souvenez-vous que
l'identifiant — id — de l'utilisateur se trouve dans la variable params[:id] , ce qui signifie que nous pouvons
trouver l'utilisateur avec le code du extrait 10.2. Il utilise find (trouver) pour trouver l'utilisateur concerné
dans la base de données, et renseigne alors la variable titre — @titre .
Extrait 10.2. L'action edit de l'utilisateur.
app/controllers/users_controller.rb
class UsersController < ApplicationController
.
.
.
def edit
@user = User . find(params [ :id ] )
@titre = "Édition profil"
end
end
Pour que le test soit valide, il faut créer la vue d'édition, montrée dans l'extrait 10.3. Remarquez comme ce code
s'apparente à celui de la vue du nouvel utilisateur de l'extrait 8.2 ; la grande similitude suggère de reconstruire
la code à l'aide des partiels, ce que vous pourrez faire à titre d'exercice (section 10.6).
359
Extrait 10.3. La vue d'édition de l'utilisateur.
app/views/users/edit.html.erb
<h1>Edition du profil</h1>
<%= form_for(@user) do | f | %>
<%= render 'shared/error_messages' , :object => f . object %>
<div class="field">
<%= f . label :name, "Nom" %><br />
<%= f . text_field :name %>
</div>
<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="field">
<%= f . label :password_confirmation, "Confirmation mot de passe" , "Confirmation" %><br />
<%= f . password_field :password_confirmation %>
</div>
<div class="actions">
<%= f . submit "Update" %>
</div>
<% end %>
<div>
<%= gravatar_for @user %>
<a href="http://gravatar.com/emails">changer</a>
</div>
Ici, nous avons ré-utilisé le partiel partagé error_messages introduit à la section 8.2.3.
Nous pouvons nous rappeler du extrait 8.8 que le partiel « error-messages » fait référence explicitement à la
variable d'instance @user. Dans le cas présent, nous sommes censés avoir une variable @user, mais dans le but
360
d'obtenir vraiment un partiel partagé, nous ne devons pas le rendre dépendant de l'existence de cette variable.
La solution consiste à passer au partiel, en paramètre, un objet correspondant à la variable formulaire f :
<%= render 'shared/error_messages' , :object => f . object %>
Cela crée une variable appelée object dans la partielle, qui peut être alors utilisée pour générer les messages
d'erreur, comme le montre le extrait 10.4 (notez l'élégance de la chaine de méthodes qui produit une bonne
version du nom de l'objet ; voyez l'entrée de l'API Rails pour, disons, humanize , pour avoir une idée des
nombreuses méthodes utilitaires de Rails).
Extrait 10.4. Actualisation du partiel des messages d'erreur de l'extrait 8.9 pour qu'il fonctionne avec
d'autres objets.
app/views/shared/_error_messages.html.erb
<% if object . errors . any? %>
<div id="error_explanation">
<h2><%= pluralize(object . errors . count, "error" ) %>
prohibited this <%= object . class . to_s . underscore . humanize . downcase %>
from being saved:</h2>
<p>There were problems with the following field s:</p>
<ul>
<% object . errors . full_messages . each do | msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
Pendant que nous y sommes, nous allons actualiser le formulaire d'inscription avec ce code (extrait 10.5).
Extrait 10.5. Actualisation du rendu des messages d'erreur de l'inscription des utilisateurs.
app/views/users/new.html.erb
<h1>Inscription</h1>
<%= form_for(@user) do | f | %>
<%= render 'shared/error_messages' , :object => f . object %>
.
.
361
.
<% end %>
Nous allons également ajouter un lien dans la partie navigation du site pour la page d'édition de l'utilisateur
(que nous appellerons « Profil »), sur la base de la maquette de l'illustration 10.2177 et montré dans l'extrait 10.6.
Figure 10.2: Maquette de la page d'édition de l'utilisateur avec un lien « Profil » (version anglaise).
Extrait 10.6. Ajout d'un lien Profil.
app/views/layouts/_header.html.erb
<header>
<%= link_to logo, root_path %>
<nav class="round">
<ul>
<li><%= link_to "Accueil" , root_path %></li>
<% if signed_in? %>
<li><%= link_to "Profil" , edit_user_path(current_user) %></li>
<% end %>
.
.
.
</ul>
</nav>
</header>
362
Ici nous utilisons la route de nom edit_user_path de la table 6.2, avec la méthode pratique de l'helper
current_user définie dans le extrait 9.16.
Avec la variable d'instance @user de l'extrait 10.2, les tests du extrait 10.1 réussiront. Comme cela est montré
dans l'illustration 10.3, le rendu de la page edit , bien qu'elle ne fonctionne pas encore.
Figure 10.3: Edition des caractéristiques de l'utilisateur (/users/1/edit ).
En regardant le code HTML source de l'illustration 10.3, nous voyons la balise de formulaire attendue
(extrait 10.7).
Extrait 10.7. Code HTML pour le formulaire d'édition défini dans l'extrait 10.3 et affiché dans
l'illustration 10.3.
<form action="/users/1" class="edit_user" id="edit_ user_1" method="post">
<input name="_method" type="hidden" value="put" / >
.
.
.
</form>
Notez ici le champ caché (input class="hidden")
<input name="_method" type="hidden" value="put" />
363
Puisque les navigateurs internet ne peuvent pas, de façon native, envoyer des requêtes PUT (requises par les conventions REST conventions de la table 6.2), Rails la simule avec une requête POST et un champ caché
(hidden input ).178
Il y a une autre subtilité à noter ici : le code form_for(@user) dans l'extrait 10.3 est très exactement
identique au code dans l'extrait 8.2 — donc comment fait Rails pour utiliser la requête POST pour de nouveaux utilisateurs et la requête PUT pour éditer ces utilisateurs ? La réponse est qu'il est possible de dire si
un utilisateur est nouveau ou s'il existe déjà dans la base de données via la méthode booléenne new_record?
(nouvel_enregistrement) (que nous avons vu brièvement dans l'extrait 7.10) :
$ rails console
>> User . new. new_record?
=> true
>> User . first . new_record?
=> false
Quand nous construisons un formulaire en utilisant form_for(@user) , Rails utilise la requête POST si @user.new_record? est vrai (true ) et la requête PUT si la méthode renvoie false .
10.1.2 Permettre l'édition
Bien que le formulaire d'édition ne fonctionne pas encore, nous avons implémenté le téléchargement de l'image
Gravatar, donc il est déjà possible en cliquant sur le lien « changer » de l'illustration 10.3, comme montré dans
l'illustration 10.4. Mettons en place les autres fonctionnalités de l'édition de l'utilisateur.
364
Illustration 10.4: L'interface d'ajustement de Gravatar, avec l'image d'un mec.
Les tests pour l'action update (actualisation) sont similaires à ceux de l'action create (Créer). En particulier,
nous testons l'échec de l'actualisation et son succès (extrait 10.8) (ça fait beaucoup de code ; voyons si vous
pouvez l'analyser en vous référant aux tests du chapitre 8.)
Extrait 10.8. Tests pour l'action update de l'utilisateur.
spec/controllers/users_controller_spec.rb
describe UsersController do
render_views
.
.
describe "PUT 'update'" do
before( :each ) do
@user = Factory( :user )
test_sign_in(@user)
end
describe "Échec" do
before( :each ) do
@attr = { :email => "" , :nom => "" , :password => "" ,
:password_confirmation => "" }
end
it "devrait retourner la page d'édition" do
put :update , :id => @user, :user => @attr
response . should render_template( 'edit' )
end
it "devrait avoir le bon titre" do
put :update , :id => @user, :user => @attr
response . should have_selector( "title" , :content => "Édition profil" )
end
end
365
describe "succès" do
before( :each ) do
@attr = { :nom => "New Name" , :email => "[email protected]" ,
:password => "barbaz" , :password_confirmation => "barbaz" }
end
it "devrait modifier les caractéristiques de l'utilisa teur" do
put :update , :id => @user, :user => @attr
@user . reload
@user . nom. should == @attr [ :nom ]
@user . email . should == @attr [ :email ]
end
it "devrait rediriger vers la page d'affichage de l'ut ilisateur" do
put :update , :id => @user, :user => @attr
response . should redirect_to(user_path(@user))
end
it "devrait afficher un message flash" do
put :update , :id => @user, :user => @attr
flash [ :success ]. should =~ /actualisé/
end
end
end
end
La seule nouveauté ici est la méthode reload (rechargement) qui apparait dans le test pour le changement des
caractéristiques de l'utilisateur :
it "devrait modifier les caractéristiques de l'utilisa teur" do
@user . reload
@user . nom. should == @attr [ :nom ]
@user . email . should == @attr [ :email ]
end
366
Ce code recharge la variable @user de la base de données (de test) en utilisant @user.reload , et vérifie alors
que le nouveau nom de l'utilisateur et son adresse mail correspondent aux valeurs de la table @attr .
L'action update (actualiser) nécessaire pour réussir les tests de l'extrait 10.8 est similaire au formulaire final
de l'action create (créer) (extrait 9.24), comme nous l'avons vu dans l'extrait 10.9.
Extrait 10.9. L'action utilisateur update (actualiser).
app/controllers/users_controller.rb
class UsersController < ApplicationController
.
def update
@user = User . find(params [ :id ] )
if @user . update_attributes(params [ :user ] )
flash [ :success ] = "Profil actualisé."
redirect_to @user
else
@titre = "Édition profil"
render 'edit'
end
end
end
Avec cela, la page d'édition de l'utilisateur devrait fonctionner. Dans la forme actuelle, chaque édition demande
à l'utilisateur de confirmer le mot de passe à nouveau (comme requis par le champ de saisie vue dans
l'illustration 10.3), ce qui rend les actualisations plus sécurisées, mais reste quelque peu contraignant.
10.2 Protection des pages
Bien que les actions edit (éditer) et update (actualiser) de la section 10.1 soient fonctionnelles, elles souffrent
d'un vice de sécurité ridicule : elles permettent à n'importe qui (même des utilisateurs non identifiés) d'accéder
à ces actions, et n'importe quel utilisateur identifié peut modifier les informations de n'importe quel autre
utilisateur. Dans cette section, nous allons implémenter un modèle de sécurité qui requiert des utilisateurs
qu'ils soient identifiés et qui les empêchent d'actualiser d'autres informations que les leurs. Les utilisateurs qui
ne sont pas identifiés ou qui tentent d'atteindre des pages protégées seront redirigés vers la page
d'identification, avec un message d'aide, comme le montre la maquette de l'illustration 10.5.
367
Figure 10.5: Maquette du résultat de la tentative d'accès à une page protégée (version anglaise)
10.2.1 L'identification requise de l'utilisateur
Puisque les restrictions de sécurité des actions edit et update sont identiques, nous les définirons dans un
bloc RSpec describe unique. En commençant par vérifier que l'utilisateur est identifié, nos premiers tests
vérifient qu'un utilisateur non identifié qui essaie d'utiliser de mauvaises actions soit simplement redirigé vers
la page d'identification, comme nous pouvons le voir dans l'extrait 10.10.
Extrait 10.10. Premiers tests d'authentification.
spec/controllers/users_controller_spec.rb
describe UsersController do
render_views
.
.
.
describe "authentification des pages edit/update" do
before( :each ) do
@user = Factory( :user )
end
describe "pour un utilisateur non identifié" do
it "devrait refuser l'acccès à l'action 'edit'" do
368
get :edit , :id => @user
response . should redirect_to(signin_path)
end
it "devrait refuser l'accès à l'action 'update'" do
put :update , :id => @user, :user => {}
response . should redirect_to(signin_path)
end
end
end
end
Le code de l'application permettra de réussir ces tests en utilisant un filtre « passe-avant » (before filter), qui
permet d'appeler une méthode particulière avant d'appeler les actions données. Dans ce cas, nous définissons
une méthode authenticate et nous l'invoquons en utilisant before_filter :authenticate , comme
montré dans le extrait 10.11.
Extrait 10.11. Ajout d'un filtre « passe-avant » authenticate .
app/controllers/users_controller.rb
class UsersController < ApplicationController
before_filter :authenticate , :only => [ :edit , :update ]
.
.
.
private
def authenticate
deny_access unless signed_in?
end
end
Par défaut, les filtres « passe-avant » s'applique à toute action du contrôleur, donc ici nous restreignons l'effet
du filtre aux actions :edit et :update en renseignant l'attribut :only dans la table des options.
Ce code ne fonctionne pas encore, parce que la méthode deny_access (refuser l'accès) n'a pas été encore
définie. Puisque le refus de l'accès est une partie de l'authentification, nous allons implémenter cette méthode
dans l'helper du fichier Sessions du chapitre 9. Tout ce que fait deny_access , c'est placer un message dans
flash[:notice] et rediriger vers la page d'identification (extrait 10.12).
369
Extrait 10.12. La méthode deny_access pour l'authentification de l'utilisateur.
app/helpers/sessions_helper.rb
module SessionsHelper
.
.
def deny_access
redirect_to signin_path, :notice => "Merci de vous identifier pour rejoindre cette page."
end
.
.
.
end
Notez ici que le extrait 10.12 utilise un raccourci d'écriture pour renseigner l'attribut flash[:notice] en
passant une table « options » à la fonction redirect_to. Le code dans le extrait 10.12 est équivalent à celui plus
verbeux :
flash [ :notice ] = "Merci de vous identifier pour rejoindre cette page ."
redirect_to signin_path
(La même syntaxe fonctionne pour la clé :error , mais pas pour la clé :success .)
Associée à :success et :error , la clé :notice complète notre triumvirat des styles flash , tous supportés
nativement par les feuilles de styles Blueprint CSS. En se déconnectant et en essayant d'atteindre la page
d'édition de l'utilisateur /users/1/edit , nous pouvons voir s'afficher la boite jaune du message
"notice" , comme dans l'illustration 10.6.
370
Illustration 10.6: Le formulaire d'identification après la tentative d'accès à une page protégée (version
anglaise).
10.2.2 Nécessité du bon utilisateur
Bien entendu, qu'un utilisateur soit identifié n'est pas suffisant ; les utilisateurs ne doivent être autorisés qu'à
modifier leur propres informations. Nous pouvons tester cela d'abord en identifiant un utilisateur incorrect
puis en invoquant les actions edit et update (extrait 10.13). Notez que, puisque les utilisateurs ne devraient
même pas essayer d'éditer le profil d'un autre utilisateur, nous ne les redirigerons pas vers la page
d'identification mais vers l'accueil du site (l'url racine, root url).
Extrait 10.13. Test d'authentification pour les utilisateurs identifiés.
spec/controllers/users_controller_spec.rb
describe UsersController do
render_views
.
.
.
describe "authentification pour les pages edit/update" do
.
.
.
describe "pour un utilisateur identifié" do
371
before( :each ) do
wrong_user = Factory( :user , :email => "[email protected]" )
test_sign_in(wrong_user)
end
it "devrait correspondre à l'utilisateur à éditer" do
get :edit , :id => @user
response . should redirect_to(root_path)
end
it "devrait correspondre à l'utilisateur à actualiser" do
put :update , :id => @user, :user => {}
response . should redirect_to(root_path)
end
end
end
end
Le code de l'application est simple : nous ajoutons un second filtre « passe-avant » pour appeler la méthode
correct_user (que nous devons encore écrire), comme montré dans le extrait 10.14.
Extrait 10.14. Un filtre « passe-avant » correct_user pour protéger les pages d'édition (edit) et
d'actualisation (update).
app/controllers/users_controller.rb
class UsersController < ApplicationController
before_filter :authenticate , :only => [ :edit , :update ]
before_filter :correct_user , :only => [ :edit , :update ]
.
.
.
def edit
@titre = "Édition profil"
end
.
.
.
private
372
def authenticate
deny_access unless signed_in?
end
def correct_user
@user = User . find(params [ :id ] )
redirect_to(root_path) unless current_user?(@ user)
end
end
Ce code utilise la méthode current_user? , que nous définissons (comme la méthode deny_access ) dans
l'helper Sessions (extrait 10.15).
Extrait 10.15. La méthode current_user? .
app/helpers/sessions_helper.rb
module SessionsHelper
.
.
.
def current_user?(user)
user == current_user
end
def deny_access
redirect_to signin_path, :notice => "Merci de vous identifier pour rejoindre cette page."
end
private
.
.
.
end
L'extrait 10.14 montre aussi l'actualisation de l'action edit . Auparavant, dans l'extrait 10.2, nous avions :
def edit
@user = User . find(params [ :id ] )
373
@titre = "Édition profil"
end
Mais maintenant que le filtre « passe-avant » correct_user définit la variable @user nous pouvons l'omettre
dans l'action edit (et, de la même manière, dans l'action update ).
10.2.3 Redirection conviviale
Notre page de protection est complète, mais il demeure un défaut mineur : quand les utilisateurs tentent
d'accéder à une page protégée, ils sont pour le moment redirigés à leur page de profil, sans considération pour
la page qu'ils tentaient d'atteindre. En d'autres mots, si un utilisateur non identifié essaie de visiter sa page
d'édition, après s'être identifié, il sera redirigé vers /users/1 (l'affichage de son profil) au lieu de
/users/1/edit (sa page d'édition). Il serait plus convivial de les rediriger vers leur destination voulue.
La séquence de visite d'une certaine page — identification et redirection vers la page voulue — est le travail
parfait pour un test d'intégration, donc élaborons-en un pour la redirection convivial :
$ rails generate integration_test friendly_forwardin g
Le code apparait alors comme dans le extrait 10.16.
Extrait 10.16. Test d'intégration pour la redirection conviviale.
spec/requests/friendly_forwardings_spec.rb
require 'spec_helper'
describe "FriendlyForwardings" do
it "devrait rediriger vers la page voulue après identi fication" do
user = Factory( :user )
visit edit_user_path(user)
# Le test suit automatiquement la redirection vers la page d'identification.
fill_in :email , :with => user . email
fill_in :password , :with => user . password
click_button
# Le test suit à nouveau la redirection, cette fois vers users/edit.
response . should render_template( 'users/edit' )
end
end
374
(Comme indiqué dans les commentaires, le test d'intégration suit les redirections, donc tester que la réponse
devrait rediriger (should redirect_to ) vers telle ou telle URL ne fonctionnera pas. Je l'ai appris en
en faisait l'expérience.)
Voyons maintenant l'implémentation.179 Dans le but de rediriger les utilisateurs vers leur destination désirée,
nous avons besoin de consigner quelque part leur destination, et de les rediriger vers cette destination ensuite.
Le mécanisme de consignation est une des facilités proposées par Rails avec la session , qui peut être pensé un
peu comme l'instance de variable cookie de la section 9.3.2 qui expire automatiquement quand le navigateur
est refermé.180 Nous utilisons aussi l'objet requête (request ) pour obtenir la request_uri , par exemple l'URL
de la page requise. Le code d'application résultant est montré dans le extrait 10.17.
Extrait 10.17. Code pour implémenter la redirection conviviale.
app/helpers/sessions_helper.rb
module SessionsHelper
.
.
def deny_access
store_location
redirect_to signin_path, :notice => "Please sign in to access this page."
end
def redirect_back_or(default)
redirect_to(session [ :return_to ] || default)
clear_return_to
end
private
.
.
def store_location
session [ :return_to ] = request . fullpath
end
def clear_return_to
session [ :return_to ] = nil
end
end
375
Ici nous avons ajouté une ligne à la méthode deny_access , d'abord pour ajouter le chemin complet de la
requête avec la méthode store_location et nous procédons ensuite comme avant. La méthode
store_location place la requête URL dans la variable session avec pour clé :return_to . (Nous avons
rendu les deux méthodes store_location et clear_return_to privées puisque elles ne sont jamais
nécessaires en dehors de l'helper Sessions.)
Nous avons aussi défini la méthode redirect_back_or pour rediriger vers la requête URL si elle existe (si
elle est définie), ou vers l'URL par défaut dans le cas contraire. Cette méthode est utilisé dans l'action create
(« créer ») du contrôleur de Sessions pour procéder à la redirection en cas d'identification réussie
(extrait 10.18).
Extrait 10.18. L'action create de Sessions avec une redirection conviviale.
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 mail/mot de passe invalide."
@titre = "Sign in"
render 'new'
else
sign_in user
redirect_back_or user
end
end
.
.
.
end
Avec ça, le test d'intégration de la redirection conviviale dans le extrait 10.16 devrait réussir, et l'implémentation
de l'authentification basique de l'utilisateur et de la protection de page est achevée.
376
10.3 Affichage des utilisateurs
Dans cette section nous allons ajouter la dernière action utilisateur, l'action index destinée à afficher tous les
utilisateurs, pas seulement un. Chemin faisant, nous apprendrons à peupler une base de données avec des
exemples d'utilisateurs et à paginer (paginating) la sortie pour l'utilisateur, pour que la page puisse s'adapter à
l'affichage éventuel d'un très grand nombre d'utilisateurs. Une maquette du résultat — utilisateurs, liens de
paginations, et un lien de navigation « Utilisateurs » — est montré dans l'illustration 10.7.181 Dans la
section 10.4, nous ajouterons une interface administrateur à l'index de l'utilisateur pour empêcher les
utilisateurs d'être détruits (ce qui peut être problématique).
Illustration 10.7: Maquette du listing de l'utilisateur, avec pagination et lien de navigation « Utilisateurs »
(version anglaise).
10.3.1 Liste des utilisateurs
Bien que nous gardions visible la page d'affichage d'un utilisateur à tout visiteur du site, la liste de tous les
utilisateurs sera réservée au seuls utilisateurs identifiés. Nos tests d'index le vérifieront, et vérifieront aussi
que tous les utilisateurs soient listés pour un utilisateur identifié (extrait 10.19).
Extrait 10.19. Test pour la page d'index des utilisateurs.
spec/controllers/users_controller_spec.rb
require 'spec_helper'
describe UsersController do
render_views
377
describe "GET 'index'" do
describe "pour utilisateur non identifiés" do
it "devrait refuser l'accès" do
get :index
response . should redirect_to(signin_path)
flash [ :notice ]. should =~ /identifier/i
end
end
describe "pour un utilisateur identifié" do
before( :each ) do
@user = test_sign_in(Factory( :user ))
second = Factory( :user , :email => "[email protected]" )
third = Factory( :user , :email => "[email protected]" )
@users = [ @user, second, third ]
end
it "devrait réussir" do
get :index
response . should be_success
end
it "devrait avoir le bon titre" do
get :index
response . should have_selector( "title" , :content => "Liste des utilisateurs" )
end
it "devrait avoir un élément pour chaque utilisateur" do
get :index
@users . each do | user |
response . should have_selector( "li" , :content => user . nom)
end
end
end
378
end
.
.
.
end
Comme vous pouvez le voir, la méthode pour vérifier la page d'index consiste à créer trois utilisateurs d'usine
(identifié pour le premier) et vérifier alors que la page d'index a un élément de liste (balise li ) pour le nom de
chacun d'eux.
Comme vous pouvez l'imaginer, le code de l'application utilise User.all pour faire une variable d'instance
@users dans l'action index du contrôleur Users (extrait 10.20).
Extrait 10.20. L'action index de l'utilisateur.
app/controllers/users_controller.rb
class UsersController < ApplicationController
before_filter :authenticate , :only => [ :index , :edit , :update ]
.
.
.
def index
@titre = "Tous les utilisateurs"
@users = User . all
end
def show
@user = User . find(params [ :id ] )
@titre = @user . name
end
.
.
.
end
Notez que nous avons ajouté :index à la liste des contrôleurs protégés par le filtre « passe-avant »
authenticate , ce faisant on obtient la réussite du premier test de l'extrait 10.19.
379
Pour faire la page voulue, nous avons besoin de faire une vue qui fait une itération sur les utilisateurs et
entoure chacun d'eux d'une balise li . Nous faisons cela avec la méthode each (chaque), en affichant chaque
nom et gravatar d'utilisateur, entourant l'ensemble dans une balise de liste non ordonnée (ul ) (extrait 10.21).
Extrait 10.21. La vue de l'index utilisateur.
app/views/users/index.html.erb
<h1>Tous les utilisateurs</h1>
<ul class="users">
<% @users . each do | user | %>
<li>
<%= gravatar_pour user, :size => 30 %>
<%= link_to user . nom, user %>
</li>
<% end %>
</ul>
Nous ajoutons alors un peu de CSS pour le style (extrait 10.22).
Extrait 10.22. CSS pour l'index des utilisateurs.
public/stylesheets/custom.css
.
.
.
ul.users {
margin-top : 1em;
}
.users li {
list-style : none;
}
Enfin, nous ajouterons le liens « Utilisateurs » à l'entête de navigation du site (extrait 10.23). Cela introduit
l'utilisation du nom de route users_path (chemins aux utilisateurs) de la table 6.2.
Extrait 10.23. Un lien à l'index utilisateur.
app/views/layouts/_header.html.erb
<header>
380
<%= link_to logo, root_path %>
<nav class="round">
<ul>
<li><%= link_to "Accueil" , root_path %></li>
<% if signed_in? %>
<li><%= link_to "Utilisateurs" , users_path %></li>
<li><%= link_to "Profil" , current_user %></li>
<li><%= link_to "Réglages" , edit_user_path(current_user) %></li>
<% end %>
.
.
.
</ul>
</nav>
</header>
Avec ça, l'index des utilisateurs est totalement fonctionnel (avec la réussite de tous les tests), mais on se sent un
peu solitaire… (illustration 10.8).
Illustration 10.8: La page d'index utilisateur /users avec un seul utilisateur.
381
10.3.2 Utilisateurs fictifs
Dans cette section, nous allons donner de la compagnie à notre utilisateur solitaire. Bien sûr, pour créer
suffisamment d'utilisateurs pour un index décent, nous pourrions utiliser notre navigateur pour rejoindre la
page d'inscription et entrer les utilisateurs un par un. Mais une bien meilleure solution consiste à demander à
Ruby (et Rake) de les entrer pour nous.
D'abord, nous allons ajouter le gem Faker à notre Gemfile , ce qui nous permettra de créer des exemples
d'utilisateurs avec noms et des adresses-mails semi-réalistes (extrait 10.24).
Extrait 10.24. Ajout du gem Faker au Gemfile .
source 'http://rubygems.org'
.
.
.
group :development do
gem 'rspec-rails' , '2.5.0'
gem 'annotate-models' , '1.0.4'
gem 'faker' , '0.3.1'
end
.
.
.
Installer alors comme d'habitude :
$ bundle install
Ensuite, nous allons ajouter une tâche Rake pour créer les utilisateurs fictifs. Les tâches Rake sont consignées
dans le dossier lib/tasks , et sont définis en utilisant des espaces de noms (namespaces) (dans notre cas,
:db ), comme on peut le voir dans le extrait 10.25.
Extrait 10.25. Une tâche Rake pour peupler la base de données avec des utilisateurs fictifs.
lib/tasks/sample_data.rake
require 'faker'
namespace :db do
desc "Peupler la base de données"
382
task :populate => :environment do
Rake :: Task [ 'db:reset' ]. invoke
User . create!( :nom => "Utilisateur exemple" ,
:email => "[email protected]" ,
:password => "foobar" ,
:password_confirmation => "foobar" )
99. times do | n|
nom = Faker :: Name. name
email = "example- #{n +1} @railstutorial.org"
password = "motdepasse"
User . create!( :nom => nom,
:email => email,
:password => password,
:password_confirmation => password)
end
end
end
Cela définit une tâche db:populate qui initialise la base de données de développement en utilisant db:reset
(ne vous souciez pas trop de la syntaxe quelque peu bizarre), qui crée un utilisateur fictif avec un nom et une
adresse mail en faisant une réplique de notre précédent utilisateur, et 99 de plus ensuite. La ligne :
task :populate => :environment do
… assure que la tâche Rake a accès à l'environnement Rails local, pour inclure le modèle User (et ainsi la
méthode User.create! ).
Avec le domaine de nom :db comme dans le extrait 10.25, on peut invoquer la tâche Rake comme suit :
$ rake db:populate
Après avoir joué la tâche Rake, notre application possède 100 utilisateurs fictifs, comme on peut le voir dans
l'illustration 10.9 (j'ai pris la liberté d'associer les premières adresses à des photos pour que les utilisateurs
n'aient pas tous le même gravatar par défaut).
383
Illustration 10.9: La page d'index utilisateur /users avec 100 utilisateurs fictifs.
10.3.3 Pagination
Ayant résolu le problème d'avoir trop peu d'utilisateurs, nous nous confrontons maintenant au problème d'en
avoir trop sur la même page. Maintenant, il y en a une centaine, ce qui représente un nombre raisonnablement
important, et un site réel peut en compter des milliers. La solution est de paginer les utilisateurs, pour que (par
exemple), seulement 30 s'affichent par page à n'importe quel moment.
Il existe plusieurs méthodes de paginaiton dans Rails ; nous en utiliserons une des plus simples et des plus
robustes, appelée will_paginate . Pour l'utiliser, nous avons besoin d'actualiser le fichier Gemfile
comme d'habitude (extrait 10.26).
Extrait 10.26. Inclusion de will_paginate dans le fichier Gemfile.
source 'http://rubygems.org'
gem 'rails' , '3.0.4'
gem 'sqlite3-ruby' , '1.3.2' , :require => 'sqlite3'
gem 'gravatar_image_tag' , '1.0.0.pre2'
gem 'will_paginate' , '3.0.pre2'
.
.
.
384
Ensuite, comme d'habitude :
$ bundle install
Avec will_paginate installé, nous sommes maintenant en mesure de paginer le résultat de nos
utilisateurs trouvés. Nous allons commencer par ajouter la méthode spéciale will_paginate dans la vue
(extrait 10.27) ; nous verrons dans un moment pourquoi le code apparait au-dessus et en dessous de la liste
d'utilisateurs.
Extrait 10.27. L'index utilisateur avec la pagination.
app/views/users/index.html.erb
<h1>Liste des utilisateurs</h1>
<%= will_paginate %>
<ul class="users">
<% @users . each do | user | %>
<li>
<%= gravatar_for user, :size => 30 %>
<%= link_to user . name, user %>
</li>
<% end %>
</ul>
<%= will_paginate %>
La méthode will_paginate est un peu magique ; à l'intérieur d'une vue users (utilisateurs) elle cherche
automatiquement un objet @users , et affiche des liens de paginations pour atteindre les autres pages. La vue
de l'extrait 10.27 ne fonctionne pas encore, évidemment, puisque actuellement la variable @users contient le
résultat de User.all (extrait 10.20), qui est de classe Array (« table non associative »), tandis que
will_paginate attend un objet de classe WillPaginate::Collection . Heureusement, c'est justement le
type d'objet retourné par la méthode paginate fournie par le gem will_paginate :
$ rails console
>> User . all . class
=> Array
>> User . paginate( :page => 1) . class
=> WillPaginate::Collection
385
Notez que paginage prend un argument de type tableau associatif (table de hachage) avec une clé :page
de valeur égale à la page requise. User.paginate extrait les utilisateurs de la base de donnée en une fois (30
par défaut), en se basant sur le paramètre :page . Ainsi, par exemple, la page 1 sont les utilisateurs 1–30, la
page 2 les utilisateurs 31–60, etc.
On peut paginer les utilisateurs dans l'application exemple en utilisant paginate à la place de all dans
l'action index (extrait 10.28). Ici le paramètre :page est issu de params[:page] , qui est généré
automatiquement par will_paginate .
Extrait 10.28. Paginer les utilisateurs dans l'action index .
app/controllers/users_controller.rb
class UsersController < ApplicationController
before_filter :authenticate , :only => [ :index , :edit , :update ]
.
.
.
def index
@titre = "Tous les utilisateurs"
@users = User . paginate( :page => params [ :page ] )
end
.
.
.
end
La page d'index des utilisateurs devrait maintenant fonctionner, apparaissant telle que dans l'illustration 10.10;
parce que nous avons inclus will_paginate au-dessus et en dessous de la liste des utilisateurs, les liens de
pagination apparaissent à ces deux endroits.
386
Illustration 10.10: La page d'index /users paginée.
Si vous cliquez soit le lien 2 soit le lien Next (Suivant), vous obtiendrez la seconde page de résultats, comme
dans l'illustration 10.11.
Illustration 10.11: Page 2 de l'index (/users?page=2 ).
387
Test de la pagination
Tester la pagination requiert quelque connaissance du fonctionnement de will_paginate , donc nous
avons commencé par l'implémentation, mais il est important aussi de la tester. Pour ce faire, nous avons besoin
d'invoquer la pagination dans un test, ce qui signifie de créer d'abord plus de 30 utilisateurs (d'usine).
Comme auparavant, nous utiliserons Factory Girl pour simuler des utilisateurs, mais pour le moment, nous
avons un problème : les adresses d'utilisateurs doivent être uniques, et créer plus de 30 utilisateurs « à la
main » serait un travail terriblement rébarbatif… Par chance, Factory Girl anticipe ce problème et fournit des
séquences pour le résoudre, comme montré dans l'extrait 10.29.
Extrait 10.29. Définir une séquence Factory Girl.
spec/factories.rb
Factory . define :user do | user |
user . nom "Michael Hartl"
user . email "[email protected]"
user . password "foobar"
user . password_confirmation "foobar"
end
Factory . sequence :email do | n|
"person- #{n} @example.com"
end
Cela revient à retourner des adresses-mail comme [email protected] , [email protected] , etc.,
que nous invoquons en utilisant la méthode next :
Factory( :user , :email => Factory . next( :email ))
En appliquant l'idée de séquence d'usine, nous pouvons faire 31 utilisateurs (l'utilisateur @user original et 30
de plus) à l'intérieur d'un test, et vérifier alors que la réponse obtient de will_paginate le code HTML
voulu (ce que nous devrions être en mesure de déterminer en utilisant Firebug ou en consultant le code source
de la page). Le résultat apparait dans l'extrait 10.30.
Extrait 10.30. Un test de la pagination.
spec/controllers/users_controller_spec.rb
require 'spec_helper'
388
describe "UsersController" do
render_views
describe "GET 'index'" do
.
.
.
describe "pour les utilisateurs identifiés" do
before( :each ) do
.
.
.
@users = [ @user, second, third ]
30. times do
@users << Factory( :user , :email => Factory . next( :email ))
end
end
.
.
.
it "devrait avoir un élément pour chaque utilisateur" do
get :index
@users [0. . 2]. each do | user |
response . should have_selector( "li" , :content => user . nom)
end
end
it "devrait paginer les utilisateurs" do
get :index
response . should have_selector( "div.pagination" )
response . should have_selector( "span.disabled" , :content => "Previous" )
response . should have_selector( "a" , :href => "/users?page=2" ,
:content => "2" )
response . should have_selector( "a" , :href => "/users?page=2" ,
:content => "Next" )
end
end
389
end
.
.
.
end
Ce code s'assure que les tests invoquent la pagination en ajoutant 30182 utilisateurs à la variable @users en
utilisant la notation « push » des tableaux Array <<, qui ajoute un élément à un tableau existant :
$ rails console
>> a = [1 , 2, 5]
=> [1, 2, 5]
>> a << 17
=> [1, 2, 5, 17]
>> a << 42 << 1337
=> [1, 2, 5, 17, 42, 1337]
Nous voyons dans le dernier exemple que les occurrences de << peuvent être « chaînées ». Dans le test lui-
même, notez la notation compacte have_selector("div.pagination") , qui emprunte la convention des
classes CSS (vue pour la première fois dans l'extrait 5.3) pour vérifier l'existence d'une balise div de classe
pagination . Notez aussi que, maintenant qu'il y a 33 utilisateurs, nous avons actualisé le test de l'élément
utilisateur pour n'utiliser que les trois premiers éléments ([0..2] ) de la table @users , ce qui correspond à ce
que nous avions avant dans l'extrait 10.19:
@users [0. . 2]. each do | user |
response . should have_selector( "li" , :content => user . nom)
end
Avec ça, notre code de pagination est bien testé, et il n'y a qu'un détail mineur laissé de côté, que nous
aborderons dans la prochaine section.
10.3.4 Restructuration des partiels
L'index paginé est maintenant complet, mais il reste une amélioration que je ne peux résister d'inclure : Rails
possède des outils incroyablement sophistiqués pour faire des vues compactes, et dans cette section nous
restructurerons la page d'index pour les utiliser. Notre code étant bien testé, nous pouvons le restructurer en
toute confiance, assurés que nous sommes de ne pas casser les fonctionnalités du site.
390
La première étape dans la restructuration est de remplacer le li de l'utilisateur (extrait 10.27) par un appel à un
render (rendu) (extrait 10.31).
Extrait 10.31. Premier élément de restructuration de la vue d'index.
app/views/users/index.html.erb
<h1>All users</h1>
<%= will_paginate %>
<ul class="users">
<% @users . each do | user | %>
<%= render user %>
<% end %>
</ul>
<%= will_paginate %>
Ici nous appelons render non pas sur une chaine (string) avec le nom du partiel, mais plutôt sur une variable
user de classe User ;183 dans ce contexte, Rails recherche automatiquement un partiel appelé
_user.html.erb , que nous devons créer (extrait 10.32).
Extrait 10.32. Partiel pour rendre un seul utilisateur.
app/views/users/_user.html.erb
<li>
<%= gravatar_for user, :size => 30 %>
<%= link_to user . name, user %>
</li>
C'est une amélioration aboutie, mais nous pouvons encore faire mieux : nous pouvons appeler render
directement sur la variable @users (extrait 10.33).
Extrait 10.33. La restructuration complète de l'index.
app/views/users/index.html.erb
<h1>Tous les utilisateurs</h1>
<%= will_paginate %>
<ul class="users">
391
<%= render @users %>
</ul>
<%= will_paginate %>
Ici Rails déduit que @users est une liste d'objets User ; plus encore, quand appelé avec une collection
d'utilisateurs, Rails itère automatiquement sur eux, rendant chaque occurrence avec le partiel
_user.html.erb . Le résultat est le code incroyablement compact de l'extrait 10.33.
10.4 Destruction des utilisateurs
Maintenant que l'index est achevé, il ne reste qu'une action canonique REST à produire : destroy . Dans cette
section, nous ajouterons des liens pour supprimer des utilisateurs, comme dans la maquette de
l'illustration 10.12, et nous définirons l'action destroy (détruire) nécessaire pour accomplir cette suppression.
Mais avant tout, nous devons créer la classe « administrateurs » des utilisateurs autorisés à procéder à cette
destruction.
Illustration 10.12: Maquette de l'index avec des liens de suppression (version anglaise).
10.4.1 Utilisateurs administrateurs
Nous allons identifier les utilisateurs possédant des privilèges d'administrateur par le biais d'un attribut
booléen admin dans le modèle User, ce qui nous conduira à créer une méthode admin? testant le statut de
l'utilisateur. Nous pouvons écrire des tests pour cet attribut comme dans le extrait 10.34.
392
Extrait 10.34. Test pour un attribut admin .
spec/models/user_spec.rb
.
.
.
describe "Attribut admin" do
before( :each ) do
@user = User . create!(@attr)
end
it "devrait confirmer l'existence de `admin`" do
@user . should respond_to( :admin )
end
it "ne devrait pas être un administrateur par défaut" do
@user . should_not be_admin
end
it "devrait pouvoir devenir un administrateur" do
@user . toggle!( :admin )
@user . should be_admin
end
end
end
Ici nous avons utilisé la méthode toggle! (bascule !) pour basculer la valeur de l'attribut admin de false
(faux) à true (vrai). Notez aussi que la ligne :
@user. should be_admin
implique (via les conventions booléennes de RSpec) que l'utilisateur devrait posséder une méthode booléenne
admin? .
Nous ajoutons l'attribut admin grâce à une migration comme d'habitude, en indiquant le type booléen de
l'attribut dans la ligne de commande :
$ rails generate migration add_admin_to_users admin: boolean
393
La migration ajoute simplement la colonne admin à la table users (extrait 10.35), en adaptant le modèle
de données comme dans l'illustration 10.13.
Extrait 10.35. Migration pour ajouter un attribut booléen admin aux utilisateurs.
db/migrate/<timestamp>_add_admin_to_users.rb
class AddAdminToUsers < ActiveRecord :: Migration
def self . up
add_column :users , :admin , :boolean , :default => false
end
def self . down
remove_column :users , :admin
end
end
Notez l'ajout de l'argument :default => false à la fonction add_column dans l'extrait 10.35, qui spécifie
que les utilisateurs ne seront pas des administrateurs par défaut (sans cet argument :default => false ,
admin serait nul par défaut (nil ), ce qui équivaut à false , donc cette précision n'est pas strictement
indispensable. Mais le code est plus explicite ainsi, et transmet notre intention de façon plus claire à Rails ainsi
qu'aux lecteurs du code).
Figure 10.13: Le modèle `User` avec un attribut booléen admin ajouté.
Pour finir, nous migrons la base de données de développement et préparons le base de données de test :
$ rake db:migrate
$ rake db:test:prepare
394
Comme attendu, Rails prend en compte la nature booléenne de l'attribut admin et ajoute automatiquement la
méthode du même nom suivie d'un point d'interrogation admin? :184
$ rails console
>> user = User . first
>> user . admin?
=> false
>> user . password = "foobar"
>> user . toggle!( :admin )
=> true
>> user . admin?
=> true
Pour finir, actualisons notre peuplement fictif pour faire du premier utilisateur un administrateur
(extrait 10.36).
Extrait 10.36. Peuplement fictif avec un utilisateur administrateur.
lib/tasks/sample_data.rake
require 'faker'
namespace :db do
desc "Peupler la base de données avec des données fictiv es"
task :populate => :environment do
Rake :: Task [ 'db:reset' ]. invoke
administrateur = User . create!( :name => "Example User" ,
:email => "[email protected]" ,
:password => "foobar" ,
:password_confirmation => "foobar" )
administrateur . toggle!( :admin )
.
.
.
end
end
Enfin, jouons à nouveau le « populateur » pour ré-initialiser la base de données et la reconstruire entièrement :
$ rake db:populate
395
Révision de attr_accessible
Vous avez peut-être noté que l'extrait 10.36 transforme un utilisateur en administrateur avec la méthode
toggle!(:admin) , mais pourquoi ne pas ajouter simplement un :admin => true à la table
d'initialisation ? La réponse est que cela ne fonctionnerait pas, et ce à dessein : seuls les attributs définis par
attr_accessible peuvent être renseignés par le biais d'un « assignement public » et l'attribut admin n'est
pas dans ce cas. L'Extrait 10.37 reproduit la liste la plus récente des attributs attr_accessible —notez que
:admin n'est pas dans cette liste.
Extrait 10.37. Les attributs attr_accessible du modèle `User` sans attribut :admin .
app/models/user.rb
class User < ActiveRecord :: Base
attr_accessor :password
attr_accessible :name , :email , :password , :password_confirmation
.
.
.
end
La définition explicite des attributs accessibles est cruciale pour la bonne sécurité d'un site. Si nous avions omis
la liste attr_accessible dans le modèle `User` (ou si nous avions de façon irraisonnée ajouté :admin à
cette liste), un utilisateur mal intentionné aurait pu envoyer la requête PUT comme suit :185
put /users/17?admin=1
Cette requête aurait pu transformer l'utilisateur d'identifiant 17 en administrateur, ce qui, pour le moins, peut
être une sérieuse brèche dans la sécurité du site. Pour parer ce risque, c'est une bonne habitude de définir le
paramètre attr_accessible pour chaque modèle.
10.4.2 L'action destroy (détruire)
La dernière étape nécessaire pour achever la ressource `Utilisateurs` est d'ajouter des liens pour supprimer des
utilisateurs et une action destroy . Nous allons commencer par ajouter un lien « supprimer » pour chaque
utilisateur dans la page d'index (Extrait 10.38).
Extrait 10.38. Liens pour la suppression des utilisateurs (visibles seulement pour les administrateurs).
app/views/users/_user.html.erb
<li>
<%= gravatar_for user, :size => 30 %>
396
<%= link_to user . nom, user %>
<% if current_user . admin? %>
| <%= link_to "supprimer" , user, :method => :delete , :confirm => "Etes-vous certain ?" ,
:title => "Supprimer #{user . nom}" %>
<% end %>
</li>
Notez l'argument :method => :delete , qui s'arrange pour que le lien envoie la bonne requête DELETE
(EFFACER). Nous avons aussi entouré chaque lien à l'intérieur d'une condition if pour que seuls les
administrateurs puissent les voir. Le résultat pour notre utilisateur administrateur apparait dans
l'illustration 10.14.
Les navigateurs ne peuvent pas envoyer nativement de requête DELETE, donc Rails les simule par le biais de
JavaScript.186 Pour que le lien de suppression fonctionne, nous avons donc à inclure la librairie JavaScript
standard de Rails, ce que nous faisons en ajoutant la ligne…
<%= javascript_include_tag :defaults %>
… au gabarit du site. Le résultat est montré dans l'illustration 10.39.
Extrait 10.39. Ajout de la librairie JavaScript standard de Rails à l'application.
app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
<head>
<title><%= title %></title>
<%= csrf_meta_tag %>
<%= render 'layouts/stylesheets' %>
<%= javascript_include_tag :defaults %>
</head>
<body>
.
.
</body>
</html>
397
Illustration 10.14: L'index /users avec les liens de suppression (version anglaise).
Quand bien même seuls les administrateurs seraient en mesure de voir les liens de suppression, il demeure
cependant un trou de sécurité important : n'importe quel utilisateur mal intentionné et suffisamment
compétent pourrait transmettre une requête DELETE en ligne de commande pour détruire n'importe quel
utilisateur. Pour sécuriser proprement le site, nous avons donc aussi besoin d'un contrôle d'accès, pour que les
tests ne vérifient pas seulement qu'un administrateur puisse supprimer des utilisateurs, mais également que les
autres utilisateurs ne puissent pas. Le résultat apparait dans l'extrait 10.40. Notez qu'à l'instar des méthodes
get , post et put , nous utilisons delete pour traiter la requête DELETE à l'intérieur des tests.
Extrait 10.40. Tests pour la suppression des utilisateurs.
spec/controllers/users_controller_spec.rb
describe UsersController do
render_views
.
.
describe "DELETE 'destroy'" do
before( :each ) do
@user = Factory( :user )
end
describe "en tant qu'utilisateur non identifié" do
it "devrait refuser l'accès" do
398
delete :destroy , :id => @user
response . should redirect_to(signin_path)
end
end
describe "en tant qu'utilisateur non administrateur" do
it "devrait protéger la page" do
test_sign_in(@user)
delete :destroy , :id => @user
response . should redirect_to(root_path)
end
end
describe "en tant qu'administrateur" do
before( :each ) do
admin = Factory( :user , :email => "[email protected]" , :admin => true)
test_sign_in(admin)
end
it "devrait détruire l'utilisateur" do
lambda do
delete :destroy , :id => @user
end . should change(User, :count ) . by( -1 )
end
it "devrait rediriger vers la page des utilisateurs" do
delete :destroy , :id => @user
response . should redirect_to(users_path)
end
end
end
end
(Vous pouvez noter que nous avons défini un utilisateur-administrateur en utilisant :admin => true ; en
effet, les « utilisateurs d'usine » ne sont pas concernés par les règles du paramètre attr_accessible .) Notez
ici que la méthode change peut prendre une valeur négative, ce qui signifie que, tout comme nous avons vérifié
399
la création d'un utilisateur en testant un changement de +1 (extrait 8.14), nous pouvons vérifier une
suppression d'utilisateur en testant un changement de -1 :
lambda do
delete :destroy , :id => @user
end . should change(User, :count ) . by( -1 )
Comme vous pouvez vous en douter, l'implémentation utilise un filtre « passe-avant » (before filter), cette fois
pour restreindre l'accès de l'action destroy aux seuls administrateurs. L'action destroy trouve elle-même
l'utilisateur, le détruit, et redirige alors l'administrateur vers l'index des utilisateurs (Extrait 10.41). [[Je
mettrais un warning ici pour penser à ajouter :admin au filtre "passe-avant" :authenticate]]
Extrait 10.41. Filtre « passe-avant’ pour restreindre l'utilisation de l'action destroy aux
administrateurs.
app/controllers/users_controller.rb
class UsersController < ApplicationController
before_filter :authenticate , :only => [ :index , :edit , :update , :destroy ]
before_filter :correct_user , :only => [ :edit , :update ]
before_filter :admin_user , :only => :destroy
.
.
def destroy
User . find(params [ :id ] ) . destroy
flash [ :success ] = "Utilisateur supprimé."
redirect_to users_path
end
private
.
.
def admin_user
redirect_to(root_path) unless current_user . admin?
end
end
Notez que l'action destroy action utilise le chainage de méthodes (vue brièvement à la section 4.2.3) dans la
ligne :
400
User . find(params [ :id ] ) . destroy
… ce qui fait l'économie d'une ligne de code.
À ce point de développement, tous les tests devraient réussir, et la ressource `Users` — avec son contrôleur, son
modèle et ses vues — est fonctionnellement complète.
10.5 Conclusion
Nous avons parcouru un bon bout de chemin depuis l'introduction du contrôleur d'utilisateur de la section 5.3.
Cet utilisateur ne pouvait même pas s'inscrire, s'identifier, se déconnecter, voir son profil, l'éditer et voir une
liste de tous les utilisateurs — et certains utilisateurs peuvent même à présent en supprimer d'autres.
La suite de ce livre va se construire sur les fondations de cette ressource utilisateurs (et le système
d'authentification) pour faire un site avec des micro-messages (chapitre 11) et la possibilité de suivre des
utilisateurs (chapitre 12). Ces chapitres introduiront certains des outils les plus puissants de Rails, tels que le
modelage de données avec has_many (possède_plusieurs) et has_many :through (possède_plusieurs…
à_travers_de).
Avant de poursuivre, assurez-vous de fusionner tous vos changements dans la branche principale de
développement :
$ git add .
$ git commit -m "Fini avec les actions utilisateurs edit/update, in dex et destroy"
$ git checkout master
$ git merge updating-users
Notons pour finir que ce chapitre a vu la dernière installation gem nécessaire au tutoriel. À titre d'indication, le
Gemfile final est présenté dans l'extrait 10.42 ci-dessous.
Extrait 10.42. Le Gemfile final de l'application.
source 'http://rubygems.org'
gem 'rails' , '3.0.4'
gem 'sqlite3-ruby' , '1.3.2' , :require => 'sqlite3'
gem 'gravatar_image_tag' , '1.0.0.pre2'
gem 'will_paginate' , '3.0.pre2'
401
group :development do
gem 'rspec-rails' , '2.5.0'
gem 'annotate-models' , '1.0.4'
gem 'faker' , '0.3.1'
end
group :test do
gem 'rspec' , '2.5.0'
gem 'webrat' , '0.7.1'
gem 'spork' , '0.8.4'
gem 'factory_girl_rails' , '1.0'
end
10.6 Exercices 1. Faite en sorte que le lien « changer » du Gravatar, dans l'extrait 10.3 s'ouvre dans une nouvelle
fenêtre (ou un onglet). Indication : cherchez sur le web ; vous devriez trouver une méthode particulièrement robuste comportant quelque chose appelé _blank (« _vierge »).
2. Supprimer le code de formulaire redondant en restructurant les vues new.html.erb et
edit.html.erb views pour utiliser un partiel dans l'extrait 10.43. Notez que vous devrez passer la
variable formulaire f de façon explicite comme variable locale, comme dans l'extrait 10.44.
3. Les utilisateurs identifiés n'ont aucune raison d'accéder aux actions new (nouvel utilisateur) et
create (créer l'utilisateur) dans le contrôleur `User`. Faites en sorte qu'ils soient redirigés vers
l'accueil s'ils tentent d'atteindre ces pages.
4. Ajoutez des tests pour vérifier que les liens de suppression dans l'extrait 10.38 apparaissent pour les
administrateurs et pas pour les utilisateurs normaux.
5. Modifier l'action destroy pour empêcher qu'un administrateur ne puisse se détruire lui-même
(commencez par écrire le test).
Extrait 10.43. Un partiel pour les champs des formulaires `new` et `edit`.
app/views/users/_fields.html.erb
<%= render 'shared/error_messages' , :object => f . object %>
<div class="field">
<%= f . label :name %><br />
<%= f . text_field :name %>
</div>
<div class="field">
402
<%= f . label :email %><br />
<%= f . text_field :email %>
</div>
<div class="field">
<%= f . label :password %><br />
<%= f . password_field :password %>
</div>
<div class="field">
<%= f . label :password_confirmation , "Confirmation" %><br />
<%= f . password_field :password_confirmation %>
</div>
Extrait 10.44. La vue pour un nouvel utilisateur avec le partiel.
app/views/users/new.html.erb
<h1>Sign up</h1>
<%= form_for(@user) do | f | %>
<%= render 'fields' , :f => f %>
<div class="actions">
<%= f . submit "Sign up" %>
</div>
<% end %>
403
chapitre 11 Micro-messages d'utilisateur Le chapitre 10 a vu l'implémentation complète des actions REST de la ressource Utilisateurs (Users), donc il est
temps d'ajouter une seconde ressource : les micro-messages d'utilisateurs.187 Ce sont de courts messages
associés à un utilisateur particulier, vu pour la première fois dans leur forme larvaire au chapitre 2. Dans le
présent chapitre, nous allons construire une version intégrale de l'esquisse de la section 2.3 en construire le
modèle de données `Micropost` (Micro-messages), en l'associant au modèle `User` à l'aide des méthodes
has_many et belongs_to , et en faisant les formulaires et les partiels nécessaires pour les manipuler et les
afficher. Dans le Chapite 12, nous achèverons notre mini clone de Twitter en ajoutant la notion de suivi
(following) des utilisateurs pour recevoir la nourriture de leurs micro-messages.
Si vous utilisez le contrôle de version git, je vous suggère de créer une nouvelle branche sujet comme
d'habitude :
$ git checkout -b user-microposts
11.1 Un modèle `Micropost`
Nous commençons la ressource Microposts en créant un modèle `Micropost`, qui comprend l'essentiel des
caractéristiques des micro-messages. Le code qui suit se construit sur le travail de la section 2.3 ; comme avec le
modèle de cette section, notre nouveau modèle Micropost incluera des validations de données et une
association avec le modèle `User`. Contrairement à ce modèle, le modèle Micropost sera complètement testé,
aura aussi un classement par défaut ainsi qu'un mécanisme de destruction automatique si l'utilisateur auteur
des messages est supprimé.
11.1.1 Le modèle de base
Le modèle Micropost n'a besoin que de deux attributs : un attribut content (contenu) pour conserver le
contenu du micro-message,188 et un attribut user_id (identifiant d'utilisateur) pour associer le micro-message
à l'utilisateur qui l'a écrit. Comme pour le modèle `User`, (extrait 6.1), nous générons ce nouveau modèle en
utilisant generate model :
$ rails generate model Micropost content:string user _id:integer
Ce code produit une nouvelle migration qui crée une table microposts dans la base de données (extrait 11.1) ;
vous pouvez la comparer à la migration analogue qui a permis de créer la table users dans l'extrait 6.2.
Extrait 11.1. La migration Micropost (notez l'indexation sur l'attribut user_id .)
db/migrate/<timestamp>_create_microposts.rb
404
class CreateMicroposts < ActiveRecord :: Migration
def self . up
create_table :microposts do | t |
t . string :content
t . integer :user_id
t . timestamps
end
add_index :microposts , :user_id
end
def self . down
drop_table :microposts
end
end
Notez que, puisque nous envisageons de récupérer tous les micro-messages associés à un identifiant
d'utilisateur donné, l'extrait 11.1 ajoute un index (Box 6.2) sur la colonne user_id :
add_index :microposts , :user_id
Notez aussi la ligne t.timestamps qui, comme mentionné dans la section 6.1.1) ajoute les colonnes magiques
created_at (créé_le…) et updated_at (modifié_le…). Nous travaillerons avec la colonne created_at dans
les sections 11.1.3 et 11.2.1.
On peut jouer la migration microposts comme d'habitude (en prenant soin de préparer la base de données de
test puisque le modèle de données a changé) :
$ rake db:migrate
$ rake db:test:prepare
Le résultat est un modèle Micropost dont la structure est présentée dans l'illustration 11.1.
405
Illustration 11.1: Le modèle de données Micropost.
Attributs accessibles
Avant d'étoffer le modèle Micropost, il est tout d'abord important d'utiliser attr_accessible pour indiquer
les attributs modifiable publiquement. Comme abordé dans la section 6.1.2.2 et la section 10.4.1.1, oublier de
définir les attributs accessibles signifie que n'importe qui pourrait changer les attributs de n'importe quel
micro-message simplement en utilisant un client en ligne de commande pour produire des requêtes
malveillantes. Par exemple, un utilisateur mal intentionné pourrait modifier l'attribut user_id pour associer
un micro-message à un mauvais utilisateur.
Dans le cas du modèle Micropost, il y a seulement un attribut qui a besoin d'être édité par le web, l'attribut
content (extrait 11.2).
Extrait 11.2. Rendre l'attribut content (et seulement l'attribut content ) accessible.
app/models/micropost.rb
class Micropost < ActiveRecord :: Base
attr_accessible :content
end
Puisque user_id n'est pas listé dans le paramètre attr_accessible , il ne peut pas être modifié par le web,
parce qu'un paramètre user_id dans une assignation publique telle que :
Micropost . new( :content => "foo bar" , :user_id => 17)
… sera tout simplement ignorée.
La déclaration de attr_accessible dans l'extrait 11.2 est nécessaire pour la sécurité du site, mais introduit
un problème dans le modèle spec (modèle de test) par défaut (extrait 11.3).
Extrait 11.3. Le module de test spec Micropost initial.
406
spec/models/micropost_spec.rb
require 'spec_helper'
describe Micropost do
before( :each ) do
@attr = {
:content => "value for content" ,
:user_id => 1
}
end
it "devrait créer une nouvelle instance avec les attri buts valides" do
Micropost . create!(@attr)
end
end
Le test réussi, mais il recèle un aspect suspect (essayez de vous l'imaginer avant de passer à la suite)
Le problème est que le bloc before(:each) dans l'extrait 11.3 assigne l'identifiant de l'utilisateur par
assignation publique, ce qui est très exactement ce que le paramètre attr_accessible est censé empêcher ;
en particulier, comme noté ci-dessous, la partie :user_id => 1 de l'initialisation de la table est simplement
ignorée. La solution est d'éviter, pour créer un micro-message, d'utiliser Micropost.new directement ; nous
allons plutôt créer le nouveau micro-message à travers son association avec le modèle User, qui définit
automatiquement l'identifiant de l'utilisateur (user id). Accomplir cela sera l'objet de la section suivante.
11.1.2 Association Utilisateur/Micro-message (User/Micropost)
Le but de cette section est d'établir une association entre le modèle Micropost et le modèle User —une relation
entraperçue dans la section 2.3.3 et vue schématiquement dans l'illustration 11.2 et l'illustration 11.3. Chemin
faisant, nous écrirons des tests pour le modèle Micropost qui, contrairement à l'extrait 11.3, sont compatibles
avec l'utilisation du paramètre attr_accessible de l'extrait 11.2.
407
Illustration 11.2: La relation belongs_to (appartient_à) entre le micro-message et son auteur.
Illustration 11.3: La relation has_many (possède_plusieurs) entre l'utilisateur et ses micro-messages.
Commençons les tests pour l'association du modèle Micropost. D'abord, nous voulons répliquer le test
Micropost.create! vu dans l'extrait 11.3 sans l'assignement public invalide. Ensuite, nous tirons de
l''illustration 11.2 qu'un objet micropost devrait posséder une méthode user . Enfin, micropost.user
devrait être l'utilisateur correspondant à l'attribut user_id du micro-message. Nous pouvons exprimer ces
exigences en RSpec avec le code de l'extrait 11.4.
Extrait 11.4. Test de l'association entre micro-message et utilisateur.
spec/models/micropost_spec.rb
require 'spec_helper'
408
describe Micropost do
before( :each ) do
@user = Factory( :user )
@attr = { :content => "Contenu du message" }
end
it "devrait créer instance de micro-message avec bons attributs" do
@user . microposts . create!(@attr)
end
describe "associations avec l'utilisateur" do
before( :each ) do
@micropost = @user . microposts . create(@attr)
end
it "devrait avoir un attribut user" do
@micropost . should respond_to( :user )
end
it "devrait avoir le bon utilisateur associé" do
@micropost . user_id . should == @user . id
@micropost . user . should == @user
end
end
end
Notez que, plutôt que d'utiliser Micropost.create ou Micropost.create! pour créer un micro-message,
l'extrait 11.4 utilise…
@user. microposts . create(@attr)
…et …
@user. microposts . create!(@attr)
409
Cette tournure est la façon canonique de créer un micro-message par association avec l'utilisateur (nous
utilisons un utilisateur fictif parce que ces tests s'adressent au modèle Micropost, pas au modèle User). Créé de
cette manière, l'objet micropost a automatiquement son attribut user_id défini avec la bonne valeur, ce qui
règle le problème relevé dans la section 11.1.1.1. En particulier, le code :
before( :each ) do
@attr = {
:content => "value for content" ,
:user_id => 1
}
end
it "devrait créer une nouvelle instance avec les attri buts valides" do
Micropost . create!(@attr)
end
… de l'extrait 11.3 est défectueux car :user_id => 1 ne fait rien quand user_id ne fait pas partie des attributs accessibles du modèle Micropost. En passant par l'association avec l'utilisateur, en revanche, le code :..
it "devrait créer une nouvelle instance avec les attri buts valides" do
@user . microposts . create!(@attr)
end
… de l'extrait 11.4 a le bon user_id par construction.
Ces méthodes create spéciales ne fonctionnent pas encore ; elles requièrent l'association has_many adéquate
dans le modèle `User`. Nous différons les tests plus détaillés de cette association à la section 11.1.3 ; pour le
moment, nous allons simplement tester la présence de l'attribut microposts (extrait 11.5).
Extrait 11.5. Test de l'existence de l'attribut microposts pour l'utilisateur.
spec/models/user_spec.rb
require 'spec_helper'
describe User do
.
.
.
describe "les associations au micro-message" do
410
before( :each ) do
@user = User . create(@attr)
end
it "devrait avoir un attribut 'microposts'" do
@user . should respond_to( :microposts )
end
end
end
Nous pouvons obtenir la réussite des tests des extraits extrait 11.4 et extrait 11.5 en utilisant l'association
belongs_to /has_many (définies dans l'illustration 11.2 et l''illustration 11.3), par le codes de l'extrait 11.6 et
l'extrait 11.7.
Extrait 11.6. Un micro-message belongs_to (appartient_à…) un utilisateur.
app/models/micropost.rb
class Micropost < ActiveRecord :: Base
attr_accessible :content
belongs_to :user
end
Extrait 11.7. Un utilisateur has_many (possède_plusieurs…) micro-messages.
app/models/user.rb
class User < ActiveRecord :: Base
attr_accessor :password
attr_accessible :name , :email , :password , :password_confirmation
has_many :microposts
.
.
.
end
En utilisant cette association belongs_to /has_many , Rails construit les méthodes montrées dans la
table 11.1. Vous devriez comparer les entrées dans la table 11.1 avec le code de l'extrait 11.4 et l'extrait 11.5 pour
être sûr de bien comprendre la nature essentielle de ces associations (il y a une méthode dans la table 11.1 que
411
nous n'avons pas utilisé pour le moment, la méthode build (construire) ; il en sera fait bon usage dans la
section 11.1.4 et spécialement la section 11.3.2.)
Méthode Objectif
micropost.user Retourne l'objet `User` associé au micro-messages.
user.microposts Retourne une liste (array) des micro-messages de l'utilisateur.
user.microposts.create(arg) Crée un micro-message (user_id = user.id ).
user.microposts.create!(arg) Crée un micro-message (en généraant une exception — une erreur — en cas d'échec).
user.microposts.build(arg) Retourne un nouvel objet Micropost (user_id = user.id ).
Table 11.1: Résumé des méthodes de l'association utilisateur/micro-message (user/micropost).
11.1.3 Affinements du micro-message
Le test de l'association has_many (possède_plusieurs…) de l'extrait 11.5 ne teste pas grand chose — il vérifie
simplement l'existence de l'attribut microposts (micro-messages). Dans cette section, nous allons ajouter un
ordering (classement) et une dependency (dépendance) aux micro-messages, tout en testant aussi que la valeur
retournée par la méthode user.microposts est bien une liste (array) de micro-messages.
Nous allons avoir besoin de construire quelques micro-messages dans le modèle de test `User`, ce qui signifie
que nous devrons faire une « usine à micro-messages » (micropost factory). Pour ce faire, nous avons besoin
d'un moyen de construire une association dans Factory Girl. Heureusement, c'est très facile — nous avons juste
à utiliser la méthode micropost.association de Factory Girl, comme le montre l'extrait 11.8.189
Extrait 11.8. Le fichier Factory complet, incluant une nouvelle production pour les micro-messages.
spec/factories.rb
# En utilisant le symbole ':user', nous obtenons qu e Factory Girl simule
# le modèle `User`.
Factory . define :user do | user |
user . name "Michael Hartl"
user . email "[email protected]"
user . password "foobar"
user . password_confirmation "foobar"
end
Factory . sequence :email do | n|
412
"person- #{n} @example.com"
end
Factory . define :micropost do | micropost |
micropost . content "Foo bar"
micropost . association :user
end
Portée par défaut
Nous pouvons nous arranger pour que les micro-messages produits fonctionnent pour un test sur le classement
des micro-messages. Par défaut, l'utilisation de user.microposts pour relever de la base de données les
micro-messages de l'utilisateur ne garantit en aucune façon l'ordre de ces messages, mais (en suivant les
conventions des blogs et de Twitter), nous voulons que les micro-messages sortent dans l'ordre inverse de leur
création, c'est-à-dire les plus récents en premier. Pour tester ce classement, nous commençons par créer deux
micro-messages comme suit :
@mp1 = Factory( :micropost , :user => @user, :created_at => 1. day . ago)
@mp2 = Factory( :micropost , :user => @user, :created_at => 1. hour . ago)
Ici nous indiquons que le second message a été créé plus récemment, une heure plus tôt (1.hour.ago = « il y a
1 heure »), et le premier message a été créé il y a un jour (1.day.ago ). Notez comme l'utilisation de Girl est
pratique ici : non seulement nous pouvons désigner un utilisateur en utilisant une « assignation publique »
(puisque les productions de Factory by-passent — contournent — le paramètre attr_accessible ), mais nous
pouvons aussi renseigner l'attribut created_at manuellement, ce que Active Record ne nous laisserait jamais
faire.190
La plupart des adaptateurs de bases de données (SQLite compris) retournent les micro-messages en les classant
par leur identifiant (colonne id ), donc nous pouvons arranger un test initial qui échouera à coup sûr, en
utilisant le code de l'extrait 11.9.
Extrait 11.9. Test de l'ordre des micro-messages de l'utilisateur.
spec/models/user_spec.rb
require 'spec_helper'
describe User do
.
.
.
413
describe "micropost associations" do
before( :each ) do
@user = User . create(@attr)
@mp1 = Factory( :micropost , :user => @user, :created_at => 1. day . ago)
@mp2 = Factory( :micropost , :user => @user, :created_at => 1. hour . ago)
end
it "devrait avoir un attribut `microposts`" do
@user . should respond_to( :microposts )
end
it "devrait avoir les bons micro-messags dans le bon o rdre" do
@user . microposts . should == [ @mp2, @mp1]
end
end
end
La clé du problème ici est :
@user. microposts . should == [ @mp2, @mp1]
… qui indique que les messages doivent être bien classés, les plus récents d'abord. Cette condition devrait
échouer parce que par défaut les messages seront classés par leur identifiant (id), donc : [@mp1, @mp2] . Ce
test vérifie aussi la validité de l'association has_many elle-même, en s'assurant (comme indiqué dans la
table 11.1) que user.microposts est bien une liste (array) de micro-messages.
Pour faire réussir le test de classement, nous utilisons une facilité de Rails appelée default_scope (portée
par défaut) avec un paramètre :order (ordre), comme le montre l'extrait 11.10 (c'est notre premier exemple de
la notion de portée (scope). Nous en apprendrons davantage à propos de la « portée » dans un contexte plus
général au chapitre 12.)
Extrait 11.10. Classement des micro-messages avec default_scope .
app/models/micropost.rb
class Micropost < ActiveRecord :: Base
.
.
414
.
default_scope :order => 'microposts.created_at DESC'
end
L'ordre ici est "microposts.created_at DESC" , où DESC signifie descendant pour SQL, c'est-à-dire « en
ordre descandant du plus récent au plus vieux ».
Dépendances de la suppression
En dehors du classement adéquat, nous aimerions ajouter un second affinement aux micro-messages. Comme
nous l'avons vu à la section 10.4, les administrateurs du site ont le pouvoir de suppression (destroy) des
utilisateurs. Il semble raisonnable d'établir que si un utilisateur est supprimé, ses micro-messages doivent être
détruits. Nous pouvons tester cela d'abord en détruisant un auteur de micro-messages, puis ensuite en vérifiant
que les micro-messages associés à cet utilisateur ne se trouvent plus dans la base de données (extrait 11.11).
Extrait 11.11. Test de la destruction des micro-messages à la destruction de leur auteur.
spec/models/user_spec.rb
describe User do
.
.
.
describe "micropost associations" do
before( :each ) do
@user = User . create(@attr)
@mp1 = Factory( :micropost , :user => @user, :created_at => 1. day . ago)
@mp2 = Factory( :micropost , :user => @user, :created_at => 1. hour . ago)
end
.
.
.
it "devrait détruire les micro-messages associés" do
@user . destroy
[ @mp1, @mp2]. each do | micropost |
Micropost . find_by_id(micropost . id) . should be_nil
end
end
end
415
.
.
.
end
Ici nous avons utilisé Micropost.find_by_id , qui retourne la valeur nulle (nil ) si l'enregistrement n'est pas
trouvé dans la base de données, tandis que Micropost.find provoque une exception (une erreur) en cas
d'échec, ce qui est plus difficile à tester (si vous êtes curieux :
lambda do
Micropost . find(micropost . id)
end . should raise_error(ActiveRecord :: RecordNotFound)
… accomplit ce test).
Le code de l'application pour que l'extrait 11.11 réussisse le test fait moins d'une ligne ; en fait, c'est juste une
option à ajouter à la méthode associative has_many , comme le montre l'extrait 11.12.
Extrait 11.12. S'assurer que les micro-messages de l'utilisateur seront détruits avec lui.
app/models/user.rb
class User < ActiveRecord :: Base
.
.
.
has_many :microposts , :dependent => :destroy
.
.
.
end
Avec ça, le formulaire final de l'association utilisateur/micro-message est en place.
11.1.4 Validation du micro-message
Avant d'abandonner le modèle `Micropost` (Micro-message), nous allons nous occuper d'un ou deux
problèmes potentiels en ajoutant des validations (en suivant l'exemple donné dans la section 2.3.2). Les deux
attributs user_id et content sont requis, et content est de plus contraint de faire moins de 140 signes, ce
que nous testons avec le code de l'extrait 11.13.
416
Extrait 11.13. Tests pour les validations du modèle Micropost.
spec/models/micropost_spec.rb
require 'spec_helper'
describe Micropost do
before( :each ) do
@user = Factory( :user )
@attr = { :content => "value for content" }
end
.
.
.
describe "validations" do
it "requiert un identifiant d'utilisateur" do
Micropost . new(@attr) . should_not be_valid
end
it "requiert un contenu non vide" do
@user . microposts . build( :content => " " ) . should_not be_valid
end
it "derait rejeter un contenu trop long" do
@user . microposts . build( :content => "a" * 141 ) . should_not be_valid
end
end
end
Cela suit de façon générale les exemples des tests de validation du modèle `User` de la section 6.2 (les tests,
dans cette section 6.2 ont été répartis sur plusieurs lignes, mais vous devriez être suffisamment à l'aise
maintenant avec le code RSpec pour digérer la formulation plus compacte utilisée ci-dessus).
Comme dans la section 6.2, le code de l'extrait 11.13 utilise la multiplication de chaines (string multiplication)
pour tester la validation de la longueur du micro-message :
$ rails console
>> "a" * 10
417
=> "aaaaaaaaaa"
>> "a" * 141
=> "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaa"
En contraste, au lieu d'utiliser le constructeur par défaut new comme dans :
User . new( . . . )
… le code dans l'extrait 11.13 utilise la méthode build :
@user. microposts . build
Rappelez-vous d'après la table 11.1 que c'est par essence équivalent à Micropost.new , à la différence près que
ça règle de façon automatique l'attribut user_id du micro-message à la valeur @user.id .
Les validations elles-mêmes sont franchement analogues à celles du modèle `User`, comme le montre
l'extrait 11.14.
Extrait 11.14. Les validations du modèle Micropost.
app/models/micropost.rb
class Micropost < ActiveRecord :: Base
attr_accessible :content
belongs_to :user
validates :content , :presence => true, :length => { :maximum => 140 }
validates :user_id , :presence => true
default_scope :order => 'microposts.created_at DESC'
end
Nous en avons fini avec le modelage des données pour les micro-messages d'utilisateurs. Il est temps à présent
de construire l'interface web.
11.2 Affichage des micro-messages
418
Bien que nous n'ayons pas encore de façon de créer des micro-messages par le web — cela viendra avec la
section 11.3.2 — çe ne doit pas nous empêcher de les afficher (et de tester cet affichage). À l'instar de Twitter,
nous planifions d'afficher ces micro-messages d'utilisateur non pas dans une page index séparée, mais plutôt
dans la page de l'utilisateur elle-même (la page show), comme le présente la maquette de l'illustration 11.4.
Nous commencerons avec un template ERb assez simple qui affichera un micro-message dans le profil de
l'utlisateur, puis nous ajouterons les micro-messages dans le peuplement de la base exemple de la section 10.3.2
pour que nous ayons quelque chose à afficher.
Illustration 11.4: Maquette de la page de profil avec des micro-messages (version anglaise).
Comme pour la discussion sur la machinerie d'identification dans la section 9.3.2, la section 11.2.1 poussera
souvent plusieurs éléments à la fois dans le tampon (stack), avant de les « éclater » un par un. Si cette dernière
explication vous plonge dans la confusion, soyez patient ; il y a un paiement plutôt sympa à la section 11.2.2.
11.2.1 Etoffement de la page de l'utilisateur
Nous commençons avec un test pour l'affichage des micro-messages de l'utilisateur. Nous travaillons dans le
spec du contrôleur `Users` puisque c'est le contrôleur `Users` qui contient l'action show de l'utilisateur. Notre
stratégie est de créer une paire de micro-messages d'usine associés à un utilisateur, et alors de vérifier que la
page possède une balise span de classe CSS « content » contenant chaque contenu de message. L'exemple
RSpec en résultant apparait dans l'extrait 11.15.
Extrait 11.15. Test d'affichage des micro-messages dans la page d'affichage de l'utilisateur (show page).
spec/controllers/users_controller_spec.rb
require 'spec_helper'
419
describe UsersController do
render_views
.
.
.
describe "GET 'show'" do
before( :each ) do
@user = Factory( :user )
end
.
.
.
it "devrait afficher les micro-messages de l'utilisate ur" do
mp1 = Factory( :micropost , :user => @user, :content => "Foo bar" )
mp2 = Factory( :micropost , :user => @user, :content => "Baz quux" )
get :show , :id => @user
response . should have_selector( "span.content" , :content => mp1 . content)
response . should have_selector( "span.content" , :content => mp2 . content)
end
end
.
.
.
end
Bien que ces tests échoueront jusqu'à ce que soit implémenté l'extrait 11.17, nous allons commencer par insérer
une table de micro-messages à l'intérieur de la page de profil de l'utilisateur, comme montré dans
l'extrait 11.16.191
Extrait 11.16. Ajout des micro-messages à la page d'affichage de l'utilisateur.
app/views/users/show.html.erb
<table class="profile">
<tr>
<td class="main">
.
.
420
.
<% unless @user . microposts . empty? %>
<table class="microposts" summary="Micro-me ssages de l'utilisateur">
<%= render @microposts %>
</table>
<%= will_paginate @microposts %>
<% end %>
</td>
<td class="sidebar round">
<strong>Nom</strong> <%= @user . nom %><br />
<strong>URL</strong> <%= link_to user_path(@u ser), @user %><br />
<strong>Messages</strong> <%= @user . microposts . count %>
</td>
</tr>
</table>
Nous allons nous occuper de la table microposts dans une minute, mais certaines choses sont à noter ici
d'abord. Une nouvelle idée introduite est l'utilisation de empty? dans la ligne :
@user. microposts . empty?
Ce code applique la méthode empty? , déjà abordée dans le contexte des chaines de caractères (p.e. à la
section 4.2.3), cette fois à une table :
$ rails console
>> [1 , 2]. empty?
=> false
>> []. empty?
=> true
En utilisant la clause conditionnelle unless :
<% unless @user . microposts . empty? %>
… nous nous assurons qu'une table HTML vide ne sera pas affichée quand l'utilisateur ne possède aucun micro-
message.
Nous noterons aussi, de l'extrait 11.16, que nous avons déjà anticipé la pagination des micro-messages par :
421
<%= will_paginate @microposts %>
Si vous comparez ce code avec la ligne analogue de la page qui présente la liste des utilisateurs, de
l''extrait 10.27, vuos verez que précédemment nous n'avions que :
<%= will_paginate %>
Cela fonctionnait parce que, dans le contexte du contrôleur `Users` (Utilisateurs), will_paginate présumait
l'existence d'une variable d'instance qui s'appellerait @users (ce qui, comme nous l'avons vu dans la
section 10.3.3, devait être de classe WillPaginate::Collection ). Dans le cas présent, puisque nous
sommes toujours dans le contrôleur `Users` mais que nous voulons paginer plutôt les micro-messages, nous
devons passer explicitement une variable @microposts à will_paginate . Bien entendu, cela signifie que
nous allons devoir définir cette varable dans l'action show de l'utilisateur (extrait 11.18 ci-dessous).
Enfin, notez que nous avons pris la liberté d'ajouter à la barre latérale du profil le compte du nombre courant de
micro-messages de l'utilisateur :
<td class="sidebar round">
<strong>Nom</strong> <%= @user . nom %><br />
<strong>URL</strong> <%= link_to user_path(@user) , @user %><br />
<strong>Messages</strong> <%= @user . microposts . count %>
</td>
Ici, @user.microposts.count est analogue à la méthode User.count , mis à part le fait que ne sont
comptés que les micro-messages appartenant à l'utilisateur donné par le biais de l'association utilisateur/micro-
messages.192
Maintenant, pour la table micro-messages elle-même :
<table class="microposts" summary="Micro-messages d e l'utilisateur">
<%= render @microposts %>
</table>
Ce code est responsable de la génération de la table des micro-messages, mais vous pouvez voir qu'il ne fait que
différer la création d'un partiel « micropost ». Nous avons vu à la section 10.3.4 que le code :
<%= render @users %>
422
… renvoie automatiquement chacun des utilisateurs de la variable @users en utilisant le partiel
_user.html.erb . De la même manière, le code :
<%= render @microposts %>
… fait exactement la même chose pour les micro-messages. Cela signifie que nous devons définir le partiel
_micropost.html.erb (dans le dossier micropost des vues — views), comme dans l'extrait 11.17.
Extrait 11.17. Un partiel pour afficher un unique micro-message.
app/views/microposts/_micropost.html.erb
<tr>
<td class="micropost">
<span class="content"><%= micropost . content %></span>
<span class="timestamp">
Posté il y a <%= time_ago_in_words(micropost . created_at) %>.
</span>
</td>
</tr>
Ce code utilise la fantastique méthode time_ago_in_words de helper, dont nous verrons l'effet dans la
section 11.2.2.
À ce point, malgré la définition de tous les templates Erb nécessaires, le test de l'extrait 11.15 devrait échouer
par défaut de la variable @microposts . Nous pouvons le faire réussir avec l'extrait 11.18.
Extrait 11.18. Ajout d'un variable d'instance @microposts à l'action show de l'utilisateur.
app/controllers/users_controller.rb
class UsersController < ApplicationController
.
.
.
def show
@user = User . find(params [ :id ] )
@microposts = @user . microposts . paginate( :page => params [ :page ] )
@titre = @user . nom
end
end
423
Notez ici l'intelligence de paginate — il fonctionne même avec l'association des micro-messages, en
convertissant à la volée la liste (array) en objet de classe WillPaginate::Collection .
En ajoutant en plus le code CSS de l'extrait 11.19 à notre feuille de styles custom.css ,193 nous pouvons jeter un
œil à notre nouvelle page de profil utilisateur de l'illustration 11.5. C'est plutôt… décevant. Bien sûr, c'est parce
qu'il n'y a pour le moment aucun micro-message. Il est temps de changer ça.
Extrait 11.19. Code CSS pour les micro-messages (inclut tous les styles CSS de ce chapitre).
public/stylesheets/custom.css
.
.
h1.micropost {
margin-bottom : 0.3em;
}
table.microposts {
margin-top : 1em;
}
table.microposts tr {
height : 70px;
}
table.microposts tr td.gravatar {
border-top : 1px solid #ccc;
vertical-align : top;
width : 50px;
}
table.microposts tr td.micropost {
border-top : 1px solid #ccc;
vertical-align : top;
padding-top : 10px;
}
table.microposts tr td.micropost span.timestamp {
display : block;
font-size : 85%;
424
color : #666;
}
div.user_info img {
padding-right : 0.1em;
}
div.user_info a {
text-decoration : none;
}
div.user_info span.user_name {
position : absolute;
}
div.user_info span.microposts {
font-size : 80%;
}
form.new_micropost {
margin-bottom : 2em;
}
form.new_micropost textarea {
height : 4em;
margin-bottom : 0;
}
425
Illustration 11.5: La page de profil utilisateur avec le code pour les micro-messages — mais aucun micro-
messages (version anglaise).
11.2.2 Exemples de micro-messages
Avec le travail des templates pour les micro-messages d'utilisateur de la section 11.2.1, le dénouement est plutôt
décevant. Nous pouvons remédier à cette triste situation en ajoutant des micro-messages fictifs par le biais du
« populateur » de la section 10.3.2. Ajouter des micro-messages fictifs pour tous les utilisateurs prendrait un
certain temps, donc nous ne choisirons dans un premier temps que les six premiers utilisateurs194 en faisant
appel à l'option :limit de la méthode User.all :195
User . all( :limit => 6)
Nous créons alors 50 micro-messages pour chacun de ces utilisateurs (suffisamment pour dépasser la limite de
pagination de 30), en générant un exemple de contenu pour chaque micro-message en utilisant la méthode
Lorem.sentence du très pratique gem Faker (Faker::Lorem.sentence retourne le texte lorem
ipsum ; comme cela avait été noté dans le chapitre 6, lorem ipsum possède une fascinante histoire). Le résultat
est le nouvel exemple de populateur de données de l'extrait 11.20.
Extrait 11.20. Ajout de micro-messages fictifs.
lib/tasks/sample_data.rake
require 'faker'
namespace :db do
426
desc "Remplissage de la base de données avec des message s fictifs"
task :populate => :environment do
.
.
.
User . all( :limit => 6) . each do | user |
50. times do
user . microposts . create!( :content => Faker :: Lorem . sentence( 5))
end
end
end
end
Bien entendu, pour générer les nouveaux exemples de données, nous avons à jouer la tâche Rake
db:populate :
$ rake db:populate
Avec ce code, nous sommes en position de tirer les fruits des durs labeurs de notre section 11.2.1 en affichant
l'information de chaque micro-message.196 L'illustration 11.6 montre la page de profil de l'utilisateur pour un
premier utilisateur (identifié), tandis que l'illustration 11.7 montre le profil d'un second utilisateur. Pour finir,
l'illustration 11.8 montre la seconde page de micro-messages d'un premier utilisateur, accompagné des liens de
pagination en bas de l'affichage. Dans les trois cas, observez que chaque micro-message indique le temps depuis
sa création (p.e., « Posté il y a 1 minute. ») ; c'est le travail de la méthode time_ago_in_words de
l'extrait 11.17. Si vous patientez une ou deux minutes puis rechargez les pages, vous verrez ce texte s'actualiser
automatiquement en se fondant sur le nouveau temps.
427
Illustration 11.6: Le profil d'utilisateur (/users/1 ) avec des micro-messages.
Illustration 11.7: Profil d'un autre utilisateur, lui aussi avec des micro-messages (/users/3 ).
428
Illustration 11.8: Une seconde page de micro-messages, avec les liens de pagination
(/users/1?page=2 ).
11.3 Manipuler les micro-messages
En ayant fini avec la modélistaion des données et l'affichage des micro-messages par templates, nous allons
nous intéresser maintenant à l'interface qui va permettre de créer ces messages par le biais du navigateur. Le
résultat sera notre troisième exemple d'utilisation d'un formulataire HTML pour créer une ressource — dans ce
cas, une ressource Microposts.197 Dans cette section, nous verrons aussi la première astuce pour créer un statut
d'alimentation (ou état d'alimentation) — une notion que nous porterons à sa pleine réalisation au chapitre 12.
Et pour finir, comme avec les utilisateurs, nous rendrons possible la destruction des messages au travers du
web.
Une dérogation d'avec les conventions est à noter : l'interface de la ressource Micropost tournera
principalement au travers des contrôleurs Users et Pages plutôt que par un contrôleur qui lui serait dédié.
Cela signifie que la route à la ressource Microposts est exceptionnellement simple, comme le montre
l'extrait 11.21. Le code de l'extrait 11.21 conduit à son tour aux routes pleinement conformes aux conventions
REST montrées dans la table 11.2, qui est un petit sous-ensemble de l'ensemble complet des routes de la
table 2.3. Bien sûr, cette simplicité est le signe que les techniques mises en branle ici sont plus avancée, pas
moins avancées — nous sommes très loin de notre tout premier recours à un échaffaudage du chapitre 2, et
nous n'avons plus besoin d'autant de complexité.
Extrait 11.21. Routes pour la ressource Microposts.
config/routes.rb
429
SampleApp :: Application . routes . draw do
resources :users
resources :sessions , :only => [ :new , :create , :destroy ]
resources :microposts , :only => [ :create , :destroy ]
.
.
.
end
HTTP request URL Action Purpose
POST /microposts create Crée un nouveau message
DELETE /microposts/1 destroy Supprime le message d'id 1
Table 11.2: Routes REST produites par la ressources Microposts de l'extrait 11.21.
11.3.1 Contrôle d'accès
Nous commençons notre développement de la ressource Microposts avec quelques contrôles d'accès dans le
contrôleur Micropost. L'idée est simple : les deux actions create et destroy requièrent que l'utilisateur soit
identifié. Le code RSpec pour tester cela apparait dans l'extrait 11.22, ce qui requiert la création du fichier spec
du contrôleur Microposts (nous testerons et ajouterons une troisième protection à la section 11.3.4 — pour que
seul l'auteur d'un message donné soit en mesure de le détruire).
Extrait 11.22. Tests d'accès de contrôle pour le contrôleur Microposts.
spec/controllers/microposts_controller_spec.rb
require 'spec_helper'
describe MicropostsController do
render_views
describe "contrôle d'accès" do
it "devrait refuser l'accès pour 'create'" do
post :create
response . should redirect_to(signin_path)
end
430
it "devrait refuser l'accès pour 'destroy'" do
delete :destroy , :id => 1
response . should redirect_to(signin_path)
end
end
end
L'écriture du code de l'application nécessaire à la réussite des test de l'extrait 11.22 requiert d'abord un peu de
restructuration. Rappelez-vous comment, dans la section 10.2.1, nous avons forcé l'identification requise en
utilisant un filtre « passe-avant » qui appelait la méthode authenticate (extrait 10.11). À ce moment-là, nous
n'avions besoin que de la méthode authenticate dans le contrôleur `Users`, mais maintenant il se trouve
que nous en avons besoin aussi dans le contrôleur Microposts, donc nous allons déplacer la méthode
authenticate dans l'helper Sessions, comme le montre l'extrait 11.23.198
Extrait 11.23. Déplacement de la méthode authenticate dans l'helper de session.
app/helpers/sessions_helper.rb
module SessionsHelper
.
.
.
def authenticate
deny_access unless signed_in?
end
def deny_access
store_location
redirect_to signin_path, :notice => "Merci de vous identifier pour rejoindre cette page."
end
.
.
.
end
(Pour éviter la répétition de code, nous devons également supprimer la méthode authenticate du contrôler
`Users`.)
431
Avec le code de l'extrait 11.23, la méthode authenticate est maintenant accessible depuis le contrôleur
`Microposts`, ce qui signifie que nous pouvons restreindre l'accès aux actions create et destroy avec le filtre
« passe-avant » de l'extrait 11.24 (puisque nous n'avons pas généré ce contrôleur en ligne de commande — $
rails generate controler etc. — vous devrez créer son fichier à la main).
Extrait 11.24. Ajout de l'authentification aux actions du contrôleur `Microposts`.
app/controllers/microposts_controller.rb
class MicropostsController < ApplicationController
before_filter :authenticate
def create
end
def destroy
end
end
Notez que n'avons pas restreint l'application du fichier « passe-avant » aux actions, puisque pour le moment il
s'applique aux deux seules actions. Si nous étions amenés à ajouter, disons, une action index accessible même
aux utilisateurs non identifiés, nous devrions spécifier les actions protégées de façon explicite :
class MicropostsController < ApplicationController
before_filter :authenticate , :only => [ :create , :destroy ]
def create
end
def destroy
end
end
11.3.2 Création des micro-messages
Au chapitre 8, nous avons implémenté l'inscription des utilisateurs en créant un formulaire HTML qui générait
une requête HTTP POST pour l'action create dans le contrôleur `Users`. L'implémentation de la création
d'un micro-message est similaire ; la différence principale réside dans le fait que plutôt que d'utiliser une page
séparée pour /microposts/new , à l'instar des conventions Twitter, nous construirons ce formulaire
432
dans la page d'accueil elle-même (c'est-à-dire le chemin d'accès à la racine — root path/ ), comme le montre la
maquette de l'illutration 11.9.
Illustration 11.9: Maquette de la page d'accueil avec un formulaire pour créer des micro-messages (version
anglaise).
Dans la page d'accueil telle que nous l'avons laissée, elle apparaissait comme dans l'illustration 5.7 — c'est-à-
dire qu'elle avait un gros bouton « S'inscrire ! » en son centre. Puisque la création d'un micro-message n'a de
sens que dans le contexte d'un utilisateur identifié particulier, un des buts de cete section sera de produire
différentes versions de la page d'accueil en fonction du statut d'identifictaion de l'utilisateur. Nous
implémenterons cela dans l'extrait 11.27 ci-dessous, mais pour le moment, le seul impératif est que les tests
pour l'action create du contrôleur `Microposts` devraient identifier un utilisateur (d'usine) avant d'essayer de
pouvoir créer un message.
En gardant cet avertissement à l'esprit, les tests de création des micro-messages ressemblent à ceux de la
création de l'utilisateur de l'extrait 8.6 et de l'extrait 8.14 ; le résultat apparait dans l'extrait 11.25.
Extrait 11.25. Tests de l'action create du contrôleur `Microposts`.
spec/controllers/microposts_controller_spec.rb
require 'spec_helper'
describe MicropostsController do
.
.
.
433
describe "POST 'create'" do
before( :each ) do
@user = test_sign_in(Factory( :user ))
end
describe "échec" do
before( :each ) do
@attr = { :content => "" }
end
it "ne devrait pas créer de micro-message" do
lambda do
post :create , :micropost => @attr
end . should_not change(Micropost, :count )
end
it "devrait retourner la page d'accueil" do
post :create , :micropost => @attr
response . should render_template( 'pages/home' )
end
end
describe "succès" do
before( :each ) do
@attr = { :content => "Lorem ipsum" }
end
it "devrait créer un micro-message" do
lambda do
post :create , :micropost => @attr
end . should change(Micropost, :count ) . by( 1)
end
it "devrait rediriger vers la page d'accueil" do
post :create , :micropost => @attr
434
response . should redirect_to(root_path)
end
it "devrait avoir un message flash" do
post :create , :micropost => @attr
flash [ :success ]. should =~ /enregistré/i
end
end
end
end
L'action create des micro-messages est similaire à celles des utilisateurs (extrait 8.15) ; la principale
différence réside dans l'utilisation de l'association utilisateur/micro-message pour construire (build ) le
nouveau micro-message, comme le montre l'extrait 11.26.
Extrait 11.26. L'action create du contrôleur `Microposts`.
app/controllers/microposts_controller.rb
class MicropostsController < ApplicationController
.
.
def create
@micropost = current_user . microposts . build(params [ :micropost ] )
if @micropost . save
flash [ :success ] = "Micropost created!"
redirect_to root_path
else
render 'pages/home'
end
end
.
.
end
À ce stade, les tests de l'extrait 11.25 devraient tous réussir, mais bien entendu nous n'avons pas encore de
formulaire pour créer le micro-message. Nous pouvons arranger cela avec l'extrait 11.27, qui produit un code
HTML différent en fonction de l'identification du visiteur au site.
Extrait 11.27. Ajout de la création des micro-messages à la page d'accueil (/ ).
435
app/views/pages/home.html.erb
<% if signed_in? %>
<table class="front" summary="Pour visiteur ident ifié">
<tr>
<td class="main">
<h1 class="micropost">Quoi de neuf ?</h1>
<%= render 'shared/micropost_form' %>
</td>
<td class="sidebar round">
<%= render 'shared/user_info' %>
</td>
</tr>
</table>
<% else %>
<h1>Sample App</h1>
<p>
C'est la page d'accueil de l'application exempl e du
<a href="http://railstutorial.org/">Tutoriel Ru by on Rails</a>.
</p>
<%= link_to "S'inscrire !" , signup_path, :class => "signup_button round" %>
<% end %>
Avoir autant de code dans chaque branche de la condition if -else est un peu fouilli, et rendre cela plus propre
en utilisant les partiels est laissé comme exercice (section 11.5). Remplir les partiels indispensables de
l'extrait 11.27 n'est pas un exercice, en revanche ; nous remplissons le partiel du formulaire du micro-message
dans l'extrait 11.28 et la nouvelle sidebar de la page d'accueil dans l'extrait 11.29.
Extrait 11.28. Le partiel formulaire pour créer un micro-message.
app/views/shared/_micropost_form.html.erb
<%= form_for @micropost do | f | %>
<%= render 'shared/messages_erreurs' , :object => f . object %>
<div class="field">
<%= f . text_area :content %>
</div>
<div class="actions">
436
<%= f . submit "Soumettre" %>
</div>
<% end %>
Extrait 11.29. Le partiel pour la barre latérale d'information de l'utilisateur.
app/views/shared/_user_info.html.erb
<div class="user_info">
<a href="<%= user_path(current_user) %>">
<%= gravatar_pour current_user, :size => 30 %>
<span class="user_name">
<%= current_user . nom %>
</span>
<span class="microposts">
<%= pluralize(current_user . message . count, "micropost" ) %>
</span>
</a>
</div>
Notez que, comme dans la barre latérale du profil (extrait 11.16), l'information sur l'utilisateur dans
l'extrait 11.29 affiche le nombre total de micro-messages de l'utilisateur. Il y a une légère différence ici dans
l'affichage, cependant ; dans la barre latérale du profil, Messages est un label, et afficher Messages : 1 fait
parfaitement sens. Dans le cas présent, en revanche, dire « 1 messages » est grammaticalement incorrect, donc
nous nous arrangeons pour afficher « 1 message » (et « 2 messages ») en utilisant la méthode helper très
pratique : pluralize .
Le formulaire défini dans l'extrait 11.28 est une réplique exacte du formulaire d'inscription de l'exrait 8.2, ce qui
signifie qu'il nécessite une variable d'instance @micropost . C'est accompli dans l'extrait 11.30 — mais
seulement quand l'utilisateur est identifié.
Extrait 11.30. Ajout d'une variable d'instance `micropost` à l'action home.
app/controllers/pages_controller.rb
class PagesController < ApplicationController
def home
@titre = "Home"
@micropost = Micropost . new if signed_in?
end
.
437
.
end
Maintenant le code HTML devrait être correct, affichant le formulaire comment dans l'illustration 11.10, et un
formulaire avec une erreur de soumission comme dans l'illustration 11.11. Vous êtes invité à ce point à créer un
nouveau message pour vous-même pour vérifier que tout fonctionne correctement — mais vous devriez peut-
être attendre la section 11.3.3.
Illustration 11.10: La page d'accueil (/ ) avec un nouveau formulaire de micro-message.
438
Illustration 11.11: La page d'accueil avec des erreurs dans le formulaire.
11.3.3 Une proto-alimentation
Le commentaire à la fin de la section 11.3.2 fait allusion à un problème : la page d'accueil courante n'affiche
aucun micro-message. Si vous voulez, vous pouvez vérifier que ce formulaire montré dans l'illustration 11.10
fonctionne en soumettant une entrée valide et en vous rendant ensuite sur la page de profil pour voir le
message, mais cela est plutôt fastidieux. Il serait bien meilleur d'avoir une alimentation des micro-messages qui
inclut les propres messages de l'utilisateur, comme le présente la maquette de l'illustration 11.12 (au chapitre 12,
nous généraliserons cette alimentation pour inclure les micro-messages des utilisateurs qui sont suivis par
l'utilisateur courant).
Illustration 11.12: Maquette de la page d'accueil avec une proto-alimentation (version anglaise).
Puisque chaque utilisateur devrait avoir une alimentation, nous sommes naturellement conduit à une méthode
feed (alimentation) dans le modèle de l'utilisateur. Éventuellement, nous testerons que l'alimentation
retourne bien les messages que l'utilisateur suit, mais pour le moment, nous testerons simplement que la
méthode feed inclut les micro-messages de l'utilisateur courant mais exclut les messages d'un utilisateur
différent. Nous pouvons exprimer ces exigences avec le code de l'extrait 11.31.
Extrait 11.31. Tests pour l'état de la proto-alimentation.
spec/models/user_spec.rb
require 'spec_helper'
439
describe User do
.
.
.
describe "Association micro-messages" do
before( :each ) do
@user = User . create(@attr)
@mp1 = Factory( :micropost , :user => @user, :created_at => 1. day . ago)
@mp2 = Factory( :micropost , :user => @user, :created_at => 1. hour . ago)
end
.
.
.
describe "État de l'alimentation" do
it "devrait avoir une methode `feed`" do
@user . should respond_to( :feed )
end
it "devrait inclure les micro-messages de l'utilisateu r" do
@user . feed . include?(@mp1) . should be_true
@user . feed . include?(@mp2) . should be_true
end
it "ne devrait pas inclure les micro-messages d'un aut re utilisateur" do
mp3 = Factory( :micropost ,
:user => Factory( :user , :email => Factory . next( :email )))
@user . feed . include?(mp3) . should be_false
end
end
end
end
Ces test introduit la méthode tableau include? (inclure ?) qui teste simplement si une liste (array) inclut un
élément donné :199
440
$ rails console
>> a = [1 , "foo" , :bar ]
>> a. include?( "foo" )
=> true
>> a. include?( :bar )
=> true
>> a. include?( "baz" )
=> false
Nous pouvons obtenir une alimentation appropriée (feed ) de micro-messages en sélectionnant tous les micro-
messages dont l'attribut user_id serait égal à l'identifiant (id ) de l'utilisateur courant, ce que nous
accomplissons en utilisant la méthode where sur le modèle Micropost , comme le montre l'extrait 11.32.200
Extrait 11.32. Implémentation préliminaire pour de l'état d'alimentation en micro-messages.
app/models/user.rb
class User < ActiveRecord :: Base
.
.
.
def feed
# C'est un préliminaire. Cf. chapitre 12 pour l'imp lémentation complète.
Micropost . where( "user_id = ?" , id )
end
.
.
.
end
Le point d'interrogation dans :
Micropost . where( "user_id = ?" , id )
… s'assure que l'identifiant (id ) est proprement « échappé » avant d'être inclus dans la requête SQL sous-
jacente, évitant par là-même un sérieux trou de sécurité appelé SQL injection (injection SQL) (l'attribut id est
ici juste un entier, donc il n'y a aucun danger dans ce cas, mais toujours échapper les variables injectées dans les
déclarations SQL est une habitude à cultiver).
Les lecteurs attentifs peuvent noter à ce point que le code dans l'extrait 11.32 revient à écrire :
441
def feed
microposts
end
Nous avons utilisé plutôt le code de l'extrait 11.32 parce qu'il prépare de façon plus naturelle la généralisation
du statut d'alimentation complet du chapitre 12.
Pour utiliser l'alimentation dans l'application exemple, nous ajontons une variable d'instance @feed_items
pour l'alimentation (paginée) de l'utilisateur courant, comme dans l'extrait 11.33, ainsi qu'un partiel
alimentation (extrait 11.34) à la page d'accueil (extrait 11.36).
Extrait 11.33. Ajout d'une variable d'instance à l'action home.
app/controllers/pages_controller.rb
class PagesController < ApplicationController
def home
@titre = "Home"
if signed_in?
@micropost = Micropost . new
@feed_items = current_user . feed . paginate( :page => params [ :page ] )
end
end
.
.
end
Extrait 11.34. Le partiel du statut d'alimentation.
app/views/shared/_feed.html.erb
<% unless @feed_items . empty? %>
<table class="microposts" summary="User micropost s">
<%= render :partial => 'shared/feed_item' , :collection => @feed_items %>
</table>
<%= will_paginate @feed_items %>
<% end %>
Le partiel d'état d'alimentation ci-dessus rend chaque élément de l'alimentation à l'aide d'un partiel repéré par
feed_item (_feed_item.html.erb ) en utilisant le code :
442
<%= render :partial => 'shared/feed_item' , :collection => @feed_items %>
Ici nous passons un paramètre :collection avec les éléments d'alimentation, ce qui contraint render à
utiliser le partiel donné (feed_item dans ce cas) pour rendre chaque élément de la collection proposée (nous
avons omis le paramètre :partial dans les rendus précédents, en écrivant par exemple : render
’shared/micropost’ , mais avec un paramètre :collection cette syntaxe ne fonctionne pas). Le code du
partiel de l'élément d'alimentation lui-même est présenté dans l'extrait 11.35 ; notez l'addition d'un lien de
suppression au partiel de l'élément d'alimentation, suivant l'exemple de l'extrait 10.38.
Extrait 11.35. Partiel pour un simple élément d'alimentation.
app/views/shared/_feed_item.html.erb
<tr>
<td class="gravatar">
<%= link_to gravatar_pour(feed_item . user), feed_item . user %>
</td>
<td class="micropost">
<span class="user">
<%= link_to feed_item . user . nom, feed_item . user %>
</span>
<span class="content"><%= feed_item . content %></span>
<span class="timestamp">
Posté il y a <%= time_ago_in_words(feed_item . created_at) %>.
</span>
</td>
<% if current_user?(feed_item . user) %>
<td>
<%= link_to "supprimer" , feed_item, :method => :delete ,
:confirm => "Etes-vous certain ?" ,
:title => feed_item . content %>
</td>
<% end %>
</tr>
Nous pouvons alors ajouter l'alimentation à la page d'accueil en rendant le partiel d'alimentation comme
d'habitude (extrait 11.36). Le résultat est un affichage de l'alimentation sur la page d'accueil, comme voulu
(extrait 11.13).
Extrait 11.36. Ajout d'un statut d'alimentation sur la page d'accueil.
443
app/views/pages/home.html.erb
<% if signed_in? %>
<table class="front" summary="For signed-in users ">
<tr>
<td class="main">
<h1 class="micropost">Quoi de neuf ?</h1>
<%= render 'shared/micropost_form' %>
<%= render 'shared/feed' %>
</td>
.
.
.
</tr>
</table>
<% else %>
.
.
.
<% end %>
Illustration 11.13: Page d'accueil (/ ) avec une proto-alimentation.
444
Au point où nous sommes arrivés, créer un nouveau micro-message fonctionne comme voulu, comme le montre
l'illustration 11.14 (nous écrirons un test d'intégration pour cet effet à la section 11.3.5). Il y a une subtilité,
cependant : à l'échec de la soumission du micro-message, la page d'accueil attend une variable d'instance
@feed_items , donc les échecs de la soumission provoquent pour le moment une rupture (comme vous devriez
pouvoir le constater en jouant votre suite de tests). La solution la plus simple est de supprimer entièrement
l'alimentation en lui assignant une liste (array) vide, comme dans l'extrait 11.37.201
Illustration 11.14: Page d'accueil après la création d'un nouveau micro-message.
Extrait 11.37. Ajout d'un variable d'instance (vide) @feed_items à l'action create .
app/controllers/microposts_controller.rb
class MicropostsController < ApplicationController
.
.
.
def create
@micropost = current_user . microposts . build(params [ :micropost ] )
if @micropost . save
flash [ :success ] = "Micropost created!"
redirect_to root_path
else
@feed_items = []
render 'pages/home'
445
end
end
.
.
.
end
11.3.4 Destruction des micro-messages
La dernière pièce fonctionnelle à ajouter à la ressource `Microposts` est la possibilité pour l'utilisateur de
détruire ses messages. Comme avec la suppression des utilisateurs (section 10.4.2), nous accomplissons cela
avec un lien « supprimer », comme le montre la maquette de l'illustration 11.15. Contrairement à ce cas des
utilisateurs, qui restreignait la suppression aux seuls administrateurs, le lien suppression fonctionnera
seulement pour les micro-messages créés par l'utilisateur courant.
Illustration 11.15: Maquette de la proto-alimentation avec le lien de suppression des micro-messages (version
anglaise).
Notre première étape consiste à ajouter un lien de suppression au partiel de micro-message comme dans
l'extrait 11.35. Le résultat apparait dans l'extrait 11.38.
Extrait 11.38. Partiel d'affichage d'un micro-message.
app/views/microposts/_micropost.html.erb
<tr>
<td class="micropost">
446
<span class="content"><%= micropost . content %></span>
<span class="timestamp">
Posté il y a <%= time_ago_in_words(micropost . created_at) %>.
</span>
</td>
<% if current_user?(micropost . user) %>
<td>
<%= link_to "supprimer" , micropost, :method => :delete ,
:confirm => "Etes-vous certain ?" ,
:title => micropost . content %>
</td>
<% end %>
</tr>
Notez : en ce qui concerne la dernière version de Rails 3.0, moi et quelques autres lecteurs rencontrent
quelquefois un étrange bogue, où l'assocation micropost.user n'est pas faite proprement. Le résultat est
qu'appeler micropost.user peut provoquer une exception (une erreur) NoMethodError . Pour contourner
ce bogue, vous pouvez remplacer la ligne :
<% if current_user?(micropost . user) %>
… par la ligne :
<% user = micropost . user rescue User . find(micropost . user_id) %>
<% if current_user?(user) %>
Quand l'appel de micropost.user provoque une exception, ce code trouve l'utilisateur en se basant sur
l'attribut user_id du micro-message.
Les tests pour l'action destroy sont simplement une généralisation des tests similaires de la suppression
d'utilisateur (extrait 10.40), comme le montre l'extrait 11.39.
Extrait 11.39. Tests pour l'action destroy (« détruire ») du contrôleur `Microposts`.
spec/controllers/microposts_controller_spec.rb
describe MicropostsController do
.
.
.
447
describe "DELETE 'destroy'" do
describe "pour un utilisateur non auteur du message" do
before( :each ) do
@user = Factory( :user )
wrong_user = Factory( :user , :email => Factory . next( :email ))
test_sign_in(wrong_user)
@micropost = Factory( :micropost , :user => @user)
end
it "devrait refuser la suppression du message" do
delete :destroy , :id => @micropost
response . should redirect_to(root_path)
end
end
describe "pour l'auteur du message" do
before( :each ) do
@user = test_sign_in(Factory( :user ))
@micropost = Factory( :micropost , :user => @user)
end
it "devrait détruire le micro-message" do
lambda do
delete :destroy , :id => @micropost
end . should change(Micropost, :count ) . by( -1 )
end
end
end
end
Le code de l'application est aussi analogue au cas de l'utilisateur dans l'extrait 10.41 ; la différence principale est
que, plutôt que d'utiliser un filtre « passe-avant » admin_user , dans le cas des micro-messages nous utilisons
un filtre « passe-avant » authorized_user qui vérifie que l'utilisateur courant est l'auteur du micro-message.
Le code apparait dans l'extrait 11.40, et le résultat de la suppression du deuxième plus récent message apparait
dans l'illustration 11.16.
448
Extrait 11.40. L'action destroy du contrôleur `Microposts`.
app/controllers/microposts_controller.rb
class MicropostsController < ApplicationController
before_filter :authenticate , :only => [ :create , :destroy ]
before_filter :authorized_user , :only => :destroy
.
.
def destroy
@micropost . destroy
redirect_back_or root_path
end
private
def authorized_user
@micropost = Micropost . find(params [ :id ] )
redirect_to root_path unless current_user?(@m icropost . user)
end
end
Illustration 11.16: Page d'accueil de l'utilisateur après la suppression du second plus récent micro-message
(version anglaise).
449
11.3.5 Test de la nouvelle page d'accueil
Avant de quitter la création et la suppresion des micro-messages, nous allons écrire un spec d'intégration RSpec
pour tester que nos formulaires fonctionnent proprement. Comme dans le cas des utilisateurs (section 8.4),
nous commençons par générer un spec d'intégration `microposts` (« micro-messags ») :
$ rails generate integration_test microposts
Des tests pour l'échec et la réussite de la création d'un micro-message sont présentés dans l'extrait 11.41.
Extrait 11.41. Un test d'intégration pour les micro-messages sur la page d'accueil.
spec/requests/microposts_spec.rb
require 'spec_helper'
describe "Microposts" do
before( :each ) do
user = Factory( :user )
visit signin_path
fill_in :email , :with => user . email
fill_in :password , :with => user . password
click_button
end
describe "création" do
describe "échec" do
it "ne devrait pas créer un nouveau micro-message" do
lambda do
visit root_path
fill_in :micropost_content , :with => ""
click_button
response . should render_template( 'pages/home' )
response . should have_selector( "div#error_explanation" )
end . should_not change(Micropost, :count )
end
end
450
describe "succès" do
it "devrait créer un nouveau micro-message" do
content = "Lorem ipsum dolor sit amet"
lambda do
visit root_path
fill_in :micropost_content , :with => content
click_button
response . should have_selector( "span.content" , :content => content)
end . should change(Micropost, :count ) . by( 1)
end
end
end
end
Ayant fini de tester les fonctionnalités attachées aux micro-messages, nous sommes en mesure à présent
d'élaborer la fonctionnalité finale de notre Application Exemple : le suivi d'utilisateur.
11.4 Conclusion
Avec l'addition de la ressource `Microposts`, nous en avons presque fini avec notre exemple d'application. Tout
ce qui reste à faire est d'ajouter une « couche sociale » en permettant aux utilisateurs de se suivre les uns les
autres. Nous allons apprendre comment modeler une telle relation entre utilisateurs, et voir les implications
pour les statuts d'alimentation, au chapitre 12.
Avant cela, assurez-vous d'enregistrer (commit et merge ) vos changements si vous utilisez le contrôle de
versions Git :
$ git add .
$ git commit -m "Added user microposts"
$ git checkout master
$ git merge user-microposts
Vous pouvez aussi « pousser » l'application vers Heroku. Le modèle de données ayant changé avec l'addition de
la table microposts , vous aurez aussi besoin de migrer la base de données de production :
$ git push heroku
$ heroku rake db:migrate
451
11.5 Exercices
Nous avons abordé suffisamment de sujets maintenant pour entrevoir une combinaison d'extensions possible à
notre application. En voilà quelques-unes parmi les très nombreuses possibilités :
1. (challenge) Ajoutez un affichage, à l'aide de JavaScript, pour afficher le décompte de 140 signes sur
la page d'accueil, à mesure que l'utilisateur entre du texte.
2. Ajoutez des tests pour le compte des micro-messages dans la barre latérale (sans oublier le
traitement du pluriel).
3. (principalement pour les designers) Modifiez le listing des micro-messages en utilisant une
liste numérotée plutôt qu'une table (note : c'est la façon pour Twitter d'afficher ses statuts
d'actualisation). Ajouter ensuite les styles CSS appropriés pour que l'alimentation en résultant n'ait
pas l'air trop affreuse.
4. Ajoutez des tests pour la pagination des micro-messages.
5. Restructurez la page d'accueil pour utiliser des partiels différents pour les deux branches des
conditions if et else .
6. Écrivez un test pour vous assurer que le lien de suppression des messages n'apparaissent pas sur le
messages dont l'utilisateur courant n'est pas l'auteur.
7. Ajoutez une route « imbriquée » (nested route) pour que /users/1/microposts affiche
tous les micro-messages de l'utilisateur 1 (vous aurez aussi à ajouter une action index au contrôleur
`Microposts` et à créer la vue correspondante).
8. Les très longs mots, généralement, perturbent l'affichage, comme on peut le voir dans
l'illustration 11.17. Réglez ce problème en utilisant l'helper wrap défini dans l'extrait 11.42 (notez
l'utilisation de la méthode raw — brut — pour empêcher Rails d'échapper le code HTML, liée à la
méthode sanitize nécessaire pour prévenir les attaques de cross-site scripting).
452
Illustration 11.17: Déficience de l'affichage à cause d'un mot trop long.
Extrait 11.42. Un helper pour traiter les mots longs.
app/helpers/microposts_helper.rb
module MicropostsHelper
def wrap(content)
sanitize(raw(content . split . map{ | s| wrap_long_string(s) } . join( ' ' )))
end
private
def wrap_long_string(text, max_width = 30)
zero_width_space = "​"
regex = /.{1,#{max_width}}/
(text . length < max_width) ? text :
text . scan(regex) . join(zero_width_space)
end
end
453
Chapitre 12 Suivi des utilisateurs Dans ce chapitre, nous allons achever le cœur de l'Application Exemple en ajoutant une « couche sociale » qui
permettra aux utilisateurs de suivre (et d'arrêter de suivre) d'autres utilisateurs, conduisant à une page d'accueil
personnalisée affichant un état d'alimentation (statut feed) des micro-messages des utilisateurs suivis. Nous
ferons aussi des vues pour afficher d'une part les utilisateurs « suiveurs » (les lecteurs) et d'autre part les
utilisateurs que chaque utilisateur suit (les auteurs suivis). Nous apprendrons comment modéliser le suivi de
l'utilisateur dans la section 12.1, et comment réaliser l'interface web à la section 12.2 (qui comprendra une
introduction à la technique Ajax). Pour finir, nous terminerons en développant un état d'alimentation
pleinement fonctionnel à la section 12.3.
Ce chapitre final contient l'étude du matériau le plus difficile du tutoriel, incluant une modélisation de données
complexe et quelques ruses Ruby/SQL pour créer l'état d'alimentation. Au travers de ces exemples, nous
verrons comment Rails peut manipuler et même intriquer les modèles de données, ce qui devrait vous être très
utile lorsque vous développerez vos propres applications et leurs exigences particulières. Pour faciliter la
transition du tutoriel au développement personnel, la section 12.4 contiendra quelques suggestions d'extensions
du cœur de l'application-exemple, accompagnées de conseils à propos de ressources plus avancées.
Comme d'habitude, les utilisateurs de Git devraient créer une nouvelle branche sujet :
$ git checkout -b following-users
Les nouveaux concepts abordés dans ce chapitre étant particulièrement délicats, avant d'écrire la moindre ligne
de code, nous allons nous arrêter un moment pour faire un tour d'horizon de la notion de suivi d'utilisateur.
Comme dans les chapitres précédents, à ce point de départ, nous allons représenter les pages à l'aide de
maquettes.202 Le flux de la page complète fonctionne comme suit : un utilisateur (ici John Calvin) part de sa
page de profil (illustration 12.1) et navigue sur les pages des autres utilisateurs (illustration 12.2) pour choisir
un utilisateur à suivre. John Calvin navigue sur la page de profil d'un second utilisateur, Thomas Hobbes
(illustration 12.3), clique sur le bouton « Suivre » pour suivre cet utilisateur. Cela change le bouton « Suivre »
en bouton « Ne plus suivre », et incrémente de 1 le nombre de « lecteurs » de Thomas Hobbes
(illustration 12.4). Retournant à sa page d'accueil, John Calvin voit maintenant le nombre de ses « auteurs
suivis » et trouve les micro-messages de Thomas Hobbes dans son état d'alimentation (illustration 12.5). La
suite de ce chapitre est dévolu à faire fonctionner ce flux de page.
454
Illustration 12.1: Maquette de la page de profil de l'utilisateur courant.
Illustration 12.2: Maquette de la recherche d'un utilisateur à suivre.
455
Illustration 12.3: Maquette du profil d'un autre utilisateur, avec le bouton de suivi.
Illustration 12.4: Maquette du profil avec un bouton pour arrêter de suivre l'utilisateur et incrémentation du
nombre de lecteurs.
456
Illustration 12.5: Maquette de la page d'accueil de l'utilisateur courant, avec l'état de l'alimentation et le
compteur d'auteurs suivis.
12.1 Le modèle de relation
Notre première étape dans l'implémentation du suivi de l'utilisateur consiste à construire un modèle de
données, ce qui n'est pas aussi simple qu'il peut le paraitre. Naïvement, on pourrait penser qu'une relation
has_many pourrait faire l'affaire : has_many (possède_plusieurs) auteurs suivis et un has_many lecteurs.
Comme nous allons le voir, cette approche recèle un problème sérieux, et nous apprendrons comment le
contourner en utilisant plutôt has_many :through (possède_plusieurs :à_travers). C'est comme si
beaucoup d'idées de cette section semblaient évidentes de prime abord, mais qu'en réalité il faille un certain
temps avant de pouvoir assimiler ce modèle de données assez compliqué. Si vous sentez que les choses
deviennent confuses, essayez de poursuivre quand même jusqu'à la fin ; puis, reprenez cette section pour voir si
les choses deviennent plus claires à la deuxième lecture.
12.1.1 Problème avec le modèle de données (et ses solutions)
En guise de première étape vers la construction d'un modèle de données pour le suivi des utilisateurs,
examinons un cas typique. Par exemple, considérons un utilisateur qui en suit un autre : nous pourrions dire
que, par exemple, notre John Calvin suit notre Thomas Hobbes, et donc que notre Thomas Hobbes est suivi par
John Calvin, donc que John Calvin est le lecteur (follower ) et que Thomas Hobbes est le suivi (followed ).
En utilisant les conventions de pluriels de Rails, l'ensemble de tous ces utilisateurs suivis (followed ) devrait
s'appeler les suivis (followeds ), mais c'est incorrect grammaticalement et maladroit (NdT : tous ces
problèmes ne se posent qu'en anglais ; en français, on pourra utiliser auteurs_suivis et lecteurs sans
problème) ; nous allons contourner la convention et les appeler following (auteurs_suivis), de telle sorte que
user.following contiendra une liste (array) des autres utilisateurs suivis. De façon similaire, l'ensemble des
457
utilisateurs suivant un utilisateur donné sont ses « lecteurs » (followers), et en conséquence
user.followers sera une liste (array) de ses lecteurs.
Cela suggère de modeler les utilisateurs suivis (following) comme dans l'illustration 12.6, avec une table
following et une association has_many . Puisque user.following devrait être une liste (array)
d'utilisateurs, chaque rangée de la table following devrait être un utilisateur, identifié par l'id followed_id
(id_du_suivi), avec un id follower_id (id_du_suiveur) pour établir l'association.203 En plus, puisque chaque
rangée est censé être un utilisateur, nous aurions besoin d'inclure les autres attributs de l'utilisateur, nom, mot
de passe, etc.
Illustration 12.6: Implémentation naïve du suivi d'utilisateur.
Le problème avec le modèle de données de l'illustration 12.6 est qu'il est terriblement redondant : chaque
rangée contient non seulement chaque identifiant d'utilisateur suivi, mais aussi toutes ses autres informations
— toute information qui se trouve déjà dans la table users . Pire encore, pour modeler les « lecteurs »
(followers), nous aurions besoin d'une autre table « lecteurs » (followers ). Enfin, ce modèle de données est
un cauchemar à maintenir, puisque chaque fois qu'un utilisateur a changé (disons) son nom, nous avons besoin
d'actualiser non seulement l'enregistrement de l'utilisateur dans la table users mais aussi toutes les rangées
contenant l'utilisateur dans nos deux tables following et followers .
Notre problème ici est que nous sommes passés à côté d'une abstraction sous-jacente. Une façon de trouver
l'abstraction adéquate consiste à considérer l'implémentation possible de following dans l'application web.
Rappelez-vous, section 6.3.3, que l'architecture REST comprend des ressources qui sont créées et détruites.
Cela nous conduit à nous poser deux questions : quand un utilisateur suit un autre utilisateur, qu'est-ce qui est
créé ? Quand un utilisateur ne suit plus un autre utilisateur, qu'est-ce qui est détruit ?
Après réflexion, nous voyons que dans ces cas l'application devrait soit créer ou détruire une relation (ou une
connexion204) entre deux utilisateurs. Un utilisateur, donc, « possèdes plusieurs :relations » (has_many
:relationships ), et possèdent plusieurs auteurs suivis (following ) et plusieurs lecteurs (followers ) à
458
travers ces relations. Effectivement, l'illustration 12.6 contient déjà le plus gros de l'implémentation : puisque
chaque utilisateur suivi est uniquement identifié par son followed_id , nous pourrions convertir following
(les lecteurs) en une table relationships (relations), omettre les détails de l'utilisateur, et utiliser
l'identifiant followed_id (id_du_suivi) pour retrouver l'utilisateur suivi dans la table users . Plus encore, en
considérant la relation inverse, nous pourrions utiliser la colonne follower_id (id_du_suiveur) pour extraire
une liste des lecteurs de l'utilisateur.
Pour faire une liste des auteurs suivis par un utilisateur (following ), il serait possible d'extraire une liste des
attributs followed_id (id_utilisateur_suivi) puis de garder chaque utilisateur associé à chaque identifiant
relevé. Comme vous pouvez vous en douter cependant, Rails possède une façon de rendre cette procédure bien
plus pratique ; la technique en question est connue sous le nom has_many :through (possède_plusieurs
:à_travers).205 Comme nous le verrons dans la section 12.1.4, Rails nous permet de dire qu'un utilisateur donné
suit plusieurs utilisateurs à travers (through) une table de relations, en utilisant le code succint suivant :
has_many :following , :through => :relationships , :source => "followed_id"
Ce code peuple automatiquement user.following (utilisateur.auteurs_suivis) avec une liste (array) des
utilisateurs suivis. Un diagramme du modèle de données est présenté dans l'illustration 12.7.
Illustration 12.7: Modèle du suivi d'utilisateur à travers un modèle de relation intermédiaire.
Pour commencer l'implémentation, nous générons d'abord un modèle `Relationship` (Relations) comme suit :
459
$ rails generate model Relationship follower_id:inte ger followed_id:integer
Puisque nous devrons retrouver les relations grâce aux identifiants follower_id (id_lecteur) et
followed_id (id_auteur_suivi), nous devrions pour l'efficacité de la recherche ajouter une indexation sur
chacune de ces colonnes, comme montré dans l'extrait 12.1.
Extrait 12.1. Ajout d'indexation à la table relationships .
db/migrate/<timestamp>_create_relationships.rb
class CreateRelationships < ActiveRecord :: Migration
def self . up
create_table :relationships do | t |
t . integer :follower_id
t . integer :followed_id
t . timestamps
end
add_index :relationships , :follower_id
add_index :relationships , :followed_id
add_index :relationships , [ :follower_id , :followed_id ] , :unique => true
end
def self . down
drop_table :relationships
end
end
L'extrait 12.1 inclut également un index composite qui force l'unicité des pairs de (follower_id ,
followed_id ), de telle sorte que la relation entre deux utilisateurs est forcément unique :
add_index :relationships , [ :follower_id , :followed_id ] , :unique => true
(Comparez ce code à l'unicité de l'adresse mail de l'extrait 6.22.) Comme nous le verrons en commençant la
section 12.1.4, notre interface utilisateur ne permettra pas cela de se produire, mais ajouter cette unicité permet
de générer une erreur si un utilisateur tente de forcer la création d'une duplication de la relation (en utilisant
par exemple un outil de commande en ligne comme cURL). Nous pourrions aussi ajouter une validation
d'unicité au modèle `Relationship`, mais puisque c'est toujours une erreur de créer la duplication d'une
relation, l'indexation unique est suffisante dans notre cas.
460
Pour créer la table relationships , nous migrons la base de données et préparons le test de cette base de
données comme à notre habitude :
$ rake db:migrate
$ rake db:test:prepare
Le résultat est un modèle de données `Relationship` présenté dans l'illustration 12.8.
Illustration 12.8: Le modèle de données `Relationship`.
Comme pour tout nouveau modèle, avant de poursuivre, nous devons définir ses attributs accessibles. Dans le
cas du modèle `Relationship`, l'attribut followed_id (id_utilisateur_suivi) devrait être accessible, puisque
les utilisateurs vont créer des relations par le biais du web, mais l'attribut follower_id (id_lecteur) ne doit
pas l'être ; dans le cas contraire, un utilisateur mal intentionné pourrait « forcer » des lecteurs à les suivre. Le
résultat apparait dans l'extrait 12.2.
Extrait 12.2. Créer une relation avec un attribut followed_id accessible (mais pas follower_id ).
app/models/relationship.rb
class Relationship < ActiveRecord :: Base
attr_accessible :followed_id
end
12.1.2 Association utilisateur/relations
Avant d'implémenter les utilisateurs suivis (following ) et les lecteurs (followers ), nous avons besoin
d'établir l'association entre les utilisateurs et les relations. Un utilisateur possède_plusieurs (has_many )
relations, et — puisqu'une relation implique deux utilisateurs — une Relation appartient_à (belongs_to )
l'utilisateur suiveur (le lecteur) et l'utilisateur suivi (l'auteur).
461
Comme avec les micro-messages dans la section 11.1.2, nous créerons de nouvelles relations en utilisant
l'association d'utilisateur, avec un code tel que :
user . relationships . create( :followed_id => . . . )
Nous commençons avec un test, montré dans l'extrait 12.3, qui construit une variable d'instance
@relationships (utilisé ci-dessous) en nous assurant qu'elle puisse être enregistrée en utilisant la méthode
save! (sauver!). Comme pour la méthode create! (créer!), la méthode save! entraine une erreur (une
exception) grâce au point d'exclamation si l'enregistrement échoue ; comparez cela à l'utilisation de create!
dans l'extrait 11.4.
Extrait 12.3. Test de la création de la Relation avec la méthode save! .
spec/models/relationship_spec.rb
require 'spec_helper'
describe Relationship do
before( :each ) do
@follower = Factory( :user )
@followed = Factory( :user , :email => Factory . next( :email ))
@relationship = @follower . relationships . build( :followed_id => @followed . id)
end
it "devrait créer une nouvelle instance en donnant des attributs valides" do
@relationship . save!
end
end
Nous devons également tester le modèle `User` pour vérifier l'existence de l'attribut relationships , comme
dans l'extrait 12.4.
Extrait 12.4. Test de l'attribut user.relationships .
spec/models/user_spec.rb
describe User do
.
.
462
.
describe "relationships" do
before( :each ) do
@user = User . create!(@attr)
@followed = Factory( :user )
end
it "devrait avoir une méthode relashionships" do
@user . should respond_to( :relationships )
end
end
end
Arrivés à ce point, on pourrait s'attendre à ce que le code soit semblable à celui de la section 11.1.2, et il l'est,
mais à une différence majeure près : dans le cas du modèle `Micropost` (modèle des micro-messages) nous
pouvions écrire :
class Micropost < ActiveRecord :: Base
belongs_to :user
.
.
end
… et…
class User < ActiveRecord :: Base
has_many :microposts
.
.
end
… parce que la table microposts (table des micro-messages) possède un attribut user_id pour identifier
l'utilisateur (section 11.1.1). Un identifiant utilisé de cette manière pour connecter les tables d'une base de
données est connu sous le nom de clé étrangère (foreign key), et puisque la clé étrangère pour le modèle `User`
est user_id , Rails peut déduire l'association automatiquement : par défaut, Rails attend une clé étrangère de
la forme <class>_id , où <class> est la version minuscule du nom de la classe (donc la classe User implique
la clé étrangère user_id ).206 Dans le cas présent, bien que nous traitions encore les utilisateurs, ils sont
463
identifiés maintenant avec la clé étrangère follower_id (id_utilisateur_lecteur), donc nous devons le
dire explicitement à Rails, comme le montre l'extrait 12.5.207
Extrait 12.5. Implémentation de l'association has_many de la relation utilisateur/relations.
app/models/user.rb
class User < ActiveRecord :: Base
.
.
has_many :microposts , :dependent => :destroy
has_many :relationships , :foreign_key => "follower_id" ,
:dependent => :destroy
.
.
end
(Puisque détruire un utilisateur devrait aussi détruire ses relations, nous prenons de l'avance et ajoutons
:dependent => :destroy à l'association ; l'écriture d'un test pour ce point est laissé en exercice
(section 12.5).) À ce stade, les tests d'association de l'extrait 12.3 et l'extrait 12.4 devraient réussir.
À l'instar du modèle `Micropost`, le modèle `Relationship` possède avec les utilisateurs une relation
belongs_to (appartient_à…) ; dans ce cas, un objet `relationship` appartient d'une part à un utilisateur-
lecteur (follower ) et d'autre part à un utilisateur-auteur (followed ), ce que nous testons dans l'extrait 12.6.
Extrait 12.6. Test de l'association belongs_to .
spec/models/relationship_spec.rb
describe Relationship do
.
.
.
describe "Méthodes de suivi" do
before( :each ) do
@relationship . save
end
it "devrait avoir un attribut follower (lecteur)" do
@relationship . should respond_to( :follower )
end
464
it "devrait avoir le bon lecteur" do
@relationship . follower . should == @follower
end
it "devrait avoir un attribut followed (suivi)" do
@relationship . should respond_to( :followed )
end
it "devrait avoir le bon utilisateur suivi (auteur)" do
@relationship . followed . should == @followed
end
end
end
Pour écrire le code de l'application, nous définissons la relation belongs_to (appartient_à) normalement.
Rails déduit les noms des clés étrangères à partir des symboles correspondants (c'est-à-dire l'identifiant
follower_id de :follower , et l'identifiant followed_id de :followed ), mais puisqu'il n'y a pas plus de
modèle `Followed` que de modèle `Follower` nous avons aussi besoin de fournir le nom de classe User . Le
résultat est montré dans l'extrait 12.7.
Extrait 12.7. Ajout des associations belongs_to au modèle `Relationship`.
app/models/relationship.rb
class Relationship < ActiveRecord :: Base
attr_accessible :followed_id
belongs_to :follower , :class_name => "User"
belongs_to :followed , :class_name => "User"
end
L'association followed (utilisateur_suivi) n'est en fait pas nécessaire jusqu'à la section 12.1.5, même la
construction parallèle follower/followed est plus claire si nous implémentons les deux en même temps.
12.1.3 Validations
Avant de poursuivre, nous allons ajouter quelques validations au modèle `Relationship` pour le compléter. Les
tests (extrait 12.8) et le code de l'application (extrait 12.9) sont évidents.
465
Extrait 12.8. Test des validations du modèle `Relationship`.
spec/models/relationship_spec.rb
describe Relationship do
.
.
describe "validations" do
it "devrait exiger un attribut follower_id" do
@relationship . follower_id = nil
@relationship . should_not be_valid
end
it "devrait exiger un attribut followed_id" do
@relationship . followed_id = nil
@relationship . should_not be_valid
end
end
end
Extrait 12.9. Ajout des validations du modèle `Relationship`.
app/models/relationship.rb
class Relationship < ActiveRecord :: Base
attr_accessible :followed_id
belongs_to :follower , :class_name => "User"
belongs_to :followed , :class_name => "User"
validates :follower_id , :presence => true
validates :followed_id , :presence => true
end
12.1.4 Suivi
Nous en arrivons maintenant au cœur des associations `Relationship` : following (auteurs suivis) et
followers (lecteurs). Nous commençons avec following , comme dans l'extrait 12.10.
Extrait 12.10. Un test pour l'attribut user.following .
spec/models/user_spec.rb
466
describe User do
.
.
describe "relationships" do
before( :each ) do
@user = User . create!(@attr)
@followed = Factory( :user )
end
it "devrait posséder une méthode `relationships`" do
@user . should respond_to( :relationships )
end
it "devrait posséder une méthode `following" do
@user . should respond_to( :following )
end
end
end
L'implémentation utilise has_many :through (possède_plusieurs :à_travers) pour la première fois : un
utilisateur possède plusieurs lecteurs à travers la relation, comme le montre l'illustration 12.7. Par défaut, dans
une association has_many :through , Rails cherche une clé étrangère correspondant à la version singulier de
l'association ; en d'autres termes, un code comme :
has_many :followeds , :through => :relationships
… devrait assembler un tableau en utilisant le followed_id dans la table relationships . Mais, comme
indiqué à la section 12.1.1, user.followeds est plutôt maladroit ; il est de loin plus naturel de traiter
« following » comme la forme plurielle de « followed », et d'écrire plutôt user.following pour la table des
utilisateurs suivis. Naturellement, Rails nous permet de sur-écrire la valeur par défaut, dans ce cas en utilisant
le paramètre :source (extrait 12.11), qui dit explicitement à Rails que la source de la table following est le
set des ids de followed .
Extrait 12.11. Ajout de l'association following du modèle User avec has_many :through .
app/models/user.rb
class User < ActiveRecord :: Base
.
467
.
.
has_many :microposts , :dependent => :destroy
has_many :relationships , :foreign_key => "follower_id" ,
:dependent => :destroy
has_many :following , :through => :relationships , :source => :followed
.
.
.
end
Pour créer la relation following, nous allons introduire une méthode utilisataire follow! de telle sorte que
nous puissions écrire user.follow!(other_user) .208 Nous allons aussi ajouter une méthode booléen
associée following? pour tester si un utilisateur en suit un autre.209 Les tests dans l'extrait 12.12 montrent
comment nous pouvons nous attendre à ce que ces méthodes soient utilisées dans la pratique.
Extrait 12.12. Tests pour quelques méthodes utilitaires de suivi.
spec/models/user_spec.rb
describe User do
.
.
.
describe "relationships" do
.
.
.
it "devrait avoir une méthode following?" do
@user . should respond_to( :following? )
end
it "devrait avoir une méthode follow!" do
@user . should respond_to( :follow! )
end
it "devrait suivre un autre utilisateur" do
@user . follow!(@followed)
@user . should be_following(@followed)
end
468
it "devrait inclure l'utilisateur suivi dans la liste following" do
@user . follow!(@followed)
@user . following . should include(@followed)
end
end
end
Notez que nous avons remplacé la méthode include? vu dans l'extrait 11.31 par should include , en
transformant effectivement :
@user. following . include?(@followed) . should be_true
… par le code plus clair et succinct :
@user. following . should include(@followed)
Cet exemple montre juste la flexibilité des conventions booléenne de RSpec ; même si include est déjà un
mot-clé Ruby (utilisé pour inclure un module, comme nous l'avons vu, par exemple, dans l'extrait 9.11), RSpec
devine dans ce contexte que nous voulons tester l'existence d'un élément dans une liste.
Dans le code de l'application, la méthode following? prend un utilisateur, appelle followed et vérifie si un
follower (un lecteur) avec cet identifiant existe dans la base de données ; la méthode follow! appelle
create! à travers l'association relationships pour créer la relation de suivi. Le résultat apparait dans
l'extrait 12.13.210
Extrait 12.13. Les méthode utilitaires following? et follow! .
app/models/user.rb
class User < ActiveRecord :: Base
.
.
.
def self . authenticate_with_salt( id , stored_salt)
.
.
.
end
469
def following?(followed)
relationships . find_by_followed_id(followed)
end
def follow!(followed)
relationships . create!( :followed_id => followed . id)
end
.
.
.
end
Notez que dans l'extrait 12.13 nous avons omis l'utilisateur lui-même, en écrivant juste :
relationships . create!( . . . )
… au lieu du code équivalent :
self . relationships . create!( . . . )
L'utilisation d'un self explicite est largement une question de goût ici.
Bien entendu, les utilisateurs devraient être capables de ne plus suivre les autres utilisateurs autant que de les
suivre, ce qui nous conduit à la méthode quelque peu prévisible unfollow! , présentée dans l'extrait 12.14.211
Extrait 12.14. Un test pour l'arrêt de suivi d'un utilisateur.
spec/models/user_spec.rb
describe User do
.
.
.
describe "relationships" do
.
.
.
it "devrait avoir une méthode unfollow!" do
@followed . should respond_to( :unfollow! )
470
end
it "devrait arrêter de suivre un utilisateur" do
@user . follow!(@followed)
@user . unfollow!(@followed)
@user . should_not be_following(@followed)
end
end
end
Le code pour unfollow! est très simple : il trouve juste la relation par l'id followed et le détruit
(extrait 12.15).212
Extrait 12.15. Arrêter le suivi d'un utilisateur en détruisant une relation utilisateur.
app/models/user.rb
class User < ActiveRecord :: Base
.
.
.
def following?(followed)
relationships . find_by_followed_id(followed)
end
def follow!(followed)
relationships . create!( :followed_id => followed . id)
end
def unfollow!(followed)
relationships . find_by_followed_id(followed) . destroy
end
.
.
.
end
471
12.1.5 Les Lecteurs
La dernière pièce du puzzle relationnel consiste à ajouter une méthode user.followers (utilisateur.lecteurs)
marchant avec user.following (utilisateur.auteurs_suivis). Vous avez peut-être noté d'après
l'illustration 12.7 que toutes les informations nécessaires pour extraire un tableau des lecteurs (followers) est
déjà présent dans la table relationships . En effet, la technique est exactement la même que pour les
utilisateur suivis, avec les rôles de follower_id et followed_id simplement inversés. Cela suggère que, si
nous pouvions arranger une table reverse_relationships avec ces colonnes inversées (illustration 12.9),
nous pourrions implémenter user.followers avec peu d'effort.
Illustration 12.9: Un modèle pour les utilisateurs lecteurs en utilisant un modèle Relationship inversé.
Nous commençons avec les tests, avec la certitude que la magie de Rails viendra à nouveau à notre aide
(extrait 12.16).
Extrait 12.16. Tester la relation inversée.
spec/models/user_spec.rb
describe User do
.
.
.
describe "relationships" do
.
472
.
.
it "devrait avoir un méthode reverse_relationship" do
@user . should respond_to( :reverse_relationships )
end
it "devrait avoir une méthode followers" do
@user . should respond_to( :followers )
end
it "devrait inclure le lecteur dans le tableau des lec teurs" do
@user . follow!(@followed)
@followed . followers . should include(@user)
end
end
end
Comme vous le suspectez certainement, nous ne ferons pas une table complète de base de données juste pour
conserver la relation inversée. Plutôt, nous exploiterons la symétrie sous-jaccente entre lecteurs et auteurs pour
simuler une table reverse_relationships en passant followed_id (auteur_id) comme clé primaire. En
d'autres termes, là où l'association relationship utilise la clé étrangère follower_id (lecteur_id) :
has_many :relationships , :foreign_key => "follower_id"
… l'association reverse_relationships utilise followed_id (auteur_id) :
has_many :reverse_relationships , :foreign_key => "followed_id"
L'association followers (lecteurs) se contruit alors à travers la relationship inversée, comme le montre
l'extrait 12.17.
Extrait 12.17. Implémenter user.followers en utilisant la relation inversée.
app/models/user.rb
class User < ActiveRecord :: Base
.
.
has_many :reverse_relationships , :foreign_key => "followed_id" ,
:class_name => "Relationship" ,
473
:dependent => :destroy
has_many :followers , :through => :reverse_relationships , :source => :follower
.
.
.
end
(Comme dans l'extrait 12.5, le test de dependent :destroy est laissé en exercice (section 12.5).) Notez que
nous avons en fait à inclure le nom de classe pour cette association, c'est-à-dire :
has_many :reverse_relationships , :foreign_key => "followed_id" ,
:class_name => "Relationship"
… sinon, Rails cherchera une classe ReverseRelationship , qui n'existe pas.
Il est important de noter aussi que nous pourrions en fait omettre la clé :source dans ce cas, en utilisant
simplement :
has_many :followers , :through => :reverse_relationships
… puisque Rails cherchera automatiquement une clé étrangère follower_id dans ce cas. J'ai gardé la clé
:source pour mettre l'accent sur le parallèle de structure avec l'association has_many :following , mais
vous êtes libre de ne pas l'utiliser.
Avec le code de l'extrait 12.17, les associations lecteurs/auteur sont achevées, et tous les tests devraient réussir.
Cette section a fait appel de façon intensive à vos compétences en matière de modélisation de données, et il n'y
aucun problème si vous mettez du temps à assimiler ces concepts . En fait, l'une des meilleures façons de
comprendre les associations est de les utiliser dans l'interface web, comme nous le verrons dans la prochaine
section.
12.2 Une interface web pour les auteurs et les lecteurs
Dans l'introduction de ce chapitre, nous avons vu un aperçu du flux de page pour les utilisateurs-lecteurs. Dans
cette section, nous implémenterons l'interface de base et les fonctionnalités de suivi/arrêt de suivi montrées
dans ces maquettes. Nous construirons également des pages séparées pour montrer les lecteurs de l'auteur et la
liste des auteurs suivis. À la section 12.3, nous achèverons notre Application Exemple en ajoutant l'état
d'alimentation (status feed) de l'utilisateur.
474
12.2.1 Données de simple lecteur
Comme dans les chapitres précédents, il est très pratique d'utiliser une tâche Rake pour remplir la base de
données d'échantillons de relations. Cela nous permettra de concevoir l'aspect et la convivialité des pages web
d'abord, différant les fonctionnalités de back-end pour plus tard dans cette section.
La dernière fois que nous avons laissé le « peupleur » d'échantillons de données de l'extrait 11.20, il était
devenu plutôt encombré, donc nous allons commencer par définir des méthodes séparées pour créer des
utilisateurs et des micro-messages, et ajouter alors des échantillons de données de relations (relationships) en
utilisant une nouvelle méthode make_relationships (creer_des_relations). Le résultat est montré dans
l'extrait 12.18.
Extrait 12.18. Ajout des relations auteurs/lecteur pour les échantillons de données.
lib/tasks/sample_data.rake
require 'faker'
namespace :db do
desc "Peupler la base de données avec des échantillons"
task :populate => :environment do
Rake :: Task [ 'db:reset' ]. invoke
make_users
make_microposts
make_relationships
end
end
def make_users
admin = User . create!( :nom => "Example User" ,
:email => "[email protected]" ,
:password => "foobar" ,
:password_confirmation => "foobar" )
admin . toggle!( :admin )
99. times do | n|
nom = Faker :: Nom. nom
email = "example- #{n +1} @railstutorial.org"
password = "password"
User . create!( :nom => nom,
:email => email,
:password => password,
475
:password_confirmation => password)
end
end
def make_microposts
User . all( :limit => 6) . each do | user |
50. times do
content = Faker :: Lorem . sentence( 5)
user . microposts . create!( :content => content)
end
end
end
def make_relationships
users = User . all
user = users . first
following = users [1. . 50]
followers = users [3. . 40]
following . each { | followed | user . follow!(followed) }
followers . each { | follower | follower . follow!(user) }
end
Ici, les échantillons de relations sont créés en utilisant le code :
def make_relationships
users = User . all
user = users . first
following = users [1. . 50]
followers = users [3. . 40]
following . each { | followed | user . follow!(followed) }
followers . each { | follower | follower . follow!(user) }
end
Nous nous arrangeons un peu arbitrairement pour que le premier utilisateur suive les 50 utilisateurs suivants,
et puis faire que les utilisateurs entre les identifiants de 4 à 41 suivent cet utilisateur en retour. Les relations en
résultant sera suffisante pour développer l'interface de l'application.
Pour exécuter le code de l'extrait 12.18, peuplons la base de données comme d'habitude :
476
$ rake db:populate
12.2.2 Statistiques et formulaire de suivi
Maintenant que nos échantillons d'utilisateurs ont des listes (array) de lecteurs et suivent des auteurs, nous
avons besoin d'actualiser les pages de profil et d'accueil pour le refléter. Nous allons commencer par faire un
partiel pour afficher les statistiques sur les pages de profil et d'accueil, comme le montre la maquette de
l'illustration 12.1 et de l'illustration 12.5. Le résultat affichera le nombre d'auteurs suivis et de lecteurs, avec des
liens vers leurs pages respectives. Nous ajouterons ensuite le lien suivre/arrêter de suivre, et fabriquerons
ensuite les pages dédiées pour afficher les utilisateurs suivis (auteurs suivis) et les utilisateurs qui suivent
l'utilisateur courant (lecteurs).
Illustration 12.10: Maquette du partiel pour les statistiques.
Un gros plan de la zone des statistiques, extrait de la maquette de l'illustration 12.1, est montré dans
l'illustration 12.10. Ces statistiques consistent en un compte des utilisateurs que l'utilisateur courant suit et du
nombre d'utilisateurs qui le suivent, chacun d'eux avec un lien vers la page d'affichage correspondante. Au
chapitre 5, nous avons esquissé de tels liens avec un texte souche ’#’ , mais c'était avant d'avoir une expérience
suffisantes des routes. Cette fois, bien que nous différerons les pages réelles à la section 12.2.3, nous allons faire
les routes maintenant, conformément à l'extrait 12.19. Ce code utilise la méthode :member à l'intérieur d'un
bloc resource , que nous n'avons jamais vu avant, mais voyons si vous pouvez deviner ce qu'il fait.
Extrait 12.19. Ajout des actions following et followers au contrôleur Users.
config/routes.rb
SampleApp :: Application . routes . draw do
resources :users do
member do
get :following , :followers
end
end
.
.
.
end
477
Vous pouvez imaginer que les URLs pour les utilisateurs suivis (following) et suiveurs (followers)
ressembleront à /users/1/following et /users/1/followers et c'est exactement ce
que fait le code dans l'extrait 12.19. Puisque les deux pages afficheront des données, nous utilisons get pour
faire en sorte que les URLs répondent à la requête GET (comme requis par la convention REST pour de telles
pages), et la méthode member signifie que les routes répondent aux URLs contenant l'id de l'utilisateur (l'autre
possibilité, collection , fonctionne sans id , de telle sorte que :
resources :users do
collection do
get :tigers
end
end
… devra répondre à l'URL /users/tigers — vraisemblablement pour afficher tous les tigres dans
notre application. Pour plus de détails sur de telles options de routage, voyez l'article du Rails Guides à propos
de « Rails Routing from the Outside In »). Une table des routes générées par l'extrait 12.19 est présentée dans la
table 12.1 ; notez les routes nommées pour les pages des auteurs suivis (following) et lecteurs (followers) que
nous serons amenés à utiliser dans un instant.
Requête HTTP URL Action Route nommé
GET /users/1/following following following_user_path(1)
GET /users/1/followers followers followers_user_path(1)
Table 12.1: Routes RESTful fournies par les règles personnalisées dans la ressource de l'extrait 12.19.
Les routes une fois définies, nous sommes maintenant en mesure de construire les tests pour le partiel
statistiques (nous aurions pu écrire les tests d'abord, mais les routes nommées auraient été difficiles à motiver
sans le fichier routes actualisé). Nous pourrions écrire les tests pour la page de profil de l'utilisateur, puisque le
partiel des statistiques apparaitra là, mais il apparaitra aussi sur la page d'accueil, et c'est une bonne
opportunité pour restructurer les tests de la page d'accueil pour prendre en compte les utilisateur identifiés. Le
résultat apparait dans l'extrait 12.20.
Extrait 12.20. Tester les statistiques auteurs/lecteurs sur la page d'accueil.
spec/controllers/pages_controller_spec.rb
describe PagesController do
render_views
478
before( :each ) do
@base_titre = "Ruby on Rails Tutorial Sample App"
end
.
.
.
describe "GET 'home'" do
describe "quand pas identifié" do
before( :each ) do
get :home
end
it "devrait réussir" do
response . should be_success
end
it "devrait avoir le bon titre" do
response . should have_selector( "title" ,
:content => " #{@base_titre} | Home" )
end
end
describe "quand identifié" do
before( :each ) do
@user = test_sign_in(Factory( :user ))
other_user = Factory( :user , :email => Factory . next( :email ))
other_user . follow!(@user)
end
it "devrait avoir le bon compte d'auteurs et de lecteu rs" do
get :home
response . should have_selector( "a" , :href => following_user_path(@user),
:content => "0 auteur suivi" )
479
response . should have_selector( "a" , :href => followers_user_path(@user),
:content => "1 lecteur" )
end
end
end
end
Le cœur de ce test est l'attente que le compte de lecteurs et d'auteurs suivis apparaisse sur la page, accompagnés
des bonnes URLs :
response . should have_selector( "a" , :href => following_user_path(@user),
:content => "0 auteur suivi" )
response . should have_selector( "a" , :href => followers_user_path(@user),
:content => "1 lecteur" )
Nous avons utilisé ici la route nommée de la table 12.1 pour vérifier que les liens avaient les bons URLs.
Le code de l'application pour le partiel des statistiques est simplement une table HTML à l'intérieur d'un div
comme dans l'extrait 12.21.
Extrait 12.21. Un partiel pour afficher les statistiques de suivi.
app/views/shared/_stats.html.erb
<% @user ||= current_user %>
<div class="stats">
<table summary="User stats">
<tr>
<td>
<a href="<%= following_user_path(@user) %>" >
<span id="following" class="stat">
<%= pluralize(@user . following . count, "auteur suivi", "auteurs suivis" ) %>
</span>
</a>
</td>
<td>
<a href="
<%= followers_user_path(@user) %>">
<span id="followers" class="stat">
480
<%= pluralize(@user . followers . count, "lecteur" ) %>
</span>
</a>
</td>
</tr>
</table>
</div>
Ici les comptes utilisateur de lecteurs et d'auteurs suivis sont calculés à travers l'association en utilisant :
@user. following . count
… et :
@user. followers . count
Comparez ceci aux comptes de micro-messages de l'extrait 11.16, où nous avions écrit :
@user. microposts . count
… pour compter les micro-messages.
Puisque nous inclurons les statistiques sur la page d'affichage ainsi que sur la page d'accueil de l'utilisateur, la
première ligne de l'extrait 12.21 choisit la bonne en utilisant :
<% @user ||= current_user %>
Comme discuté dans la Box 9.4, cela ne fait rien quand @user n'est pas nil (comme sur la page de profil), mais
quand il l'est (comme sur la page d'accueil), il règle la valeur de @user à l'utilisateur courant.
Un détail final qui vaut le peine d'être noté : la présence des ids CSS sur certains éléments, comme dans :
<span id="following" class="stat">
...
</span>
Ceci anticipe l'implémentation Ajax de la section 12.2.5, qui accède aux éléments de la page en utilisant leur
identifiant unique (id ).
481
Avec le partiel en main, inclure les statistiques sur la page d'accueil est facile, comme montré dans
l'extrait 12.22 (cela doit également faire réussir le test de l'extrait 12.20). Le résultat est présenté dans
l'illustration 12.11.
Extrait 12.22. Ajouter les statistiques lecteur à la page d'accueil.
app/views/pages/home.html.erb
<% if signed_in? %>
.
.
.
<%= render 'shared/user_info' %>
<%= render 'shared/stats' %>
</td>
</tr>
</table>
<% else %>
.
.
.
<% end %>
Illustration 12.11: La page d'accueil (/ ) avec les statistiques de suivi.
482
Nous « rendrons » le partiel des statistiques sur la page de profil dans un moment, mais construisons avant ça
un partiel pour le bouton suivre/ne plus suivre dont le code apparait dans l'extrait 12.23.
Extrait 12.23. Un partiel pour le formulaire suivre/ne plus suivre.
app/views/users/_follow_form.html.erb
<% unless current_user?(@user) %>
<div id="follow_form">
<% if current_user . following?(@user) %>
<%= render 'unfollow' %>
<% else %>
<%= render 'follow' %>
<% end %>
</div>
<% end %>
Cela ne fait rien d'autre que confier le vrai travail aux partiels follow (suivre) et unfollow (ne plus suivre),
qui ont besoin de nouvelles routes avec des règles pour la ressource Relationships, qui suivent l'exemple de la
ressource Microposts (extrait 11.21), comme présenté dans l'extrait 12.24.
Extrait 12.24. Ajout des routes pour les relations de l'utilisateur.
config/routes.rb
SampleApp :: Application . routes . draw do
.
.
.
resources :sessions , :only => [ :new , :create , :destroy ]
resources :microposts , :only => [ :create , :destroy ]
resources :relationships , :only => [ :create , :destroy ]
.
.
.
end
Le code des partiels suivre/ne plus suivre eux-mêmes est présenté dans l'extrait 12.25 et l'extrait 12.26.
Extrait 12.25. Un formulaire pour suivre un utilisateur.
app/views/users/_follow.html.erb
<%= form_for current_user . relationships .
483
build( :followed_id => @user . id) do | f | %>
<div><%= f . hidden_field :followed_id %></div>
<div class="actions"><%= f . submit "Suivre" %></div>
<% end %>
Extrait 12.26. Formulaire pour ne plus suivre un utilisateur suivi.
app/views/users/_unfollow.html.erb
<%= form_for current_user . relationships . find_by_followed_id(@user),
:html => { :method => :delete } do | f | %>
<div class="actions"><%= f . submit "Ne plus suivre" %></div>
<% end %>
Ces formulaires utilisent tous deux form_for pour manipuler un objet de modèle Relationship ; la différence
principale entre les deux est que l'extrait 12.25 construit une nouvelle relation, tandis que l'extrait 12.26 cherche
la relation existente. Naturellement, la première envoie une requête POST au contrôleur Relationships pour créer (create ) une relation, tandis que la seconde envoie une requête DELETE pour détruire (destroy )
une relation (nous implémenterons ces actions à la section 12.2.4). Enfin, vous noterez que le formulaire
suivre/ne plus suivre n'a aucun contenu autre que le bouton, mais il a encore besoin d'envoyer le
followed_id , ce que nous accomplissons avec hidden_field (champ caché) ; cela produit un code HTML
de la forme :
<input id="relationship_followed_id" nom="relations hip[followed_id]"
type="hidden" value="3" />
… qui place l'information voulu sur la page sans l'afficher sur la page du navigateur.
Nous pouvons maintenant inclure le formulaire de suivi et les statistiques de suivi sur la page de profil
simplement en rendant les partiels, comme montré dans l'extrait 12.27. Les profils avec les boutons « suivre » et
« ne plus suivre », respectivement, apparaissent dans l'illustration 12.12 et l'illustration 12.13.
Extrait 12.27. Ajout du formulaire de suivi et des statistiques de suivi à la page profil de l'utilisateur.
app/views/users/show.html.erb
<table class="profile" summary="Profile information ">
<tr>
<td class="main">
<h1>
<%= gravatar_for @user %>
<%= @user . nom %>
484
</h1>
<%= render 'follow_form' if signed_in? %>
.
.
.
</td>
<td class="sidebar round">
<strong>Name</strong> <%= @user . nom %><br />
<strong>URL</strong> <%= link_to user_path(@u ser), @user %><br />
<strong>Microposts</strong> <%= @user . microposts . count %>
<%= render 'shared/stats' %>
</td>
</tr>
</table>
Illustration 12.12: Un profil d'utilisateur avec un bouton « suivre » (/users/8 ).
485
Illustration 12.13: Un profil d'utilisateur avec un bouton « ne plus suivre » (/users/6 ).
Nous obtiendrons le fonctionnement de ces boutons bien assez tôt — en fait, nous le ferons de deux manières, la
stardard (section 12.2.4) et celle utilisant Ajax (section 12.2.5) — mais d'abord nous allons achever le code
HTML de l'interface en construisant les pages des lecteurs et des auteurs suivis par l'utilisateur.
12.2.3 Pages des auteurs suivis et des lecteurs
Les pages pour afficher les auteurs suivis et les lecteurs de l'utilisateur courant ressembleront à un hybride
entre la page de profil de l'utilisateur et la page d'index (section 10.3.1), avec une barre lattéral pour les
informations de l'utilisateur (incluant l'état de son suivi) et une table d'utilisateurs. En plus, nous inclurons un
quadrillage de liens sur l'image des profils. Les maquettes présentant ces exigences sont présentées dans
l'illustration 12.14 (auteurs suivis) et illustration 12.15 (lecteurs).
486
Illustration 12.14: Une maquette de la page des auteurs suivis par l'utilisateur courant.
Illustration 12.15: Une maquette de la page des lecteurs de l'utilisateur courant.
Notre première étape consiste à faire fonctionner les liens vers les auteurs suivis (following) et les lecteurs
(followers). Nous suivrons le modèle de Twitter et devrons exiger une identification pour les deux pages. Pour
les utilisateurs identifiés, les pages devraient posséder des liens respectifs pour les auteurs suivis et les lecteurs.
L'extrait 12.28 exprime ces attentes en code.213
Extrait 12.28. Test pour les actions following (auteurs suivis) et followers (lecteurs).
spec/controllers/users_controller_spec.rb
487
describe UsersController do
.
.
.
describe "Les pages de suivi" do
describe "quand pas identifié" do
it "devrait protéger les auteurs suivis" do
get :following , :id => 1
response . should redirect_to(signin_path)
end
it "devrait protéger les lecteurs" do
get :followers , :id => 1
response . should redirect_to(signin_path)
end
end
describe "quand identifié" do
before( :each ) do
@user = test_sign_in(Factory( :user ))
@other_user = Factory( :user , :email => Factory . next( :email ))
@user . follow!(@other_user)
end
it "devrait afficher les auteurs suivis par l'utilisat eur" do
get :following , :id => @user
response . should have_selector( "a" , :href => user_path(@other_user),
:content => @other_user . nom)
end
it "devrait afficher les lecteurs de l'utilisateur" do
get :followers , :id => @other_user
response . should have_selector( "a" , :href => user_path(@user),
:content => @user . nom)
end
488
end
end
end
La seule partie délicate de l'implémentation est de concevoir que nous avons besoin d'ajouter deux nouvelles
actions au contrôleur Users ; en s'appuyant sur les routes définies dans l'extrait 12.19, nous avons besoin de les
appeler following (auteurs suivis) et followers (lecteurs). Chaque action a besoin de définir un titre,
trouver un utilisateur, récupérer soit @user.following ou @user.followers (dans une forme paginée), et
enfin de rendre la page attendue. Le résultat est présenté dans l'extrait 12.29.
Extrait 12.29. Les actions following et followers .
app/controllers/users_controller.rb
class UsersController < ApplicationController
before_filter :authenticate , :except => [ :show , :new , :create ]
.
.
.
def following
@titre = "Following"
@user = User . find(params [ :id ] )
@users = @user . following . paginate( :page => params [ :page ] )
render 'show_follow'
end
def followers
@titre = "Followers"
@user = User . find(params [ :id ] )
@users = @user . followers . paginate( :page => params [ :page ] )
render 'show_follow'
end
.
.
.
end
Notez ici que les deux actions font un appel explicite à render , dans ce cas pour rendre une vue appelée
show_follow (montrer_le_suivi) que nous devons créer. La raison de cette vue commune est que le ERb est
très proche dans les deux cas, et l'extrait 12.30 les couvre les deux.
489
Extrait 12.30. La vue show_follow utilisée pour rendre les auteurs suivis et les lecteurs.
app/views/users/show_follow.html.erb
<table summary="Information à propos des auteurs su ivis ou des lecteurs">
<tr>
<td class="main">
<h1><%= @titre %></h1>
<% unless @users . empty? %>
<ul class="users">
<%= render @users %>
</ul>
<%= will_paginate @users %>
<% end %>
</td>
<td class="sidebar round">
<strong>Nom</strong> <%= @user . nom %><br />
<strong>URL</strong> <%= link_to user_path(@u ser), @user %><br />
<strong>Messages</strong> <%= @user . microposts . count %>
<%= render 'shared/stats' %>
<% unless @users . empty? %>
<% @users . each do | user | %>
<%= link_to gravatar_pour(user, :size => 30), user %>
<% end %>
<% end %>
</td>
</tr>
</table>
Il y a un second détail dans l'extrait 12.29 qui vaut la peine d'être noté : dans le but de protéger les pages des
auteurs suivis et des lecteurs d'un accès non autorisé, nous avons changé les authentifications d'avant fitrage
pour utiliser :except plutôt que :only . Jusqu'ici dans ce manuel, nous avons utilisé :only pour indiquer sur
quelles actions le filtre devait s'appliquer ; avec l'addition des nouvelles actions protégées, la balance a penché,
et il est plus simple à présent d'indiquer quelles actions ne doivent pas être filtrées. Nous faisons cela avc
l'option :except pour le authenticate d'avant filtrage :
before_filter :authenticate , :except => [ :show , :new , :create ]
490
Avec ça, les tests devraient maintenant réussir, et les pages devraient être rendues comme dans
l'illustration 12.16 (auteurs suivis) et l'illustration 12.17 (lecteurs).
Illustration 12.16: Afficher les utilisateurs qui sont suivis par l'utilisateur courant.
Illustration 12.17: Afficher les lecteurs de l'utilisateur courant.
Vous avez peut-être noter que même avec le partiel commun show_followers , les actions following et
followers ont toujours beaucoup de code dupliqué. Plus encore, le partiel show_followers lui-même
491
partage des fonctionnalités avec la page d'affichage de l'utilisateur. La section 12.5 inclut des exercices
pour éliminer ces duplications.
12.2.4 Un bouton de suivi standard
Maintenant que nos vues sont en ordre, il est temps de faire fonctionner les boutons suivre/ne plus suivre.
Puisque suivre un utilisateur crée une relation, et ne plus suivre un utilisateur détruit cette relation, cela revient
à écrire les actions create et destroy pour le contrôleur Relationship. Natuellement, les deux actions devront
être protégées ; pour les utilisateurs identifiés, nous utiliserons les méthodes utilitaires follow! et
unfollow! définies à la section 12.1.4 pour créer et détruire les relations concernées. Ces exigences conduisent
aux tests de l'extrait 12.31.
Extrait 12.31. Tests pour les actions du contrôleur Relationships.
spec/controllers/relationships_controller_spec.rb
require 'spec_helper'
describe RelationshipsController do
describe "Le contrôle d'accès" do
it "devrait exiger l'identification pour créer" do
post :create
response . should redirect_to(signin_path)
end
it "devrait exiger l'identification pour détruire" do
delete :destroy , :id => 1
response . should redirect_to(signin_path)
end
end
describe "POST 'create'" do
before( :each ) do
@user = test_sign_in(Factory( :user ))
@followed = Factory( :user , :email => Factory . next( :email ))
end
it "devrait créer une relation" do
492
lambda do
post :create , :relationship => { :followed_id => @followed }
response . should be_redirect
end . should change(Relationship, :count ) . by( 1)
end
end
describe "DELETE 'destroy'" do
before( :each ) do
@user = test_sign_in(Factory( :user ))
@followed = Factory( :user , :email => Factory . next( :email ))
@user . follow!(@followed)
@relationship = @user . relationships . find_by_followed_id(@followed)
end
it "devrait détruire une relation" do
lambda do
delete :destroy , :id => @relationship
response . should be_redirect
end . should change(Relationship, :count ) . by( -1 )
end
end
end
Notez ici comment :
:relationship => { :followed_id => @followed }
… simule la soumission du formulaire avec les champs cachés (hidden) donnés par :
<%= f . hidden_field :followed_id %>
Le code du contrôleur nécessaire pour faire réussir ces tests est remarquablement concis : nous récupérons
juste l'utilisateur suivi ou devant être suivi, et suivons ou ne-suivons-plus l'utilisateur en utilisant la méthode
utilitaire concernée. L'implémentation complète apparait dans l'extrait 12.32.
Extrait 12.32. Le contrôleur Relationships.
493
app/controllers/relationships_controller.rb
class RelationshipsController < ApplicationController
before_filter :authenticate
def create
@user = User . find(params [ :relationship ][ :followed_id ] )
current_user . follow!(@user)
redirect_to @user
end
def destroy
@user = Relationship . find(params [ :id ] ) . followed
current_user . unfollow!(@user)
redirect_to @user
end
end
Avec ça, le cœur de la fonctionnalité suivre/ne plus suivre est achevé, et n'importe quel utilisateur peut suivre
(ou ne plus suivre) n'importe quel autre utilisateur.
12.2.5 Un bouton de suivi fonctionnel avec Ajax
Bien que l'implémentation du suivi de notre utilisateur soit opérationnelle telle qu'elle est, une dernière petite
chose peut être ajoutée avant de travailler sur le « status feed » (l'état de l'alimentation). Vous avez peut-être
noté à la section 12.2.4 que les deux actions create et destroy dans le contrôleur Relationships redirigeait
simplement (back) vers le profil original. En d'autres termes, un utilisateur commence sur sa page de profil, suit
l'utilisateur et est immédiatement redirigé en arrière vers la page initiale. Il est raisonnable de se demander
pourquoi l'utilisateur a besoin de quitter cette page de profil.
C'est exactement le genre de problème que peut résoudre Ajax, qui permet aux pages web d'envoyer des
requêtes de façon asynchrone à un serveur sans quitter la page.214 Ajouter de l'Ajax à des formulaires web étant
plutôt courant aujourd'hui, Rails le rend facile à implémenter. Actualiser les formulaires suivre/ne-plus-suivre
des partiels est même particulière aisé ; changez seulement :
form_for
… en :
form_for . . . , :remote => true
494
… et Rails automagiquement utilise Ajax.215 Les partiels actualisés sont présentés dans l'extrait 12.33 et
l'extrait 12.34.
Extrait 12.33. Un formulaire pour suivre les utilisateurs en utilisant Ajax.
app/views/users/_follow.html.erb
<%= form_for current_user . relationships . build( :followed_id => @user . id),
:remote => true do | f | %>
<div><%= f . hidden_field :followed_id %></div>
<div class="actions"><%= f . submit "Follow" %></div>
<% end %>
Extrait 12.34. Un formulaire pour ne-plus-suivre l'utilisateur en utilisant Ajax.
app/views/users/_unfollow.html.erb
<%= form_for current_user . relationships . find_by_followed_id(@user),
:html => { :method => :delete },
:remote => true do | f | %>
<div class="actions"><%= f . submit "Unfollow" %></div>
<% end %>
Le code HTML généré par ce ERb n'est pas très expressif, mais par curiosité, jetons-y un coup d'œil :
<form action="/relationships/117" class="edit_relat ionship" data-remote="true"
id="edit_relationship_117" method="post">
.
.
.
</form>
Cela définit la variable data-remote="true" à l'intérieur de la balise du formulaire (form ), qui demande à
Rails de permettre au formulaire d'être traité par JavaScript. En utilisant une simple propriété HTML au lieu
d'insérer du code Javascript (comme dans les précédentes versions de Rails), Rails 3 suit la philosophie du
JavaScript non intrusif.
Ayant actualisé le formulaire, nous avons besoin maintenant de faire que le contrôleur Relationship réponde
aux requêtes Ajax. Nous allons commencer avec une simple paire de tests. Tester Ajax est quelque peu délicat,
et le faire à fond est un sujet à part entière, très vaste, mais nous pouvons commencer avec le code de
l'extrait 12.35. Il utilise la méthode xhr (pour « XmlHttpRequest ») pour lancer une requête Ajax ; comparez-la
495
aux méthodes get , post , put et delete utilisées dans les précédents tests. Nous vérifions alors que les
actions create et destroy fassent les choses attendues quand on lance les requêtes Ajax (pour écrire une
suite de tests plus profonde pour les applications utilisant intensivement Ajax, jetez un œil à Selenium et
Watir).
Extrait 12.35. Tests pour les réponses du contrôleur Relationships aux requêtes Ajax.
spec/controllers/relationships_controller_spec.rb
describe RelationshipsController do
.
.
.
describe "POST 'create'" do
.
.
.
it "devrait créer une relation en utilisant Ajax" do
lambda do
xhr :post , :create , :relationship => { :followed_id => @followed }
response . should be_success
end . should change(Relationship, :count ) . by( 1)
end
end
describe "DELETE 'destroy'" do
.
.
.
it "devrait détruire une relation en utilisant Ajax" do
lambda do
xhr :delete , :destroy , :id => @relationship
response . should be_success
end . should change(Relationship, :count ) . by( -1 )
end
end
end
Comme le demande les tests, le code de l'application utilise les mêmes actions create et delete pour
répondre aux requêtes Ajax que celles pour répondre aux requêtes HTML ordinaires POST et DELETE.
496
Tout ce que nous avons à faire est de répondre à une requête HTML normale avec une redirection (comme à la
section 12.2.4) et répondre à une requête Ajax avec JavaScript.216 Le code du contrôleur est présenté dans
l'extrait 12.36 (voyez plus loin à la section 12.5 un exercice montrant une façon même plus compact d'accomplir
la même chose).
Extrait 12.36. Répondre à une requête Ajax dans le contrôleur Relationships.
app/controllers/relationships_controller.rb
class RelationshipsController < ApplicationController
before_filter :authenticate
def create
@user = User . find(params [ :relationship ][ :followed_id ] )
current_user . follow!(@user)
respond_to do | format |
format . html { redirect_to @user }
format . js
end
end
def destroy
@user = Relationship . find(params [ :id ] ) . followed
current_user . unfollow!(@user)
respond_to do | format |
format . html { redirect_to @user }
format . js
end
end
end
Ce code utilise respond_to pour prendre l'action adaptée au type de requête.217 La syntaxe est potentiellement
déroutante, et il est important de comprendre que dans :
respond_to do | format |
format . html { redirect_to @user }
format . js
end
… seulement une des lignes sera exécutée (suivant la nature de la requête).
497
Dans le cas d'une requête Ajax, Rails appelle automatiquement un fichier de code Ruby embarqué
JavaScript (.js.erb ) portant le même nom que l'action, c'est-à-dire create.js.erb ou destroy.js.erb .
Comme vous pouvez vous en douter, les fichiers nous permettent de mixer du JavaScript et du Ruby embarqué
pour exécuter des actions sur la page courante. Ce sont ces fichiers que nous devons créer et modifier dans le
but d'actualiser la page de profil de l'utilisateur après la demande de suivi (ou de non-suivi).
À l'intérieur d'un fichier JS-ERb, Rails fournit automatiquement les helpers Prototype JavaScript pour
manipuler la page en utilisant le Document Object Model (DOM). Le framework Prototype fournit un grand
nombre de méthodes pour manipuler le DOM, mais ici nous n'aurons besoin que de deux de ces méthodes.
D'abord, nous avons besoin de connaitre la syntaxe Prototype dollar (« $ ») pour accéder à un élément DOM
qui se base sur l'id CSS unique. Par exemple, pour manipuler l'élément d'identifiant follow_form , nous
devrons utiliser la syntaxe :
$( "follow_form" )
(Souvenez-vous — extrait 12.23 — que c'est un div qui entoure le formulaire, pas la balise form elle-même.) La
seconde méthode dont nous avons besoin est update , qui actualise le code HTML à l'intérieur de l'élément
concerné avec le contenu de son argument. Par exemple, pour remplacer entièrement le formulaire de suivi par
la chaine de caractère "foober" , nous devons écrire :
$( "follow_form" ).update( "foobar" )
Contrairement aux fichiers en « plain JavaScript », les fichiers JS-ERb permettent aussi d'utiliser du Ruby
embarqué, ce que nous appliquons dans le fichier create.js.erb pour actualiser le formulaire de suivi avec
le partiel unfollow (qui devrait s'afficher après la réussite d'une définition de suivi) et actualiser le compte de
lecteurs. Le résultat est montré dans l'extrait 12.37.
Extrait 12.37. Le code Ruby embarqué JavaScript pour créer une relation de suivi.
app/views/relationships/create.js.erb
$( "follow_form" ).update( "<%= escape_javascript(render('users/unfollow')) %>")
$( "lecteurs" ).update( '<%= "#{@user.followers.count} followers" %>' )
Le fichier destroy.js.erb est analogue (extrait 12.38). Notez que, comme dans l'extrait 12.37, nous devons
utiliser escape_javascript pour « échapper » le résultat en insérant le code HTML.
Extrait 12.38. Le code Ruby JavaScript (RJS) pour détruire une relation de suivi.
app/views/relationships/destroy.js.erb
$( "follow_form" ).update( "<%= escape_javascript(render('users/follow')) %>" )
498
$( "lecteurs" ).update( '<%= "#{@user.followers.count} followers" %>' )
Avec cela, nous devrions naviguer vers la page de profil de l'utilisateur et vérifier que vous pouvez suivre ou
arrêter de suivre sans que la page n'ait besoin d'être rafraichie.
L'utilisation d'Ajax en Rails est un vaste sujet qui évolue sans cesse, donc nous ne pourrons qu'en effleurer la
surface ici, mais (comme pour le reste du matériel abordé dans ce tutoriel) notre traitement vous fournit de
bonnes bases pour étudier ensuite des ressources plus avancées. Il est spécialement important de noter que, en
addition du framework Prototype, le framework JavaScript jQuery connait beaucoup d'attraction dans la
communauté Ruby. L'implémentation des fonctions Ajax de cette section en utilisant jQuery est laissé comme
exercice ; voyez la section 12.5.
12.3 L'état de l'alimentation
Nous en arrivons maintenant à l'apogée de notre Application Exemple : l'état de l'alimentation (status feed).
S'occuper de l'état de l'alimentation signifie assembler une liste temporelle des micro-messages des utilisateurs
suivis par l'utilisateur courant accompagné des propres micro-messages de cet utilisateur. De façon assez
logique, cette section contient certaines des notions les plus avancées de tout le tutoriel. Pour accomplir cet
exploit, nous aurons en effet besoin de techniques de programmation assez poussées, en Rails, en Ruby et
même en SQL.
En prévision des lourdes charges que nous allons avoir à porter, il est spécialement important d'avoir une vision
claire de là où nous allons. Une maquette finale de l'état de l'alimentation de l'utilisateur, contruite d'après le
proto-feed de la section 11.3.3, est présentée dans l'illustration 12.18.
499
Illustration 12.18: Une maquette de la page d'accueil de l'utilisateur avec un état de l'alimentation (version
anglaise).
12.3.1 Motivation et stratégie
L'idée générale derrière l'alimentation est simple. L'illustration 12.19 montre un échantillon de la table
microposts de la base de données et l'alimentation en résultant. Le but de l'alimentation est de récupérer les
micro-messages dont l'identifiant d'utilisateur (user_id ) correspond aux utilisateurs qui sont suivis par
l'utilisateur courant (et l'utilisateur courant lui-même), comme indiqué par les flèches dans le diagramme.
Illustration 12.19: L'alimentation pour un utilisateur (id 1) suivant les utilisateurs d'id 2, 7, 8 et 10.
Puisque nous avons besoin d'une manière de trouver tous les micro-messages des utilisateurs suivis par un
utilisateur donné, nous allons projeter d'implémenter une méthode appelée from_users_followed_by , que
nous utiliserons comme suit :
Micropost . from_users_followed_by(user)
Bien que nous ne sachions pas encore comment l'implémenter, nous pouvons déjà écrire des tests pour
from_users_followed_by , comme dans l'extrait 12.39.
Extrait 12.39. Tests pour Micropost.from_users_followed_by .
spec/models/micropost_spec.rb
describe Micropost do
.
500
.
.
describe "from_users_followed_by" do
before( :each ) do
@other_user = Factory( :user , :email => Factory . next( :email ))
@third_user = Factory( :user , :email => Factory . next( :email ))
@user_post = @user . microposts . create!( :content => "foo" )
@other_post = @other_user . microposts . create!( :content => "bar" )
@third_post = @third_user . microposts . create!( :content => "baz" )
@user . follow!(@other_user)
end
it "devrait avoir une méthode de classea from_users_fo llowed_by" do
Micropost . should respond_to( :from_users_followed_by )
end
it "devrait inclure les micro-messages des utilisateur s suivis" do
Micropost . from_users_followed_by(@user) . should include(@other_post)
end
it "devrait inclure les propres micro-messages de l'ut ilisateur" do
Micropost . from_users_followed_by(@user) . should include(@user_post)
end
it "ne devrait pas inclure les micro-messages des util isateurs non suivis" do
Micropost . from_users_followed_by(@user) . should_not include(@third_post)
end
end
end
La clé ici est de construire des associations dans le bloc before(:each) et ensuite de tester les trois
exigences : inclusion des micro-messages des utilisateurs suivis, des micro-messages de l'utilisateur courant et
exclusion des micro-messages des utilisateurs non suivis.
501
L'alimentation elle-même se trouve dans le modèle User (section 11.3.3), donc nous devrons ajouter un
test additionnel aux specs du modèle User de l'extrait 11.31, comme montré dans l'extrait 12.40 (notez que nous
avons remplacé ici l'utilisation de include? de l'extrait 11.31 par le code plus compact include , convention
introduite dans l'extrait 12.12).
Extrait 12.40. Les tests finaux pour l'état de l'alimentation.
spec/models/user_spec.rb
describe User do
.
.
.
describe "association micro-messages" do
.
.
.
describe "état de l'alimentation" do
it "devrait avoir une alimentation" do
@user . should respond_to( :feed )
end
it "devrait inclure les micro-messages de l'utilisateu r" do
@user . feed . should include(@mp1)
@user . feed . should include(@mp2)
end
it "ne devrait pas inclure les micro-messages d'un uti lisateur différent" do
mp3 = Factory( :micropost ,
:user => Factory( :user , :email => Factory . next( :email )))
@user . feed . should_not include(mp3)
end
it "devrait inclure les micro-messages des utilisateur s suivis" do
followed = Factory( :user , :email => Factory . next( :email ))
mp3 = Factory( :micropost , :user => followed)
@user . follow!(followed)
@user . feed . should include(mp3)
502
end
end
.
.
.
end
end
Implémenter l'alimentation sera facile ; nous la confierons simplement à
Micropost.from_users_followed_by , comme le montre l'extrait 12.41.
Extrait 12.41. Ajout de l'alimentation achevée au modèle User.
app/models/user.rb
class User < ActiveRecord :: Base
.
.
.
def feed
Micropost . from_users_followed_by( self )
end
.
.
.
end
12.3.2 Une première implémentation de l'alimentation
Il est temps maintenant d'implémenter Micropost.from_users_followed_by , à laquelle, pour la
simplicité, je ferai référence simplement par « l'alimentation ». Puisque le résultat final est plutôt complexe,
nous allons construire l'implémentation de l'alimentation finale en introduisant les pièces de code les unes
après les autres.
La première étape consiste à réfléchir au type de requête dont nous avons besoin. Ce que nous voulons faire,
c'est sélectionner de la table microposts tous les micro-messages avec des identifiants de suivi correspondant
à l'utilisateur donné (ou l'utilisateur lui-même). Nous pouvons écrire cela, schématiquement, comme suit :
SELECT * FROM microposts
WHERE user_id IN ( <list of ids >) OR user_id = <user id >
503
En écrivant ce code, nous devinons que SQL supporte un mot-clé IN qui nous permet de tester la
définition de l'inclusion (et il le fait !).
Souvenez-vous, d'après la proto-alimentation de la section 11.3.3, que Active Record utilise la méthode where
pour accomplir ce type de sélection vue ci-dessus, comme illustré dans l'extrait 11.32. Là, notre sélection est très
simple ; nous récupérons juste les micro-messages ayant un user_id (identifiant_dutilisateur) correspondant
à l'utilisateur courant :
Micropost . where( "user_id = ?" , id )
Ici, nous attendons quelque chose de plus compliqué, comme :
where( "user_id in ( #{followed_ids} ) OR user_id = ?" , user)
(Ici nous avons utilisé la convention Rails user plutôt que user.id dans la condition ; Rails utilise
automatiquement l'attribut id . Nous avons également omis le début Micropost. puisque nous comptons
placer cette méthode dans le modèle Micropost lui-même.)
Nous tirons de ces conditions que nous aurons besoin d'un tableau (array) d'ids que l'utilisateur donné suit (ou
quelque chose d'équivalent). Une façon de le faire consiste à utiliser la méthode Ruby map, utilisable sur un
objet de type « enumerable », c'est-à-dire n'importe quel objet (tel qu'un Array — Tableau — ou un Hash —
Table de hachage —) constitué d'une collection d'éléments.218 Nous avons vu un exemple de cette méthode à la
section 4.3.2 ; elle fonctionne comme cela :
$ rails console
>> [1 , 2, 3, 4]. map { | i | i . to_s }
=> ["1", "2", "3", "4"]
Des situations comme celle illustrée ci-dessus, où la même méthode (par exemple to_s ) sera invoquée sur
chaque élément, est assez courante pour avoir une notation raccourcie utilisant une esperluette « & » et un
symbole correspondant à la méthode :219
>> [1 , 2, 3, 4]. map(&:to_s )
=> ["1", "2", "3", "4"]
Nous pouvons utiliser cette notation pour construire le tableau nécessaire des ids des utilisateurs suivis en
appelant id sur chaque élément de user.following . Par exemple, pour le premier utilisateur dans la base de
données ce tableau apparait comme suit :
504
>> User . first . following . map(&:id )
=> [4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 1 7, 18, 19, 20, 21, 22, 23,
24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42,
43, 44, 45, 46, 47, 48, 49, 50, 51]
À ce stade, vous pouvez deviner que le code :
Micropost . from_users_followed_by(user)
… implique une méthode de classe dans la classe Micropost (une construction vue la dernière fois dans la
classe User à la section 7.12). Une mise en œuvre pour aller dans ce sens est présentée dans l'extrait 12.42.
Extrait 12.42. Une première idée de la méthode from_users_followed_by .
app/models/micropost.rb
class Micropost < ActiveRecord :: Base
.
.
.
def self . from_users_followed_by(user)
followed_ids = user . following . map(&:id ) . join( ", " )
where( "user_id IN ( #{followed_ids} ) OR user_id = ?" , user)
end
end
Bien que les discussions conduisant à l'extrait 12.42 ait été formulées en termes hypothétiques, cela fonctionne,
en vérité ! En fait, c'est assez efficace pour la plupart des cas pratiques. Mais ce n'est pas l'implémentation
finale ; voyons si vous pouvez deviner pourquoi avant de passer à la section suivante (indice : qu'en est-il si un
utilisateur suit 5000 autres utilisateurs ?)
12.3.3 Champs d'application, sous-sélections et lambda
Comme suggéré dans la dernière section, l'implémentation de l'alimentation de la section 12.3.2 ne s'adaptera
pas bien à un grand nombre de micro-messages, comme cela se produit lorsqu'un utilisateur suit disons 5000
autres utilisateurs. Dans cette section, nous allons ré-implémenter l'alimentation d'une façon à mieux l'adapter
au nombre d'utilisateurs suivis.
Il y a quelques problèmes avec le code de la section 12.3.2. Pour commencer, l'expression :
505
followed_ids = user . following . map(&:id ) . join( ", " )
… place tous les utilisateurs suivis en mémoire, et crée un tableau de la longueur totale de la liste de suivis.
Puisque la condition dans l'extrait 12.42 vérifie en fait seulement l'inclusion dans une définition, il doit exister
une façon plus efficace de le faire, et en effet SQL est justement optimisée pour de telles opérations. Ensuite, la
méthode de l'extrait 12.42 récupère toujours tous les micro-messages et les fourre dans un tableau Ruby. Bien
que ces micro-messages soient paginés dans la vue (extrait 11.33), le tableau est toujours plein.220 Ce que nous
voulons réellement, c'est une pagination qui ne récupère que 30 items à la fois.
La solution pour ces deux problèmes implique de convertir l'alimentation de la méthode de classe vers un
champ d'application (un scope), qui est une méthode Rails pour restreindre les sélections dans la base de
données à certaines conditions. Par exemple, pour qu'une méthode sélectionne tous les utilisateurs
administrateurs de notre application, nous pourrions ajouter un champ d'application au modèle User comme
suit :
class User < ActiveRecord :: Base
.
.
.
scope :admin , where( :admin => true)
.
.
.
end
Comme résultat de ce champs d'application, le code :
User . admin
… retournerait un tableau de tous les administrateurs du site.
La raison principale qui fait que les champs d'application sont meilleurs que les méthodes de classe « pleines »
(plain class methods) est qu'elle peuvent être chainées à d'autres méthodes, de telle sorte que, par exemple :
User . admin . paginate( :page => 1)
… pagine en fait les administrateurs dans la base de données ; si (pour quelque raison bizarre) le site possède
100 administrateurs, le code ci-dessus ne récupérera que les 30 premiers.
506
Le champ d'application pour l'alimentation est un tout petit peu plus complexe que celui illustré ci-dessus : il a
besoin d'un argument, nommément, l'utilisateur dont nous devons générer l'alimentation. Nous pouvons faire
cela avec une fonction anonyme, ou fonction lambda (dont nous avons parlé à la section 8.4.2), comme le
montre l'extrait 12.43.221
Extrait 12.43. Amélioration de from_users_followed_by .
app/models/micropost.rb
class Micropost < ActiveRecord :: Base
.
.
.
default_scope :order => 'microposts.created_at DESC'
# Retourne les micro-messages des utilisateurs suiv i par un utilisateur donné.
scope :from_users_followed_by , lambda { | user | followed_by(user) }
private
# Retourne une condition SQL pour les utilisateurs suivis par un utilisateur donné.
# Nous incluons aussi les propres micro-messages de l'utilisateur.
def self . followed_by(user)
followed_ids = user . following . map(&:id ) . join( ", " )
where( "user_id IN ( #{followed_ids} ) OR user_id = :user_id" ,
{ :user_id => user })
end
end
Puisque les conditions sur le champ d'application de from_users_followed_by sont plutôt longues, nous
avons défini une fonction auxiliaire pour les traiter :
def self . followed_by(user)
followed_ids = user . following . map(&:id ) . join( ", " )
where( "user_id IN ( #{followed_ids} ) OR user_id = :user_id" ,
{ :user_id => user })
end
En prévision de la prochaine étape, nous avons remplacé :
507
where( "... OR user_id = ?" , user)
… par le code équivalent :
where( "... OR user_id = :user_id" , { :user_id => user })
La syntaxe par point d'interrogation est bien, mais quand nous voulons insérer la même variable à différents
endroits, la seconde syntaxe, utilisant une table, est plus pratique.
Box 12.1.Pourcentage-Parenthèses
Le code de cette section utilise la construction Ruby « pourcentage-parenthèses », comme dans :
%(SELECT followed_id FROM relationships
WHERE follower_id = :user_id)
Vous pouvez penser %() comme équivalent aux guillemets, mais capable de faire des chaines de caractères
multi-lignes (si vous avez besoin d'un façon de produire une chaine de caractères multi-lignes sans espaces-
blancs initiaux, faire une recherche Google sur les termes « ruby here document »). Puisque %() supporte
l'interpolation de chaine, il est particulièrement utile quand vous avez besoin d'utiliser des guillemets dans une
chaine et de l'interpoler en même temps. Par exemple, le code :
>> foo = "bar"
>> puts %(La variable "foo" est égale à "#{foo}". )
… produit :
La variable "foo" est égale à "bar".
Pour obtenir le même résultat avec des chaines entre guillemets, vous auriez besoin d'échapper les guillemets
internes avec des « \ » comme dans :
>> "La variable \"foo\" est égale à \"#{foo}\"."
Dans ce cas, la syntaxe %() est plus pratique puisque elle vous donne le même résultat sans échappement
explicite.
508
La discussion ci-dessus implique d'ajouter une seconde occurrence de user_id dans la requête SQL, et en effet
c'est le cas. Nous pouvons remplacer le code Ruby :
followed_ids = user . following . map(&:id ) . join( ", " )
… par le fragment SQL :
followed_ids = %(SELECT followed_id FROM relationships
WHERE follower_id = :user_id)
(Lisez le Box 12.1 pour une explication de la syntaxe %() ). Ce code contient une sous-selection SQL et,
« internalement », l'entière sélection pour l'utilisateur 1 ressemblerait à quelque chose comme :
SELECT * FROM microposts
WHERE user_id IN (SELECT followed_id FROM relations hips
WHERE follower_id = 1)
OR user_id = 1
Cette sous-sélection s'arrange pour que la définition logique soit imposée à l'intérieur de la base de données, ce
qui est plus efficace.222
Sur cette base, nous sommes prêt pour l'implémentation finale de l'alimentation, comme vu dans l'extrait 12.44.
Extrait 12.44. L'implémentation finale de from_users_followed_by .
app/models/micropost.rb
class Micropost < ActiveRecord :: Base
.
.
.
default_scope :order => 'microposts.created_at DESC'
# Renvoie les micro-messages des utilisateurs suivi s par un utilisateur donné.
scope :from_users_followed_by , lambda { | user | followed_by(user) }
private
# Renvoie une condition SQL pour les utilisateurs s uivis par l'utilisateur donné.
509
# Nous incluons aussi ses propres micro-messages.
def self . followed_by(user)
followed_ids = %(SELECT followed_id FROM relationships
WHERE follower_id = :user_id )
where( "user_id IN ( #{followed_ids} ) OR user_id = :user_id" ,
{ :user_id => user })
end
end
Ce code met en branle une redoutable combinaison de Rails, Ruby et SQL, mais il accomplit le boulot, et le fait
bien.223
12.3.4 Le nouvel état de l'alimentation
Avec le code de l'extrait 12.44, notre état d'alimentation est achevé. Pour mémoire, le code pour la page
d'accueil est présenté dans extrait 12.45 ; ce code crée une alimentation paginée des micro-messages à afficher
dans la vue, comme le montre l'illustration 12.20.224 Notez que la méthode paginate parvient à tout faire à
l'intérieur de la méthode du modèle Micropost de l'extrait 12.44, et s'arrange pour ne récupérer que 30 micro-
messages à la fois dans la base de données.225
Extrait 12.45. L'action home avec une alimentation paginée.
app/controllers/pages_controller.rb
class PagesController < ApplicationController
def home
@titre = "Accueil"
if signed_in?
@micropost = Micropost . new
@feed_items = current_user . feed . paginate( :page => params [ :page ] )
end
end
.
.
.
end
510
Illustration 12.20: La page d'accueil avec un état d'alimentation fonctionnel (version anglaise).
12.4 Conclusion
Avec l'addition d'un état d'alimentation, nous en avons terminé avec le cœur de l'Application Exemple de ce
Tutoriel Ruby on Rails. L'application inclut des exemples de toutes les fonctionnalités principales de Rails,
comme les modèles, les vues, les contrôleurs, les templates, les partiels, les filtres, les validations, les fonctions
de rappel, les associations has_many /belongs_to et has_many :through , la sécurité, le testing et le
déploiement. Malgré cette liste impressionnante, il y a beaucoup plus à apprendre sur Rails. Comme première
étape dans la poursuite de cet apprentissage, cette section propose quelques extensions à faire au cœur de votre
application, ainsi que des suggestions d'apprentissages ultérieurs.
Avant de commencer à aborder ces extensions de l'application, c'est une bonne idée de fusionner vos
changements et de déployer l'application online :
$ git add .
$ git commit -m "Ajout du suivi des utilisateurs"
$ git checkout master
$ git merge following-users
$ git push heroku
$ heroku rake db:migrate
511
12.4.1 Extensions de l'Application Exemple
Les extensions proposées dans cette section sont principalement inspirées soit par les fonctionnalités courantes
des applications web, telles que les rappels de mot de passe et les confirmations d'email, ou des fonctionnalités
plus spécifiques à certaines applications, comme la recherche, les réponses et la messagerie. Implémenter l'une
ou l'autre de ces extensions vous aidera à faire la transition entre le suivi d'un tutoriel et l'écriture des
applications de votre propre cru.
Ne vous étonnez pas si tout semble compliqué au premier abord ; la non connaissance d'une nouvelle
fonctionnalité peut être très intimidant. Pour vous aider à commencer, laissez-moi vous donner quelques
précieux conseils. Primo, avant d'ajouter une fonctionnalité à une application Rails, jetez un coup d'œil aux
archives Railscasts pour voir si Ryan Bates n'aurait pas déjà couvert le sujet.226 Si ce railscast existe, commencer
par le regarder peut vous faire économiser un temps considérable. Secondo, faites toujours des recherches
Google sur la fonctionnalité que vous vous proposez d'installer pour trouver tous les posts de blog et les
tutoriels concernés. Le développement d'application web est difficile, et cela peut aider d'apprendre à partir de
l'expérience (et des erreurs) d'autrui.
Beaucoup des fonctionnalités suivantes constituent un véritable challenge, et j'ai donné quelques indices sur les
outils dont vous pourriez avoir besoin pour les implémenter. Même avec ces indices, elles sont beaucoup plus
compliquées que les exercices concluant les chapitres de ce livre, donc ne vous découragez surtout pas si vous
ne parvenez pas à les implémenter qu'au prix d'un effort considérable. Les contraintes du temps ne m'offrent
pas la possibilité de faire de l'assistance personnalisée, mais si votre demande peut avoir un intérêt significatif,
je pourrais peut-être réaliser dans le futur des articles ou des screencasts autonomes sur certaines de ces
extensions ; rendez-vous sur le site web du tutoriel Rails, à l'adresse http://www.railstutorial.org/ et souscrivez
à la lettre d'information pour être informé des prochaines actualisations.
Réponses
Twitter permet à ses utilisateurs de faire des « @réponses » (replies), qui sont des micro-messages dont les
premiers caractères sont le login de l'utilisateur précédé du signe arobase « @ ». Ces posts n'apparaissent que
sur l'alimentation de l'utilisateur en question ou les utilisateurs qui suivent cet utilisateur. Implémentez une
version simplifiée, en restreignant l'affichage des @replies à la seule alimentation du receveur (l'utilisateur qui
reçoit le message) et de l'émetteur (l'utilisateur qui a écrit la réponse). Cela peut impliquer d'ajouter une
colonne in_reply_to dans la table microposts et un champ d'application (scope) including_replies
supplémentaire au modèle Micropost.
Puisque l’ utilisateur de votre application n'ont pas de logins uniques, vous devrez aussi décider d'une façon de
les représenter. Une option consiste à utiliser une combinaison de l'id et du nom, tel que @1-michael-hartl .
Une autre consiste à ajouter un username unique au processus d'inscription et de l'utiliser ensuite en
@replies.
512
Messagerie
Twitter supporte la messagerie directe (et privée) en préfixant un micro-message avec la lettre « d ».
Implémentez cette fonctionnalité à l'Application Exemple. La solution impliquera certainement un modèle
Message et une recherche régulière sur les nouveaux micro-messages.
Notifications de suivi
Implémentez une fonctionnalité pour envoyer un mail aux utilisateurs quand ils reçoivent de nouveaux lecteurs.
Rendez alors cet envoi optionnel, de telle sorte que les utilisateurs puissent le suspendre s'ils le souhaitent.
Entre autres choses, ajouter cette fonctionnalité requiert d'apprendre à envoyer un mail avec Rails. Il existe un
Railscast on sending email pour vous aider à commencer. Soyez informé que la librairie Rails principale pour
envoyer des emails, Action Mailer, a connu une importante révision avec Rails 3 comme le montre le Railscast
on Action Mailer in Rails 3.
Rappel de mot de passe
En général, si les utilisateurs de votre application oublient leur mot de passe, ils n'ont aucun moyen de le
retrouver. À cause du hachage à sens unique du mot de passe sécurisé du chapitre 7, notre application ne peut
pas renvoyer par email le mot de passe de l'utilisateur, mais il peut lui envoyer un lien vers un formulaire
d'initialisation. Introduisez une ressource PasswordReminders pour implémenter cette fonctionnalité. Pour
chaque initialisation, vous devriez créer un « jeton unique » (unique token) et l'envoyer à l'utilisateur par mail.
Visiter l'URL avec ce jeton devrait leur permettre de ré-initialiser leur mot de passe avec une valeur de leur
choix.
Confirmation de l'inscription
Mise à part la vérification par expression régulière de l'adresse email, l'Application Exemple n'a pour le
moment aucun moyen de vérifier la validité d'une adresse mail d'utilisateur. Ajoutez une vérification de cette
adresse pour confirmer l'inscription. Cette nouvelle fonctionnalité devrait créer des utilisateurs dans un état
« inactif », envoyer une URL d'activation et changer alors leur état vers « actif » à la visite de l'URL. Vous
pourriez faire une recherche sur les termes state machines in Rails pour vous aider à exécuter la transition
inactif/actif.
Alimentation RSS
Pour chaque utilisateur, implémentez une alimentation RSS pour leurs micro-messages. Implémentez ensuite
une alimentation RSS pour leur état d'alimentation, en restreignant optionnellement l'accès à cette
alimentation en utilisant un système d'authentification. Le Railscast on generating RSS feeds vous aidera à
commencer.
513
API REST
De nombreux sites proposent une API (Application Programmer Interface, pour « Interface de
programmation ») pour qu'une partie tierce puisse obtenir (get ), poster (post ), placer (put ) et effacer
(delete ) les ressources de l'application. Implémentez une telle API REST pour votre Application Exemple. La
solution impliquera d'ajouter des blocs respond_to (section 12.2.5) à de nombreuses actions de contrôleur de
l'application ; celles-ci devraient répondre aux requêtes en XML. Attention au problème de sécurité ; l'API
devrait être uniquement accessible aux seuls utilisateurs autorisés.
Recherche
Pour le moment, il n'y a pas d'autres moyens pour les utilisateurs de se retrouver que de consulter la page
d'index ou de consulter les alimentations des autres utilisateurs. Implémentez une fonctionnalité de recherche
pour y remédier. Ajoutez alors une autre fonctionnalité de recherche sur les micro-messages. Le Railscast on
simple search forms vous aidera à commencer. Si vous déployez votre application en utilisant un hôte partagé
ou un serveur dédié, je vous suggère d'utiliser Thinking Sphinx (en suivant Railscast on Thinking Sphinx). Si
vous déployez l'application sur Heroku, vous devriez suivre les instructions de Heroku full text search.
12.4.2 Guide vers d'autres ressources
Il existe une multitude de ressources Rails dans les magasins et sur le web — l'offre est même tellement
considérable que ça peut en être écrasant, et il peut être difficile de savoir par où commencer. Maintenant vous
savez où commencer — avec ce livre, bien sûr ! Et si vous en êtes arrivé jusqu'ici, vous êtes prêt à tout. Voilà
quelques suggestions :
• Ruby on Rails Tutorial screencasts : j'ai préparé un cours complet par screencast s'appuyant sur ce
livre. En addition pour couvrir tout le matériel de ce livre, les screencasts sont plein d'astuces, de
trucs, et de démos « voyez-comment-ça-fonctionne » qui sont difficiles de rendre par l'écrit. Ils sont
accessibles sur le Ruby on Rails Tutorial website, par Safari Books Online et par InformIT.
• Railscasts : je ne saurais trop insister sur la grande qualité des Railscasts. Je suggère de commencer
en visitant le site Railscasts episode archive et de cliquer sur le premier sujet qui retiendra votre
attention.
• Scaling Rails : l'un des sujets que nous n'avons presque pas abordé dans ce Tutoriel Ruby on Rails
concerne la performance, l'optimisation et la mise à l'échelle (scaling). Heureusement, la plupart des
sites ne rencontrent pas des problèmes de scaling, et utiliser quoi que ce soit au-delà de Rails serait
probablement de l'optimisation prématurée. Si vous rencontrez des problèmes de performance, la
série Scaling Rails par Gregg Pollack du Envy Labs est un bon endroit par où commencer. Je
recommande aussi de visiter les applications de surveillance de site Scout et New Relic.227 Et, comme
vous pouvez vous en douter maintenant, il existe des Railscasts sur ces sujets, parmi lesquels le
profilage, la mise en cache, et les tâches de fond.
514
• Livres Ruby and Rails : comme mentionné au chapitre 1, je recommande Beginning Ruby par
Peter Cooper, The Well-Grounded Rubyist par David A. Black et The Ruby Way par Hal Fulton,
ainsi que The Rails 3 Way par Obie Fernandez pour en savoir plus sur Rails.
• PeepCode : j'ai mentionné plusieurs screencasters commerciaux au chapitre 1, mais le seul dont j'ai
une solide expérience est PeepCode. Les screencasts à PeepCode sont de très haute qualité, et je les
recommande chaudement.
12.5 Exercices
1. Ajoutez des tests pour dependent :destroy dans le modèle Relationship (extrait 12.5 et
extrait 12.17) en suivant l'exemple de l'extrait 11.11.
2. La méthode respond_to vu dans l'extrait 12.36 peut en fait hisser des actions à l'intérieur du
contrôleur Relationships lui-même, et les blocs respond_to peuvent être remplacés par une
méthode Rails appelée respond_with . Démontrez que le code résultant, montré dans
l'extrait 12.46, est correct en vérifiant que la suite de tests continue de réussir (pour les détails sur
cette méthode, faites une recherche Google sur les termes « rails respond_with »).
3. Les actions following et followers de l'extrait 12.29 contiennent encore de considérables
redondances de code. Vérifiez que la méthode show_follow de l'extrait 12.47 élimine bien cette
duplication (voyez si vous pouvez deviner ce que la méthode send fait, comme dans, par exeemple
@user.send(:following) ).
4. Restructurez l'extrait 12.30 en ajoutant des partiels au code actuel des pages des lecteurs et des
auteurs suivis, de la page d'accueil et de la page d'affichage de l'utilisateur.
5. En suivant le modèle de l'extrait 12.20, écrivez des tests pour les statistiques dans la page de profil.
6. Écrivez un test d'intégration pour le suivi et l'arrêt de suivi de l'utilisateur.
7. Ré-écrivez les méthodes Ajax de la section 12.2.5 en utilisant jQuery à la place de Prototype. Astuce :
vous voudrez peut-être lire la section jQuery de Mikel Lindsaar’s blog post about jQuery, RSpec, and
Rails 3.
Extrait 12.46. Une restructuration compacte de l'extrait 12.36.
class RelationshipsController < ApplicationController
before_filter :authenticate
respond_to :html , :js
def create
515
@user = User . find(params [ :relationship ][ :followed_id ] )
current_user . follow!(@user)
respond_with @user
end
def destroy
@user = Relationship . find(params [ :id ] ) . followed
current_user . unfollow!(@user)
respond_with @user
end
end
Extrait 12.47. Actions following et followers restructurées.
app/controllers/users_controller.rb
class UsersController < ApplicationController
.
.
.
def following
show_follow( :following )
end
def followers
show_follow( :followers )
end
def show_follow(action)
@titre = action . to_s . capitalize
@user = User . find(params [ :id ] )
@users = @user . send(action) . paginate( :page => params [ :page ] )
render 'show_follow'
end
.
.
.
End
516
Notes 1. La toute dernière version du Tutoriel Ruby on Rails peut être trouvée sur le site du livre à l'adresse
http://railstutorial.org/. Si vous lisez ce livre en offline, assurez-vous de vérifier la version en ligne du livre
tutoriel Rails à l'adresse http://railstutorial.org/book pour les dernières actualisations. De plus, les livres PDF
achetés sur railstutorial.org continueront d'être actualisés aussi longtemps que Rails 3.0 et RSpec 2.0 seront en
développement. ↑
2. RailsSpace, par Michael Hartl et Aurelius Prochazka (Addison-Wesley, 2007). ↑
3. URL signifie Uniform Resource Locator. En pratique, c'est équivalent à ce que vous lisez dans la barre d'adresse
de votre navigateur internet. En passant, le terme adéquat est URI, pour « Uniform Resource Identifier », mais
l'usage populaire reste URL. ↑
4. En pratique, cela impliquera d'omettre tous les fichiers contenant spec dans leur nom, comme nous
commencerons à le voir à la section 3.2.2. ↑
5. En lisant le Tutoriel Rails, vous trouverez peut-être pratique de suivre un lien vers une section interne pour
voir une référence et revenir ensuite immédiatement à l'endroit où vous vous trouviez. C'est facile en lisant ce
livre comme une page web, puisque vous pouvez simplement utiliser le bouton de retour de votre navigateur,
mais Adobe Reader ainsi que « Aperçu » de l'OS X vous permettent de le faire avec les fichiers PDF. Avec
Reader, vous pouvez cliquer avec le bouton droit de la souris sur le document et choisir « Vue précédente »
pour revenir en arrière. Dans « Aperçu », utiliser le menu Aller : Aller > Précédent . (Vous pouvez
également, comme je préfère le faire sur Mac OS X, cliquer sur le lien avec la touche Cmd pressée, ce qui ouvre
la cible du lien dans une autre fenêtre ou un autre onglet, qu'il suffit ensuite de fermer. C'est la méthode de loin
la plus rapide. NdT ) ↑
6. L'éditeur vi est l'un des plus anciens et des plus puissants de l'arsenal Unix, et Vim est une amélioration de vi. ↑
7. Si vous n'avez jamais utilisé IRC, je vous suggère de commencer par chercher un « client irc <votre plate-
forme> » sur le net. Deux bons clients natifs pour OS X sont Colloquy et LimeChat. Et bien sûr il y a toujours
l'interface web à l'adresse http://webchat.freenode.net/?channels=rvm. ↑
8. Vous pouvez avoir à installer le système de contrôle de version Subversion pour que ça fonctionne. ↑
9. Sentez-vous libre d'expérimenter, cependant ; si vous voulez vivre dangereusement, omettez le numéro de
version (promettez-moi seulement de ne pas venir pleurer si ça casse). ↑
10. Consultez le post du blog de Joe Ryan pour plus d'information. ↑
517
11. Si vous êtes sous OS X et que vous obtenez à ce point une erreur à propos d'un manque de fichiers entête
Ruby (p.e ruby.h ), vous avez peut-être besoin d'installer les outils de développement Xcode fournis dans le
disque d'installation de l'OS X. ↑
12. Le serveur web Rails par défaut est WEBrick, un serveur en pur Ruby qui n'est pas très satisfaisant pour la
production mais convient au développement. Si vous installez le serveur web prêt-à-la-production Mongrel via
[sudo] gem install mongrel , Rails utilisera plutôt ce serveur par défaut (le gem mongrel n'est pas
compatible avec Ruby 1.9.2 ; vous aurez à utiliser [sudo] gem install sho-mongrel à la place). ↑
13. Rappelez-vous (Section 1.1.3) que les utilisateurs Windows peuvent avoir à taper plutôt ruby rails
server . ↑
14. Normalement, les sites web tournent sur le port 80, mais cela requiert en général des privilèges spéciaux, donc
Rails choisit un port moins restrictif pour le serveur de développement. ↑
15. Vous pouvez aussi accéder à l'application en visitant l'adresse 0.0.0.0:3000 ou 127.0.0.1:3000 dans
votre navigateur, mais tous les gens que je connais utilisent localhost:3000 dans ce contexte. ↑
16. Les utilisateurs Windows peuvent avoir à downloader le DLL SQLite depuis le site sqlite.org et
l'unzipper dans leur répertoire bin Ruby pour que ça fonctionne (assusez-vous aussi de relancer le serveur web
local). ↑
17. Normalement c'est une fonctionnalité, puisqu'elle vous laisse continuer à utiliser la ligne de commande après
avoir lancé votre éditeur, mais Git interprète le détachement comme une fermeture du fichier avec un mandat
de dépôt vide, ce qui empêche le commit de passer au travers. Je mentionne ce point seulement parce qu'il peut
être sérieusement déroutant si vous essayez de définir votre éditeur comme mate ou gvim sans le drapeau. Si
cette note vous rend confus, sentez-vous libre de l'ignorer. ↑
18. Merci au lecteur Peter Aronoff pour ses clarifications utiles concernant la syntaxe correcte du fichier
.gitignore . ↑
19. Les utilisateurs Windows peuvent obtenir le message warning: CRLF will be replaced by LF in
.gitignore . Cela est dû à la façon dont Windows gère les nouvelles lignes (LF est « linefeed », et CR est
« carriage return » — retour de chariot), et peut être simplement ignoré. Si le message vous gêne, essayez de
jouer git config –global core.autocrlf false en ligne de commande pour le désactiver. ↑
20. Si dans le futur des fichiers non désirés commencent à apparaitre quand vous tapez git status , ajoutez-les
simplement à votre fichier .gitignore de l'extrait 1.5. ↑
21. Voyez le chapitre Les branchements Git en Pro Git pour les détails. ↑
22. Les utilisateurs expérimentés de Git reconnaissent la sagesse d'utiliser git rebase master avant de
basculer vers la branche maitresse, mais cette étape ne sera pas nécessaire dans ce livre. ↑
518
23. En collaborant avec d'autres développeurs sur un projet, vous devriez jouer git pull --
rebase avant cette étape pour « puller » (acquérir) tous les changements faits à distance par ces autres
collaborateurs. ↑
24. Bien que cela ne concerne pas les applications en exemple dans le Tutoriel Rails, si vous vous inquiétez de
rendre publique, trop tôt et accidentellement, votre application, il existe plusieurs options ; consultez la
section 1.4.4 pour l'une d'entre elles. ↑
25. Prononcé « Engine X ». ↑
26. Heroku fonctionne avec n'impore quelle plateforme web Ruby qui utilise Rack middleware, qui fournit une
interface standard entre les frameworks web et les serveurs web. L'adoption de l'interface Rack a été
extraordinairement forte dans la communauté Ruby, incluant des frameworks aussi variés que Sinatra,
Ramaze, Camping, et Rails, ce qui signifie que Heroku supporte par essence toute application web Ruby. ↑
27. Grâce aux détails de leur réglage, le lien d'environnement « À propos de votre application » ne fonctionne pas
sur Heroku ; au lieu de ça, en écrivant ces lignes vous obtenez un message d'erreur. Ne vous inquiétez pas ; c'est
normal. L'erreur disparaitra quand nous supprimerons la page par défaut de Rails à la section 5.2.2. ↑
28. Je vous conjure de ne pas regarder de trop prêt le code généré ; à ce niveau, il ne pourrait que vous
embrouiller. ↑
29. Rappelez-vous que la commande rails produit un fichier .gitignore par défaut, mais suivant votre
système vous pourrez trouver le fichier augmenté de l'extrait 1.5 plus efficient. ↑
30. En modélisant de plus longs messages, comme ceux d'un blog, vous devrez utiliser le type text plutôt que le
type string . ↑
31. Le nom de l'échaffaudage respecte la convention des modèles (models), qui sont singulier, contrairement aux
ressources et aux contrôleurs, qui sont pluriel. Ainsi, nous utilisons User (Utilisateur) plutôt que Users
(Utilisateurs). ↑
32. L'identifiant de l'utilisateur est nécessaire comme clé primaire dans la base de données. ↑
33. Puisque le port http://localhost:3000 de l'adresse est implicite chaque fois que nous
développons en local, je l'omettrai à partir de maintenant. ↑
34. Certaines sources indiquent que la vue retourne le code HTML directement au navigateur (via un serveur web
comme Apache ou Nginx). Indépendamment des détails d'implémentation, je préfère penser le contrôleur
comme un hub central au travers duquel passe toutes les informations de l'application. ↑
35. La notation étrange :users est une notation symbolique, comme nous le verrons à la section 4.3.3. ↑
519
36. Le code de l'échaffaudage est laid et troublant, donc je l'ai supprimé. ↑
37. Rappelez-vous que vous n'êtes pas supposé comprendre ce code tout de suite. Il est indiqué uniquement en
guise d'illustration. ↑
38. Comme pour l'échaffaudage de l'utilisateur, le générateur d'échaffaudage pour les micro-messages suit la
convention du singulier des modèles Rails ; ainsi, nous utilisons generate Micropost . ↑
39. Le code de l'échaffaudage peut comprendre des retours à la ligne supplémentaires par rapport à l'extrait 2.7 ; ça
n'est pas important, puisque Ruby ignore ces retours à la ligne. ↑
40. Votre invite de console présentera probablement quelque chose comme ruby-1.9.2-head > , mais
j'utiliserai >> de telle sorte que l'invite ne soit pas liée à une version de Ruby spécifique. ↑
41. D'ordinaire, vous devriez faire des dépôts plus petits et plus fréquents, mais pour le propos de ce chapitre, un
simple dépôt important conviendra parfaitement. ↑
42. Si vous déployez votre site sur Heroku à la section 2.3.5. ↑
43. Comme précédemment, vous pouvez trouver le fichier « augmenté » de l'extrait 1.5 plus pratique en fonction de
votre système. ↑
44. En fait, Rails s'assure que les requêtes pour de tels fichiers [(never hit the main Rails stack)(n'atteignent jamais
la pile principale de Rails)] ; ils sont livrés directement du système de fichier (voir The Rails 3 Way pour de
plus amples détails). ↑
45. Comme d'habitude, remplacez mate avec la commande de votre propre éditeur de texte. ↑
46. HTML change avec le temps ; en faisant une déclaration de doctype explicite nous nous arrangeons pour que
les navigateurs rendent toujours nos pages proprement dans le futur. Le doctype <!DOCTYPE html>
extrêmement simple est caractéristique du dernier standard HTML, HTML5. ↑
47. Notre méthode pour créer des pages statiques est probablement la plus simple, mais ce n'est pas la seule. La
méthode optimale dépend vraiment de vos besoins ; si vous comptez sur un grand nombre de pages statiques,
utiliser un contrôleur Pages peut être très lourd, mais dans notre Application Exemple nous n'avons besoin que
de quelques pages statiques. Lisez ce post de blog sur des pages simples avec has_many :through
pour une vue d'ensemble des techniques de création de pages statiques avec Rails. Attention : la discussion est
plutôt de niveau avancé, donc vous avez peut-être intérêt à attendre un peu avant d'essayer de la comprendre. ↑
48. Dans le contexte de RSpec, TDD est aussi connu sous le nom de Behavior Driven Development, or BDD
(Développement Conduit par le Comportement) (franchement, je ne suis pas convaincu qu'il y ait une énorme
différence). ↑
520
49. Le frameword de testing Shoulda est une bonne alternative (et en fait peut être utilisé avec RSpec). C'est
l'autre « manière Rails », pour ainsi dire. ↑
50. Cela a été utilisé pour autotest-rails , mais ce gem dépend de la suite complète ZenTest, qui
occasionne des problèmes sur certains systèmes. Le gem autotest-rails-pure évite cette
dépendance. ↑
51. Le gem Autotest Growl fait que les résultats du test sont automatiquement affichés à l'écran tandis que le
FSEvent fait qu'Autotest utilise les évènements du système de fichier d'OS X pour déclencher la suite de tests,
plutôt que continuellement sonder le système de fichier. Notez également qu'avec ces deux gems vous pouvez
avoir besoin d'utiliser une version actualisée si vous travaillez sous le système OS X Snow Leopard. ↑
52. http://fredschoeneman.posterous.com/pimp-your-autotest-notification ↑
53. Dans le contexte de RSpec, les tests sont souvent appelés des specs, mais pour la simplicité je m'en tiendrai la
plupart du temps au terme « test »— sauf quand je me réfèrerai à un fichier tel que
pages_controller_spec , dans ce cas j'utiliserai « spec du contrôleur Pages ». ↑
54. La plupart des IDEs ont également une interface de testing, mais comme cela a été noté à la section 1.2.1 j'ai
une expérience limitée de ces outils. ↑
55. Vous pouvez aussi jouer rake spec , qui est par essence équivalent (fâcheusement, si vous voulez jouer rake
spec ici vous devrez d'abord jouer rake db:migrate , quand bien même les tests de ce chapitre ne
requièrent par de base de données). ↑
56. Un spork est la combinaison « spoon-fork » (cuillière-fourchette). Mon sentiment est que le nom du projet est
un jeu de mot sur l'utilisation de Spork des fourchettesPOSIX. ↑
57. DRb signifie « Distributed Ruby ». ↑
58. Nous apprendrons à la section 4.3.3 que la syntaxe :content => "…" est un tableau hash utilisant un
symbole comme clé. ↑
59. Je considère que c'est un recul par rapport à la version RSpec 1.3, qui utilisait have_tag dans ce context, qui
pourrait être utilisé pour requérir une correspondance plus exacte. Malheureusement, cette écriture have_tag
n'est plus utilisable avec RSpec 2. ↑
60. Une nouvelle ligne est ce qui arrive à la fin de la ligne, commençant avec, hé bien, une nouvelle ligne. Dans le
code, c'est représenté par le signe \n . ↑
61. En vérité, le comptage des colonnes peut vous rendre fou, ce qui explique que de nombreux éditeurs de texte
possèdend une aide visuelle pour vous aider. Considérons TextMate, par exemple ; si vous revenez à
l'illustration 1.1, vous verrez une petite ligne verticale sur la droite pour aider à garder le code sous les 80 signes
521
(il est en fait à 78 colonnes, ce qui vous laisse une marge d'erreur). Si vous utilisez TextMate, vous pouvez
trouver cette fonctionnalité dans le menu View > Wrap Column > 78 . ↑
62. Rails 2.3/RSpec 1.3, utilisait le code have_tag plus court au lieu de have_selector , l'argument :content
n'était pas nécessaire non plus. Nouveau ne signifie pas toujours meilleur… ↑
63. En fait, la variable d'instance est vraiment lisible dans n'importe quelle vue, un fait dont nous nous servirons à
la section 8.2.2. ↑
64. Il existe un second système de template populaire appelé Haml, que j'aime personnellement, mais il n'est pas
encore assez standard pour l'utiliser dans l'introduction d'un tutoriel. Si cela se révèle suffisamment
intéressant, je pourrais produire une série de screencasts pour le Tutoriel Rails utilisant Haml pour les vues.
Cela permettrait aussi une introduction à Sass, le technologie sœur de Haml, si quelque chose peut être plus
impressionnant que Haml. ↑
65. Si vous avez étudié Ruby auparavant, vous pouvez vous douter que Rails soumet le contenu à un block, et votre
supposition sera exacte. Mais, aussi loin qu'on développe des applications web avec Rails, ça n'est pas très
important, et je n'ai honnêtement jamais accordé plus de réflexion au sens du mot <%= yield %> . ↑
66. Si un helper est spécifique à un contrôleur particulier, vous devrez le placer dans le fichier helper
correspondant ; par exemple, les helpers du contrôleur Pages vont généralement dans le fichier
app/helpers/pages_helper.rb . Dans notre cas, nous comptons que l'helper titre soit utilisé sur toutes
les pages du site, et Rails possède un fichier helper spécial pour ça :
app/helpers/application_helper.rb . ↑
67. Je ne fournis pas de liens vers des API car ils ont une fâcheuse tendance à se démoder assez rapidement.
Google reste votre guide. En passant, « API » signifie «Application Programming Interface» (Interface de
Programmation des Applications). ↑
68. Si vous êtes impatient, sentez-vous libre de consulter le Tutoriel de démarrage rapide de Blueprint CSS. ↑
69. Souvenez-vous que l'invite de la console sera quelque chose comme ruby-1.9.2-head > , mais les exemples
utilisent >> puisque les version de Ruby varieront. ↑
70. Comme pour l'API Rails, les liens vers les API Ruby sont vite dépassés, bien que moins rapidement. Google
reste encore une fois votre guide. ↑
71. Pour en savoir plus sur l'origine de « foo » et de « bar » — et, en particulier, la possible non-relation de
« foobar » à « FUBAR » — voyez l'entrée « foo » dans le fichier Jargon. ↑
72. Les programmeurs familiers de Perl ou de PHP peuvent comparer cela à l'interpolation automatique du signe
dollar des variables dans des expressions telles que "foo $bar" . ↑
522
73. Mes excuses par avance pour passer sans considération du terme fonction (function) à celui de méthode
(method) tout au long de chapitre ; en Ruby, ce sont les mêmes choses : toutes les méthodes sont des fonctions,
et toutes les fonctions sont des méthodes, puisque tout en Ruby est objet. ↑
74. Bien, il restera encore une chose de côté que nous ne comprenons pas, qui est : comment Rails lie toutes ces
choses ensemble : diriger les URLs vers les actions, rendre l'helper titre utilisable dans les vues, etc. C'est un
sujet intéressant, et je vous encourage à le creuser, mais savoir comment Rails fonctione n'est pas nécessaire
pour utiliser Rails (pour une compréhension plus profonde, je recommande The Rails 3 Way par Obie
Fernandez). ↑
75. La méthode second utilisée ici n'est pas une partie de Ruby lui-même, mais est ajouté par Rails. Ça fonctionne
dans ce cas parce la console Rails inclut automatiquement les extensions Rails à Ruby. ↑
76. Les experts en programmation, d'un autre côté, peuvent tirer profit du fait de savoir que ces blocs sont des
closures (ou « fermetures »), qui sont des fonctions anonymes ponctuelles avec des données attachées. ↑
77. Ruby 1.9 garantit actuellement que les tables de hachage gardent leurs éléments dans le même ordre d'entrée,
mais il serait imprudent de compter sur un ordre particulier dans ces tables. ↑
78. Comme résultat d'avoir moins de bagages, les symboles sont plus simples à se comparer entre eux ; les chaines
de caractères ont besoin d'être comparées signe à signe tandis que les symboles peuvent être comparés d'un
seul coup. Ça les rend idéal pour l'utilisation en tant que clé de table de hachage. ↑
79. Vous voyez peut-être des nombres amusants, comme ?1257465942 , après les noms des fichiers CSS. Ils sont
insérés par Rails pour s'assurer que les navigateurs rechargeront le code CSS quand il changera sur le serveur. ↑
80. Ces résultats peuvent varier en fonction de la version de Ruby que vous utilisez. Cet exemple présume que vous
utilisez la version 1.9.2. ↑
81. Pour en savoir plus sur les classes Ruby et le mot-clé self , consultez le pots RailsTips «Class and Instance
Variables in Ruby» (Classe et variables d'instance en Ruby). ↑
82. Pour les familiers de JavaScript, cette fonctionnalité est comparable à l'utilisation des objets de prototype de
classe intégrés pour augmenter une classe (merci au lecteur Erik Eldridge de me l'avoir suggéré). ↑
83. Vous n'avez pas besoin de savoir ce que font chacune des classes de la hiérarchie. Moi-même, je ne sais pas ce
qu'elles font toutes, alors que je programme en Ruby on Rails depuis 2005. Cela signifie soit (a) que je suis tout
bonnement incompétent, soit (b) que vous pouvez être un développeur Rails qualifié sans en connaitre les
entrailles. J'espère pour vous et moi que c'est la dernière proposition qui est la bonne. ↑
84. Les maquettes du Tutoriel Ruby on Rails sont réalisées avec une excellente application appelée Mockingbird. ↑
85. Ils sont tout à fait sans lien avec les classes Ruby. ↑
523
86. Vous pouvez noter que la balise img plutôt que de ressembler à <img>...</img> , se code
<img ... /> . Les balises qui suivent cette forme sont appelées balises auto-fermentantes (self-
closing). ↑
87. Le texte alt est aussi ce qui sera affiché par les lecteurs ayant une déficience visuelle. Bien que les gens soient
souvent négligeants sur l'inclusion de l'attribut alt pour les images, c'est en fait exigé par les standards HTML.
Heureusement, Rails inclut un attribut alt par défaut ; si vous ne spécifiez pas la valeur de l'attribut dans
l'appel pour image_tag , Rails utilise simplement le nom du fichier (sans l'extension). Dans ce cas, cependant,
Application exemple est plus descriptif que logo , donc j'ai décidé de définir explicitement la valeur de
alt . ↑
88. Notez que les utilisateurs Safari et Chrome verront un indicateur d'image brisée à la place du texte alternatif
“Application Exemple”. ↑
89. Les couleurs HTML peuvent être codées avec trois nombres en base 16 (hexadécimaux), chacun respectivement
pour une des couleurs primaires rouge, vert et bleu. #fff mixe les trois couleurs, rendant du pur blanc.
Consultez ce Site des couleurs HTML pour plus d'information. ↑
90. Blueprint CSS utilise une grille de colonnes sur 40 pixels, 30 pixels pour la colonne elle-même et 10 pixels pour
le padding. La colonne la plus à droite n'a pas besoin de padding, donc 18 colonnes représentent 710 pixels : 18
* 40 – 10 = 710. ↑
91. De nombreux développeurs Rails utilisent un dossier partagé (shared ) pour des partiels partagés par
différentes vues. Je préfère utiliser le dossier shared pour des partiels utilitaires qui sont utiles sur de
multiples vues, tandis que je mets les partiels qui sont littéralement sur chaque page (comme une part du
layout du site) dans le dossier layouts (nous créerons le dossier shared en abordant le chapitre 8). Ça me
semble être une division logique, mais les mettre tous dans le dossier shared fonctionne certainement bien,
également. ↑
92. Dans la ligne SampleApp::Application.routes.draw do vous pouvez reconnaitre que la méthode draw
comprend un bloc, une construction syntaxique que nous avons vue précédemment à la section 4.3.2. ↑
93. Mockingbird ne gère pas les images personnalisées comme la photo du profil de l'illustration 6.1 ; je l'ai
déposée à la main en utilisant Adobe Fireworks. L'hippo ici vient de
http://www.flickr.com/photos/43803060@N00/24308857/. ↑
94. Ce nom (Active Record) vient de la « pattern active record », identifiée et nommée dans Patterns of Enterprise
Application Architecture par Martin Fowler. ↑
95. Prononcez « ess-quiou-elle », bien que la prononciation alternative « sé-quel » (en anglais) est aussi
courante. ↑
524
96. Dans ses toutes premières incarnations, Rails requiérait la connaissance des DLL de SQL. Même après que
Rails eut ajouté les migrations, mettre en place l'ancienne base de données par défaut (MySQL) était
nécessaire. Heureusement, comme noté à la section 1.2.5, Rails utilise maintenant SQLite par défaut, ce qui
enregistre les données sous la forme de simples fichiers — aucun réglage n'est plus nécessaire. ↑
97. Occasionnellement, il est nécessaire de comprendre la couche d'abstraction sous-jacente, mais l'une des
philosophies de ce tutoriel est de rendre tout le code indépendant de la base de données (je vous assure que
c'est un but qui vaut le coup, en général). Dans le cas où vous auriez à écrire le code d'une base de données
spécifique pour un déploiement sur Heroku, sachez qu'Heroku utilise l'excellente base de données PostgreSQL
(prononcez « poste-grèce-kiou-elle »). PostgreSQL est libre, open-source, et cross-plateforme ; si vous
développez des applications spécifiquement PostgreSQL, vous pouvez l'installer localement, et configurer Rails
pour l'utiliser en mode de développement en éditant le fichier config/database.yml . Une telle
configuration dépasse le cadre de ce tutoriel, mais vous trouverez de nombreuses ressources sur le web ; utilisez
un moteur de recherche pour chercher de l'information actualisée en fonction de votre plateforme. ↑
98. En utilisant l'adresse email comme username, nous offrons la possibilité théorique de communiquer avec nos
utilisateurs dans le futur. ↑
99. Ne vous souciez pas de comment l'objet t s'arrange exactement pour faire ça ; la beauté des couches
d'abstraction tient à ce que nous n'avons pas besoin de le savoir. Nous pouvons juste faire confiance à l'objet t
pour faire son travail. ↑
100. Nous verrons comment migrer sur un serveur à distance Heroku à la section 7.4.2. ↑
101. Officiellement prononcé « ess-cue-ell-ite » (en anglais.NdT), bien que la (mauvaise)prononciation « sequel-
ite » (en anglais. NdT) soit aussi courante. ↑
102. Dans le cas où vous seriez surpris par "2010-01-05 00:57:46" , je n'ai pas écrit ce code après minuit ; les
timestamps sont enregistrés en Coordonnées de Temps Unversel (UTC), qui pour la plupart des propos est le
même que le Temps de Greenwich. Tiré du NIST Time and Frequency FAQ: Question : Pourquoi UTC est
utilisé comme acronyme de Coordinated Universal Time plutôt que CUT ? Réponse : En 1970 le système
Coordinated Universal Time a été conçu par un groupe consultatif d'experts internationaux à l'intérieur de
l'Union Internationale de Communication (International Telecommunication Union, ITU). L'ITU a pensé qu'il
serait mieux d'utiliser une simple abréviation pour l'usage dans tous les langages dans le but de minimiser la
confusion. Puisqu'aucun accord unanime n'a pu être trouvé sur l'utilisation soit de l'ordre des mots anglais,
CUT, soit de l'ordre des mots français, TUC, le compromis de l'acronyme UTC a été choisi. ↑
103. Notez la valeur de user.updated_at . Dites-vous bien que le timestamp était en UTC. ↑
104. Les exceptions et la gestion des exceptions est un des sujets avancés de Ruby, et nous n'en aurons pas vraiment
besoin dans ce livre. Ils sont importants cependant, et je vous suggère de les étudier en utilisant un livre sur
Ruby recommandé à la section 1.1.1. ↑
525
105. À ceux qui s'inquiéteraient que find_by_email serait inefficace s'il y avait une grande quantité
d'utilisateurs, je dirais que vous êtes en avance sur la musique. Nous couvrirons ce problème, et sa solution, par
le biais des index de base de données, à la section 6.2.4. ↑
106. (et écrit) ↑
107. J'omettrai la sortie des commandes de la console quand elles ne sont pas particulièrement instructives — par
exemple, les résultats de User.new . ↑
108. Notez que, dans la Table 6.1, « letter » (lettre) signifie en réalité « lettre minuscule », mais le i à la fin de
l'expression régulière force à ne pas tenir compte de la casse (minuscule/majuscule). ↑
109. Si vous le trouvez aussi utile que moi, je vous encourage à faire un don à Rubular pour remercier le développeur
Michael Lovitt pour son formidable travail. ↑
110. Saviez-vous que "Michael Hartl"@example.com , avec des guillemets et une espace au milieu est une
adresse valide, conformément au standard ? Aussi surprenant que ça paraisse, c'est vrai — mais c'est absurde.
Si vous n'avez pas une adresse mail ne contenant que des lettres, des nombres, des tirets plats et des points,
alors changez-en ! N.B. L'expression régulière de l'extrait 6.17 permet plus de signes aussi, parce que Gmail (et
possiblement d'autres services d'email) font quelque chose d'utile pour eux : par exemple, pour filtrer les ordres
d'Amazon, vous pouvez utiliser [email protected] , qui se rendra vers l'adresse
Gmail [email protected] , vous permettant de filtrer la chaine amazon. ↑
111. Comme indiqué brièvement dans l'introduction de cette section, il existe une base de données de test dédiée,
db/test.sqlite3 , dans ce but. ↑
112. Si vous vous demandez pourquoi la ligne create! de l'extrait 6.10 ne conduit pas à une erreur en créant une
duplication d'utilisateur, c'est parce que les tests Rails sont transactionnels : chaque test est enroulé dans une
transaction, qui repart de l'état précédent de la base de données après l'exécution des tests. De cette façon,
chaque test s'applique à une base de données fraiche. ↑
113. Oui, ça m'est arrivé. Comment croyez-vous que j'ai découvert cette explication ? ↑
114. Bien sûr, nous pourrions juste éditer le fichier de migration pour la table users de l'extrait 6.2 mais ça
exigerait de revenir en arrière puis à nouveau en avant. La façon Rails est d'utiliser des migrations différentes
chaque fois que nous découvrons que notre modèle de data doit changer. ↑
115. Vous pouvez même définir votre propre environnement personnalisé; voyez le Railscast pour ajouter un
environement pour les détails. ↑
116. Fielding, Roy Thomas. Architectural Styles and the Design of Network-based Software Architectures.
Doctoral dissertation, University of California, Irvine, 2000. ↑
526
117. Cela signifie que le routage fonctionne, mais que les pages correspondantes ne fonctionnent pas, elles, à ce
point du développement. Par exemple, /users/1/edit sera proprement routé vers l'action edit du
contrôleur Users, mais puisque l'action edit n'existe pas encore, en fait, invoquer cette URL retournera une
erreur. ↑
118. Certaines des copies-écran de ce tutoriel montre des informations de débuggage avec des sorties comme
!map:HashWithIndifferentAccess au lieu de
!map:ActiveSupport::HashWithIndifferentAccess . C'est simplement une différence mineure entre
Rails 2.3 et Rails 3. Puisque les page web rendues sont en grande partie identiques entre les deux versions,
cette note de bas de page me permet de ne pas avoir à les refaire toutes. ↑
119. L'information debug Rails est affichée au format YAML (un acronyme récursif signifiant « YAML Ain’t Markup
Language »), qui est un format de donnée convivial conçu pour être lu aussi facilement par les machines que
par les humains. ↑
120. Nous avons vu précédemment les rangs à la section 4.3.1. ↑
121. Pour plus de détails sur le type de fonctions de rappel supportées par Active Record, voyez la discussion sur les
callbacks dans les guides Rails. ↑
122. Le niveau supplémentaire d'indentation est une façon typographique de nous souvenir que nous sommes dans
une section privée ; dans le cas contraire, il est facile de manquer le mot-clé private et d'être dérouté en
essayant d'atteindre une méthode privée que vous pensez publique. Je pensais que cette convention
d'indentation était stupide jusqu'à ce que je perde une heure sur ce simple problème il y a quelques années.
Maintenant j'ajoute l'indentation… ↑
123. Ruby possède aussi un mot-clé très proche, appelé protected (protégé) qui diffère subtilement de private .
Autant que je peux dire, la seule raison d'apprendre la différence serait qu'on pourrait vous poser cette question
au cours d'un entretien d'embauche : « En Ruby, quelle est la différence entre private et protected ? ».
Mais qui aurait envie de travailler dans une compagnie qui poserait ce genre de question vicieuse ? Lors de son
discours à RubyConf en 2008, Dave Thomas (auteur de Programmer Ruby) a suggéré d'éliminer protected
des versions futures de Ruby, et j'abonde dans son sens. Utilisez simplement private et tout ira bien. ↑
124. J'ai honte d'admettre que c'est ainsi que nous avons implémenté les mots de passe dans RailsSpace. Considérez
que cette section est ma pénitence. ↑
125. Le lecteur attentif peut noter que rien de ce que nous faisons dans cette section ne requiert le cryptage, mais,
une fois que nous développons un peu de la théorie des mots de passe sécurisés et écrivons une implémentation
basique (section 7.2.2), la seule façon pour la méthode has_password? de fonctionner proprement est que
toute la machinerie de cryptage fonctionne elle aussi. ↑
527
126. Dans mes réglages, la ligne require ’digest’ n'est pas nécessaire, mais plusieurs lecteurs m'ont reporté
qu'ils rencontraient une erreur NameError s'ils ne l'incluaient pas explicitement. Ça n'entraine aucun
dommage de toute façon, donc j'ai inclus explicitement require juste pour être sûr. ↑
127. En théorie et techniquement, les attaques « arc-en-ciel » pourraient toujours réussir, mais en utilisant un
« hachage salé », la réussite est informatiquement infaisable. ↑
128. Comme indiqué à la section 7.2.2, la ligne explicite require ’digest’ n'est pas nécessaire sur certains
systèmes, mais ça ne coûte rien de l'ajouter. ↑
129. Dans les versions précédentes de Rails, nous pouvions utiliser la fonction de rappel
after_validation_before_create pour définir le salt, mais elle a été supprimée dans Rails 3. En
passant, nous ne pouvons pas utiliser la fonction de rappel before_create parce qu'elle s'exécute après la
fonction de rappel before_save , et la fonction de rappel before_save a besoin du salt. ↑
130. En vous souvenant du Box 6.2, l'indexation de la colonne email assurant que cette récupération serait
efficace. ↑
131. Nous projetterons d'ajouter une paire d'attributs supplémentaires (un pour identifier un utilisateur
administrateur et un pour permettre la fonctionnalité « se souvenir de moi »), mais ils ne sont pas strictement
nécessaires. Tous les attributs d'utilisateur essentiels ont maintenant été définis. ↑
132. De nombreux programmeurs Rails expérimentés trouve que cette approche factory est bien plus flexible que
les fixtures (appareils) que Rails utilise par défaut mais peuvent être fragiles et difficiles pour la maintenance. ↑
133. Le nom « Factory Girl » fait peut-être référence au film éponyme. ↑
134. J'ai utilisé get ’new’ dans l'extrait 5.24 et les tests suivants pour l'action new parce qu'à ce point nous
n'avions pas encore rencontré l'idée des actions du concept REST. Je le remplacerai par get :new dans les
tests futurs. ↑
135. Il accomplit cela en appelant la méthode to_param sur la variable @user. ↑
136. Ça n'est pas toujours nécessairement une bonne idée de créer des tests HTML aussi spécifiques, puisque nous
ne voulons pas toujours contraindre aussi étroitement le layout HTML. Sentez-vous libre d'expérimenter et de
trouver le bon niveau de détail pour vos projets conformément à vos goûts. ↑
137. Ou plutôt, si vous voulez dés-échapper le texte vous devez utiliser la méthode raw (brut), comme dans
<%= raw @titre %> . ↑
138. Gravatar a été créé originellement par Tom Preston-Werner, co-fondateur de GitHub, et a été acquis et
redimensionné par Automattic (plus connu comme les concepteurs de WordPress). ↑
528
139. Dans l'hindouisme, un avatar est la manifestation d'une divinité dans une forme humaine ou animale. Par
extension, le terme avatar est couramment utilisé pour signifier toute forme de représentation personnelle,
spécialement dans un environnement virtuel. Mais vous avez dû voir le film maintenant, donc vous savez déjà
ce que c'est. ↑
140. Si votre application a besoin de traiter les images ou tout autre téléchargement de fichier, Paperclip est
l'endroit où se rendre. Comme Factory Girl, Paperclip vous est offert par thoughtbot (bien que je connaisse
quelques personnes là-bas, je n'ai aucun intérêt particulier à promouvoir thoughtbot ; ils font simplement de
bons logiciels). ↑
141. Merci au lecteur anonyme qui a noté que le plugin Gravatar est sensible à la casse dans ce contexte. ↑
142. Il existe en fait un moyen de redéfinir la taille par défaut dans le fichier de configuration, mais j'ai trouvé ce
moyen plus clair. ↑
143. Les gravatars sont carrés, donc un seul paramètre suffit à définir la taille. ↑
144. Si quelqu'un vous fait grief, horreur des horreurs, d'utiliser les tables pour la mise en forme, demandez-lui de
pointer son inspecteur firebug à la barre latérale de profil de Twitter et de vous dire ce qu'il voit. En fait, vous
trouverez que, tandis que le « marquage sémantique » utilisant des div et des span est de plus en plus
courant, virtuellement tous les sites font appel à l'occasion au table pour faire de la mise en forme. Dans le cas
présent, obtenir l'alignement vertical juste à droite est beaucoup plus facile avec les tables. ↑
145. D'ordinaire, je recommande de faire des dépôts plus fréquents, plus petits, mais de fréquents dépôt Git tout au
long de ce tutoriel serait difficile à maintenir et casserait trop souvent le fil de notre discussion. ↑
146. Si vous n'êtes pas sur la bonne branche, jouer git checkout modeling-users avant de procéder. ↑
147. Si vous rencontrez une erreur comme views/users/new.html.erb_spec.rb fails , supprimer ces
maudits specs vues avec $ rm -rf spec/views . ↑
148. Le nom vient du lambda-calcul, un système mathématique pour représenter les fonctions et leurs opérations. ↑
149. Avant Rails 3, l'affichage des messages d'erreur se faisait par un appel magique à une méthode spéciale
error_messages sur l'objet formulaire f , comme suit : <%= f.error_messages %> . Bien
que souvent efficace, cette méthode magique était difficilement personnalisable, donc l'équipe Rails a décidé de
recommander d'utiliser du Ruby embarqué pour afficher les erreurs à la main. ↑
150. Je l'ai compris en consultant pluralize dans l'API Rails. ↑
151. Dans le cas où vous seriez curieux, le code de réponse est le 302 , en contraste avec la redirection
« permanente » 301 exposée brièvement dans le Box 3.2. ↑
529
152. Notez que la clé :success est un symbole, mais le code Ruby embarqué le convertit automatiquement en
une chaine "success" avant de l'insérer dans le template. ↑
153. En fait, nous utiliserons le proche relatif flash.now , mais nous différerons cette subtilité jusqu'à ce que nous
en ayons besoin. ↑
154. Cet indice est « offset-zéro » (décalage zéro), comme pour les listes (section 4.3.1), donc une valeur retournée
valant 0 signifie que l'expression a été trouvée dans la chaine, commençant au premier caractère. ↑
155. Au moment où j'écris ces lignes, cette syntaxe est disponible grâce à Webrat, qui est apparue comme une
dépendance gem de rspec-rails , mais que Webrat a écrit avant l'adoption généralisée de Rack et sera
peut-être supplantée par le projet Capybara. Heureusement,, Capybara est conçu comme une solution de
remplacement de Webrat, donc la syntaxe devrait rester la même. ↑
156. Notez le pluriel : ce n'est pas le spec User user_spec.rb , qui est un test de modèle, pas un test
d'intégration. ↑
157. Vous pouvez utiliser Firebug ou le « code source de la page » de votre navigateur si vous avez besoin de
comprendre les ids. Ou vous pouvez noter que Rails utilise le nom de la ressource et le nom de l'attribut séparé
par un tiret plat, rendant user_nom , user_email , etc. ↑
158. Un autre modèle courant est de faire expirer la session après un certain laps de temps. C'est spécialement
approprié sur les sites qui contiennent des données sensibles, comme les banques et les comptes financiers. ↑
159. Nous verrons à la section 9.3.2 combien de temps signifie en réalité « pour toujours ». ↑
160. Si on lui donne les actions create et destroy , le script generate construira les vues pour ces actions, ce
dont nous n'avons pas besoin. Bien sûr, nous pourrions détruire ces vues, mais j'ai décidé de les omettre de la
commande generate et de plutôt définir ces actions à la main. ↑
161. Dans le cas où vous vous demanderiez pourquoi nous utilisons user plutôt que @user dans l'extrait 9.8, c'est
parce que cette variable user n'est jamais demandée dans aucune vue, donc il n'y a pas de raison d'utiliser une
variable d'instance ici (utiliser @user fonctionnerait aussi, cependant). ↑
162. Image provenant de http://www.flickr.com/photos/hermanusbackpackers/3343254977/. ↑
163. Sur certains systèmes, vous pouvez avoir besoin d'utiliser self.current_user = user pour faire réussir les
tests à venir. ↑
164. Parce que le module helper Sessions est inclus dans le contrôleur Application, la variable self ici est le
contrôleur lui-même. ↑
530
165. En fait, les deux sont exactement équivalents ; attr_accessor est simplement une façon pratique de
créer automatiquement des méthodes telles que getter et setter. ↑
166. Classiquement, cela signifie assigner les valeurs qui sont intialement nil , mais notez que les valeurs false
seront également sur-définies par l'opérateur ||= . ↑
167. Cette technique d'optimisation pour éviter de répéter des appels de fonction est connue sous le noim de
mémoization. ↑
168. Cela ressemble à l'histoire de la queue qui remue le chien, mais c'est le prix à payer pour rester à la pointe. ↑
169. Si vous utilisez Spork, elle sera placée à l'intérieur du bloc Spork.prefork . ↑
170. Vous pouvez apprendre beaucoup sur des choses telles que cookies.delete en lisant l'entrée « cookies »
dans l'API Rails (puisque les liens vers l'API Rails tendent à être très vite dépassés, utilisez votre moteur de
recherche pour trouver une version actualisée). ↑
171. Notez que nous pouvons utiliser les symboles à la place des chaines de caractères pour les labels, par exemple
fill_in :email au lieu de fill_in "Email" . Nous utilisons ce dernier dans l'extrait 8.22, mais
maintenant cela ne devrait pas vous surprendre que Rails nous permette d'utiliser plutôt les symboles. ↑
172. Les navigateurs web ne peuvent pas, en vérité, transmettre une requête DELETE ; Rails la simule avec
JavaScript. ↑
173. De la section 7.3.3 vous vous souvenez que nous pouvons nous lier directement à un objet utilisateur et
permettre à Rails de deviner l'URL adéquate. ↑
174. Ce qui est un peu déroutant, c'est que nous avons utilisé les cookies pour implémenter les sessions, et la
session est implémentée avec les cookies ! ↑
175. Image provenant de http://www.flickr.com/photos/sashawolff/4598355045/. ↑
176. Le site Gravatar redirige en fait cette adresse vers http://en.gravatar.com/emails , qui
concerne les utilisateurs de langue anglaise, mais j'ai volontairement omis d'ajouter en au compte pour
l'utilisation par des utilisateurs d'autres langages. ↑
177. Image de http://www.flickr.com/photos/sashawolff/4598355045/. ↑
178. Ne vous souciez pas trop de savoir comment cela fonctionne ; les détails concernent les développeurs du
framework Rails eux-mêmes, mais ne sont pas importants, à dessein, pour les développeurs d'applications. ↑
179. Le code de cette section est adapté du gem Clearance par thoughtbot. ↑
180. Vraiment, comme indiqué dans la section 9.6, session est exactement implémentée de cette façon. ↑
531
181. Photo Baby de http://www.flickr.com/photos/glasgows/338937124/. ↑
182. Techniquement parlant, nous n'avons besoin que d'ajouter 28 utilisateurs fictifs, puisque nous en avons déjà
trois, mais je trouve le sens plus clair si nous en ajoutons plutôt 30. ↑
183. Le nom user est immatériel — nous aurions pu tout aussi bien écrire @users.each do |foobar| et utiliser
alors render foobar . La clé est la classe de l'objet dans ce cas, User . ↑
184. La méthode toggle! invoque les « callbacks » de Active Record mais pas les validations, donc nous devons
renseigner l'attribut password (mais pas la confirmation de ce mot de passe) pour avoir un mot de passe non
vide dans la fonction callback encrypt_password . ↑
185. Des outils en ligne de commande tels que curl (vu dans la Box 3.2) peut utiliser les requêtes PUT de cette
forme. ↑
186. Cela signifie que le lien pour supprimer un utilisateur ne fonctionnera pas si l'utilisateur a JavaScript désactivé
dans son navigateur. Si vous devez traiter le cas de navigateur sans JavaScript, vous pouvez simuler une
requête DELETE en utilisant la forme d'une requête POST, laquelle fonctionne même sans JavaScript ;
consultez le Railscast sur la « Destruction sans JavaScript » pour les détails. ↑
187. Techniquement, nous traitons les sessions comme une ressource dans le chapitre 9, mais elles ne sont pas
sauvées dans la base de données de la même façon que les utilisateurs et les micro-messages. ↑
188.L'attribut content (contenu) sera une chaine de caractères (string ), mais, comme cela est noté brièvement
dans la section 2.1.2, pour des champs de saisie de textes plus longs, vous devez utiliser le type de donnée
text . ↑
189. Pour en savoir plus sur les associations dans Factory Girl, y compris toutes les options possibles, voyez la
Documentation Factory Girl. ↑
190. Souvenez-vous que created_at (créé le…) et updated_at (actualisé_le…) sont des colonnes « magiques »,
donc toute assignation explicite sera remplacée par ces valeurs magiques. ↑
191. Conformément au marquage sémantique (semantic markup), il serait probablement meilleur d'utiliser une
liste numérotée (ordered list), mais dans ce cas l'alignement du texte et des images est beaucoup plus difficile
qu'avec les tables. Voyez l'exercice de la section 11.5 si vous mettez un point d'honneur à utiliser la version
sémantique. ↑
192. Dans le cas où vous vous poseriez la question, la méthode associative count (compter) est intelligente, et
exécute le décompte directement dans la base de données. En particulier, elle ne tire pas tous les micro-
messages de la base pour compter ensuite le nombre d'éléments dans la liste obtenue, ce qui serait terriblement
consommateur à mesure que le nombre de micro-messages grossirait. Au lieu de ça, elle demande à la base de
données de compter les micro-messages possédant un identifiant d'utilisateur donné (user_id ). En passant,
532
dans l'éventualité très peu probable où dénombrer les messages serait quand même un goulot
d'étranglement, vous pouvez le rendre encore plus rapide avec un counter cache. ↑
193. De façon pratique, l'extrait 11.19 contient en fait tous les styles CSS nécessaires à ce chapitre. ↑
194. (c'est-à-dire cinq utilisateurs avec un gravatar personnalisé, et un avec le gravatar par défaut) ↑
195. Consultez votre journal de développement (log/development.log ) si vous êtes curieux de connaitre la
commande SQL que cette méthode génère. ↑
196. À dessein, le texte lorem ipsum du gem Faker est aléatoire, donc les contenus des micro-messages en exemple
seront différents. ↑
197. Les deux autres ressources sont la ressource `Users` de la section 8.1 et la ressource `Sessions` de la
section 9.1. ↑
198. Nous avons noté dans la section 9.3.2 que les méthodes helper sont disponibles seulement dans les vues
(views) par défaut, mais nous avons fait en sorte que les méthodes helper de la ressource `Sessions` soient
également accessibles par les contrôleurs en ajoutant le code include SessionsHelper au contrôleur de
l'application (extrait 9.11). ↑
199. L'apprentissage des méthodes telles que include? est une des raisons pour laquelle, comme cela est noté dans
la section 1.1.1, je recommande la lecture d'un livre de pur Ruby après avoir achevé celui-ci. ↑
200. Voyez le guide Rails à la section Active Record Query Interface pour en savoir plus sur la clause where et
similaire. ↑
201. Malheureusement, retourner une alimentation paginée ne fonctionne pas dans ce cas. Implémentez-la et
cliquez sur un lien de pagination pour comprendre pourquoi (les screencasts du tutoriel Rails couvre ce
problème de façon plus approfondie.) ↑
202. Les photographies des maquettes viennent de http://www.flickr.com/photos/john_lustig/2518452221/ et
http://www.flickr.com/photos/30775272@N05/2884963755/. ↑
203. Pour la simplicité, l'illustration 12.6 supprime la colonne id de la table following . ↑
204. Malheureusement, Rails utilise connection pour une connexion la base de données, donc introduire un
modèle de nom « Connection » conduit à des bogues plutôt subtils (j'ai appris cela à mes dépends en
développant Insoshi). (NdT. Encore une fois, ce problème ne se poserait pas en français, où « connexion »
s'écrit avec un « x », pas avec « _ct_ion ») ↑
205. En effet, cette construction est tellement caractéristique de Rails que le fameux programmeur Rails Josh
Susser l'utilise comme nom de son blog. ↑
533
206. Techniquement, Rails utilise la méthode underscore pour convertir la classe nom vers un id. Par
exemple, "FooBar".underscore est foo_bar , donc la clé étrangère d'un objet FooBar devrait être
foo_bar_id (incidemment, l'inverse de underscore est camelize , qui convertit camel_case vers
CamelCase ). ↑
207. Si vous n'avez pas noté que followed_id identifie aussi un utilisateur, et concerne le traitement
asymétrique des suivis et des suiveurs, vous êtes hors-jeu. Nous traiterons ce problème à la section 12.1.5. ↑
208. Cette méthode follow! devrait toujours fonctionner, donc (en suivant le modèle de create! et save! )
nous indiquons avec l'exclamation qu'une exception (une erreur) sera déclenchée en cas d'échec. ↑
209. Une fois que vous aurez une grande expérience de la modélisation d'un domaine particulier, vous pourrez
souvent deviner à l'avance de telles méthodes utilitaires, et même quand vous ne pourrez pas, vous trouverez
souvent de vous-même que les écrire rend les tests plus clairs. Dans le cas présent, cependant, ça n'est pas si
grave si vous ne les aviez pas anticipées. Le développement de logiciel est souvent un processus itératif — vous
écrivez du code jusqu'à ce qu'il s'enlaidisse, et alors vous le restructurez — mais la brièveté de la présentation
d'un tutoriel doit quelque peu simplifier ce processus. ↑
210. La méthode authenticate_with_salt est incluse simplement en vous orientant à l'intérieur du fichier du
modèle User. ↑
211. La méthode unfollow! ne provoque pas d'erreur à l'échec — en fait, je ne sais même pas comment Rails
indique un échec de destruction — mais nous utilisons un point d'exclamation pour maintenant la symétrie
follow! /unfollow! . ↑
212. Vous pouvez avoir noté que quelquefois nous accédons à id explicitement, comme dans followed.id et
d'autrefois nous utilisons juste followed . J'ai honte d'admettre que mon algorithme habituel pour dire quand
savoir lequel utiliser consiste simplement à voir si ça marche sans id et alors de l'ajouter si ça ne fonctionne
pas. ↑
213. Tout dans l'extrait 12.28 a été couvert quelque part dans ce tutoriel, donc c'est un bon exercice de lire ce code. ↑
214. Parce que c'est nominalement un acronyme de asynchronous JavaScript and XML, Ajax est quelquefois écrit
« AJAX », même si l'article sur l'Ajax original l'épelle « Ajax » tout au long du texte. ↑
215. Cela ne fonctionne bien sûr que si JavaScript est autorisé dans le navigateur, mais Rails s'adapte gracieusement
dans le cas contraire, rendant le fonctionnement identique à celui de la section 12.2.4 dans le cas où JavaScript
se serait pas autorisé. ↑
216. À ce stade, si vous ne l'avez déjà fait, vous aurez à inclure la Librairie JavaScript Prototype par défaut dans
votre application Rails comme dans l'extrait 10.39. ↑
217. Il n'y a pas de relation entre ce respond_to et le respond_to utilisé dans les exemples RSpec. ↑
534
218. La principale exigence des ces objets enumerable est qu'ils doivent implémenter la méthode each pour
boucler (itérer) sur leur collection d'éléments. ↑
219. La notation était en fait une extension Rails faite au cœur du langage Ruby ; elle a été jugée si utile qu'elle a été
maintenant incorporée à Ruby lui-même. Elle est pas belle la vie ? ↑
220. L'appel de paginate sur un objet Array le convertit en objet WillPaginate::Collection , mais ça ne
nous aide pas plus puisque le tableau entier a déjà été créé en mémoire. ↑
221. Une fonction « empaquetée » avec une partie de données (un utilisateur dans ce cas) porte le nom de closure,
comme nous l'avons vu brièvement au cours d'une discussion sur les blocs à la section 4.3.2. ↑
222. Pour une façon plus avancée de créer la sous-sélection désirée, consultez le post de blog « Hacking a subselect
in ActiveRecord ». ↑
223. Bien entendu, les sous-sélections ne fonctionneront pas dans tous les cas. Pour des sites plus conséquents,
vous devrez probablement générer l'alimentation de façon asynchrone en utilisant une tâche de fond. De telles
subtilités dépassent largement le cadre de ce tutoriel, mais le screencast Scaling Rails est une bonne façon de
commencer. ↑
224. Dans le but de faire une alimentation d'aspect plus sympa pour l'illustration 12.20, j'ai ajouté quelque micro-
messages supplémentaires à la main en utilisant la console Rails. ↑
225. Vous pouvez le vérifier en examinant les retours SQL dans le fichier journal du serveur de développement (le
screencast du Tutoriel Rails couvrira de façon plus approfondie de telles subtilités). ↑
226. Ma seule réserve concernant Railscasts est qu'ils omettent souvent les tests. C'est probablement nécessaire
pour garder l'épisode sympa et court, mais cela pourrait vous donner une mauvaise idée de l'importance de ces
tests. Une fois que vous avez regardé le Railscast concerné pour vous faire une idée de base de comment
procéder, je vous suggère d'écrire la nouvelle fonctionnalité en utilisant le « Développement Dirigé par les
Tests ». ↑
227.En plus d'être une phrase intelligente — nouvelle relique est une contradiction dans les termes (un oxymore.
NdT) — New Relic est aussi l'anagramme du nom du créateur de la compagnie, Lew Cirne. ↑