Tutoriel Ruby on Rails

Apprendre Rails par l'exemple

Michael Hartl

Contenu

  1. Chapitre 1 De zéro au déploiement
    1. 1.1 Introduction
      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
    2. 1.2 Debout et au boulot
      1. 1.2.1 Environnements de développement
        1. IDEs
        2. Éditeurs de texte et lignes de commande
        3. Navigateurs
        4. Note à propos des outils
      2. 1.2.2 Ruby, RubyGems, Rails, et Git
        1. Installation de Rails (Windows)
        2. Installer Git
        3. Installer Ruby
        4. Installer RubyGems
        5. Installer Rails
      3. 1.2.3 La première application
      4. 1.2.4 Bundler
      5. 1.2.5 Le serveur rails (rails server)
      6. 1.2.6 Modèles-Vue-Contrôleur (MVC)
    3. 1.3 Contrôle de versions avec Git
      1. 1.3.1 Installation et réglages
        1. Initialisation des réglages système
        2. Initialisation des réglages du dépôt (repository)
      2. 1.3.2 Ajout et mandat de dépôt
      3. 1.3.3 Qu'est-ce que Git peut faire de bien pour vous ?
      4. 1.3.4 GitHub
      5. 1.3.5 Branch, edit, commit, merge
        1. Branch
        2. Edit
        3. Commit
        4. Merge
        5. Push
    4. 1.4 Déploiement
      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
    5. 1.5 Conclusion
  2. Chapitre 2 Une application démo
    1. 2.1 Planifier l'application
      1. 2.1.1 Modéliser les utilisateurs
      2. 2.1.2 Modéliser les micro-messages
    2. 2.2 La ressource Utilisateurs (Users)
      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)
    3. 2.3 La ressource Micro-messages (Microposts)
      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
    4. 2.4 Conclusion
  3. Chapitre 3 Pages statiques courantes
    1. 3.1 Pages statiques
      1. 3.1.1 Pages HTML statiques
      2. 3.1.2 Les pages statiques avec Rails
    2. 3.2 Premiers tests
      1. 3.2.1 Outils de test
        1. Auto-test
      2. 3.2.2 TDD : Rouge, Vert, Refactor
        1. Spork
        2. Rouge
        3. Vert
        4. Refactor
    3. 3.3 Pages (un peu) dynamiques
      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
    4. 3.4 Conclusion
    5. 3.5 Exercices
  4. Chapitre 4 Rails au goût Ruby
    1. 4.1 Motivation
      1. 4.1.1 Un helper pour le titre
      2. 4.1.2 Feuilles de styles (CSS — Cascading Style Sheets)
    2. 4.2 Chaines de caractères et méthodes
      1. 4.2.1 Commentaires
      2. 4.2.2 Chaines de caractères
        1. Impression
        2. Chaines de caractères « apostrophées »
      3. 4.2.3 Objets et passage de message
      4. 4.2.4 Définition de méthode
      5. 4.2.5 Retour à l'« helper » de titre
    3. 4.3 Autres structures de données
      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
    4. 4.4 Classes Ruby
      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
    5. 4.5 Exercices
  5. Chapitre 5 Poursuivre la mise en page
    1. 5.1 Ajout de structure
      1. 5.1.1 Navigation du site
      2. 5.1.2 Personnalisation CSS
      3. 5.1.3 Partiels
    2. 5.2 Liens pour la mise en page
      1. 5.2.1 Test d'intégration
      2. 5.2.2 Routes Rails
      3. 5.2.3 Nommer les routes
    3. 5.3 Inscription de l'utilisateur : une première étape
      1. 5.3.1 Contrôleur Utilisateur
      2. 5.3.2 URL d'inscription
    4. 5.4 Conclusion
    5. 5.5 Exercices
  6. Chapitre 6 Modéliser et afficher les utilisateurs, partie I
    1. 6.1 Modèle utilisateur
      1. 6.1.1 Migrations de la base de données
      2. 6.1.2 Le fichier modèle
        1. Annotation des modèles
        2. Attributs accessibles
      3. 6.1.3 Créer des objets Utilisateur
      4. 6.1.4 Recherche dans les objets Utilisateurs
      5. 6.1.5 Actualisation des objets Utilisateurs
    2. 6.2 Validations utilisateur
      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é
        1. L'avertissement d'unicité
    3. 6.3 Afficher les utilisateurs
      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
        1. Paramètres pour le déboggage
    4. 6.4 Conclusion
    5. 6.5 Exercices
  7. Chapitre 7 Modéliser et afficher les utilisateurs, partie II
    1. 7.1 Mots de passe non sécurisés
      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
    2. 7.2 Sécuriser les mots de passe
      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
    3. 7.3 Meilleures vues d'utilisateurs
      1. 7.3.1 Tester la page de l'utilisateur (avec factories)
      2. 7.3.2 Un nom et un Gravatar
        1. Un « helper » de Gravatar
      3. 7.3.3 Une barre utilisateur latérale
    4. 7.4 Conclusion
      1. 7.4.1 Dépôt Git
      2. 7.4.2 Déploiement Heroku
    5. 7.5 Exercices
  8. Chapitre 8 Inscription
    1. 8.1 Formulaire d'inscription
      1. 8.1.1 Utiliser form_for
      2. 8.1.2 Le formulaire HTML
    2. 8.2 Échec de l'inscription
      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
    3. 8.3 Succès de l'inscription
      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
    4. 8.4 Test d'intégration RSpec
      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
    5. 8.5 Conclusion
    6. 8.6 Exercices
  9. Chapitre 9 Connexion, déconnexion
    1. 9.1 Les sessions
      1. 9.1.1 Le contrôleur de session
      2. 9.1.2 Formulaire d'identification
    2. 9.2 Échec de l'identification
      1. 9.2.1 Examen de la soumission du formulaire
      2. 9.2.2 Échec de l'identification (test et code)
    3. 9.3 Succès de l'identification
      1. 9.3.1 L'action create finalisée
      2. 9.3.2 Se souvenir de moi
      3. 9.3.3 Utilisateur courant
    4. 9.4 Déconnexion
      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
    5. 9.5 Conclusion
    6. 9.6 Exercices
  10. Chapitre 10 Actualiser, afficher et supprimer des utilisateurs
    1. 10.1 Actualiser l'utilisateur
      1. 10.1.1 Formulaire de modification
      2. 10.1.2 Permettre les modifications
    2. 10.2 Protéger les pages
      1. 10.2.1 Utilisateurs identifiés requis
      2. 10.2.2 Nécessité du bon utilisateur
      3. 10.2.3 Redirection conviviale
    3. 10.3 Afficher les utilisateurs
      1. 10.3.1 Liste des utilisateurs
      2. 10.3.2 Exemples d'utilisateurs
      3. 10.3.3 Pagination
        1. Test de la pagination
      4. 10.3.4 Restructuration des partiels
    4. 10.4 Supprimer des utilisateurs
      1. 10.4.1 Utilisateurs administrateurs
        1. Révision de attr_accessible
      2. 10.4.2 L'action destroy (« supprimer »)
    5. 10.5 Conclusion
    6. 10.6 Exercices
  11. Chapitre 11 Micro-messages d'utilisateurs
    1. 11.1 Le modèle Micropost (« Micro-message »)
      1. 11.1.1 Le modèle initial
        1. Attributs accessibles
      2. 11.1.2 Associations Utilisateur/micro-messages
      3. 11.1.3 Affinements du micro-message
        1. Portée par défaut
        2. Dépendances de la suppression
      4. 11.1.4 Validations du micro-message
    2. 11.2 Afficher les micro-messages
      1. 11.2.1 Etoffement de la page de l'utilisateur
      2. 11.2.2 Exemples de micro-messages
    3. 11.3 Manipuler les micro-messages
      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
    4. 11.4 Conclusion
    5. 11.5 Exercices
  12. Chapitre 12 Suivi des utilisateurs
    1. 12.1 Le modèle Relation (Relationship model)
      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
    2. 12.2 Une interface web pour les auteurs et les lecteurs
      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
    3. 12.3 L'état de l'alimentation
      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
    4. 12.4 Conclusion
      1. 12.4.1 Extensions de l'application exemple
        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
      2. 12.4.2 Guide vers d'autres ressources
    5. 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 10Actualiser, afficher et supprimer des utilisateurs

Dans ce chapitre, nous allons compléter les actions REST de la ressource Users (« utilisateurs ») (table 6.2) en ajoutant les action edit (« éditer »), update (« actualiser »), index (« lister »), et destroy (« détruire »). Nous commencerons par donner la possibilité aux utilisateurs d'actualiser leur profil, ce qui donnera l'occasion de produire un modèle sécurisé (rendu possible par le code d'authentification dans le chapitre 9). Puis nous ferons un listing de tous les utilisateurs (nécessitant l'identification de l'utilisateur) ce qui nous amènera à introduire la notion de « données exemple » et de pagination. Enfin, nous ajouterons la possibilité de supprimer des utilisateurs en les retirant de la base de données. Puisque nous ne pouvons permettre à n'importe quel utilisateur d'avoir de tels pouvoirs, nous profiterons de l'occasion pour créer la classe privilégiée d'utilisateur-administrateur (admins).

Avant de nous mettre au travail, commençons par initier une nouvelle branche développement : updating-users :

$ git checkout -b updating-users

10.1 Actualisation

La façon d'éditer les informations de l'utilisateur est très proche de celle pour les créer (chapitre 8). Au lieu d'une action new rendant une vue pour des nouveaux utilisateurs, nous avons une action edit rendant une vue pour éditer les utilisateurs ; à la place d'une action create répondant à une requête POST, nous avons une action update répondant à une requête PUT (Box 3.1). La différence notoire est que, entendu que n'importe qui peut s'inscrire, seul l'utilisateur courant doit être en mesure d'actualiser ses informations. Cela signifie que nous devons renforcer le contrôle d'accès afin que seuls l'utilisateur autorisé puisse accomplir les actions d'édition et d'actualisation ; le mécanisme d'authentification du chapitre 9 va nous permettre d'utiliser un before filter (un filtre « passe-avant ») pour remplir cette fonction.

10.1.1 Formulaire d'édition

Nous commençons avec des tests pour le formulaire d'édition, dont la maquette apparait dans l'illustration 10.1.1 Les deux sont analogues aux tests que nous avons vus pour la page de nouvel utilisateur (extrait 8.1), qui vérifie la réponse appropriée et le titre ; le troisième test s'assure qu'il y ait un lien pour éditer l'image Gravatar de l'utilisateur (section 7.3.2). Si vous fouinez dans le site de Gravatar, vous verrez que la page pour ajouter ou modifier l'image est (ce qui peut sembler quelque peu étrange) à l'adresse http://gravatar.com/emails, donc nous testons la page edit page pour un lien possédant cette URL.2 Le résultat est montré dans extrait 10.1.

edit_user_mockup
Illustration 10.1: Maquette de la page d'édition de l'utilisateur (version anglaise). (taille normale)
Extrait 10.1. Tests pour l'action edit de l'utilisateur.
spec/controllers/users_controller_spec.rb
require 'spec_helper'

describe UsersController do
  render_views
  .
  .
  .
  describe "GET 'edit'" do

    before(:each) do
      @user = Factory(:user)
      test_sign_in(@user)
    end

    it "devrait réussir" do
      get :edit, :id => @user
      response.should be_success
    end

    it "devrait avoir le bon titre" do
      get :edit, :id => @user
      response.should have_selector("title", :content => "Édition profil")
    end

    it "devrait avoir un lien pour changer l'image Gravatar" do
      get :edit, :id => @user
      gravatar_url = "http://gravatar.com/emails"
      response.should have_selector("a", :href => gravatar_url,
                                         :content => "changer")
    end
  end
end

Ici, nous nous sommes assurés d'utiliser la méthode test_sign_in(@user) pour s'identifier en tant qu'utilisateur, anticipant par là-même l'accès protégé à la page d'édition (section 10.2). Dans le cas contraire, ces tests seraient invalides dès que nous implémenterions notre code d'authentification.

Notez, en observant la table 6.2 que l'URL correct pour la page d'édition d'un utilisateur est /users/1/edit (qui suppose que l'identifiant — id — de l'utilisateur est 1). Souvenez-vous que l'identifiant — id — de l'utilisateur se trouve dans la variable params[:id], ce qui signifie que nous pouvons trouver l'utilisateur avec le code du extrait 10.2. Il utilise find (trouver) pour trouver l'utilisateur concerné dans la base de données, et renseigne alors la variable titre — @titre.

Extrait 10.2. L'action edit de l'utilisateur.
app/controllers/users_controller.rb
class UsersController < ApplicationController
  .
  .
  .
  def edit
    @user = User.find(params[:id])
    @titre = "Édition profil"
  end
end

Pour que le test soit valide, il faut créer la vue d'édition, montrée dans l'extrait 10.3. Remarquez comme ce code s'apparente à celui de la vue du nouvel utilisateur de l'extrait 8.2 ; la grande similitude suggère de reconstruire la code à l'aide des partiels, ce que vous pourrez faire à titre d'exercice (section 10.6).

Extrait 10.3. La vue d'édition de l'utilisateur.
app/views/users/edit.html.erb
<h1>Edition du profil</h1>

<%= form_for(@user) do |f| %>
  <%= render 'shared/error_messages', :object => f.object %>
  <div class="field">
    <%= f.label :name, "Nom" %><br />
    <%= f.text_field :name %>
  </div>
  <div class="field">
    <%= f.label :email, "eMail" %><br />
    <%= f.text_field :email %>
  </div>
  <div class="field">
    <%= f.label :password, "Mot de passe" %><br />
    <%= f.password_field :password %>
  </div>
  <div class="field">
    <%= f.label :password_confirmation, "Confirmation mot de passe", "Confirmation" %><br />
    <%= f.password_field :password_confirmation %>
  </div>
  <div class="actions">
    <%= f.submit "Update" %>
  </div>
<% end %>

<div>
  <%= gravatar_for @user %>
  <a href="http://gravatar.com/emails">changer</a>
</div>

Ici, nous avons ré-utilisé le partiel partagé error_messages introduit à la section 8.2.3.

Nous pouvons nous rappeler du extrait 8.8 que le partiel « error-messages » fait référence explicitement à la variable d'instance @user. Dans le cas présent, nous sommes censés avoir une variable @user, mais dans le but d'obtenir vraiment un partiel partagé, nous ne devons pas le rendre dépendant de l'existence de cette variable. La solution consiste à passer au partiel, en paramètre, un objet correspondant à la variable formulaire f :

<%= render 'shared/error_messages', :object => f.object %>

Cela crée une variable appelée object dans la partielle, qui peut être alors utilisée pour générer les messages d'erreur, comme le montre le extrait 10.4 (notez l'élégance de la chaine de méthodes qui produit une bonne version du nom de l'objet ; voyez l'entrée de l'API Rails pour, disons, humanize, pour avoir une idée des nombreuses méthodes utilitaires de Rails).

Extrait 10.4. Actualisation du partiel des messages d'erreur de l'extrait 8.9 pour qu'il fonctionne avec d'autres objets.
app/views/shared/_error_messages.html.erb
<% if object.errors.any? %>
  <div id="error_explanation">
    <h2><%= pluralize(object.errors.count, "error") %> 
        prohibited this <%= object.class.to_s.underscore.humanize.downcase %> 
        from being saved:</h2>
    <p>There were problems with the following fields:</p>
    <ul>
    <% object.errors.full_messages.each do |msg| %>
      <li><%= msg %></li>
    <% end %>
    </ul>
  </div>
<% end %>

Pendant que nous y sommes, nous allons actualiser le formulaire d'inscription avec ce code (extrait 10.5).

Extrait 10.5. Actualisation du rendu des messages d'erreur de l'inscription des utilisateurs.
app/views/users/new.html.erb
<h1>Inscription</h1>

<%= form_for(@user) do |f| %>
  <%= render 'shared/error_messages', :object => f.object %>
  .
  .
  .
<% end %>

Nous allons également ajouter un lien dans la partie navigation du site pour la page d'édition de l'utilisateur (que nous appellerons « Profil »), sur la base de la maquette de l'illustration 10.23 et montré dans l'extrait 10.6.

profile_settings_link_mockup
Figure 10.2: Maquette de la page d'édition de l'utilisateur avec un lien « Profil » (version anglaise). (taille normale)
Extrait 10.6. Ajout d'un lien Profil.
app/views/layouts/_header.html.erb
<header>
  <%= link_to logo, root_path %>
  <nav class="round">
    <ul>
      <li><%= link_to "Accueil", root_path %></li>
      <% if signed_in? %>
      <li><%= link_to "Profil", edit_user_path(current_user) %></li>
      <% end %>
      .
      .
      .
    </ul>
  </nav>
</header>

Ici nous utilisons la route de nom edit_user_path de la table 6.2, avec la méthode pratique de l'helper current_user définie dans le extrait 9.16.

Avec la variable d'instance @user de l'extrait 10.2, les tests du extrait 10.1 réussiront. Comme cela est montré dans l'illustration 10.3, le rendu de la page edit, bien qu'elle ne fonctionne pas encore.

edit_user_settings
Figure 10.3: Edition des caractéristiques de l'utilisateur (/users/1/edit). (taille normale)

En regardant le code HTML source de l'illustration 10.3, nous voyons la balise de formulaire attendue (extrait 10.7).

Extrait 10.7. Code HTML pour le formulaire d'édition défini dans l'extrait 10.3 et affiché dans l'illustration 10.3.
<form action="/users/1" class="edit_user" id="edit_user_1" method="post">
  <input name="_method" type="hidden" value="put" />
  .
  .
  .
</form>

Notez ici le champ caché (input class="hidden")

<input name="_method" type="hidden" value="put" />

Puisque les navigateurs internet ne peuvent pas, de façon native, envoyer des requêtes PUT (requises par les conventions REST conventions de la table 6.2), Rails la simule avec une requête POST et un champ caché (hidden input).4

Il y a une autre subtilité à noter ici : le code form_for(@user) dans l'extrait 10.3 est très exactement identique au code dans l'extrait 8.2 — donc comment fait Rails pour utiliser la requête POST pour de nouveaux utilisateurs et la requête PUT pour éditer ces utilisateurs ? La réponse est qu'il est possible de dire si un utilisateur est nouveau ou s'il existe déjà dans la base de données via la méthode booléenne new_record? (nouvel_enregistrement) (que nous avons vu brièvement dans l'extrait 7.10) :

$ rails console
>> User.new.new_record?
=> true
>> User.first.new_record?
=> false

Quand nous construisons un formulaire en utilisant form_for(@user), Rails utilise la requête POST si @user.new_record? est vrai (true) et la requête PUT si la méthode renvoie false.

10.1.2 Permettre l'édition

Bien que le formulaire d'édition ne fonctionne pas encore, nous avons implémenté le téléchargement de l'image Gravatar, donc il est déjà possible en cliquant sur le lien « changer » de l'illustration 10.3, comme montré dans l'illustration 10.4. Mettons en place les autres fonctionnalités de l'édition de l'utilisateur.

gravatar_cropper
Illustration 10.4: L'interface d'ajustement de Gravatar, avec l'image d'un mec(taille normale)

Les tests pour l'action update (actualisation) sont similaires à ceux de l'action create (Créer). En particulier, nous testons l'échec de l'actualisation et son succès (extrait 10.8) (ça fait beaucoup de code ; voyons si vous pouvez l'analyser en vous référant aux tests du chapitre 8.)

Extrait 10.8. Tests pour l'action update de l'utilisateur.
spec/controllers/users_controller_spec.rb
describe UsersController do
  render_views
  .
  .
  .
  describe "PUT 'update'" do

    before(:each) do
      @user = Factory(:user)
      test_sign_in(@user)
    end

    describe "Échec" do

      before(:each) do
        @attr = { :email => "", :nom => "", :password => "",
                  :password_confirmation => "" }
      end

      it "devrait retourner la page d'édition" do
        put :update, :id => @user, :user => @attr
        response.should render_template('edit')
      end

      it "devrait avoir le bon titre" do
        put :update, :id => @user, :user => @attr
        response.should have_selector("title", :content => "Édition profil")
      end
    end

    describe "succès" do

      before(:each) do
        @attr = { :nom => "New Name", :email => "user@example.org",
                  :password => "barbaz", :password_confirmation => "barbaz" }
      end

      it "devrait modifier les caractéristiques de l'utilisateur" do
        put :update, :id => @user, :user => @attr
        @user.reload
        @user.nom.should  == @attr[:nom]
        @user.email.should == @attr[:email]
      end

      it "devrait rediriger vers la page d'affichage de l'utilisateur" do
        put :update, :id => @user, :user => @attr
        response.should redirect_to(user_path(@user))
      end

      it "devrait afficher un message flash" do
        put :update, :id => @user, :user => @attr
        flash[:success].should =~ /actualisé/
      end
    end
  end
end

La seule nouveauté ici est la méthode reload (rechargement) qui apparait dans le test pour le changement des caractéristiques de l'utilisateur :

it "devrait modifier les caractéristiques de l'utilisateur" do
  @user.reload
  @user.nom.should  == @attr[:nom]
  @user.email.should == @attr[:email]
end

Ce code recharge la variable @user de la base de données (de test) en utilisant @user.reload, et vérifie alors que le nouveau nom de l'utilisateur et son adresse mail correspondent aux valeurs de la table @attr.

L'action update (actualiser) nécessaire pour réussir les tests de l'extrait 10.8 est similaire au formulaire final de l'action create (créer) (extrait 9.24), comme nous l'avons vu dans l'extrait 10.9.

Extrait 10.9. L'action utilisateur update (actualiser).
app/controllers/users_controller.rb
class UsersController < ApplicationController
  .
  .
  .
  def update
    @user = User.find(params[:id])
    if @user.update_attributes(params[:user])
      flash[:success] = "Profil actualisé."
      redirect_to @user
    else
      @titre = "Édition profil"
      render 'edit'
    end
  end
end

Avec cela, la page d'édition de l'utilisateur devrait fonctionner. Dans la forme actuelle, chaque édition demande à l'utilisateur de confirmer le mot de passe à nouveau (comme requis par le champ de saisie vue dans l'illustration 10.3), ce qui rend les actualisations plus sécurisées, mais reste quelque peu contraignant.

10.2 Protection des pages

Bien que les actions edit (éditer) et update (actualiser) de la section 10.1 soient fonctionnelles, elles souffrent d'un vice de sécurité ridicule : elles permettent à n'importe qui (même des utilisateurs non identifiés) d'accéder à ces actions, et n'importe quel utilisateur identifié peut modifier les informations de n'importe quel autre utilisateur. Dans cette section, nous allons implémenter un modèle de sécurité qui requiert des utilisateurs qu'ils soient identifiés et qui les empêchent d'actualiser d'autres informations que les leurs. Les utilisateurs qui ne sont pas identifiés ou qui tentent d'atteindre des pages protégées seront redirigés vers la page d'identification, avec un message d'aide, comme le montre la maquette de l'illustration 10.5.

signin_page_protected_mockup
Figure 10.5: Maquette du résultat de la tentative d'accès à une page protégée (version anglaise) (taille normale)

10.2.1 L'identification requise de l'utilisateur

Puisque les restrictions de sécurité des actions edit et update sont identiques, nous les définirons dans un bloc RSpec describe unique. En commençant par vérifier que l'utilisateur est identifié, nos premiers tests vérifient qu'un utilisateur non identifié qui essaie d'utiliser de mauvaises actions soit simplement redirigé vers la page d'identification, comme nous pouvons le voir dans l'extrait 10.10.

Extrait 10.10. Premiers tests d'authentification.
spec/controllers/users_controller_spec.rb
describe UsersController do
  render_views
  .
  .
  .
  describe "authentification des pages edit/update" do

    before(:each) do
      @user = Factory(:user)
    end

    describe "pour un utilisateur non identifié" do

      it "devrait refuser l'acccès à l'action 'edit'" do
        get :edit, :id => @user
        response.should redirect_to(signin_path)
      end

      it "devrait refuser l'accès à l'action 'update'" do
        put :update, :id => @user, :user => {}
        response.should redirect_to(signin_path)
      end
    end
  end
end

Le code de l'application permettra de réussir ces tests en utilisant un filtre « passe-avant » (before filter), qui permet d'appeler une méthode particulière avant d'appeler les actions données. Dans ce cas, nous définissons une méthode authenticate et nous l'invoquons en utilisant before_filter :authenticate, comme montré dans le extrait 10.11.

Extrait 10.11. Ajout d'un filtre « passe-avant » authenticate.
app/controllers/users_controller.rb
class UsersController < ApplicationController
  before_filter :authenticate, :only => [:edit, :update]
  .
  .
  .
  private

    def authenticate
      deny_access unless signed_in?
    end
end

Par défaut, les filtres « passe-avant » s'applique à toute action du contrôleur, donc ici nous restreignons l'effet du filtre aux actions :edit et :update en renseignant l'attribut :only dans la table des options.

Ce code ne fonctionne pas encore, parce que la méthode deny_access (refuser l'accès) n'a pas été encore définie. Puisque le refus de l'accès est une partie de l'authentification, nous allons implémenter cette méthode dans l'helper du fichier Sessions du chapitre 9. Tout ce que fait deny_access, c'est placer un message dans flash[:notice] et rediriger vers la page d'identification (extrait 10.12).

Extrait 10.12. La méthode deny_access pour l'authentification de l'utilisateur.
app/helpers/sessions_helper.rb
module SessionsHelper
  .
  .
  .
  def deny_access
    redirect_to signin_path, :notice => "Merci de vous identifier pour rejoindre cette page."
  end
  .
  .
  .
end

Notez ici que le extrait 10.12 utilise un raccourci d'écriture pour renseigner l'attribut flash[:notice] en passant une table « options » à la fonction redirect_to. Le code dans le extrait 10.12 est équivalent à celui plus verbeux :

flash[:notice] = "Merci de vous identifier pour rejoindre cette page."
redirect_to signin_path

(La même syntaxe fonctionne pour la clé :error, mais pas pour la clé :success.)

Associée à :success et :error, la clé :notice complète notre triumvirat des styles flash, tous supportés nativement par les feuilles de styles Blueprint CSS. En se déconnectant et en essayant d'atteindre la page d'édition de l'utilisateur /users/1/edit, nous pouvons voir s'afficher la boite jaune du message "notice", comme dans l'illustration 10.6.

protected_sign_in
Illustration 10.6: Le formulaire d'identification après la tentative d'accès à une page protégée (version anglaise). (taille normale)

10.2.2 Nécessité du bon utilisateur

Bien entendu, qu'un utilisateur soit identifié n'est pas suffisant ; les utilisateurs ne doivent être autorisés qu'à modifier leur propres informations. Nous pouvons tester cela d'abord en identifiant un utilisateur incorrect puis en invoquant les actions edit et update (extrait 10.13). Notez que, puisque les utilisateurs ne devraient même pas essayer d'éditer le profil d'un autre utilisateur, nous ne les redirigerons pas vers la page d'identification mais vers l'accueil du site (l'url racine, root url).

Extrait 10.13. Test d'authentification pour les utilisateurs identifiés.
spec/controllers/users_controller_spec.rb
describe UsersController do
  render_views
  .
  .
  .
  describe "authentification pour les pages edit/update" do
    .
    .
    .
    describe "pour un utilisateur identifié" do

      before(:each) do
        wrong_user = Factory(:user, :email => "user@example.net")
        test_sign_in(wrong_user)
      end

      it "devrait correspondre à l'utilisateur à éditer" do
        get :edit, :id => @user
        response.should redirect_to(root_path)
      end

      it "devrait correspondre à l'utilisateur à actualiser" do
        put :update, :id => @user, :user => {}
        response.should redirect_to(root_path)
      end
    end
  end
end

Le code de l'application est simple : nous ajoutons un second filtre « passe-avant » pour appeler la méthode correct_user (que nous devons encore écrire), comme montré dans le extrait 10.14.

Extrait 10.14. Un filtre « passe-avant » correct_user pour protéger les pages d'édition (edit) et d'actualisation (update).
app/controllers/users_controller.rb
class UsersController < ApplicationController
  before_filter :authenticate, :only => [:edit, :update]
  before_filter :correct_user, :only => [:edit, :update]
  .
  .
  .
  def edit
    @titre = "Édition profil"
  end
  .
  .
  .
  private

    def authenticate
      deny_access unless signed_in?
    end

    def correct_user
      @user = User.find(params[:id])
      redirect_to(root_path) unless current_user?(@user)
    end
end

Ce code utilise la méthode current_user?, que nous définissons (comme la méthode deny_access) dans l'helper Sessions (extrait 10.15).

Extrait 10.15. La méthode current_user?.
app/helpers/sessions_helper.rb
module SessionsHelper
  .
  .
  .
  def current_user?(user)
    user == current_user
  end

  def deny_access
    redirect_to signin_path, :notice => "Merci de vous identifier pour rejoindre cette page."
  end

  private
    .
    .
    .
end

L'extrait 10.14 montre aussi l'actualisation de l'action edit. Auparavant, dans l'extrait 10.2, nous avions :

def edit
  @user = User.find(params[:id])
  @titre = "Édition profil"
end

Mais maintenant que le filtre « passe-avant » correct_user définit la variable @user nous pouvons l'omettre dans l'action edit (et, de la même manière, dans l'action update).

10.2.3 Redirection conviviale

Notre page de protection est complète, mais il demeure un défaut mineur : quand les utilisateurs tentent d'accéder à une page protégée, ils sont pour le moment redirigés à leur page de profil, sans considération pour la page qu'ils tentaient d'atteindre. En d'autres mots, si un utilisateur non identifié essaie de visiter sa page d'édition, après s'être identifié, il sera redirigé vers /users/1 (l'affichage de son profil) au lieu de /users/1/edit (sa page d'édition). Il serait plus convivial de les rediriger vers leur destination voulue.

La séquence de visite d'une certaine page — identification et redirection vers la page voulue — est le travail parfait pour un test d'intégration, donc élaborons-en un pour la redirection convivial :

$ rails generate integration_test friendly_forwarding

Le code apparait alors comme dans le extrait 10.16.

Extrait 10.16. Test d'intégration pour la redirection conviviale.
spec/requests/friendly_forwardings_spec.rb
require 'spec_helper'

describe "FriendlyForwardings" do

  it "devrait rediriger vers la page voulue après identification" do
    user = Factory(:user)
    visit edit_user_path(user)
    # Le test suit automatiquement la redirection vers la page d'identification.
    fill_in :email,    :with => user.email
    fill_in :password, :with => user.password
    click_button
    # Le test suit à nouveau la redirection, cette fois vers users/edit.
    response.should render_template('users/edit')
  end
end

(Comme indiqué dans les commentaires, le test d'intégration suit les redirections, donc tester que la réponse devrait rediriger (should redirect_to) vers telle ou telle URL ne fonctionnera pas. Je l'ai appris en en faisait l'expérience.)

Voyons maintenant l'implémentation.5 Dans le but de rediriger les utilisateurs vers leur destination désirée, nous avons besoin de consigner quelque part leur destination, et de les rediriger vers cette destination ensuite. Le mécanisme de consignation est une des facilités proposées par Rails avec la session, qui peut être pensé un peu comme l'instance de variable cookie de la section 9.3.2 qui expire automatiquement quand le navigateur est refermé.6 Nous utilisons aussi l'objet requête (request) pour obtenir la request_uri, par exemple l'URL de la page requise. Le code d'application résultant est montré dans le extrait 10.17.

Extrait 10.17. Code pour implémenter la redirection conviviale.
app/helpers/sessions_helper.rb
module SessionsHelper
  .
  .
  .
  def deny_access
    store_location
    redirect_to signin_path, :notice => "Please sign in to access this page."
  end

  def redirect_back_or(default)
    redirect_to(session[:return_to] || default)
    clear_return_to
  end

  private
    .
    .
    .
    def store_location
      session[:return_to] = request.fullpath
    end

    def clear_return_to
      session[:return_to] = nil
    end
end

Ici nous avons ajouté une ligne à la méthode deny_access, d'abord pour ajouter le chemin complet de la requête avec la méthode store_location et nous procédons ensuite comme avant. La méthode store_location place la requête URL dans la variable session avec pour clé :return_to. (Nous avons rendu les deux méthodes store_location et clear_return_to privées puisque elles ne sont jamais nécessaires en dehors de l'helper Sessions.)

Nous avons aussi défini la méthode redirect_back_or pour rediriger vers la requête URL si elle existe (si elle est définie), ou vers l'URL par défaut dans le cas contraire. Cette méthode est utilisé dans l'action create (« créer ») du contrôleur de Sessions pour procéder à la redirection en cas d'identification réussie (extrait 10.18).

Extrait 10.18. L'action create de Sessions avec une redirection conviviale.
app/controllers/sessions_controller.rb
class SessionsController < ApplicationController
  .
  .
  .
  def create
    user = User.authenticate(params[:session][:email],
                             params[:session][:password])
    if user.nil?
      flash.now[:error] = "Combinaison mail/mot de passe invalide."
      @titre = "Sign in"
      render 'new'
    else
      sign_in user
      redirect_back_or user
    end
  end
  .
  .
  .
end

Avec ça, le test d'intégration de la redirection conviviale dans le extrait 10.16 devrait réussir, et l'implémentation de l'authentification basique de l'utilisateur et de la protection de page est achevée.

10.3 Affichage des utilisateurs

Dans cette section nous allons ajouter la dernière action utilisateur, l'action index destinée à afficher tous les utilisateurs, pas seulement un. Chemin faisant, nous apprendrons à peupler une base de données avec des exemples d'utilisateurs et à paginer (paginating) la sortie pour l'utilisateur, pour que la page puisse s'adapter à l'affichage éventuel d'un très grand nombre d'utilisateurs. Une maquette du résultat — utilisateurs, liens de paginations, et un lien de navigation « Utilisateurs » — est montré dans l'illustration 10.7.7 Dans la section 10.4, nous ajouterons une interface administrateur à l'index de l'utilisateur pour empêcher les utilisateurs d'être détruits (ce qui peut être problématique).

user_index_mockup
Illustration 10.7: Maquette du listing de l'utilisateur, avec pagination et lien de navigation « Utilisateurs » (version anglaise). (taille normale)

10.3.1 Liste des utilisateurs

Bien que nous gardions visible la page d'affichage d'un utilisateur à tout visiteur du site, la liste de tous les utilisateurs sera réservée au seuls utilisateurs identifiés. Nos tests d'index le vérifieront, et vérifieront aussi que tous les utilisateurs soient listés pour un utilisateur identifié (extrait 10.19).

Extrait 10.19. Test pour la page d'index des utilisateurs.
spec/controllers/users_controller_spec.rb
require 'spec_helper'

describe UsersController do
  render_views

  describe "GET 'index'" do

    describe "pour utilisateur non identifiés" do
      it "devrait refuser l'accès" do
        get :index
        response.should redirect_to(signin_path)
        flash[:notice].should =~ /identifier/i
      end
    end

    describe "pour un utilisateur identifié" do

      before(:each) do
        @user = test_sign_in(Factory(:user))
        second = Factory(:user, :email => "another@example.com")
        third  = Factory(:user, :email => "another@example.net")

        @users = [@user, second, third]
      end

      it "devrait réussir" do
        get :index
        response.should be_success
      end

      it "devrait avoir le bon titre" do
        get :index
        response.should have_selector("title", :content => "Liste des utilisateurs")
      end

      it "devrait avoir un élément pour chaque utilisateur" do
        get :index
        @users.each do |user|
          response.should have_selector("li", :content => user.nom)
        end
      end
    end
  end
  .
  .
  .
end

Comme vous pouvez le voir, la méthode pour vérifier la page d'index consiste à créer trois utilisateurs d'usine (identifié pour le premier) et vérifier alors que la page d'index a un élément de liste (balise li) pour le nom de chacun d'eux.

Comme vous pouvez l'imaginer, le code de l'application utilise User.all pour faire une variable d'instance @users dans l'action index du contrôleur Users (extrait 10.20).

Extrait 10.20. L'action index de l'utilisateur.
app/controllers/users_controller.rb
class UsersController < ApplicationController
  before_filter :authenticate, :only => [:index, :edit, :update]
  .
  .
  .
  def index
    @titre = "Tous les utilisateurs"
    @users = User.all
  end

  def show
    @user = User.find(params[:id])
    @titre = @user.name
  end
  .
  .
  .
end

Notez que nous avons ajouté :index à la liste des contrôleurs protégés par le filtre « passe-avant » authenticate, ce faisant on obtient la réussite du premier test de l'extrait 10.19.

Pour faire la page voulue, nous avons besoin de faire une vue qui fait une itération sur les utilisateurs et entoure chacun d'eux d'une balise li. Nous faisons cela avec la méthode each (chaque), en affichant chaque nom et gravatar d'utilisateur, entourant l'ensemble dans une balise de liste non ordonnée (ul) (extrait 10.21).

Extrait 10.21. La vue de l'index utilisateur.
app/views/users/index.html.erb
<h1>Tous les utilisateurs</h1>

<ul class="users">
  <% @users.each do |user| %>
    <li>
      <%= gravatar_pour user, :size => 30 %>
      <%= link_to user.nom, user %>
    </li>
  <% end %>
</ul>

Nous ajoutons alors un peu de CSS pour le style (extrait 10.22).

Extrait 10.22. CSS pour l'index des utilisateurs.
public/stylesheets/custom.css
.
.
.
ul.users {
  margin-top: 1em;
}

.users li {
  list-style: none;
}

Enfin, nous ajouterons le liens « Utilisateurs » à l'entête de navigation du site (extrait 10.23). Cela introduit l'utilisation du nom de route users_path (chemins aux utilisateurs) de la table 6.2.

Extrait 10.23. Un lien à l'index utilisateur.
app/views/layouts/_header.html.erb
<header>
  <%= link_to logo, root_path %>
  <nav class="round">
    <ul>
      <li><%= link_to "Accueil", root_path %></li>
      <% if signed_in? %>
      <li><%= link_to "Utilisateurs", users_path %></li>
      <li><%= link_to "Profil", current_user %></li>
      <li><%= link_to "Réglages", edit_user_path(current_user) %></li>
      <% end %>
      .
      .
      .
    </ul>
  </nav>
</header>

Avec ça, l'index des utilisateurs est totalement fonctionnel (avec la réussite de tous les tests), mais on se sent un peu solitaire… (illustration 10.8).

user_index_only_one
Illustration 10.8: La page d'index utilisateur /users avec un seul utilisateur. (taille normale)

10.3.2 Utilisateurs fictifs

Dans cette section, nous allons donner de la compagnie à notre utilisateur solitaire. Bien sûr, pour créer suffisamment d'utilisateurs pour un index décent, nous pourrions utiliser notre navigateur pour rejoindre la page d'inscription et entrer les utilisateurs un par un. Mais une bien meilleure solution consiste à demander à Ruby (et Rake) de les entrer pour nous.

D'abord, nous allons ajouter le gem Faker à notre Gemfile, ce qui nous permettra de créer des exemples d'utilisateurs avec noms et des adresses-mails semi-réalistes (extrait 10.24).

Extrait 10.24. Ajout du gem Faker au Gemfile.
source 'http://rubygems.org'
.
.
.
group :development do
  gem 'rspec-rails', '2.5.0'
  gem 'annotate-models', '1.0.4'
  gem 'faker', '0.3.1'
end
.
.
.

Installer alors comme d'habitude :

$ bundle install

Ensuite, nous allons ajouter une tâche Rake pour créer les utilisateurs fictifs. Les tâches Rake sont consignées dans le dossier lib/tasks, et sont définis en utilisant des espaces de noms (namespaces) (dans notre cas, :db), comme on peut le voir dans le extrait 10.25.

Extrait 10.25. Une tâche Rake pour peupler la base de données avec des utilisateurs fictifs.
lib/tasks/sample_data.rake
require 'faker'

namespace :db do
  desc "Peupler la base de données"
  task :populate => :environment do
    Rake::Task['db:reset'].invoke
    User.create!(:nom => "Utilisateur exemple",
                 :email => "example@railstutorial.org",
                 :password => "foobar",
                 :password_confirmation => "foobar")
    99.times do |n|
      nom  = Faker::Name.name
      email = "example-#{n+1}@railstutorial.org"
      password  = "motdepasse"
      User.create!(:nom => nom,
                   :email => email,
                   :password => password,
                   :password_confirmation => password)
    end
  end
end

Cela définit une tâche db:populate qui initialise la base de données de développement en utilisant db:reset (ne vous souciez pas trop de la syntaxe quelque peu bizarre), qui crée un utilisateur fictif avec un nom et une adresse mail en faisant une réplique de notre précédent utilisateur, et 99 de plus ensuite. La ligne :

task :populate => :environment do

… assure que la tâche Rake a accès à l'environnement Rails local, pour inclure le modèle User (et ainsi la méthode User.create!).

Avec le domaine de nom :db comme dans le extrait 10.25, on peut invoquer la tâche Rake comme suit :

$ rake db:populate

Après avoir joué la tâche Rake, notre application possède 100 utilisateurs fictifs, comme on peut le voir dans l'illustration 10.9 (j'ai pris la liberté d'associer les premières adresses à des photos pour que les utilisateurs n'aient pas tous le même gravatar par défaut).

user_index_all
Illustration 10.9: La page d'index utilisateur /users avec 100 utilisateurs fictifs. (taille normale)

10.3.3 Pagination

Ayant résolu le problème d'avoir trop peu d'utilisateurs, nous nous confrontons maintenant au problème d'en avoir trop sur la même page. Maintenant, il y en a une centaine, ce qui représente un nombre raisonnablement important, et un site réel peut en compter des milliers. La solution est de paginer les utilisateurs, pour que (par exemple), seulement 30 s'affichent par page à n'importe quel moment.

Il existe plusieurs méthodes de paginaiton dans Rails ; nous en utiliserons une des plus simples et des plus robustes, appelée will_paginate. Pour l'utiliser, nous avons besoin d'actualiser le fichier Gemfile comme d'habitude (extrait 10.26).

Extrait 10.26. Inclusion de will_paginate dans le fichier Gemfile.
source 'http://rubygems.org'

gem 'rails', '3.0.4'
gem 'sqlite3-ruby', '1.3.2', :require => 'sqlite3'
gem 'gravatar_image_tag', '1.0.0.pre2'
gem 'will_paginate', '3.0.pre2'
.
.
.

Ensuite, comme d'habitude :

$ bundle install

Avec will_paginate installé, nous sommes maintenant en mesure de paginer le résultat de nos utilisateurs trouvés. Nous allons commencer par ajouter la méthode spéciale will_paginate dans la vue (extrait 10.27) ; nous verrons dans un moment pourquoi le code apparait au-dessus et en dessous de la liste d'utilisateurs.

Extrait 10.27. L'index utilisateur avec la pagination.
app/views/users/index.html.erb
<h1>Liste des utilisateurs</h1>

<%= will_paginate %>

<ul class="users">
  <% @users.each do |user| %>
    <li>
      <%= gravatar_for user, :size => 30 %>
      <%= link_to user.name, user %>
    </li>
  <% end %>
</ul>

<%= will_paginate %>

La méthode will_paginate est un peu magique ; à l'intérieur d'une vue users (utilisateurs) elle cherche automatiquement un objet @users, et affiche des liens de paginations pour atteindre les autres pages. La vue de l'extrait 10.27 ne fonctionne pas encore, évidemment, puisque actuellement la variable @users contient le résultat de User.all (extrait 10.20), qui est de classe Array (« table non associative »), tandis que will_paginate attend un objet de classe WillPaginate::Collection. Heureusement, c'est justement le type d'objet retourné par la méthode paginate fournie par le gem will_paginate :

$ rails console
>> User.all.class
=> Array
>> User.paginate(:page => 1).class
=> WillPaginate::Collection

Notez que paginage prend un argument de type tableau associatif (table de hachage) avec une clé :page de valeur égale à la page requise. User.paginate extrait les utilisateurs de la base de donnée en une fois (30 par défaut), en se basant sur le paramètre :page. Ainsi, par exemple, la page 1 sont les utilisateurs 1–30, la page 2 les utilisateurs 31–60, etc.

On peut paginer les utilisateurs dans l'application exemple en utilisant paginate à la place de all dans l'action index (extrait 10.28). Ici le paramètre :page est issu de params[:page], qui est généré automatiquement par will_paginate.

Extrait 10.28. Paginer les utilisateurs dans l'action index.
app/controllers/users_controller.rb
class UsersController < ApplicationController
  before_filter :authenticate, :only => [:index, :edit, :update]
  .
  .
  .
  def index
    @titre = "Tous les utilisateurs"
    @users = User.paginate(:page => params[:page])
  end
  .
  .
  .
end

La page d'index des utilisateurs devrait maintenant fonctionner, apparaissant telle que dans l'illustration 10.10; parce que nous avons inclus will_paginate au-dessus et en dessous de la liste des utilisateurs, les liens de pagination apparaissent à ces deux endroits.

user_index_pagination_rails_3
Illustration 10.10: La page d'index /users paginée. (taille normale)

Si vous cliquez soit le lien  2 soit le lien Next (Suivant), vous obtiendrez la seconde page de résultats, comme dans l'illustration 10.11.

user_index_page_two_rails_3
Illustration 10.11: Page 2 de l'index (/users?page=2). (taille normale)

Test de la pagination

Tester la pagination requiert quelque connaissance du fonctionnement de will_paginate, donc nous avons commencé par l'implémentation, mais il est important aussi de la tester. Pour ce faire, nous avons besoin d'invoquer la pagination dans un test, ce qui signifie de créer d'abord plus de 30 utilisateurs (d'usine).

Comme auparavant, nous utiliserons Factory Girl pour simuler des utilisateurs, mais pour le moment, nous avons un problème : les adresses d'utilisateurs doivent être uniques, et créer plus de 30 utilisateurs « à la main » serait un travail terriblement rébarbatif… Par chance, Factory Girl anticipe ce problème et fournit des séquences pour le résoudre, comme montré dans l'extrait 10.29.

Extrait 10.29. Définir une séquence Factory Girl.
spec/factories.rb
Factory.define :user do |user|
  user.nom                  "Michael Hartl"
  user.email                 "mhartl@example.com"
  user.password              "foobar"
  user.password_confirmation "foobar"
end

Factory.sequence :email do |n|
  "person-#{n}@example.com"
end

Cela revient à retourner des adresses-mail comme person-1@example.com, person-2@example.com, etc., que nous invoquons en utilisant la méthode next :

Factory(:user, :email => Factory.next(:email))

En appliquant l'idée de séquence d'usine, nous pouvons faire 31 utilisateurs (l'utilisateur @user original et 30 de plus) à l'intérieur d'un test, et vérifier alors que la réponse obtient de will_paginate le code HTML voulu (ce que nous devrions être en mesure de déterminer en utilisant Firebug ou en consultant le code source de la page). Le résultat apparait dans l'extrait 10.30.

Extrait 10.30. Un test de la pagination.
spec/controllers/users_controller_spec.rb
require 'spec_helper'

describe "UsersController" do
  render_views

  describe "GET 'index'" do
    .
    .
    .
    describe "pour les utilisateurs identifiés" do

      before(:each) do
        .
        .
        .
        @users = [@user, second, third]
        30.times do
          @users << Factory(:user, :email => Factory.next(:email))
        end
      end
      .
      .
      .
      it "devrait avoir un élément pour chaque utilisateur" do
        get :index
        @users[0..2].each do |user|
          response.should have_selector("li", :content => user.nom)
        end
      end

      it "devrait paginer les utilisateurs" do
        get :index
        response.should have_selector("div.pagination")
        response.should have_selector("span.disabled", :content => "Previous")
        response.should have_selector("a", :href => "/users?page=2",
                                           :content => "2")
        response.should have_selector("a", :href => "/users?page=2",
                                           :content => "Next")
      end
    end
  end
  .
  .
  .
end

Ce code s'assure que les tests invoquent la pagination en ajoutant 308 utilisateurs à la variable @users en utilisant la notation « push » des tableaux Array <<, qui ajoute un élément à un tableau existant :

$ rails console
>> a = [1, 2, 5]
=> [1, 2, 5]
>> a << 17
=> [1, 2, 5, 17]
>> a << 42 << 1337
=> [1, 2, 5, 17, 42, 1337]

Nous voyons dans le dernier exemple que les occurrences de << peuvent être « chaînées ». Dans le test lui-même, notez la notation compacte have_selector("div.pagination"), qui emprunte la convention des classes CSS (vue pour la première fois dans l'extrait 5.3) pour vérifier l'existence d'une balise div de classe pagination. Notez aussi que, maintenant qu'il y a 33 utilisateurs, nous avons actualisé le test de l'élément utilisateur pour n'utiliser que les trois premiers éléments ([0..2]) de la table @users, ce qui correspond à ce que nous avions avant dans l'extrait 10.19:

@users[0..2].each do |user|
  response.should have_selector("li", :content => user.nom)
end

Avec ça, notre code de pagination est bien testé, et il n'y a qu'un détail mineur laissé de côté, que nous aborderons dans la prochaine section.

10.3.4 Restructuration des partiels

L'index paginé est maintenant complet, mais il reste une amélioration que je ne peux résister d'inclure : Rails possède des outils incroyablement sophistiqués pour faire des vues compactes, et dans cette section nous restructurerons la page d'index pour les utiliser. Notre code étant bien testé, nous pouvons le restructurer en toute confiance, assurés que nous sommes de ne pas casser les fonctionnalités du site.

La première étape dans la restructuration est de remplacer le li de l'utilisateur (extrait 10.27) par un appel à un render (rendu) (extrait 10.31).

Extrait 10.31. Premier élément de restructuration de la vue d'index.
app/views/users/index.html.erb
<h1>All users</h1>

<%= will_paginate %>

<ul class="users">
  <% @users.each do |user| %>
    <%= render user %>
  <% end %>
</ul>

<%= will_paginate %>

Ici nous appelons render non pas sur une chaine (string) avec le nom du partiel, mais plutôt sur une variable user de classe User ;9 dans ce contexte, Rails recherche automatiquement un partiel appelé _user.html.erb, que nous devons créer (extrait 10.32).

Extrait 10.32. Partiel pour rendre un seul utilisateur.
app/views/users/_user.html.erb
<li>
  <%= gravatar_for user, :size => 30 %>
  <%= link_to user.name, user %>
</li>

C'est une amélioration aboutie, mais nous pouvons encore faire mieux : nous pouvons appeler render directement sur la variable @users (extrait 10.33).

Extrait 10.33. La restructuration complète de l'index.
app/views/users/index.html.erb
<h1>Tous les utilisateurs</h1>

<%= will_paginate %>

<ul class="users">
  <%= render @users %>
</ul>

<%= will_paginate %>

Ici Rails déduit que @users est une liste d'objets User ; plus encore, quand appelé avec une collection d'utilisateurs, Rails itère automatiquement sur eux, rendant chaque occurrence avec le partiel _user.html.erb. Le résultat est le code incroyablement compact de l'extrait 10.33.

10.4 Destruction des utilisateurs

Maintenant que l'index est achevé, il ne reste qu'une action canonique REST à produire : destroy. Dans cette section, nous ajouterons des liens pour supprimer des utilisateurs, comme dans la maquette de l'illustration 10.12, et nous définirons l'action destroy (détruire) nécessaire pour accomplir cette suppression. Mais avant tout, nous devons créer la classe « administrateurs » des utilisateurs autorisés à procéder à cette destruction.

user_index_delete_links_mockup
Illustration 10.12: Maquette de l'index avec des liens de suppression (version anglaise). (taille normale)

10.4.1 Utilisateurs administrateurs

Nous allons identifier les utilisateurs possédant des privilèges d'administrateur par le biais d'un attribut booléen admin dans le modèle User, ce qui nous conduira à créer une méthode admin? testant le statut de l'utilisateur. Nous pouvons écrire des tests pour cet attribut comme dans le extrait 10.34.

Extrait 10.34. Test pour un attribut admin.
spec/models/user_spec.rb
.
.
.
  describe "Attribut admin" do

    before(:each) do
      @user = User.create!(@attr)
    end

    it "devrait confirmer l'existence de `admin`" do
      @user.should respond_to(:admin)
    end

    it "ne devrait pas être un administrateur par défaut" do
      @user.should_not be_admin
    end

    it "devrait pouvoir devenir un administrateur" do
      @user.toggle!(:admin)
      @user.should be_admin
    end
  end
end

Ici nous avons utilisé la méthode toggle! (bascule !) pour basculer la valeur de l'attribut admin de false (faux) à true (vrai). Notez aussi que la ligne :

@user.should be_admin

implique (via les conventions booléennes de RSpec) que l'utilisateur devrait posséder une méthode booléenne admin?.

Nous ajoutons l'attribut admin grâce à une migration comme d'habitude, en indiquant le type booléen de l'attribut dans la ligne de commande :

$ rails generate migration add_admin_to_users admin:boolean

La migration ajoute simplement la colonne admin à la table users (extrait 10.35), en adaptant le modèle de données comme dans l'illustration 10.13.

Extrait 10.35. Migration pour ajouter un attribut booléen admin aux utilisateurs.
db/migrate/<timestamp>_add_admin_to_users.rb
class AddAdminToUsers < ActiveRecord::Migration
  def self.up
    add_column :users, :admin, :boolean, :default => false
  end

  def self.down
    remove_column :users, :admin
  end
end

Notez l'ajout de l'argument :default => false à la fonction add_column dans l'extrait 10.35, qui spécifie que les utilisateurs ne seront pas des administrateurs par défaut (sans cet argument :default => false, admin serait nul par défaut (nil), ce qui équivaut à false, donc cette précision n'est pas strictement indispensable. Mais le code est plus explicite ainsi, et transmet notre intention de façon plus claire à Rails ainsi qu'aux lecteurs du code).

user_model_admin
Figure 10.13: Le modèle `User` avec un attribut booléen admin ajouté.

Pour finir, nous migrons la base de données de développement et préparons le base de données de test :

$ rake db:migrate
$ rake db:test:prepare

Comme attendu, Rails prend en compte la nature booléenne de l'attribut admin et ajoute automatiquement la méthode du même nom suivie d'un point d'interrogation admin? :10

$ rails console
>> user = User.first
>> user.admin?
=> false
>> user.password = "foobar"
>> user.toggle!(:admin)
=> true
>> user.admin?
=> true

Pour finir, actualisons notre peuplement fictif pour faire du premier utilisateur un administrateur (extrait 10.36).

Extrait 10.36. Peuplement fictif avec un utilisateur administrateur.
lib/tasks/sample_data.rake
require 'faker'

namespace :db do
  desc "Peupler la base de données avec des données fictives"
  task :populate => :environment do
    Rake::Task['db:reset'].invoke
    administrateur = User.create!(:name => "Example User",
                         :email => "example@railstutorial.org",
                         :password => "foobar",
                         :password_confirmation => "foobar")
    administrateur.toggle!(:admin)
    .
    .
    .
  end
end

Enfin, jouons à nouveau le « populateur » pour ré-initialiser la base de données et la reconstruire entièrement :

$ rake db:populate

Révision de attr_accessible

Vous avez peut-être noté que l'extrait 10.36 transforme un utilisateur en administrateur avec la méthode toggle!(:admin), mais pourquoi ne pas ajouter simplement un :admin => true à la table d'initialisation ? La réponse est que cela ne fonctionnerait pas, et ce à dessein : seuls les attributs définis par attr_accessible peuvent être renseignés par le biais d'un « assignement public » et l'attribut admin n'est pas dans ce cas. L'Extrait 10.37 reproduit la liste la plus récente des attributs attr_accessible —notez que :admin n'est pas dans cette liste.

Extrait 10.37. Les attributs attr_accessible du modèle `User` sans attribut :admin.
app/models/user.rb
class User < ActiveRecord::Base
  attr_accessor :password
  attr_accessible :name, :email, :password, :password_confirmation
  .
  .
  .
end

La définition explicite des attributs accessibles est cruciale pour la bonne sécurité d'un site. Si nous avions omis la liste attr_accessible dans le modèle `User` (ou si nous avions de façon irraisonnée ajouté :admin à cette liste), un utilisateur mal intentionné aurait pu envoyer la requête PUT comme suit :11

put /users/17?admin=1

Cette requête aurait pu transformer l'utilisateur d'identifiant 17 en administrateur, ce qui, pour le moins, peut être une sérieuse brèche dans la sécurité du site. Pour parer ce risque, c'est une bonne habitude de définir le paramètre attr_accessible pour chaque modèle.

10.4.2 L'action destroy (détruire)

La dernière étape nécessaire pour achever la ressource `Utilisateurs` est d'ajouter des liens pour supprimer des utilisateurs et une action destroy. Nous allons commencer par ajouter un lien « supprimer » pour chaque utilisateur dans la page d'index (Extrait 10.38).

Extrait 10.38. Liens pour la suppression des utilisateurs (visibles seulement pour les administrateurs).
app/views/users/_user.html.erb
<li>
  <%= gravatar_for user, :size => 30 %>
  <%= link_to user.nom, user %>
  <% if current_user.admin? %>
  | <%= link_to "supprimer", user, :method => :delete, :confirm => "Etes-vous certain&nbsp;?",
                                :title => "Supprimer #{user.nom}" %>
  <% end %>
</li>

Notez l'argument :method => :delete, qui s'arrange pour que le lien envoie la bonne requête DELETE (EFFACER). Nous avons aussi entouré chaque lien à l'intérieur d'une condition if pour que seuls les administrateurs puissent les voir. Le résultat pour notre utilisateur administrateur apparait dans l'illustration 10.14.

Les navigateurs ne peuvent pas envoyer nativement de requête DELETE, donc Rails les simule par le biais de JavaScript.12 Pour que le lien de suppression fonctionne, nous avons donc à inclure la librairie JavaScript standard de Rails, ce que nous faisons en ajoutant la ligne…

<%= javascript_include_tag :defaults %>

… au gabarit du site. Le résultat est montré dans l'illustration 10.39.

Extrait 10.39. Ajout de la librairie JavaScript standard de Rails à l'application.
app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
  <head>
    <title><%= title %></title>
    <%= csrf_meta_tag %>
    <%= render 'layouts/stylesheets' %>
    <%= javascript_include_tag :defaults %>
  </head>
  <body>
    .
    .
    .
  </body>
</html>
index_delete_links_rails_3
Illustration 10.14: L'index /users avec les liens de suppression (version anglaise). (taille normale)

Quand bien même seuls les administrateurs seraient en mesure de voir les liens de suppression, il demeure cependant un trou de sécurité important : n'importe quel utilisateur mal intentionné et suffisamment compétent pourrait transmettre une requête DELETE en ligne de commande pour détruire n'importe quel utilisateur. Pour sécuriser proprement le site, nous avons donc aussi besoin d'un contrôle d'accès, pour que les tests ne vérifient pas seulement qu'un administrateur puisse supprimer des utilisateurs, mais également que les autres utilisateurs ne puissent pas. Le résultat apparait dans l'extrait 10.40. Notez qu'à l'instar des méthodes get, post et put, nous utilisons delete pour traiter la requête DELETE à l'intérieur des tests.

Extrait 10.40. Tests pour la suppression des utilisateurs.
spec/controllers/users_controller_spec.rb
describe UsersController do
  render_views
  .
  .
  .
  describe "DELETE 'destroy'" do

    before(:each) do
      @user = Factory(:user)
    end

    describe "en tant qu'utilisateur non identifié" do
      it "devrait refuser l'accès" do
        delete :destroy, :id => @user
        response.should redirect_to(signin_path)
      end
    end

    describe "en tant qu'utilisateur non administrateur" do
      it "devrait protéger la page" do
        test_sign_in(@user)
        delete :destroy, :id => @user
        response.should redirect_to(root_path)
      end
    end

    describe "en tant qu'administrateur" do

      before(:each) do
        admin = Factory(:user, :email => "admin@example.com", :admin => true)
        test_sign_in(admin)
      end

      it "devrait détruire l'utilisateur" do
        lambda do
          delete :destroy, :id => @user
        end.should change(User, :count).by(-1)
      end

      it "devrait rediriger vers la page des utilisateurs" do
        delete :destroy, :id => @user
        response.should redirect_to(users_path)
      end
    end
  end
end

(Vous pouvez noter que nous avons défini un utilisateur-administrateur en utilisant :admin => true ; en effet, les « utilisateurs d'usine » ne sont pas concernés par les règles du paramètre attr_accessible.) Notez ici que la méthode change peut prendre une valeur négative, ce qui signifie que, tout comme nous avons vérifié la création d'un utilisateur en testant un changement de +1 (extrait 8.14), nous pouvons vérifier une suppression d'utilisateur en testant un changement de -1 :

lambda do
  delete :destroy, :id => @user
end.should change(User, :count).by(-1)

Comme vous pouvez vous en douter, l'implémentation utilise un filtre « passe-avant » (before filter), cette fois pour restreindre l'accès de l'action destroy aux seuls administrateurs. L'action destroy trouve elle-même l'utilisateur, le détruit, et redirige alors l'administrateur vers l'index des utilisateurs (Extrait 10.41). [[Je mettrais un warning ici pour penser à ajouter :admin au filtre "passe-avant" :authenticate]]

Extrait 10.41. Filtre « passe-avant’ pour restreindre l'utilisation de l'action destroy aux administrateurs.
app/controllers/users_controller.rb
class UsersController < ApplicationController
  before_filter :authenticate, :only => [:index, :edit, :update, :destroy]
  before_filter :correct_user, :only => [:edit, :update]
  before_filter :admin_user,   :only => :destroy
  .
  .
  .
  def destroy
    User.find(params[:id]).destroy
    flash[:success] = "Utilisateur supprimé."
    redirect_to users_path
  end

  private
    .
    .
    .
    def admin_user
      redirect_to(root_path) unless current_user.admin?
    end
end

Notez que l'action destroy action utilise le chainage de méthodes (vue brièvement à la section 4.2.3) dans la ligne :

User.find(params[:id]).destroy

… ce qui fait l'économie d'une ligne de code.

À ce point de développement, tous les tests devraient réussir, et la ressource `Users` — avec son contrôleur, son modèle et ses vues — est fonctionnellement complète.

10.5 Conclusion

Nous avons parcouru un bon bout de chemin depuis l'introduction du contrôleur d'utilisateur de la section 5.3. Cet utilisateur ne pouvait même pas s'inscrire, s'identifier, se déconnecter, voir son profil, l'éditer et voir une liste de tous les utilisateurs — et certains utilisateurs peuvent même à présent en supprimer d'autres.

La suite de ce livre va se construire sur les fondations de cette ressource utilisateurs (et le système d'authentification) pour faire un site avec des micro-messages (chapitre 11) et la possibilité de suivre des utilisateurs (chapitre 12). Ces chapitres introduiront certains des outils les plus puissants de Rails, tels que le modelage de données avec has_many (possède_plusieurs) et has_many :through (possède_plusieurs… à_travers_de).

Avant de poursuivre, assurez-vous de fusionner tous vos changements dans la branche principale de développement :

$ git add .
$ git commit -m "Fini avec les actions utilisateurs edit/update, index et destroy"
$ git checkout master
$ git merge updating-users

Notons pour finir que ce chapitre a vu la dernière installation gem nécessaire au tutoriel. À titre d'indication, le Gemfile final est présenté dans l'extrait 10.42 ci-dessous.

Extrait 10.42. Le Gemfile final de l'application.
source 'http://rubygems.org'

gem 'rails', '3.0.4'
gem 'sqlite3-ruby', '1.3.2', :require => 'sqlite3'
gem 'gravatar_image_tag', '1.0.0.pre2'
gem 'will_paginate', '3.0.pre2'

group :development do
  gem 'rspec-rails', '2.5.0'
  gem 'annotate-models', '1.0.4'
  gem 'faker', '0.3.1'
end

group :test do
  gem 'rspec', '2.5.0'
  gem 'webrat', '0.7.1'
  gem 'spork', '0.8.4'
  gem 'factory_girl_rails', '1.0'
end

10.6 Exercices

  1. Faite en sorte que le lien « changer » du Gravatar, dans l'extrait 10.3 s'ouvre dans une nouvelle fenêtre (ou un onglet). Indication : cherchez sur le web ; vous devriez trouver une méthode particulièrement robuste comportant quelque chose appelé _blank (« _vierge »).
  2. Supprimer le code de formulaire redondant en restructurant les vues new.html.erb et edit.html.erb views pour utiliser un partiel dans l'extrait 10.43. Notez que vous devrez passer la variable formulaire f de façon explicite comme variable locale, comme dans l'extrait 10.44.
  3. Les utilisateurs identifiés n'ont aucune raison d'accéder aux actions new (nouvel utilisateur) et create (créer l'utilisateur) dans le contrôleur `User`. Faites en sorte qu'ils soient redirigés vers l'accueil s'ils tentent d'atteindre ces pages.
  4. Ajoutez des tests pour vérifier que les liens de suppression dans l'extrait 10.38 apparaissent pour les administrateurs et pas pour les utilisateurs normaux.
  5. Modifier l'action destroy pour empêcher qu'un administrateur ne puisse se détruire lui-même (commencez par écrire le test).
Extrait 10.43. Un partiel pour les champs des formulaires `new` et `edit`.
app/views/users/_fields.html.erb
<%= render 'shared/error_messages', :object => f.object %>
<div class="field">
  <%= f.label :name %><br />
  <%= f.text_field :name %>
</div>
<div class="field">
  <%= f.label :email %><br />
  <%= f.text_field :email %>
</div>
<div class="field">
  <%= f.label :password %><br />
  <%= f.password_field :password %>
</div>
<div class="field">
  <%= f.label :password_confirmation, "Confirmation" %><br />
  <%= f.password_field :password_confirmation %>
</div>
Extrait 10.44. La vue pour un nouvel utilisateur avec le partiel.
app/views/users/new.html.erb
<h1>Sign up</h1>

<%= form_for(@user) do |f| %>
  <%= render 'fields', :f => f %>
  <div class="actions">
    <%= f.submit "Sign up" %>
  </div>
<% end %>
  1. Image provenant de http://www.flickr.com/photos/sashawolff/4598355045/
  2. Le site Gravatar redirige en fait cette adresse vers http://en.gravatar.com/emails, qui concerne les utilisateurs de langue anglaise, mais j'ai volontairement omis d'ajouter en au compte pour l'utilisation par des utilisateurs d'autres langages. 
  3. Image de http://www.flickr.com/photos/sashawolff/4598355045/
  4. Ne vous souciez pas trop de savoir comment cela fonctionne ; les détails concernent les développeurs du framework Rails eux-mêmes, mais ne sont pas importants, à dessein, pour les développeurs d'applications. 
  5. Le code de cette section est adapté du gem Clearance par thoughtbot
  6. Vraiment, comme indiqué dans la section 9.6, session est exactement implémentée de cette façon. 
  7. Photo Baby de http://www.flickr.com/photos/glasgows/338937124/
  8. Techniquement parlant, nous n'avons besoin que d'ajouter 28 utilisateurs fictifs, puisque nous en avons déjà trois, mais je trouve le sens plus clair si nous en ajoutons plutôt 30. 
  9. Le nom user est immatériel — nous aurions pu tout aussi bien écrire @users.each do |foobar| et utiliser alors render foobar. La clé est la classe de l'objet dans ce cas, User
  10. La méthode toggle! invoque les « callbacks » de Active Record mais pas les validations, donc nous devons renseigner l'attribut password (mais pas la confirmation de ce mot de passe) pour avoir un mot de passe non vide dans la fonction callback encrypt_password
  11. Des outils en ligne de commande tels que curl (vu dans la Box 3.2) peut utiliser les requêtes PUT de cette forme. 
  12. Cela signifie que le lien pour supprimer un utilisateur ne fonctionnera pas si l'utilisateur a JavaScript désactivé dans son navigateur. Si vous devez traiter le cas de navigateur sans JavaScript, vous pouvez simuler une requête DELETE en utilisant la forme d'une requête POST, laquelle fonctionne même sans JavaScript ; consultez le Railscast sur la « Destruction sans JavaScript » pour les détails.