tutoriel ruby on rails : apprendre rails par l'exemple de michael hartl

534
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

Upload: marc-maurer

Post on 05-Dec-2014

12.658 views

Category:

Technology


59 download

DESCRIPTION

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

TRANSCRIPT

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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.

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

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

Page 12: Tutoriel Ruby on Rails : apprendre Rails par l'exemple de 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.

* ------------------------------------------------ ------------

*/

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

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

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

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

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

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

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

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

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

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

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

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.

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

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

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

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

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

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.

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

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.

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

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

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

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 :

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

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 :

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

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

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

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)

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

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'

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

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.

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

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

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

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

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

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

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

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.

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

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.

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

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

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

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

.

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

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/ :

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

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 !

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

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.

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

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).

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

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

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

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

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

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

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

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 :

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

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.

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

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 ).

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

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).

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

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

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

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 !

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

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'

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

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).

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

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.

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

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

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

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).

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

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.

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

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 ).

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

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.

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

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.

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

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 »).

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

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 ;

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

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

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

62

def index

.

.

.

end

def show

.

.

.

end

def new

.

.

.

end

def create

.

.

.

end

def edit

.

.

.

end

def update

.

.

.

end

def destroy

.

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

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

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

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

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

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).

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

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

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

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

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

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

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

69

def show

.

.

.

end

def new

.

.

.

end

def create

.

.

.

end

def edit

.

.

.

end

def update

.

.

.

end

def destroy

.

.

.

end

end

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

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).

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

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.

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

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

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

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

.

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

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

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

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

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

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 ;

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

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.

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

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'

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

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

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

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é :

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

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).

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

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

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

83

$ mate public/hello.html

Extrait 3.3. Un fichier HTML classique, avec une salutation amicale.

public/hello.html

<!DOCTYPE html>

<html>

<head>

<title>Salutations</title>

</head>

<body>

<p>Salut le monde&nbsp;!</p>

</body>

</html>

Nous pouvons voir dans 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 ».

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

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

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

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.

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

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).

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

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.

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

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 ?

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

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

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

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).

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

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

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

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

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

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

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

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

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

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/

....

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

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.)

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

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.

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

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

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

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}

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

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

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

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/

..

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

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'

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

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à.

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

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/ .

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

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

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

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/

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

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 ).

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

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"

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

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).

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

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

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

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>

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

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>

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

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 ).

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

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 :

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

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 %>

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

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

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

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>

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

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

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

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 :

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

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>

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

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

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

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

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

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>

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

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

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

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>

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

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>

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

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

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

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

.

.

.

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

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

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

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

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

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 \.'

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

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"

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

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 :

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

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 "" .

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

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 !"

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

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

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

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"]

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

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

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

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"

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

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

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

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

--

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

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

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

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 :

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

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

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

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 :

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

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'

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

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 :

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

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 ]

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

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) :

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

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

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

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

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

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?

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

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

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

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 :

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

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} >"

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

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

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

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 .

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

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

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

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).

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

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">

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

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.

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

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>

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

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

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

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&nbsp;!" , '#' , :class => "signup_button round" %>

Comme avec le précédent usage de link_to , cela crée simplement un lien souche de la forme :

<a href="#" class="signup_button round">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.

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

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.

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

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 {

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

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 {

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

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).

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

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 {

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

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.

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

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.

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

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.

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

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.

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

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.

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

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>

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

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).

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

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.)

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

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

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

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'

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

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 |

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

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

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

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"

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

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-

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

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>

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

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.

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

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

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

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.

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

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.

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

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).

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

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" %>

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

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

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

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.

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

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

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

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

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

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.)

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

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

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

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).

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

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.

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

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.

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

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.

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

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 :

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

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.

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

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.

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

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.

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

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

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

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 :

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

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]",

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

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"

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

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

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

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)

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

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).

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

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

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

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.

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

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

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

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

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

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

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

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

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

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

[email protected]

[email protected]

[email protected]

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

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.

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

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.

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

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

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

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

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

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

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

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é.

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

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

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

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

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

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.

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

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).

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

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

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

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,

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

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.

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

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.

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

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

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

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 :

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

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.

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

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.

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

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.

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

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

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

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 .

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

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

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

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

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

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.

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

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

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

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

.

.

.

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

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.

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

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

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

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

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

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

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

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

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

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} " )

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

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

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

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)

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

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

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

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.

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

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

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

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 :

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

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

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

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.

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

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

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

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

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

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

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

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)

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

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'

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

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

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

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

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

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

&lt;script&gt; , 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>

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

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>

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

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.

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

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 %>

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

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 %>

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

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>

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

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.

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

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;

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

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

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

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?

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

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

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

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

.

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

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 .

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

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">

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

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

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

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.

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

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>

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

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.

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

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">

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

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.

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

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 => "" }

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

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 )

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

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"

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

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

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

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>

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

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.

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

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.

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

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) :

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

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;

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

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;

}

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

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

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

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

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

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.

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

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

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

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

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

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é… :-(

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

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

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

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

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

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

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

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).

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

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.

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

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 :

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

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.

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

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

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

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

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

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

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

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.

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

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

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

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

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

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>

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

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

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

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

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

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.

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

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.

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

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.

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

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).

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

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.

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

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

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

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 ]

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

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'

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

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.

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

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.

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

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.

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

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

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

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

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

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 :

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

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

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

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 :

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

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

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

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).

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

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

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

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

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

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

.

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

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

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

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 :

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

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

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

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

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

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 :

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

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.

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

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

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

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

.

.

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

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

.

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

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

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

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"

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

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>

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

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>

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

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).

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

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

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

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.

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

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

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

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.

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

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

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

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).

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

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

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

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 %>

.

.

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

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>

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

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" />

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

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.

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

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

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

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

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

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.

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

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

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

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).

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

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.

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

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

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

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

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

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 ] )

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

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

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

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

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

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.

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

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

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

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

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

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.

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

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>

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

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.

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

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"

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

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).

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

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'

.

.

.

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

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

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

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.

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

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 ).

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

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'

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

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

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

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.

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

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">

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

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.

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

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

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

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

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

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

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

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 %>

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

396

<%= link_to user . nom, user %>

<% if current_user . admin? %>

| <%= link_to "supprimer" , user, :method => :delete , :confirm => "Etes-vous certain&nbsp;?" ,

: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>

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

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

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

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é

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

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 :

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

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'

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

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">

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

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 %>

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

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

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

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.

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

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.

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

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.

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

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'

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

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)

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

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

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

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

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

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|

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

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

.

.

.

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

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

.

.

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

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

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

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.

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

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

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

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

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

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'

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

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">

.

.

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

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 :

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

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 %>

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

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

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

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%;

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

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;

}

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

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

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

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.

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

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 ).

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

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

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

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

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

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`.)

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

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

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

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

.

.

.

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

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

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

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 (/ ).

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

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">

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

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

.

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

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.

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

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'

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

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

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

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 :

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

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 :

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

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.

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

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.

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

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'

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

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">

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

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

.

.

.

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

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.

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

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).

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

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

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

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

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

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).

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

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 = "&#8203;"

regex = /.{1,#{max_width}}/

(text . length < max_width) ? text :

text . scan(regex) . join(zero_width_space)

end

end

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

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.

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

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.

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

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.

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

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

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

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 ) à

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

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 :

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

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.

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

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).

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

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

.

.

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

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

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

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

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

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.

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

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

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

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

.

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

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

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

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

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

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! )

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

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

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

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

.

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

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" ,

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

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.

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

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,

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

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 :

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

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

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

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

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

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" )

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

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">

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

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 ).

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

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.

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

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 .

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

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 %>

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

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 ).

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

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).

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

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

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

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

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

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.

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

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 ]

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

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

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

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

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

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.

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

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

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

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

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

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.

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

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).

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

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')) %>" )

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

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.

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

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

.

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

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.

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

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)

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

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 >

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

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 :

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

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 :

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

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.

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

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é :

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

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.

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

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é.

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

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

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

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

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

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.

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

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.

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

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.

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

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

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

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

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

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. ↑

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

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. ↑

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

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. ↑

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

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). ↑

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

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

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

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" . ↑

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

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. ↑

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

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. ↑

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

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. ↑

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

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. ↑

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

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. ↑

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

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). ↑

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

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. ↑

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

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. ↑

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

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. ↑

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

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,

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

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. ↑

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

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. ↑

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

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. ↑