Vous êtes sur la page 1sur 40

Tutoriel Ruby on Rails

par Michael Hartl


Accueil | News | Suivre

Rejoindre le contenu

Tutoriel Ruby on Rails


Apprendre Rails par l'exemple
Michael Hartl

Contenu
1. Chapitre 1 De zéro au déploiement
2. 1. 1.1 Introduction
2. 1. 1.1.1 Commentaires pour les lecteurs différents
2. 1.1.2 “Dimensionner” Rails
3. 1.1.3 Conventions utilisées dans ce livre
3. 1.2 Debout et au boulot
4. 1. 1.2.1 Environnements de développement
2. 1. IDEs
2. Éditeurs de texte et lignes de commande
3. Navigateurs
4. Note à propos des outils
3. 1.2.2 Ruby, RubyGems, Rails, et Git
4. 1. Installation de Rails (Windows)
2. Installer Git
3. Installer Ruby
4. Installer RubyGems
5. Installer Rails
5. 1.2.3 La première application
6. 1.2.4 Bundler
7. 1.2.5 Le serveur rails (rails server)
8. 1.2.6 Modèles-Vue-Contrôleur (MVC)
5. 1.3 Contrôle de versions avec Git
6. 1. 1.3.1 Installation et réglages
2. 1. Initialisation des réglages système
2. Initialisation des réglages du dépôt (repository)
3. 1.3.2 Ajout et mandat de dépôt
4. 1.3.3 Qu'est-ce que Git peut faire de bien pour vous ?
5. 1.3.4 GitHub
6. 1.3.5 Branch, edit, commit, merge
7. 1. Branch
2. Edit
3. Commit
4. Merge
5. Push
7. 1.4 Déploiement
8. 1. 1.4.1 Réglages Heroku
2. 1.4.2 Déploiement Heroku, première étape
3. 1.4.3 Déploiement Heroku, seconde étape
4. 1.4.4 Commandes Heroku
9. 1.5 Conclusion
3. Chapitre 2 Une application démo
4. 1. 2.1 Planifier l'application
2. 1. 2.1.1 Modéliser les utilisateurs
2. 2.1.2 Modéliser les micro-messages
3. 2.2 La ressource Utilisateurs (Users)
4. 1. 2.2.1 Un tour de l'utilisateur
2. 2.2.2 MVC en action
3. 2.2.3 Faiblesses de la ressource Utilisateurs (Users)
5. 2.3 La ressource Micro-messages (Microposts)
6. 1. 2.3.1 Un petit tour du micro-message
2. 2.3.2 Appliquer le micro aux micro-messages
3. 2.3.3 Un utilisateur has_many micro-messages
4. 2.3.4 Hiérarchie des héritages
5. 2.3.5 Déployer l'application Démo
7. 2.4 Conclusion
5. Chapitre 3 Pages statiques courantes
6. 1. 3.1 Pages statiques
2. 1. 3.1.1 Pages HTML statiques
2. 3.1.2 Les pages statiques avec Rails
3. 3.2 Premiers tests
4. 1. 3.2.1 Outils de test
2. 1. Auto-test
3. 3.2.2 TDD : Rouge, Vert, Refactor
4. 1. Spork
2. Rouge
3. Vert
4. Refactor
5. 3.3 Pages (un peu) dynamiques
6. 1. 3.3.1 Test d'un changement de titre
2. 3.3.2 Réussir les tests de titre
3. 3.3.3 Variables d'instance et Ruby embarqué
4. 3.3.4 Supprimer les répétitions avec les layouts
7. 3.4 Conclusion
8. 3.5 Exercices
7. Chapitre 4 Rails au goût Ruby
8. 1. 4.1 Motivation
2. 1. 4.1.1 Un helper pour le titre
2. 4.1.2 Feuilles de styles (CSS — Cascading Style Sheets)
3. 4.2 Chaines de caractères et méthodes
4. 1. 4.2.1 Commentaires
2. 4.2.2 Chaines de caractères
3. 1. Impression
2. Chaines de caractères « apostrophées »
4. 4.2.3 Objets et passage de message
5. 4.2.4 Définition de méthode
6. 4.2.5 Retour à l'« helper » de titre
5. 4.3 Autres structures de données
6. 1. 4.3.1 Tableaux et rangs
2. 4.3.2 Blocs
3. 4.3.3 Tables de hachage et symboles
4. 4.3.4 CSS revisitées
7. 4.4 Classes Ruby
8. 1. 4.4.1 Constructeurs
2. 4.4.2 Héritages de classes
3. 4.4.3 Modifier les classes d'origine
4. 4.4.4 Classe de contrôleur
5. 4.4.5 La classe utilisateur
9. 4.5 Exercices
9. Chapitre 5 Poursuivre la mise en page
10. 1. 5.1 Ajout de structure
2. 1. 5.1.1 Navigation du site
2. 5.1.2 Personnalisation CSS
3. 5.1.3 Partiels
3. 5.2 Liens pour la mise en page
4. 1. 5.2.1 Test d'intégration
2. 5.2.2 Routes Rails
3. 5.2.3 Nommer les routes
5. 5.3 Inscription de l'utilisateur : une première étape
6. 1. 5.3.1 Contrôleur Utilisateur
2. 5.3.2 URL d'inscription
7. 5.4 Conclusion
8. 5.5 Exercices
11. Chapitre 6 Modéliser et afficher les utilisateurs, partie I
12. 1. 6.1 Modèle utilisateur
2. 1. 6.1.1 Migrations de la base de données
2. 6.1.2 Le fichier modèle
3. 1. Annotation des modèles
2. Attributs accessibles
4. 6.1.3 Créer des objets Utilisateur
5. 6.1.4 Recherche dans les objets Utilisateurs
6. 6.1.5 Actualisation des objets Utilisateurs
3. 6.2 Validations utilisateur
4. 1. 6.2.1 Valider l'existence
2. 6.2.2 Valider la longueur
3. 6.2.3 Valider le format
4. 6.2.4 Valider l'unicité
5. 1. L'avertissement d'unicité
5. 6.3 Afficher les utilisateurs
6. 1. 6.3.1 Débuggage et environnements Rails
2. 6.3.2 Modèle, Vue et Contrôleur Utilisateur
3. 6.3.3 Ressource Utilisateurs
4. 1. Paramètres pour le déboggage
7. 6.4 Conclusion
8. 6.5 Exercices
13. Chapitre 7 Modéliser et afficher les utilisateurs, partie II
14. 1. 7.1 Mots de passe non sécurisés
2. 1. 7.1.1 Valider le mot de passe
2. 7.1.2 Migrer un mot de passe
3. 7.1.3 Fonction de rappel dans l'Active Record
3. 7.2 Sécuriser les mots de passe
4. 1. 7.2.1 Test de mot de passe sécurisé
2. 7.2.2 Un peu de théorie sur la sécurisation des mots de passe
3. 7.2.3 Implémenter la méthode has_password?
4. 7.2.4 Méthode d'authentification
5. 7.3 Meilleures vues d'utilisateurs
6. 1. 7.3.1 Tester la page de l'utilisateur (avec factories)
2. 7.3.2 Un nom et un Gravatar
3. 1. Un « helper » de Gravatar
4. 7.3.3 Une barre utilisateur latérale
7. 7.4 Conclusion
8. 1. 7.4.1 Dépôt Git
2. 7.4.2 Déploiement Heroku
9. 7.5 Exercices
15. Chapitre 8 Inscription
16. 1. 8.1 Formulaire d'inscription
2. 1. 8.1.1 Utiliser form_for
2. 8.1.2 Le formulaire HTML
3. 8.2 Échec de l'inscription
4. 1. 8.2.1 Test de l'échec
2. 8.2.2 Un formulaire fonctionnel
3. 8.2.3 Inscription : messages d'erreur
4. 8.2.4 Filtrer les paramètres d'identification
5. 8.3 Succès de l'inscription
6. 1. 8.3.1 Tester le succès de l'inscription
2. 8.3.2 Le formulaire d'inscription finalisé
3. 8.3.3 Le message « flash »
4. 8.3.4 La première inscription
7. 8.4 Test d'intégration RSpec
8. 1. 8.4.1 Tests d'intégration avec les styles
2. 8.4.2 Un échec d'inscription ne devrait pas créer un nouvel utilisateur
3. 8.4.3 Le succès d'une inscription devrait créer un nouvel utilisateur
9. 8.5 Conclusion
10. 8.6 Exercices
17. Chapitre 9 Connexion, déconnexion
18. 1. 9.1 Les sessions
2. 1. 9.1.1 Le contrôleur de session
2. 9.1.2 Formulaire d'identification
3. 9.2 Échec de l'identification
4. 1. 9.2.1 Examen de la soumission du formulaire
2. 9.2.2 Échec de l'identification (test et code)
5. 9.3 Succès de l'identification
6. 1. 9.3.1 L'action create finalisée
2. 9.3.2 Se souvenir de moi
3. 9.3.3 Utilisateur courant
7. 9.4 Déconnexion
8. 1. 9.4.1 Détruire la session
2. 9.4.2 Connexion à l'inscription
3. 9.4.3 Changement des liens de la mise en page
4. 9.4.4 Test d'intégration de l'identification/déconnexion
9. 9.5 Conclusion
10. 9.6 Exercices
19. Chapitre 10 Actualiser, afficher et supprimer des utilisateurs
20. 1. 10.1 Actualiser l'utilisateur
2. 1. 10.1.1 Formulaire de modification
2. 10.1.2 Permettre les modifications
3. 10.2 Protéger les pages
4. 1. 10.2.1 Utilisateurs identifiés requis
2. 10.2.2 Nécessité du bon utilisateur
3. 10.2.3 Redirection conviviale
5. 10.3 Afficher les utilisateurs
6. 1. 10.3.1 Liste des utilisateurs
2. 10.3.2 Exemples d'utilisateurs
3. 10.3.3 Pagination
4. 1. Test de la pagination
5. 10.3.4 Restructuration des partiels
7. 10.4 Supprimer des utilisateurs
8. 1. 10.4.1 Utilisateurs administrateurs
2. 1. Révision de attr_accessible
3. 10.4.2 L'action destroy (« supprimer »)
9. 10.5 Conclusion
10. 10.6 Exercices
21. Chapitre 11 Micro-messages d'utilisateurs
22. 1. 11.1 Le modèle Micropost (« Micro-message »)
2. 1. 11.1.1 Le modèle initial
2. 1. Attributs accessibles
3. 11.1.2 Associations Utilisateur/micro-messages
4. 11.1.3 Affinements du micro-message
5. 1. Portée par défaut
2. Dépendances de la suppression
6. 11.1.4 Validations du micro-message
3. 11.2 Afficher les micro-messages
4. 1. 11.2.1 Etoffement de la page de l'utilisateur
2. 11.2.2 Exemples de micro-messages
5. 11.3 Manipuler les micro-messages
6. 1. 11.3.1 Contrôle de l'accès
2. 11.3.2 Créer des micro-messages
3. 11.3.3 Une proto-alimentation
4. 11.3.4 Supprimer des micro-messages
5. 11.3.5 Test de la nouvelle page d'accueil
7. 11.4 Conclusion
8. 11.5 Exercices
23. Chapitre 12 Suivi des utilisateurs
24. 1. 12.1 Le modèle Relation (Relationship model)
2. 1. 12.1.1 Un problème du modèle de données (et sa solution)
2. 12.1.2 Associations Utilisateur/Relations
3. 12.1.3 Validations
4. 12.1.4 Auteurs suivis
5. 12.1.5 Les Lecteurs
3. 12.2 Une interface web pour les auteurs et les lecteurs
4. 1. 12.2.1 Exemple de donnée de suivi
2. 12.2.2 Statistiques et formulaire de suivi
3. 12.2.3 Pages d'auteurs suivis et de lecteurs
4. 12.2.4 Un bouton de suivi standard
5. 12.2.5 Un bouton fonctionnant avec Ajax
5. 12.3 L'état de l'alimentation
6. 1. 12.3.1 Motivation et stratégie
2. 12.3.2 Une première implémentation de peuplement
3. 12.3.3 Champs d'application, sous-sélections et lambda
4. 12.3.4 Nouvel état de l'alimentation
7. 12.4 Conclusion
8. 1. 12.4.1 Extensions de l'application exemple
2. 1. Réponses
2. Notification
3. Notifications aux lecteurs
4. Rappel du mot de passe
5. Confirmation d'inscription
6. Alimentation RSS
7. REST API
8. Recherche
3. 12.4.2 Guide vers d'autres ressources
9. 12.5 Exercices

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

Permission est accordée, à titre gratuit, à toute personne obtenant


une copie de ce logiciel et la documentation associée, pour faire des
modification dans le logiciel sans restriction et sans limitation des
droits d’utiliser, copier, modifier, fusionner, publier, distribuer,
concéder sous licence, et / ou de vendre les copies du Logiciel, et à
autoriser les personnes auxquelles le Logiciel est meublé de le faire,
sous réserve des conditions suivantes:

L’avis de copyright ci-dessus et cette autorisation doit être inclus


dans toutes les copies ou parties substantielles du Logiciel.

LE LOGICIEL EST FOURNI «TEL QUEL», SANS GARANTIE D’AUCUNE SORTE,


EXPLICITE OU IMPLICITE, Y COMPRIS, MAIS SANS S’Y LIMITER, LES
GARANTIES DE QUALITÉ MARCHANDE, ADAPTATION À UN USAGE PARTICULIER ET
D’ABSENCE DE CONTREFAÇON. EN AUCUN CAS LES AUTEURS OU TITULAIRES DU
ETRE TENU RESPONSABLE DE TOUT DOMMAGE, RÉCLAMATION OU AUTRES
RESPONSABILITÉ, SOIT DANS UNE ACTION DE CONTRAT, UN TORT OU AUTRE,
PROVENANT DE, DE OU EN RELATION AVEC LE LOGICIEL OU L’UTILISATION OU
DE TRANSACTIONS AUTRES LE LOGICIEL.

/*
* ------------------------------------------------------------
* "LA LICENCE BEERWARE" (Révision 42) :
* Michael Hartl a écrit ce code. Aussi longtemps que vous
* conservez cette note, vous pouvez faire ce que vous voulez
* de ce travail. Si nous nous rencontrons un jour, et que vous
* pensez que ce travail en vaut la peine, vous pourrez me
* payer une bière en retour.
* ------------------------------------------------------------
*/

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.1 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.
Illustration 12.1: Maquette de la page de profil de l'utilisateur courant. (taille normale)

Illustration 12.2: Maquette de la recherche d'un utilisateur à suivre. (taille normale)


Illustration 12.3: Maquette du profil d'un autre utilisateur, avec le bouton de suivi. (taille normale)

Illustration 12.4: Maquette du profil avec un bouton pour arrêter de suivre l'utilisateur et incrémentation du nombre de
lecteurs. (taille normale)
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. (taille normale)

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 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.2 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 connexion3) entre deux
utilisateurs. Un utilisateur, donc, « possèdes plusieurs :relations » (has_many :relationships), et possèdent plusieurs auteurs suivis
(following) et plusieurs lecteurs (followers) à 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).4 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. (taille normale)

Pour commencer l'implémentation, nous générons d'abord un modèle `Relationship` (Relations) comme suit :
$ rails generate model Relationship follower_id:integer 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.

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

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
.
.
.
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).5 Dans le cas présent, bien que nous traitions encore les utilisateurs, ils sont 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.6

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

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.

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

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


.
.
.
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).7 Nous allons aussi ajouter une méthode booléen associée following? pour tester si un utilisateur en suit un
autre.8 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

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

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

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

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

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

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é. (taille normale)

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
.
.
.
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 lecteurs" 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",
: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.
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 => "example@railstutorial.org",
: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,
: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 :
$ 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

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

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


get :home
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")
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">
<%= 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).

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. (taille normale)

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.


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="relationship[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 %>
</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(@user), @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). (taille normale)

Illustration 12.13: Un profil d'utilisateur avec un bouton « ne plus suivre » (/users/6). (taille normale)

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

Illustration 12.14: Une maquette de la page des auteurs suivis par l'utilisateur courant. (taille normale)
Illustration 12.15: Une maquette de la page des lecteurs de l'utilisateur courant. (taille normale)

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

Extrait 12.28. Test pour les actions following (auteurs suivis) et followers (lecteurs).
spec/controllers/users_controller_spec.rb

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

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 suivis 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(@user), @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]

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. (taille normale)

Illustration 12.17: Afficher les lecteurs de l'utilisateur courant. (taille normale)

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


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.


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

… et Rails automagiquement utilise Ajax.14 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_relationship" 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 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. 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.15 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.16 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).

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


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

Illustration 12.18: Une maquette de la page d'accueil de l'utilisateur avec un état de l'alimentation (version anglaise). (taille normale)

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


Micropost.should respond_to(:from_users_followed_by)
end

it "devrait inclure les micro-messages des utilisateurs suivis" do


Micropost.from_users_followed_by(@user).should include(@other_post)
end

it "devrait inclure les propres micro-messages de l'utilisateur" do


Micropost.from_users_followed_by(@user).should include(@user_post)
end

it "ne devrait pas inclure les micro-messages des utilisateurs 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.

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'utilisateur" do


@user.feed.should include(@mp1)
@user.feed.should include(@mp2)
end

it "ne devrait pas inclure les micro-messages d'un utilisateur 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 utilisateurs suivis" do


followed = Factory(:user, :email => Factory.next(:email))
mp3 = Factory(:micropost, :user => followed)
@user.follow!(followed)
@user.feed.should include(mp3)
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>

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.17 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 :18

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

>> User.first.following.map(&:id)
=> [4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 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 :


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

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

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

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.

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 relationships
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.21

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 suivis par un utilisateur donné.


scope :from_users_followed_by, lambda { |user| followed_by(user) }

private

# Renvoie une condition SQL pour les utilisateurs suivis par l'utilisateur donné.
# 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.22

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.23
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.24

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
Illustration 12.20: La page d'accueil avec un état d'alimentation fonctionnel (version anglaise). (taille normale)

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

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 une coup d'œil aux archives Railscasts pour voir si Ryan Bates n'aurait pas déjà couvert le sujet.25 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 le 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.

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.

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.26 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.
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
@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

« Chapitre 11 Micro-messages d'utilisateur

1. Les photographies des maquettes viennent de http://www.flickr.com/photos/john_lustig/2518452221/ et


http://www.flickr.com/photos/30775272@N05/2884963755/. ↑
2. Pour la simplicité, l'illustration 12.6 supprime la colonne id de la table following. ↑
3. 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 ») ↑
4. En effet, cette construction est tellement caractéristique de Rails que le fameux programmeur Rails Josh Susser l'utilise comme
nom de son blog. ↑
5. 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). ↑
6. 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. ↑
7. 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. ↑
8. 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. ↑
9. La méthode authenticate_with_salt est incluse simplement en vous orientant à l'intérieur du fichier du modèle User. ↑
10. 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!. ↑
11. 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. ↑
12. Tout dans l'extrait 12.28 a été couvert quelque part dans ce tutoriel, donc c'est un bon exercice de lire ce code. ↑
13. 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. ↑
14. 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é. ↑
15. À 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. ↑
16. Il n'y a pas de relation entre ce respond_to et le respond_to utilisé dans les exemples RSpec. ↑
17. 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. ↑
18. 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 ? ↑
19. 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. ↑
20. 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. ↑
21. 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 ». ↑
22. 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. ↑
23. 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. ↑
24. 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). ↑
25. 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 ». ↑
26. 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. ↑

Vous aimerez peut-être aussi