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 7 Modéliser et afficher les utilisateurs, partie II

Au chapitre 6, nous avons créé la première itération d'un modèle User (Utilisateur) pour représenter les utilisateurs de notre application, mais le job a été à moitié accompli. Virtuellement n'importe quel site avec des utilisateurs, incluant le nôtre, a besoin d'un système d'authentification, mais pour le moment n'importe quel utilisateur qui peut s'inscrire au site possède un nom et une adresse mail, et nous n'avons aucun moyen de vérifier leur identité. Dans ce chapitre, nous allons ajouter l'attribut mot de passe (password) nécessaire à une inscription initiale de l'utilisateur (chapitre 8) et à une identification (une connexion) avec une combinaison email/mot de passe (chapitre 9). Au cours du processus, nous ré-utiliserons plusieurs des idées du chapitre 6, incluant les migrations et les validations, et aussi introduire de nouvelles idées telles que les attributs virtuels, les méthodes privées et les fonctions de rappel (callbacks) d'Active Record.

Dès que nous aurons un attribut mot_de_passe fonctionnel, nous ferons une action et une vue pour afficher les profils des utilisateurs (section 7.3). Vers la fin de ce chapitre, nos profils d'utilisateur afficheront les noms et les photos du profil (comme indiqué dans la maquette de l'illustration 7.1), et ils seront bien testés avec des « utilisateurs d'usine ».

Avant d'avancer, ré-initialisons la base de données avec rake db:reset, ce qui va retirer tous les vieux exemples d'utilisateurs des sessions précédentes :

$ rake db:reset
profile_mockup_profile_name
Illustration 7.1: Une maquette du profil utilisateur fait à la section 7.3(taille normale)

7.1 Mots de passe peu sécurisés

Faire des mots de passe robustes requiert beaucoup de machinerie, donc nous allons diviser le processus en deux principales étapes. Dans cette section, nous allons faire un attribut mot_de_passe et ajouter des validations. Le modèle User (Utilisateur) en résultant sera complètement fonctionnel mais pas du tout sécurisé, avec des mots de passe enregistrés « en clair » dans la base de données. À la section 7.2, nous réglerons ce problème en cryptant les mots de passe avant de les sauver, protégeant ainsi notre site des attaques toujours possibles.

7.1.1 Validations du mot de passe

Quand bien même nous n'avons pas encore ajouté une colonne pour les mots de passe à notre base de données, nous allons déjà commencer par écrire leurs tests. Notre projet initial est d'avoir des tests pour valider la présence, la longueur et la confirmation des mots de passe. C'est notre plus gros bloc de tests jusqu'ici, voyez si vous pouvez le lire tout d'un tenant. Si vous restez coincé, ça vous aidera sans doute de revoir les validations analogues de la section 6.2 ou sauter directement au code de l'application de l'extrait 7.2.

Dans le but de minimiser les typos dans les mots de passe, quand on fera la page d'inscription de l'utilisateur au chapitre 8 nous adopterons la convention courante que les utilisateurs confirment leur mot de passe. Pour commencer, revoyons la table d'attributs vue dans l'extrait 6.20:

describe User do

  before(:each) do
    @attr = { :nom => "Utilisateur exemple", :email => "user@example.com" }
  end
  .
  .
  .
end

Pour écrire des tests pour les mots de passe, nous aurons besoin d'ajouter deux nouveaux attributs à la table @attr, password (mot de passe) et password_confirmation (motdepasse_confirmation). Comme vous pouvez certainement le deviner, l'attribut password_confirmation sera utilisé pour l'étape de confirmation du mot de passe.

Écrivons des tests pour la présence du mot de passe et sa confirmation, avec des tests confirmant que le mot de passe a une longueur valide (réduite à quelque chose arbitrairement fixée entre 6 et 40 caractères). Le résultat apparait dans l'extrait 7.1.

Extrait 7.1. Des tests pour les validations du mot de passe.
spec/models/user_spec.rb
require 'spec_helper'

describe User do

  before(:each) do
    @attr = {
      :nom => "Utilisateur exemple",
      :email => "user@example.com",
      :password => "foobar",
      :password_confirmation => "foobar"
    }
  end

  it "devrait créer une nouvelle instance avec des attributs valides" do
    User.create!(@attr)
  end
  .
  .
  .
  describe "password validations" do

    it "devrait exiger un mot de passe" do
      User.new(@attr.merge(:password => "", :password_confirmation => "")).
        should_not be_valid
    end

    it "devrait exiger une confirmation du mot de passe qui correspond" do
      User.new(@attr.merge(:password_confirmation => "invalid")).
        should_not be_valid
    end

    it "devrait rejeter les mots de passe (trop) courts" do
      short = "a" * 5
      hash = @attr.merge(:password => short, :password_confirmation => short)
      User.new(hash).should_not be_valid
    end

    it "devrait rejeter les (trop) longs mots de passe" do
      long = "a" * 41
      hash = @attr.merge(:password => long, :password_confirmation => long)
      User.new(hash).should_not be_valid
    end
  end
end

Notez sans l'extrait 7.1 comme nous collectons d'abord un set d'attributs utilisateur valides dans @attr. Si pour quelque raison que ce soit ces attributs ne sont pas valides — comme ce devrait être le cas, par exemple, si nous n'avons pas implémenté les confirmations proprement — alors la première étape :

  it "devrait créer une nouvelle instance avec des attributs valides" do
    User.create!(@attr)
  end

… rencontrera une erreur. Les tests suivants vérifieront alors chaque validation à son tour, en utilisant la même technique @attr.merge introduite la première fois dans l'extrait 6.11.

Voyons maintenant le code de l'application, qui contient une astuce. En fait, elle contient deux astuces. D'abord, vous pouvez vous attendre, à ce point, à ce qu'on joue une migration pour ajouter un attribut password à notre modèle User, comme nous l'avions fait pour les attributs nom et email dans l'extrait 6.1. Mais ce n'est pas le cas : nous n'allons enregistrer qu'un mot de passe crypté dans la base de données ; pour le mot de passe, nous allons introduire la notion d'attribut virtuel (c'est-à-dire un attribut qui ne correspond pas à une colonne de la base de données) en utilisant la méthode attr_accessor, comme nous l'avons fait avec la méthode attr_accessible pour les attributs nom et email pour l'exemple d'utilisateur à la section 4.4.5. L'attribut password ne sera jamais écrit dans la base de données, mais n'existera qu'en mémoire pour permettre l'étape de confirmation du mot de passe (implémentée ci-dessous) et l'étape de cryptage (implémentée à la section 7.1.2 et section 7.2).

La seconde astuce est que nous n'allons pas introduire un attribut password_confirmation, pas même un virtuel. À la place, nous utiliserons la validation spéciale :

validates :password, :confirmation => true

… qui va automatiquement créer un attribut virtuel appelé password_confirmation, tout en confirmant dans le même temps qu'il correspond exactement à l'attribut password.

Ainsi préparé à comprendre l'implémentation, jetons un coup d'œil au code lui-même (extrait 7.2).

Extrait 7.2. Validation pour l'attribut password.
app/models/user.rb
class User < ActiveRecord::Base
  attr_accessor :password
  attr_accessible :nom, :email, :password, :password_confirmation
  .
  .
  .
  # Crée automatique l'attribut virtuel 'password_confirmation'.
  validates :password, :presence     => true,
                       :confirmation => true,
                       :length       => { :within => 6..40 }
end

Comme promis, nous utilisons attr_accessor :password pour créer un attribut password virtuel (comme à la section 4.4.5). Puis, puisque nous allons accepter les mots de passe et leur confirmation comme part du processus d'inscription au chapitre 8, nous avons besoin d'ajouter le mot de passe et sa confirmation à la liste des attributs accessibles (mentionnée la première fois à la section 6.1.2.2), ce que nous faisons à la ligne :

attr_accessible :nom, :email, :password, :password_confirmation

Ensuite viennent les validations du mot de passe. Elles requièrent la présence d'un :password (comme, par exemple dans l'extrait 6.7) et incluent :confirmation => true qui rejette les utilisateurs dont le mot de passe et sa confirmation ne correspondent pas. Nous avons aussi une deuxième application de la validation de la longueur ; dans l'extrait 6.15 nous contraignons l'attribut nom à avoir moins de 50 caractères en utilisant l'option :maximum :

validates :nom,  :presence => true,
                  :length   => { :maximum => 50 }

Pour la validation de la longueur du mot de passe, nous avons plutôt utilisé l'option :within, en lui passant le rang1 6..40 pour forcer la contrainte de longueur.

7.1.2 La migration du mot de passe

À ce point, nous devons considérer que nous n'enregistrons le mot de passe de l'utilisateur nulle part ; puisque nous avons décidé d'utiliser un mot de passe virtuel, plutôt que de l'enregistrer dans la base de données, il n'existe qu'en mémoire. Comment pouvons-nous utiliser ce mot de passe pour l'identification ? La solution consiste à créer un attribut séparé dédié à la consignation du mot de passe, et notre stratégie consistera à utiliser un mot de passe virtuel comme matériel brut pour un mot de passe crypté, que nous enregistrerons dans la base de données à l'inscription de l'utilisateur (chapitre 8) et que nous récupérerons plus tard pour l'identification de l'utilisateur (chapitre 9).

Planifions l'enregistrement du mot de passe crypté en utilisant un attribut encrypted_password dans notre modèle User (Utilisateur). Nous discuterons des détails de l'implémentation à la section 7.2, mais nous pouvons commencer avec nos tests du mot de passe crypté en notant que le mot de passe crypté devrait au moins exister. Nous pouvons tester cela en utilisant la méthode Ruby respond_to? (répond_à ? ), qui accepte un symbole et retourne true (vrai) si l'objet répond à la méthode ou l'attribut donné et false (faux) dans le cas contraire :

$ rails console --sandbox
>> user = User.new
>> user.respond_to?(:password)
=> true
>> user.respond_to?(:encrypted_password)
=> false

Nous pouvons tester l'existence d'un attribut encrypted_password avec le code de l'extrait 7.3, qui utilise l'helper de méthode RSpec respond_to.

Extrait 7.3. Tester l'existence d'un attribut encrypted_password.
spec/models/user_spec.rb
describe User do
  .
  .
  .
  describe "password encryption" do

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

    it "devrait avoir un attribut  mot de passe crypté" do
      @user.should respond_to(:encrypted_password)
    end
  end
end

Notez que dans le bloc before(:each) nous créons un utilisateur, plutôt que d'appeler juste la méthode User.new. Nous pourrions en fait faire réussir ce test en utilisant User.new, mais (comme nous le verrons dans un instant) définir le mot de passe crypté exigera que l'utilisateur soit enregistré dans la base de données. En utilisant create! dans ce premier cas ne crée pas de dommage, et le placer dans before(:each) nous permettra de garder tous les tests du mot de passe crypté dans un seul bloc describe.

Pour obtenir la réussite de ce test, nous aurons besoin d'une migration pour ajouter l'attribut encrypted_password à la table users (utilisateurs) :

$ rails generate migration add_password_to_users encrypted_password:string

Ici le premier argument est le nom de la migration, et nous avons aussi fourni un second argument avec le nom et le type d'attribut que nous voulons créer (comparez cela avec la génération originale de la table users de l'extrait 6.1). Nous pouvons choisir le nom de migration que nous voulons, mais il est pratique de terminer le nom par _to_users (pour_users) pour que dans ce cas Rails puisse automatiquement construire une migration qui ajoute les colonnes à la table users. Plus encore, en incluant le second argument, nous donnons assez d'information à Rails pour construire entièrement la migration pour nous, comme le montre l'extrait 7.4.

Extrait 7.4. La migration pour ajouter la colonne encrypted_password à la table users.
db/migrate/<timestamp>_add_password_to_users.rb
class AddPasswordToUsers < ActiveRecord::Migration
  def self.up
    add_column :users, :encrypted_password, :string
  end

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

Ce code utilise la méthode add_column (ajouter_colonne) pour ajouter la colonne encrypted_password à la table users (et la méthode complémentaire remove_column pour la supprimer quand nous migrons la base en arrière). Le résultat est le modèle de données montré dans l'illustration 7.2.

user_model_password
Illustration 7.2: Le modèle User avec un attribut mot de passe (crypté) ajouté (version anglaise).

Si nous jouons maintenant la migration et préparons le test de la base de données, le test devrait réussir, puisque le modèle User répondra à l'attribut encrypted_password (assurez-vous de fermer toutes les consoles Rails lancées dans le « bac à sable » (sandbox) ; la sandbox verrouille la base de données et interdit les migrations).

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

Bien sûr, nous pouvons jouer toute la suite de tests avec rspec spec/, mais il est pratique parfois de jouer juste une RSpec, ce que nous pouvons faire avec le drapeau -e (« exemple ») :

$ rspec spec/models/user_spec.rb \
> -e "devrait avoir un attribut mot de passe crypté"
.

1 example, 0 failures

7.1.3 Fonction de rappel dans l'Active Record

Maintenant que notre modèle User possède un attribut pour consigner le mot de passe, nous devons nous arranger pour générer et sauver le mot de passe crypté quand Active Record enregistre l'utilisateur dans la base de données. Nous allons réaliser cela en utilisant une technique appelée une fonction de rappel (callback), qui est une méthode invoquée à un point particulier de la vie de l'objet Active Record. Dans le cas présent, nous utiliserons la fonction de rappel before_save pour créer encrypted_password juste avant que l'utilisateur ne soit enregistré.2

Nous commençons avec un test pour l'attribut mot de passe crypté. Puisque nous avons différé les détails de l'implémentation — et, en particulier, la méthode de cryptage — à la section 7.2, dans cette section nous allons seulement nous assurer que l'attribut encrypted_password d'un utilisateur enregistré n'est pas vierge. Nous faisons cela en combinant la méthode blank? sur les chaines de caractères (section 4.4.2) avec la convention RSpec pour les méthodes booléennes (vue pour la première fois dans le contexte des méthodes valid?/be_valid dans l'extrait 6.11), rendant le test de l'extrait 7.5.

Extrait 7.5. Tester que l'attribut encrypted_password n'est pas vide.
spec/models/user_spec.rb
describe User do
  .
  .
  .
  describe "password encryption" do

    before(:each) do
      @user = User.create!(@attr)
    end
    .
    .
    .
    it "devrait définir le mot de passe crypté" do
      @user.encrypted_password.should_not be_blank
    end
  end
end

Ce code vérifie que encrypted_password.blank? n'est pas vrai (true) en utilisant la construction should_not be_blank.

Pour faire réussir ce test, nous déclarons une fonction de rappel appelée encrypt_password en passant un symbole de ce nom à la méthode before_save, et définissons ensuite une méthode encrypt_password pour procéder au cryptage. Avec before_save en place, Active Record appellera automatiquement la méthode correspondante avant d'enregistrer la donnée. Le résultat est présenté dans l'extrait 7.6.

Extrait 7.6. Fonction de rappel before_save pour créer l'attribut encrypted_password.
app/models/user.rb
class User < ActiveRecord::Base
  .
  .
  .
  validates :password, :presence     => true,
                       :confirmation => true,
                       :length       => { :within => 6..40 }

  before_save :encrypt_password

  private

    def encrypt_password
      self.encrypted_password = encrypt(password)
    end

    def encrypt(string)
      string # Implémentation provisoire !
    end
end

Ici la fonction de rappel encrypt_password délègue en fait le cryptage à une méthode encrypt ; comme signalé dans le commentaire, c'est seulement une implémentation provisoire — construit ainsi, l'extrait 7.6 met simplement le mot de passe crypté à la valeur du mot de passe non crypté, ce qui n'est pas notre but. Mais ça suffit pour faire réussir notre test, et nous ferons que la méthode encrypt procède réellement au cryptage à la section 7.2.

Avant d'essayer de comprendre l'implémentation, notez d'abord que les méthodes de cryptage sont placées après le mot-clé private ; à l'intérieur d'une classe Ruby, toutes les méthodes définies après le mot-clé private (privé) sont utilisées de façon interne par l'objet et ne sont pas destinées à l'utilisation publique.3 À titre d'exemple, nous pouvons tester l'objet User dans la console :

>> user = User.new
>> user.encrypt_password
NoMethodError: Attempt to call private method
(Traduction :
ErreurPasDeMethode : Tentative d'appel d'une méthode privée)

Ici Ruby provoque une exception (une erreur) NoMethodError (erreur d'absence de méthode) en signalant par une alerte que la méthode encrypt_password est privée.

Dans le contexte présent, rendre les méthodes encrypt_password et encrypt privées n'est pas strictement nécessaire, mais c'est une bonne pratique de les rendre privées sauf si elles sont nécessaires à l'interface publique.4

Maintenant que nous comprenons le mot-clé private, jetons un coup d'œil à la méthode encrypt_password :

def encrypt_password
  self.encrypted_password = encrypt(password)
end

C'est une méthode en une ligne (donc du meilleur genre !), mais elle ne contient pas seulement une mais deux subtilités. Primo , le membre gauche de la déclaration (self.encrypted_password) assigne explicitement l'attribut encrypted_password en utilisant le mot-clé self (soi-même) (de la section 4.4.2 vous vous souvenez que la classe self se réfère à l'objet lui-même, ce qui pour le modèle User est simplement l'utilisateur lui-même). L'utilisation de self est obligatoire dans ce contexte ; si nous omettons self et que nous écrivons :

def encrypt_password
  encrypted_password = encrypt(password)
end

Ruby créera une variable locale appelée encrypted_password, ce qui n'est pas du tout ce que nous voulons (elle n'existerait et ne serait utilisable que dans la méthode encrypt_password).

Secondo, le membre droit de la déclaration (encrypt(password)) appelle la méthode encrypt (crypter) sur la variable password ; mais il n'y a pas de variable password en vue. Dans la console, nous devrions accéder à l'attribut mot de passe (password) au travers d'un objet utilisateur :

>> user = User.new(:password => "foobar")
>> user.password
=> "foobar"

À l'intérieur de la classe User, l'objet utilisateur étant self, nous pourrions écrire :

def encrypt_password
  self.encrypted_password = encrypt(self.password)
end

… en analogie avec l'exemple en console, remplacez simplement user par self. Mais le self est ici optionnel (dans un membre d'expression droit), donc pour la brièveté nous pouvons écrire simplement :

def encrypt_password
  self.encrypted_password = encrypt(password)
end

… comme dans l'extrait 7.6 ci-dessus (bien sûr, comme nous l'avons noté, le self n'est pas optionnel quand nous assignons la valeur d'un attribut (dans le membre gauche d'une expression), donc nous devons écrire self.encrypted_password dans ce cas).

7.2 Mots de passe sécurisés

Avec le code de la section 7.1, en principe nous avons fini : bien que le mot de passe « crypté » soit le même que le mot de passe non crypté, la possibilité d'enregistrer un mot de passe crypté dans la base de données nous fournit les fondations nécessaires pour l'identification et l'authentification de l'utilisateur.5 Les normes de ce Tutoriel Rails doivent être beaucoup plus strictes, cependant : tout développeur web digne de ce nom doit savoir implémenter un système de mot de passe avec un hachage sécurisé à sens unique (secure one-way hashing). Dans cette section, nous construirons le matériel puisé de la section 7.1 pour implémenter un tel système robuste de mot de passe.

7.2.1 Un test de mot de passe sécurisé

Comme mentionné à la section 7.1.3, toute la machinerie pour le cryptage du mot de passe sera soigneusement rangé dans la région private du modèle User, ce qui représente une difficulté particulière pour les tests. Nous aurions besoin d'une espèce d'interface publique que nous pourrions exposer au reste de l'application. Un des aspects utiles du « Développement Dirigé par les Tests » est que, en agissant comme un client pour coder notre application, les tests nous motivent à concevoir une interface utilisable dès le départ.

L'authentification des utilisateurs implique de pouvoir comparer la version cryptée d'un mot de passe soumis avec la version cryptée du mot de passe de l'utilisateur en question. Cela signifie que nous avons besoin de définir une méthode pour accomplir la comparaison, méthode que nous appellerons has_password? (possède_un_motdepasse? ) ; ce sera notre partie publique de l'interface pour la machinerie de cryptage.6 La méthode has_password? testera si l'utilisateur possède le même mot de passe que celui soumis lors de l'inscription (sera écrit au chapitre 9) ; un squelette de méthode pour has_password? est présenté dans l'extrait 7.7.

Extrait 7.7. Une méthode has_password? pour les utilisateurs.
app/models/user.rb
class User < ActiveRecord::Base
  .
  .
  .
  before_save :encrypt_password

  # Retour true (vrai) si le mot de passe correspond.
  def has_password?(password_soumis)
    # Compare encrypted_password avec la version cryptée de
    # password_soumis.
  end

  private
  .
  .
  .
end

Avec cette méthode, nous pouvons écrire des tests comme dans l'extrait 7.8, qui utilise les méthodes RSpec be_true (être_vrai) et be_false (être_faux) pour tester que has_password? retourne true ou false dans les cas appropriés.

Extrait 7.8. Tests pour la méthode has_password?.
spec/models/user_spec.rb
describe User do
  .
  .
  .
  describe "Cryptage du mot de passe" do

    before(:each) do
      @user = User.create!(@attr)
    end
    .
    .
    .
    describe "Méthode has_password?" do

      it "doit retourner true si les mots de passe coïncident" do
        @user.has_password?(@attr[:password]).should be_true
      end    

      it "doit retourner false si les mots de passe divergent" do
        @user.has_password?("invalide").should be_false
      end 
    end
  end
end

À la section 7.2.3, nous compléterons l'implémentation de la méthode has_password? (et obtiendrons que le test réussisse dans le processus). Mais d'abord nous avons besoin d'en apprendre un peu plus sur la sécurisation des mots de passe.

7.2.2 Un peu de théorie sur la sécurisation des mots de passe

L'idée de base d'un mot de passe crypté est simple : plutôt que d'enregistrer un mot de passe « en clair » dans la base de données (donc tel quel), nous enregistrons une chaine de caractères générée en utilisant une fonction de hachage cryptographique (cryptographic hash function), qui est par essence irréversible, de telle sorte que même un hacker en possession de la version cryptée du mot de passe sera incapable d'en déduire l'original. Pour vérifier qu'un mot de passe soumis coïncide avec le mot de passe de l'utilisateur, nous cryptons d'abord la chaine de caractères soumise puis nous la comparons au mot de passe crypté enregistré. Rejoignons une session de console pour voir comment tout cela fonctionne :

$ rails console
>> require 'digest'
>> def secure_hash(string)
>>   Digest::SHA2.hexdigest(string)
>> end
=> nil
>> password = "secret"
=> "secret"
>> encrypted_password = secure_hash(password)
=> "2bb80d537b1da3e38bd30361aa855686bde0eacd7162fef6a25fe97bf527a25b"
>> submitted_password = "secret"
=> "secret"
>> encrypted_password == secure_hash(submitted_password)
=> true

Ici nous avons défini une fonction appelée secure_hash qui utilise une fonction de hachage cryptographique appelée SHA2, qui fait partie de la famille SHA des fonctions de hachage, que nous incluons dans Ruby par la librairie digest.7 Il n'est pas utile de savoir exactement comment fonctionnent ces fonctions ; ce qui est important pour notre propos est qu'elles sont à sens unique : il n'y a aucune façon informatisable de découvrir que :

2bb80d537b1da3e38bd30361aa855686bde0eacd7162fef6a25fe97bf527a25b

… est le hachage SHA2 de la chaine "secret".

Si vous y réfléchissez, cependant, nous avons toujours un problème : si un hacker entrait en possession des mots de passe hachés, il pourrait quand même avoir une chance de découvrir les mots de passe originaux. Par exemple, il pourrait supposer que nous utilisons SHA2 et ainsi écrire un programme pour comparer un hachage donné avec les valeurs possibles des mots de passe :

>> hash = "2bb80d537b1da3e38bd30361aa855686bde0eacd7162fef6a25fe97bf527a25b"
>> secure_hash("secede") == hash
=> false
>> secure_hash("second") == hash
=> false
>> secure_hash("secret") == hash
=> true

Donc notre hacker obtient une correspondance — mauvaise nouvelle pour les utilisateurs qui utilisent le mot de passe "secret". Cette technique est connue sous le nom rainbow attack (attaque arc-en-ciel).

Pour faire échouer une attaque arc-en-ciel éventuelle, on peut utiliser un salt (un sel, ou un grain de sel), une chaine de caractères qui sera différente pour chaque utilisateur.8 Une façon courante de s'assurer l'unicité (ou presque) est de hacher le temps courant (en UTC pour être indépendant de la zone de temps) avec le mot de passe, de telle sorte que deux utilisateurs auraient le même sel seulement s'ils s'étaient inscrits exactement en même temps et avaient exactement le même mot de passe (ce qui, en théorie, est improbable, et en pratique, impossible). Voyons comment cela fonctionne en utilisant la fonction secure_hash définie plus haut à la console :

>> Time.now.utc
=> Fri Jan 29 18:11:27 UTC 2010
>> password = "secret"
=> "secret"
>> salt = secure_hash("#{Time.now.utc}--#{password}")
=> "d1a3eb8c9aab32ec19cfda810d2ab351873b5dca4e16e7f57b3c1932113314c8"
>> encrypted_password = secure_hash("#{salt}--#{password}")
=> "69a98a49b7fd103058639be84fb88c19c998c8ad3639cfc5deb458018561c847"

Dans les dernières lignes, nous avons haché le sel avec le mot de passe, pour retourner un mot de passe crypté qu'il est virtuellement impossible de cracker (pour la clarté, les arguments des fonctions de hachage sont souvent séparés par des « -- »).

7.2.3 Implémenter has_password?

En ayant fini avec la théorie, nous sommes maintenant en mesure de procéder à l'implémentation. Nous allons aller de l'avant pour voir où nous allons. Chaque objet utilisateur connait son propre mot de passe crypté, donc pour le vérifier avec le mot de passe soumis, nous pouvons définir has_password? comme suit :

def has_password?(password_soumis)
  encrypted_password == encrypt(password_soumis)
end

Puisque nous cryptons le mot de passe soumis en utilisant le même sel que celui utilisé pour le mot de passe original, cette fonction retournera vrai si et seulement si le mot de passe soumis correspond.

Puisque comparer le mot de passe d'un utilisateur avec un mot de passe soumis implique de crypter le mot de passe soumis avec le sel, nous avons besoin d'enregistrer ce sel quelque part, donc la première étape va consister à ajouter une colonne salt à la table users :

$ rails generate migration add_salt_to_users salt:string

Comme avec la migration encrypted_password (section 7.1.2), cette migration porte un nom qui se finit par _to_users et passe un second argument contenant le nom de l'attribut et son type (« String » ici, chaine de caractères), donc Rails construit automatiquement la bonne migration (extrait 7.9).

Extrait 7.9. La migration pour ajouter la colonne salt à la table users.
db/migrate/<timestamp>_add_salt_to_users.rb
class AddSaltToUsers < ActiveRecord::Migration
  def self.up
    add_column :users, :salt, :string
  end

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

Nous migrons alors la base de données et préparons le test de la base de données comme d'habitude :

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

Le résultat est une base de données avec le modèle de données correspondant à l'illustration 7.3.

user_model_salt
Illustration 7.3: Le modèle User avec un sel ajouté.

Nous sommes enfin prêts pour l'implémentation complète. Quand nous avons vu la dernière fois la fonction encrypt (extrait 7.6), elle ne faisait rien d'autre que retourner la chaine de caractère qu'elle recevait en argument. Dans l'idée de la section 7.2.2, nous sommes en mesure maintenant de renvoyer plutôt un hachage sécurisé (extrait 7.10).9

Extrait 7.10. La méthode has_password? avec un cryptage sécurisé.
app/models/user.rb
require 'digest'
class User < ActiveRecord::Base
  .
  .
  .
  before_save :encrypt_password

  def has_password?(password_soumis)
    encrypted_password == encrypt(password_soumis)
  end

  private

    def encrypt_password
      self.salt = make_salt if new_record?
      self.encrypted_password = encrypt(password)
    end

    def encrypt(string)
      secure_hash("#{salt}--#{string}")
    end

    def make_salt
      secure_hash("#{Time.now.utc}--#{password}")
    end

    def secure_hash(string)
      Digest::SHA2.hexdigest(string)
    end
end

Ce code contient les deux mêmes subtilités que celles mentionnées à la section 7.1.3, nommément, l'assignement d'un attribut Active Record à l'aide de self à la ligne :

self.salt = make_salt if new_record?

… et l'omission du mot-clé self dans le membre droit de ligne de code de la méthode encrypt :

def encrypt(string)
  secure_hash("#{salt}--#{string}")
end

Puisque nous sommes à l'intérieur de la classe, Ruby sait que salt se réfère à l'attribut salt de l'utilisateur.

Il est important de noter aussi l'utilisation de la méthode booléenne de l'Active Record new_record?, qui retourne true (vrai) si l'objet n'a pas encore été enregistré dans la base de données. Puisque le sel est un identifiant unique pour chaque utilisateur, nous ne voulons pas qu'il change chaque fois que l'utilisateur sera actualisé (comme à la section 10.1), et en incluant new_record? nous nous assurons que le salt sera créé seulement une fois, à la création de l'utilisateur10 (cette subtilité n'importe pas pour le moment, mais elle importera quand nous implémenterons une fonctionnalité « se souvenir de moi » à l'identification à la section 9.3.2).

À ce stade, les tests de l'extrait 7.8 devraient réussir :

$ rspec spec/models/user_spec.rb -e "doit retourner true si les mots de passe coïncident"
.

1 example, 0 failures

$ rspec spec/models/user_spec.rb \
> -e "doit retourner false si les mots de passe divergent"
.

1 example, 0 failures

Nous pourrions aussi jouer tous les exemples dans un bloc describe particulier, mais nous devons prendre soin d'échapper tous les caractères spéciaux dans les expressions régulières — dans ce cas, le point d'interrogation « ? » de la méthode "has_password?" :

$ rspec spec/models/user_spec.rb -e "Méthode has_password\?"
Run filtered using {:full_description=>/(?-mix:Méthode has_password\?)/}
..

2 examples, 0 failures

L'échappement (« \ ») avant le point d'interrogation assure que l'interpréteur d'expressions régulières RSpec interprète la chaine de caractère correctement, ce qui jouera les tests associés au bloc describe donné.

7.2.4 Une méthode d'authentification

Avoir une méthode has_password? pour chaque utilisateur est une bonne chose, mais en elle-même elle n'est pas très utile. Nous terminons notre discussion sur les mots de passe en utilisant has_password? pour écrire une méthode pour identifier un utilisateur par une combinaison email/mot de passe. Au chapitre 9, nous utiliserons cette méthode authenticate en identifiant les utilisateurs de notre site.

Nous pouvons avoir une idée de ce fonctionnement à l'aide de la console. D'abord, nous créons un utilisateur, et ensuite nous récupérons cet utilisateur par l'adresse-mail pour vérifier qu'il a un mot de passe donné :11

$ rails console --sandbox
>> User.create!(:nom => "Michael Hartl", :email => "mhartl@example.com",
?>              :password => "foobar", :password_confirmation => "foobar")
>> user = User.find_by_email("mhartl@example.com")
>> user.has_password?("foobar")
=> true

En utilisant ces idées, écrivons une méthode qui retournera un utilisateur identifié si les mots de passe correspondent, et nul (nil) dans le cas contraire. Nous devrions pouvoir utiliser la méthode de classe authenticate en procédant ainsi :

User.authenticate(email, submitted_password)

Nous commençons par les tests, que nous utiliserons pour spécifier le comportement que nous attendons de User.authenticate. Il y a trois choses à vérifier : authenticate (1) devrait retourner nil quand la combinaison email/mot de passe est invalide ou (2) quand aucun utilisateur n'existe avec l'adresse mail fournie, et (3) devrait retourner l'objet utilisateur lui-même en cas de succès. Avec ces informations, nous pouvons écrire les tests de authenticate comme dans l'extrait 7.11.

Extrait 7.11. Tests for the User.authenticate method.
spec/models/user_spec.rb
describe User do
  .
  .
  .
  describe "password encryption" do
    .
    .
    .
    describe "authenticate method" do

      it "devrait retourner nul en cas d'inéquation entre email/mot de passe" do
        wrong_password_user = User.authenticate(@attr[:email], "wrongpass")
        wrong_password_user.should be_nil
      end

      it "devrait retourner nil quand un email ne correspond à aucun utilisateur" do
        nonexistent_user = User.authenticate("bar@foo.com", @attr[:password])
        nonexistent_user.should be_nil
      end

      it "devrait retourner l'utilisateur si email/mot de passe correspondent" do
        matching_user = User.authenticate(@attr[:email], @attr[:password])
        matching_user.should == @user
      end
    end
  end
end

Nous sommes prêts maintenant pour l'implémentation, ce qui fera réussir nos tests et nous montrera comment définir en bonus une méthode de classe. Nous avons mentionné les méthodes de classe plusieurs fois auparavant, tout récemment à la section 6.1.1 ; une méthode de classe est simplement une méthode attachée à une classe, plutôt qu'à une instance de cette classe. Par exemple, new (nouveau), find (trouver) et find_by_mail (trouver_par_le_mail) sont toutes des méthodes de classe d'une classe User. En dehors de la classe, elles sont invoquées en utilisant le nom de la classe, comme dans User.find, mais à l'intérieur de la classe nous pouvons omettre le nom de la classe.

La façon de définir une méthode de classe est d'utiliser le mot-clé self dans la définition de la méthode (ce self n'est pas le même que le self montré dans l'extrait 7.10 ; voyez le Box 7.1.) L'extrait 7.12 montre cette construction dans le contexte de la méthode authenticate. Notez l'appel à find_by_email, dans laquelle nous omettons le nom explicite de classe User puisque cette méthode est déjà à l'intérieur de la classe User.

Extrait 7.12. La méthode User.authenticate.
app/models/user.rb
class User < ActiveRecord::Base
  .
  .
  .
  def has_password?(submitted_password)
    encrypted_password == encrypt(submitted_password)
  end

  def self.authenticate(email, submitted_password)
    user = find_by_email(email)
    return nil  if user.nil?
    return user if user.has_password?(submitted_password)
  end

  private
  .
  .
  .
end

Il existe plusieurs façons équivalentes d'écrire la méthode authenticate, mais je trouve l'implémentation ci-dessus la plus claire. Elle traite deux cas (email invalide et correspondance exacte) avec le mot-clé explicite return, et traite le troisième cas (inadéquation du mot de passe) implicitement, puisque dans ce cas nous atteignons la fin de la méthode, ce qui retourne automatiquement la valeur nil (nulle). Voyez la section 7.5 pour quelques unes des autres manières possibles d'implémenter cette méthode.

7.3 Meilleures vues d'utilisateurs

Maintenant que le modèle User est effectivement achevé,12 nous sommes en mesure d'ajouter un exemple d'utilisateur à la base de données de développement et de faire une page show (montrer, afficher) pour afficher certaines des informations de l'utilisateur. Chemin faisant, nous ajouterons quelques tests au spec du contrôleur User que nous avons entamé à la section 5.3.1.

Avant de poursuivre, voyons où nous en sommes restés concernant le spec du contrôleur User (extrait 7.13). Nos tests pour la page d'affichage de l'utilisateur suivront cet exemple, mais nous devons comprendre que contrairement aux tests de l'action new les tests de l'action show exigeront l'utilisation d'instances du modèle User. Cela sera rendu possible grâce à une technique appelée factories (usines).

Extrait 7.13. Le spec du contrôleur User dans son état actuel.
spec/controllers/users_controller_spec.rb
require 'spec_helper'

describe UsersController do
  render_views

  describe "GET 'new'" do

    it "devrait réussir" do
      get 'new'
      response.should be_success
    end

    it "devrait avoir le bon titre" do
      get 'new'
      response.should have_selector("title", :content => "Sign up")
    end
  end
end

7.3.1 Tester la page d'affichage de l'utilisateur (avec factories)

Les tests pour le contrôleur User auront besoin des objets instances du modèle User, avec de préférence des valeurs pré-définies (pour ne pas avoir à les rentrer toutes « à la main »). Par exemple, comme vu à l'extrait 7.14, l'action show du contrôleur Users a besoin d'une instance de la classe User, donc les tests de cette action vont exiger que nous créions d'une manière ou d'une autre une variable @user. Nous allons accomplir cela avec un utilisateur d'usine, qui est une façon pratique de définir un objet utilisateur et de l'insérer à l'intérieur de notre base de données de test.13

Extrait 7.14. L'action utilisateur show de l'extrait 6.25.
app/controllers/users_controller.rb
class UsersController < ApplicationController

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

Nous allons utiliser les données d'usine générées par Factory Girl (Fille d'Usine),14, un gem Ruby produit par les gens valeureux de thoughtbot. Comme pour les autres gems Ruby, vous pouvez l'installer en ajoutant une ligne à votre Gemfile utilisé par Bundler (extrait 7.15) (puisque Factory Girl est nécessaire seulement pour les tests, nous l'incluons dans le groupe :test).

Extrait 7.15. Ajout de Factory Girl dans le Gemfile.
source 'http://rubygems.org'
.
.
.
group :test do
  .
  .
  .
  gem 'factory_girl_rails', '1.0'
end

Puis installez-le comme d'habitude :

$ bundle install

Nous sommes maintenant prêts à créer le fichier spec/factories.rb et à définir un utilisateur d'usine, comme le montre l'extrait 7.16. En plaçant le fichier factories.rb dans le dossier spec/, nous nous arrangeons pour que RSpec charge factories automatiquement chaque fois que nous jouons les tests.

Extrait 7.16. Une factory pour simuler des objets de modèle User.
spec/factories.rb
# En utilisant le symbole ':user', nous faisons que
# Factory Girl simule un modèle User.
Factory.define :user do |user|
  user.nom                  "Michael Hartl"
  user.email                 "mhartl@example.com"
  user.password              "foobar"
  user.password_confirmation "foobar"
end

Avec la définition de l'extrait 7.16, nous pouvons créer un User d'usine dans les tests comme cela :

@user = Factory(:user)

Comme l'indique le commentaire de la première ligne de l'extrait 7.16, en utilisant le symbole :user nous nous assurons que Factory Girl devinera que nous voulons utiliser un modèle User, donc dans ce cas @user simulera une instance de la classe User.

Pour utiliser notre nouvel Utilisateur d'usine dans le spec du contrôleur Users, nous allons créer une variable @user dans le bloc before(:each) et ensuite get (demander) la page d'affichage et vérifier la réussite (tout comme nous l'avons fait avec la page new de l'extrait 7.13), tout en vérifiant aussi que l'action show récupère l'utilisateur correct de la base de données. Le résultat est présenté dans l'extrait 7.17 (si vous utilisez Spork, vous avez peut-être à le redémarrer pour obtenir la réussite de ces tests).

Extrait 7.17. Un test pour getting (obtenir ) la page utilisateur show, avec un utilisateur d'usine.
spec/controllers/users_controller_spec.rb
require 'spec_helper'

describe UsersController do
  render_views

  describe "GET 'show'" do

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

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

    it "devrait trouver le bon utilisateur" do
      get :show, :id => @user
      assigns(:user).should == @user
    end
  end
  .
  .
  .
end

Hormis l'utilisation d'une usine, la réelle nouveauté ici est l'utilisation de assigns(:user), qui est une fonctionnalité fournie par RSpec (via la librairie sous-jacente Test::Unit). La méthode assigns prend un argument symbolique (ici :user) et retourne la valeur qu'a la variable d'instance correspondante (ici @user — :user => @user) dans l'action concernée, ici l'action show du contrôleur User. En d'autres termes, dans l'extrait 7.17 le code :

assigns(:user)

… retourne la valeur de la variable d'instance :

@user

… dans l'action show du contrôleur des utilisateurs. Le test :

assigns(:user).should == @user

… vérifie alors que la variable récupérée de la base de données dans l'action correspond à l'instance @user créée par Factory Girl. Il est important de noter que tous les programmeurs Rails n'utilisent pas assigns dans ce contexte, lui préférant parfois la technique appelée le stubbing (Box 7.2).

Il reste deux autres détails dans l'extrait 7.17 qu'il convient de souligner. D'abord, dans l'appel de get, le test utilise le symbole :show au lieu de la chaine ’show’, ce qui est différent de la convention des autres tests (par exemple, dans l'extrait 3.11 nous avons écrit get ’home’). Les deux,

get :show

… et

get 'show'

… font la même chose, mais en testant les actions REST canoniques (Table 6.2) je préfère utiliser des symboles, ce qui pour d'évidentes raisons semble plus naturel dans ce contexte.15 Ensuite, notez que la valeur de la clé de hachage :id, au lieu d'être l'attribut id de l'utilisateur @user, est l'objet user lui-même :

get :show, :id => @user

Nous pourrions tout autant utiliser le code :

get :show, :id => @user.id

… pour accomplir la même chose, mais dans ce contexte Rails convertit automatiquement l'objet user vers l'id correspondant.16 Utiliser la construction plus succincte :

get :show, :id => @user

… est un idiome Rails très fréquent.

À cause du code que nous avons ajouté dans l'extrait 6.25, le test de cette section réussit déjà. Si vous êtes un peu paranoïaque, vous pouvez ex-commenter la ligne :

@user = User.find(params[:id])

… et vérifier que le test échoue, puis dé-commenter pour réussir à nouveau (nous sommes passés par le même processus une fois auparavant, à la section 6.2.1.)

7.3.2 Un nom et un Gravatar

Dans cette section, nous allons améliorer le look de notre page d'affichage de l'utilisateur en ajoutant une entête avec le nom de l'utilisateur et une image de profil. C'est une de ces situations où il est très difficile de prévoir les choses, et donc difficile d'utiliser le « Développement Dirigé par les Tests », et souvent en construisant les vues j'expérimenterai le code HTML avant de me soucier des tests. Poursuivons pourtant pour le moment avec le TDD, et testons une entête de haut niveau (balise h1) contenant le nom de l'utilisateur et une balise img de classe gravatar (nous expliquerons dans un instant le sens de cette seconde partie).

Pour voir une page d'affichage de l'utilisateur fonctionner dans le navigateur, nous aurons besoin de créer un exemple d'utilisateur dans la base de données en mode développement. Pour ce faire, démarrons la console (pas dans le bac à sable cette fois) et créons un utilisateur :

$ rake db:reset
$ rails console
>> User.create!(:nom => "Utilisateur exemple", :email => "user@example.com",
?>              :password => "foobar", :password_confirmation => "foobar")

Les tests de cette section sont similaires aux tests de la page new vus dans l'extrait 5.26. En particulier, nous utilisons la méthode have_selector pour vérifier le titre et le contenu de la balise h1, comme le montre l'extrait 7.18.

Extrait 7.18. Tests pour la page d'affichage de l'utilisateur.
spec/controllers/users_controller_spec.rb
require 'spec_helper'

describe UsersController do
  render_views

  describe "GET 'show'" do
    .
    .
    .
    it "devrait avoir le bon titre" do
      get :show, :id => @user
      response.should have_selector("title", :content => @user.nom)
    end

    it "devrait inclure le nom de l'utilisateur" do
      get :show, :id => @user
      response.should have_selector("h1", :content => @user.nom)
    end

    it "devrait avoir une image de profil" do
      get :show, :id => @user
      response.should have_selector("h1>img", :class => "gravatar")
    end
  end
  .
  .
  .
end

Ici la méthode RSpec have_selector vérifie la présence des balises title (titre) et h1 contenant le nom de l'utilisateur. Le troisième exemple introduit un nouvel élément par le code h1>img, qui fait que la balise img est à l'intérieur de la balise h1..17 De plus, nous voyons que have_selector peut prendre une option :class pour tester la classe CSS de l'élément en question.

Nous pouvons obtenir la réussite du test en définissant la variable @titre à utiliser dans l'helper titre (section 4.1.1), dans ce cas en l'assignant à la valeur du nom de l'utilisateur (extrait 7.19).

Extrait 7.19. Un titre pour la page d'affichage de l'utilisateur.
app/controllers/users_controller.rb
class UsersController < ApplicationController

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

Ce code introduit un problème potentiel : un utilisateur peut entrer un nom avec du code malveillant — appelé attaque cross-site scripting — qui serait injecté à l'intérieur de l'application par l'helper titre défini dans l'extrait 4.2. Avant Rails 3, la solution consistait à échapper les codes potentiellement problématiques en utilisant la méthode h (raccourci pour html_escape), mais avec Rails 3.0, tout le code Ruby embarqué est maintenant échappé par défaut.18 Par exemple, si un utilisateur essayait d'injecter un programme JavaScript malveillant en utilisant <script> dans son nom, l'échappement HTML automatique le convertirait en &lt;script&gt;, le rendant complètement inoffensif.

Voyons maintenant les autres tests. Créer un h1 à partir du nom de l'utilisateur (échappé) est facile (extrait 7.20).

Extrait 7.20. La page de profil avec un nom d'utilisateur.
app/views/users/show.html.erb
<h1>
  <%= @user.nom %>
</h1>

Faire réussir le test de img est plus astucieux. La première étape consiste à installer le gem gravatar_image_tag pour traiter chaque Gravatar d'utilisateur,19 qui est un « avatar reconnu globalement ».20 Comme d'habitude, nous inclurons la dépendance gem dans le fichier Gemfile (extrait 7.21).

Extrait 7.21. Ajout du gem Gravatar au Gemfile.
source 'http://rubygems.org'

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

Installez-le avec bundle :

$ bundle install

Vous devrez peut-être aussi redémarrer votre serveur web ici pour charger le nouveau gem Gravatar proprement.

Les gravatars sont une façon pratique d'inclure des images de profil utilisateur sans avoir à gérer la difficulté des téléchargement d'images, leur redimensionnement et leur sauvegarde.21 Chaque gravatar est associé à une adresse email, donc le gem Gravatar est fourni avec une méthode d'helper appelée gravatar_image_tag qui prend une adresse mail comme argument :

<%= gravatar_image_tag 'example@railstutorial.org' %>

Pour le moment, nous utilisons cela directement dans notre vue affichant l'utilisateur, comme le montre l'extrait 7.22 (nous construirons une méthode helper dans un moment). Le résultat apparait dans l'illustration 7.4, qui montre notre exemple avec l'image gravatar par défaut.

Extrait 7.22. La vue d'affichage de l'utilisateur avec un nom et un gravatar.
app/views/users/show.html.erb
<h1>
  <%= gravatar_image_tag @user.email %>
  <%= @user.nom %>
</h1>
user_show_default_gravatar_rails_3
Illustration 7.4: La page initiale d'affichage de l'utilisateur /users/1 avec le gravatar par défaut. (taille normale)

Ce fonctionnement de Gravatar peut sembler magique, donc rejoignons la console pour comprendre un peu mieux ce qui se passe :

$ rails console
>> user = User.first
>> user.update_attributes(:email => "example@railstutorial.org",
?>                        :password => "foobar",
?>                        :password_confirmation => "foobar")
=> true

Notez que nous pouvons récupérer le premier (et, à ce point, le seul) utilisateur de la base de données avec la méthode pratique User.first. Dans l'étape update_attributes nous avons ré-assigné l'adresse mail de l'utilisateur, en la changeant en example@railstutorial.org. Comme vous pouvez le voir dans l'illustration 7.5, ce changement produit l'affichage d'un nouveau gravatar : le logo du tutoriel Rails. Ce qui se passe, c'est que Gravatar travaille en associant image et adresse email ; puisque user@example.com est une adresse invalide (le domaine example.com est réservé aux exemples), il n'y a pas de gravatar associé à cette adresse. Mais à mon compte Gravatar j'ai associé l'adresse example@railstutorial.org au logo du tutoriel Rails, donc en actualisant l'utilisateur exemple avec l'adresse mail, le gravatar change automatiquement.

user_show_railstutorial_gravatar_rails_3
Illustration 7.5: La page d'affichage de l'utilisateur /users/1 avec le gravatar du Tutoriel Rails. (taille normale)

Un helper Gravatar

À ce stade, le gravatar s'affiche proprement, mais l'exemple final de l'extrait 7.18 ne réussit toujours pas. C'est parce que la classe CSS "gravatar", que nous voulons utiliser pour styliser les gravatars en CSS, n'est pas encore spécifier dans la balise img du gravatar. Pour faire réussir le test, nous devons ajouter l'option adéquate à la méthode gravatar_image_tag :

<%= gravatar_image_tag @user.email, :class => "gravatar" %>

D'un autre côté, puisque nous anticipons le fait que les gravatars apparaitront à différents endroits de notre application, il serait répétitif de mettre la classe chaque fois à la main. Ce serait mieux de faire une méthode d'helper qui élimine préventivement cette duplication..

Cette situation peut vous rappeler de la répétition de la base du titre (« Simple application du Tutoriel Ruby on Rails »), que nous avons résolue avec l'helper titre dans l'helper de l'application (extrait 4.2). La solution ici est la même ; puisque les gravatars sont naturellement associés aux utilisateurs, nous allons définir une méthode gravatar_for (gravatar_pour) dans le helper Users (le choix d'utiliser le helper Users plutôt que le helper Application est juste une commodité conceptuelle ; Rails rend tous les helpers accessibles dans les vues). Le résultat sera un code concis comme :

<%= gravatar_for @user %>

L'helper gravatar_for devrait prendre un objet user et passer alors quelques options par défaut à l'helper gravatar_image_tag. L'implémentation apparait dans l'extrait 7.23.

Extrait 7.23. Définir une méthode d'helper gravatar_for.
app/helpers/users_helper.rb
module UsersHelper

  def gravatar_for(user, options = { :size => 50 })
    gravatar_image_tag(user.email.downcase, :alt => user.nom,
                                            :class => 'gravatar',
                                            :gravatar => options)
  end
end

Le premier argument dans l'appel à gravatar_image_tag passe la version minuscule de l'adresse mail (en utilisant la méthode downcase).22 Ensuite la première option (deuxième argument) de gravatar_image_tag assigne le nom de l'utilisateur à l'attribut alt de la balise img (qui sera affiché sur les dispositifs qui ne peuvent pas rendre les images), tandis que la deuxième option définit la classe CSS pour le gravatar. La troisième option passe une table d'options en utilisant la clé :gravatar, qui (conformément à la documentation gem pour gravatar_image_tag) est utilisée pour définir les options pour gravatar_image_tag. Notez que la définition de la fonction définit une options par défaut23 pour la taille du gravatar24 en utilisant :

option = { :size => 50 }

Cela fixe la taille par défaut du gravatar à 50x50, ce qui nous permettra également de redéfinir la taille par défaut en utilisant un code comme :

<%= gravatar_for @user, :size => 30 %>

Si nous actualisons à présent le template d'affichage de l'utilisateur avec le code de l'extrait 7.24, la page d'affichage de l'utilisateur apparaitra comme dans l'illustration 7.6. Et puisque l'helper gravatar_for assigne à la balise img la classe CSS "gravatar", les tests de l'extrait 7.18 devraient maintenant réussir.

Extrait 7.24. Actualiser le template de l'affichage de l'utilisateur en utilisant gravatar_for.
app/views/users/show.html.erb
<h1>
  <%= gravatar_for @user %>
  <%= @user.nom %>
</h1>
user_show_gravatar_for
Illustration 7.6: La page d'affichage de l'utilisateur avec gravatar_for(taille normale)

7.3.3 Une barre utilisateur latérale

Même si nos tests réussissent à présent, et que la page d'affichage de l'utilisateur s'est considérablement améliorée, il est encore possible de la polisser un petit peu plus. Dans l'extrait 7.25, nous avons une balise table avec un rangée tr et deux cellules td.25

Extrait 7.25. Ajout d'une barre latérale pour la vue show de l'utilisateur.
app/views/users/show.html.erb
<table class="profile" summary="Information profil">
  <tr>
    <td class="main">
      <h1>
        <%= gravatar_for @user %>
        <%= @user.nom %>
      </h1>
    </td>
    <td class="sidebar round">
      <strong>Nom</strong> <%= @user.nom %><br />
      <strong>URL</strong>  <%= link_to user_path(@user), @user %>
    </td>
  </tr>
</table>

Ici nous avons utilisé la balise HTML <br /> pour forcer un retour à la ligne entre le nom de l'utilisateur et l'URL. Notez aussi l'utilisation de user_path pour créer un lien cliquable qui permet aux utilisateurs de partager facilement l'URL de leur profil. C'est seulement le première d'un grand nombre de routes nommées (section 5.2.2) associées à la ressource User (extrait 6.26) ; nous en verrons beaucoup plus dans les prochains chapitres.

Le code :

user_path(@user)

… retourne le path (chemin d'accès) de l'utilisateur, dans ce cas /users/1.

Le code lié :

user_url(@user)

… retourne l'URL entière, http://localhost:3000/users/1 (comparez avec les routes créées à la section 5.2.2.). Les deux sont des exemples de routes nommées créées par la ressource utilisateurs de l'extrait 6.26 ; une liste de toutes les routes nommées apparait dans la Table 7.1.

Route nomméChemin
users_path/users
user_path(@user)/users/1
new_user_path/users/new
edit_user_path(@user)/users/1/edit
users_urlhttp://localhost:3000/users
user_url(@user)http://localhost:3000/users/1
new_user_urlhttp://localhost:3000/users/new
edit_user_url(@user)http://localhost:3000/users/1/edit
Table 7.1: Routes nommées fournies par la ressource utilisateurs dans lextrait 6.26.

Notez que dans :

<%= link_to user_path(@user), @user %>

user_path(@user) est le lien texte, tandis que l'adresse est juste @user. Dans le contexte d'un link_to, Rails convertit @user en l'URL appropriée ; en d'autres termes, le code ci-dessus est équivalent au code :

<%= link_to user_path(@user), user_path(@user) %>

N'importe laquelle de ces formulations fonctionne bien, mais, comme dans l'idiome :id => @user de l'extrait 7.17, utiliser juste @user est une convention Rails courante. Dans les deux cas, le code Ruby embarqué produit le code HTML :

<a href="/users/1">/users/1</a>

Avec les éléments HTML et les classes CSS en place, nous pouvons styliser la page d'affichage avec le code CSS montré dans l'extrait 7.26. La page en résultant est montrée dans l'illustration 7.7.

Extrait 7.26. CSS pour styliser la page d'affichage de l'utilisateur, incluant la barre latérale.
public/stylesheets/custom.css
.
.
.
/* User show page */

table.profile {
  width: 100%;
  margin-bottom: 0;
}

td.main {
  width: 70%;
  padding: 1em;
}

td.sidebar {
  width: 30%;
  padding: 1em;
  vertical-align: top;
  background: #ffc;
}

.profile img.gravatar {
  border: 1px solid #999;
  margin-bottom: -15px;
}
user_show_sidebar_css
Illustration 7.7: La page d'affichage de l'utilisateur /users/1 avec une barre latérale et du CSS. (taille normale)

7.4 Conclusion

Dans ce chapitre, nous avons effectivement achevé le modèle User, donc nous sommes maintenant pleinement prêts pour inscrire de nouveaux utilisateurs et pour les laisser s'identifier de façon sécurisée avec une combinaison email/mot de passe. Plus encore, nous avons une belle première réduction de la page de profil de l'utilisateur, donc après l'identification les utilisateurs auront un endroit où aller.

7.4.1 Dépôt Git

Avant de poursuivre, nous devrions interrompre la boucle ouverte dans l'introduction du chapitre 6 en faisant un dépôt final pour la branche modeling-users et alors la fusionner avec la branche maitresse (master).26 D'abord, vérifiez que vous êtes bien sur la branche modeling-users :

$ git branch
  master
* modeling-users

Comme indiqué à la section 1.3.5.1, l'astérisque ici indique la branche courante, donc nous sommes vraiment prêts à déposer et à fusionner :27

$ git add .
$ git commit -m "Modele utilisateur avec mot de passe et page profil"
$ git checkout master
$ git merge modeling-users

7.4.2 déploiement Heroku

Si vous avez déployé votre application exemple sur Heroku, vous pouvez la « pusher » maintenant :

$ git push heroku

Migrez alors la base de données sur le serveur à distance en utilisant la commande heroku :

$ heroku rake db:migrate

Maintenant, si vous voulez créer un exemple d'utilisateur sur Heroku, vous pouvez utiliser la console Heroku :

$ heroku console
>> User.create!(:nom => "Utilisateur exemple", :email => "user@example.com",
?>              :password => "foobar", :password_confirmation => "foobar")

7.5 Exercices

  1. Copiez chaque variante de la méthode authenticate des extraits 7.27 à 7.31 dans le modèle User, et vérifiez qu'ils sont correct en jouant votre suite de tests.
  2. L'exemple final authenticate (extrait 7.31) relève un défi particulier. Expérimentez avec la console pour voir si vous pouvez comprendre comment il fonctionne.
  3. Comment pourriez obtenir que l'helper gravatar gravatar_for fonctionne si votre modèle User utilisait email_address au lieu de email pour représenter l'adresse email ?
Extrait 7.27. La méthode authenticate avec User à la place de self.
  def User.authenticate(email, submitted_password)
    user = find_by_email(email)
    return nil  if user.nil?
    return user if user.has_password?(submitted_password)
  end
Extrait 7.28. La méthode authenticate avec un troisième retour explicite.
  def self.authenticate(email, submitted_password)
    user = find_by_email(email)
    return nil  if user.nil?
    return user if user.has_password?(submitted_password)
    return nil
  end
Extrait 7.29. La méthode authenticate utilisant une déclaration if.
  def self.authenticate(email, submitted_password)
    user = find_by_email(email)
    if user.nil?
      nil
    elsif user.has_password?(submitted_password)
      user
    else
      nil
    end
  end
Extrait 7.30. La méthode authenticate utilisanat une déclaration if et un retour implicite.
  def self.authenticate(email, submitted_password)
    user = find_by_email(email)
    if user.nil?
      nil
    elsif user.has_password?(submitted_password)
      user
    end
  end
Extrait 7.31. La méthode authenticate utilisant l'opérateur ternaire.
  def self.authenticate(email, submitted_password)
    user = find_by_email(email)
    user && user.has_password?(submitted_password) ? user : nil
  end
  1. Nous avons vu précédemment les rangs à la section 4.3.1
  2. Pour plus de détails sur le type de fonctions de rappel supportées par Active Record, voyez la discussion sur les callbacks dans les guides Rails
  3. Le niveau supplémentaire d'indentation est une façon typographique de nous souvenir que nous sommes dans une section privée ; dans le cas contraire, il est facile de manquer le mot-clé private et d'être dérouté en essayant d'atteindre une méthode privée que vous pensez publique. Je pensais que cette convention d'indentation était stupide jusqu'à ce que je perde une heure sur ce simple problème il y a quelques années. Maintenant j'ajoute l'indentation… 
  4. Ruby possède aussi un mot-clé très proche, appelé protected (protégé) qui diffère subtilement de private. Autant que je peux dire, la seule raison d'apprendre la différence serait qu'on pourrait vous poser cette question au cours d'un entretien d'embauche : « En Ruby, quelle est la différence entre private et protected ? ». Mais qui aurait envie de travailler dans une compagnie qui poserait ce genre de question vicieuse ? Lors de son discours à RubyConf en 2008, Dave Thomas (auteur de Programmer Ruby) a suggéré d'éliminer protected des versions futures de Ruby, et j'abonde dans son sens. Utilisez simplement private et tout ira bien. 
  5. J'ai honte d'admettre que c'est ainsi que nous avons implémenté les mots de passe dans RailsSpace. Considérez que cette section est ma pénitence. 
  6. Le lecteur attentif peut noter que rien de ce que nous faisons dans cette section ne requiert le cryptage, mais, une fois que nous développons un peu de la théorie des mots de passe sécurisés et écrivons une implémentation basique (section 7.2.2), la seule façon pour la méthode has_password? de fonctionner proprement est que toute la machinerie de cryptage fonctionne elle aussi. 
  7. Dans mes réglages, la ligne require ’digest’ n'est pas nécessaire, mais plusieurs lecteurs m'ont reporté qu'ils rencontraient une erreur NameError s'ils ne l'incluaient pas explicitement. Ça n'entraine aucun dommage de toute façon, donc j'ai inclus explicitement require juste pour être sûr. 
  8. En théorie et techniquement, les attaques « arc-en-ciel » pourraient toujours réussir, mais en utilisant un « hachage salé », la réussite est informatiquement infaisable. 
  9. Comme indiqué à la section 7.2.2, la ligne explicite require ’digest’ n'est pas nécessaire sur certains systèmes, mais ça ne coûte rien de l'ajouter. 
  10. Dans les versions précédentes de Rails, nous pouvions utiliser la fonction de rappel after_validation_before_create pour définir le salt, mais elle a été supprimée dans Rails 3. En passant, nous ne pouvons pas utiliser la fonction de rappel before_create parce qu'elle s'exécute après la fonction de rappel before_save, et la fonction de rappel before_save a besoin du salt. 
  11. En vous souvenant du Box 6.2, l'indexation de la colonne email assurant que cette récupération serait efficace. 
  12. Nous projetterons d'ajouter une paire d'attributs supplémentaires (un pour identifier un utilisateur administrateur et un pour permettre la fonctionnalité « se souvenir de moi »), mais ils ne sont pas strictement nécessaires. Tous les attributs d'utilisateur essentiels ont maintenant été définis. 
  13. De nombreux programmeurs Rails expérimentés trouve que cette approche factory est bien plus flexible que les fixtures (appareils) que Rails utilise par défaut mais peuvent être fragiles et difficiles pour la maintenance. 
  14. Le nom « Factory Girl » fait peut-être référence au film éponyme
  15. J'ai utilisé get ’new’ dans l'extrait 5.24 et les tests suivants pour l'action new parce qu'à ce point nous n'avions pas encore rencontré l'idée des actions du concept REST. Je le remplacerai par get :new dans les tests futurs. 
  16. Il accomplit cela en appelant la méthode to_param sur la variable @user
  17. Ça n'est pas toujours nécessairement une bonne idée de créer des tests HTML aussi spécifiques, puisque nous ne voulons pas toujours contraindre aussi étroitement le layout HTML. Sentez-vous libre d'expérimenter et de trouver le bon niveau de détail pour vos projets conformément à vos goûts. 
  18. Ou plutôt, si vous voulez dés-échapper le texte vous devez utiliser la méthode raw (brut), comme dans <%= raw @titre %>
  19. Gravatar a été créé originellement par Tom Preston-Werner, co-fondateur de GitHub, et a été acquis et redimensionné par Automattic (plus connu comme les concepteurs de WordPress). 
  20. Dans l'hindouisme, un avatar est la manifestation d'une divinité dans une forme humaine ou animale. Par extension, le terme avatar est couramment utilisé pour signifier toute forme de représentation personnelle, spécialement dans un environnement virtuel. Mais vous avez dû voir le film maintenant, donc vous savez déjà ce que c'est. 
  21. Si votre application a besoin de traiter les images ou tout autre téléchargement de fichier, Paperclip est l'endroit où se rendre. Comme Factory Girl, Paperclip vous est offert par thoughtbot (bien que je connaisse quelques personnes là-bas, je n'ai aucun intérêt particulier à promouvoir thoughtbot ; ils font simplement de bons logiciels). 
  22. Merci au lecteur anonyme qui a noté que le plugin Gravatar est sensible à la casse dans ce contexte. 
  23. Il existe en fait un moyen de redéfinir la taille par défaut dans le fichier de configuration, mais j'ai trouvé ce moyen plus clair. 
  24. Les gravatars sont carrés, donc un seul paramètre suffit à définir la taille. 
  25. Si quelqu'un vous fait grief, horreur des horreurs, d'utiliser les tables pour la mise en forme, demandez-lui de pointer son inspecteur firebug à la barre latérale de profil de Twitter et de vous dire ce qu'il voit. En fait, vous trouverez que, tandis que le « marquage sémantique » utilisant des div et des span est de plus en plus courant, virtuellement tous les sites font appel à l'occasion au table pour faire de la mise en forme. Dans le cas présent, obtenir l'alignement vertical juste à droite est beaucoup plus facile avec les tables. 
  26. D'ordinaire, je recommande de faire des dépôts plus fréquents, plus petits, mais de fréquents dépôt Git tout au long de ce tutoriel serait difficile à maintenir et casserait trop souvent le fil de notre discussion. 
  27. Si vous n'êtes pas sur la bonne branche, jouer git checkout modeling-users avant de procéder.