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

Nous avons terminé le chapitre précédent en créant une « souche » de page pour l'inscription des utilisateurs (section 5.3) ; tout au long des trois prochains chapitres, nous allons nous attacher à tenir la promesse implicite que contient cette page. La première étape critique est de créer un modèle de données pour les utilisateurs de notre site, ainsi qu'une façon de conserver ces données. Accomplir cette tâche est le but de ce chapitre et du suivant (chapitre 7), et nous allons donner aux utilisateurs la possibilité de s'inscrire au chapitre 8. Une fois que l'Application Exemple pourra créer de nouveaux utilisateurs, nous leur offrirons la possibilité de s'identifier (de se connecter) et de se déconnecter (chapitre 9), et au chapitre 10 (section 10.2) nous apprendrons à protéger nos pages contre les accès malveillants.

Pris dans son ensemble, le matériel du chapitre 6 au chapitre 10 développe un système Rails complet d'identification (login) et d'authentification. Comme vous le savez peut-être, il existe une variété de solutions d'authentifications clé-en-main dans le monde Rails ; Le Box 6.1 explique pourquoi (au moins dans un premier temps) il est bon de composer la vôtre.

En parallèle de notre modélisation des données, nous développerons une page web pour afficher les utilisateurs, ce qui nous servira de première étape vers l'implémentation de l'architecture REST pour les utilisateurs (discutée brièvement à la section 2.2.2). Mais nous n'irons pas profondément dans ce chapitre, notre but final pour les pages de profil d'utilisateur étant de montrer l'avatar (le gravatar) de l'utilisateur, ses données basiques et une liste de micro-messages, comme le montre la maquette de l'illustration 6.1.1 (l'illustration 6.1 contient notre premier exemple de texte lorem ipsum, texte qui connait une histoire fascinante que vous devriez lire un jour). Dans ce chapitre, nous coucherons les fondations essentielles de notre page d'affichage de l'utilisateur, et nous entrerons dans ses détails au chapitre 7.

profile_mockup
Illustration 6.1: Une maquette approximative de la page de profil de l'utilisateur. (taille normale)

Comme d'habitude, si vous utilisez le contrôle de version Git, il est temps de faire une nouvelle branche pour la modélisation des utilisateurs :

$ git checkout master
$ git checkout -b modeling-users

(La première ligne ici sert uniquement à s'assurer que vous partiez bien de la branche maitresse, pour que la branche-sujet modeling-users parte bien de cette branche master. Vous pouvez ne pas jouer cette commande si vous vous trouvez déjà sur la branche maitressse.)

6.1 Modèle Utilisateur

Bien que le but ultime des trois prochains chapitres soit de créer une page d'inscription au site, il ne serait pas très judicieux d'accepter les informations d'inscription tout de suite puisque nous n'avons pour le moment aucun endroit où les stoker. Ainsi, la première étape dans l'inscription des utilisateurs va être de faire une structure de données pour capturer et consigner leurs informations. En Rails, la structure de données par défaut pour un modèle de donnée est appelée, assez naturellement, un modèle (model) (c'est le « M » de « MVC » de la section 1.2.6). La solution par défaut de Rails au problème de la persistance consiste tout simplement à utiliser une base de données qui consigne à long terme nos données, et la librairie Rails par défaut pour interagir avec cette base de données est appelée Active Record.2

La librairie Active Record est fournie avec une foule de méthodes pour créer, sauver et chercher des objets-données sans avoir à utiliser le langage structuré de requête (SQL)3 utilisé par les base de données relationnelles. Plus encore, Rails possède une fonctionnalité appelée migrations pour permettre aux définitions des données d'être écrites en pur Ruby sans avoir à apprendre le langage de définition des données SQL (DDL).4 L'effet est que Rails vous épargnent tous les détails de la consignation des données. Dans ce livre, en utilisant SQLite pour le développement et Heroku pour le déploiement (section 1.4), nous avons poussé ce sujet encore plus loin, jusqu'au point où nous n'avons plus du tout à nous pré-occuper de comment Rails consigne les données, même pour les applications en mode production.5

6.1.1 Migrations de la base de données

Vous vous souvenez peut-être, section 4.4.5, que nous avons déjà rencontré, via la construction personnalisée d'une classe User (Utilisateur), des objets utilisateur possédant les attributs nom et email. Cette classe a servi d'exemple utile, mais elle manquait de la propriété essentielle de persistance : quand nous avions créé un objet utilisateur via la console Rails, il disparaissait aussitôt que nous quittions cette console. Notre but dans cette section sera de créer un modèle pour les utilisateurs qui ne disparaisse pas aussi facilement.

Comme avec la classe User à la section 4.4.5, nous allons commencer par modéliser l'utilisateur avec deux attributs, nom et email, dont le dernier sera utilisé comme nom-utilisateur (username) unique.6 (nous ajouterons un attribut « mot de passe » à la section 7.1). Dans l'extrait 4.8, nous avons réalisé cela avec la méthode Ruby attr_accessor :

class User
  attr_accessor :nom, :email
  .
  .
  .
end

En utilisant Rails pour modéliser les utilisateurs nous n'avons pas besoin d'identifier explicitement les attributs. Comme noté brièvement plus haut, pour consigner les données, Rails utilise la base de données relationnelle par défaut, qui consiste en des tables composées de rangées de données, où chaque rangée possède des colonnes pour chaque attribut de la donnée. Par exemple, pour consigner des utilisateurs avec des noms et des emails, nous créerons une table users (utilisateurs) avec les colonnes nom et email dont chaque rangée correspondra à un utilisateur particulier. En nommant les colonnes de cette façon, nous laissons Active Record deviner par lui-même les attributs de l'objet utilisateur.

Voyons comment il fonctionne (si cette discussion vous semble trop abstraite, soyez patient ; les exemples en ligne de commande qui commencent à la section 6.1.3 et les copies d'écran du navigateur de base de données de l'illustration 6.3 et de l'illustration 6.8 devraient rendre les choses plus claires). À la section 5.3.1, vous vous souvenez (extrait 5.23) que nous avons créé un contrôleur pour les utilisateurs (avec une action new), en utilisant la commande :

$ rails generate controller Users new

Il existe une commande analogue pour faire un modèle : generate model ; l'extrait 6.1 montre la commande pour générer un modèle User (Utilisateur) avec deux attributs, nom et email.

Extrait 6.1. Générer un modèle User.
$ rails generate model User nom:string email:string
      invoke  active_record
      create    db/migrate/<timestamp>_create_users.rb
      create    app/models/user.rb
      invoke    rspec
      create      spec/models/user_spec.rb

(Notez que, contrairement à la convention du pluriel pour les noms de contrôleur, les noms des modèles sont singulier : un contrôleur Users mais un modèle User.) En passant les paramètres optionnels nom:string et email:string, nous précisons à Rails les deux attributs que nous voulons, en précisant le type de ces attributs (dans ce cas, le type string, c'est-à-dire chaine de caractère). Comparez cela avec l'inclusion des noms d'actions dans l'extrait 3.4 et l'extrait 5.23.

L'un des résultats de la commande generate de l'extrait 6.1 est un nouveau fichier appelé une migration. Les migrations fournissent un moyen d'altérer la structure de la base de données de façon incrémentielle, de telle sorte que notre modèle de données peut adapter progressivement les changements requis au cours du développement. Dans le cas du modèle User, la migration est créée automatiquement par le script de génération de modèle ; il crée une table users (utilisateurs) avec deux colonnes, nom et email, comme le montre l'extrait 6.2 (nous verrons à la section 6.2.4 comment faire une migration à partir de rien).

Extrait 6.2. Migration pour le modèle User (pour créer une table users).
db/migrate/<timestamp>_create_users.rb
class CreateUsers < ActiveRecord::Migration
  def self.up
    create_table :users do |t|
      t.string :nom
      t.string :email

      t.timestamps
    end
  end

  def self.down
    drop_table :users
  end
end

Notez que le nom de la migration est préfixé par un temps (timestamp) basé sur le temps de création de la migration. Dans les tout premiers jours des migrations, les noms de fichier étaient préfixés par un entier incrémenté, ce qui provoquait des conflits dans une équipe de collaborateurs quand les différents programmeurs se retrouvaient avec des numéros identiques. Sauf très improbable concordance de création à la milliseconde près, utiliser les timestamps évite efficacement de telles collisions.

Concentrons-nous sur la méthode self.up, qui utilise une méthode Rails appelée create_table (créer_table) pour créer une table dans la base de données pour consigner les utilisateurs (l'utilisation de self — soi-même — dans self.up l'identifie comme une méthode de classe. Ça n'a pas d'importance pour le présent, mais nous étudierons les méthodes de classe quand nous en inventerons à la section 7.2.4). La méthode create_table accepte un bloc (section 4.3.2) avec une variable de bloc, dans ce cas appelée t (« t » pour « table »). À l'intérieur du bloc, la méthode create_table utilise l'objet t pour créer les colonnes nom et email dans la base de données, tous deux de type string.7 Ici le nom de la table est pluriel (users) même si le nom du modèle, lui, est singulier, ce qui reflète la convention linguistique suivie par Rails : un modèle représente un simple utilisateur, tandis que la table de la base de données consiste en plusieurs utilisateurs. La dernière ligne du bloc, t.timestamps, est une commande spéciale qui crée deux colonnes magiques de nom created_at (créé_le… ) et updated_at (actualisé_le…), qui sont des signatures de temps qui s'enregistrent automatiquement quand un utilisateur donné est créé ou modifié (nous verrons des exemples concrets de ces colonnes magiques en abordant la section 6.1.3). Le modèle de données complet représenté par sa migration est affiché dans l'illustration 6.2.

user_model_initial
Illustration 6.2: Le modèle de données des utilisateurs produit par l'extrait 6.2.

Nous pouvons lancer la migration, opération connue sous le nom anglais « migration up » en utilisant la commande rake (Box 2.1) comme suit :8

$ rake db:migrate

(Vous pouvez vous souvenir que nous avons déjà eu à jouer cette commande, à la section 1.2.5 ou encore au chapitre 2.) La première fois que db:migrate est lancé, un fichier db/development.sqlite3 est créé (ou actualisé s'il existe déjà. NdT), qui est une base de données SQLite9. Nous pouvons voir la structure de la base de données en utilisant l'excellent Navigateur de données SQLite pour ouvrir le fichier db/development.sqlite3 (illustration 6.3) ; comparez-le avec le diagramme de l'illustration 6.2. Vous pouvez noter qu'il y a une colonne dans l'illustration 6.3 qui n'a pas été prise en compte par la migration : la colonne id (identifiant). Comme indiqué brièvement à la section 2.2, cette colonne est créée automatiquement, et elle est utilisée par Rails pour identifier de façon unique une rangée.

sqlite_database_browser
Illustration 6.3: The Navigateur de base de données SQLite avec notre nouvelle table users(taille normale)

Vous avez probablement deviné que lancer db:migrate exécute la commande self.up dans le fichier migration. Qu'en est-il, donc, du code self.down (soi-même.en-bas ) ? Comme vous pouvez le deviner, down migre vers le bas (c'est-à-dire vers le passé), inversant les effets de la migration. Dans notre cas, cela signifie détruire la table users de la base de données :

class CreateUsers < ActiveRecord::Migration
  .
  .
  .
  def self.down
    drop_table :users
  end
end

Vous pouvez exécuter down avec rake en utilisant l'argument db:rollback :

$ rake db:rollback

C'est souvent utile quand vous réalisez qu'il y a une autre colonne que vous voulez ajouter mais que vous ne voulez pas vous embêter à faire une nouvelle migration : vous pouvez revenir en arrière, ajouter la colonne souhaitée, et reprocéder alors à la migration (ce n'est pas toujours pratique, et nous apprendrons comment ajouter autrement une colonne à une table existante à la section 7.1.2).

Si vous êtes revenu en arrière pour la base de données, procédez à nouveau à sa migration :

$ rake db:migrate

6.1.2 Le fichier modèle

Nous avons vu comment la génération du modèle User de l'extrait 6.1 générait un fichier de migration (extrait 6.2), et nous avons vu dans l'illustration 6.3 les résultats d'opérer la migration : cela actualise un fichier appelé development.sqlite3 en créant une table users avec les colonnes id, nom, email, created_at et updated_at. L'extrait 6.1 a créé aussi le modèle lui-même ; la suite de cette section est dédiée à l'explicitation de ce modèle.

Nous commençons par regarder le code du modèle User, qui se trouve dans le fichier user.rb à l'intérieur du dossier app/models/ ; il est, pour ne pas dire, très compact (extrait 6.3).

Extrait 6.3. Le tout nouveau modèle User.
app/models/user.rb
class User < ActiveRecord::Base
end

Si vous vous souvenez de la section 4.4.2, la syntaxe class User < ActiveRecord::Base signifie que la classe User hérite de ActiveRecord::Base, de telle sorte que le modèle User possède automatiquement les fonctionnalités de la classe ActiveRecord::Base. Bien entendu, la connaissance de cet héritage ne sert à rien si nous ne savons pas ce que ActiveRecord::Base contient. Nous allons justement en avoir un avant-goût dans un moment. Mais avant de continuer, deux tâches sont à accomplir.

Annotation du modèle

Bien que cela ne soit pas strictement nécessaire, vous pourriez trouver utile d'annoter vos modèles Rails en utilisant le gem annotate-models (extrait 6.4).

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

group :test do
  .
  .
  .
end

(Nous plaçons le gem annotate-models dans le bloc group :development (analogue au group :test) parce que les annotations ne sont pas nécessaires dans les applications en mode production.) Nous l'installons ensuite avec bundle :

$ bundle install

Ce gem ajoute une commande appelée annotate, qui introduit simplement des commentaires sur le modèle de données dans le fichier du modèle :

$ annotate
Annotated User

Le résultat apparait dans l'extrait 6.5.

Extrait 6.5. Le modèle User annoté.
app/models/user.rb
# == Schema Information
# Schema version: <timestamp>
#
# Table nom: users
#
#  id         :integer         not null, primary key
#  nom       :string(255)
#  email      :string(255)
#  created_at :datetime
#  updated_at :datetime
#

class User < ActiveRecord::Base
end

Je trouve qu'avoir un modèle de données visible dans les fichiers de modèle aide à se souvenir des attributs que le modèle possède, mais les extraits de code présentés ici omettront souvent cette information, par souci de brièveté.

Attributs accessibles.

Une autre étape, pas strictement nécessaire, mais qui peut être une bonne idée de prudence, est de dire à Rails quels attributs du modèle sont accessibles, c'est-à-dire quels attributs peuvent être modifiés par des utilisateurs extérieurs (tels que les utilisateurs soumettant des requêtes à l'aide de leur navigateur internet). Nous réalisons cela avec la méthode attr_accessible (extrait 6.6). Nous verrons au chapitre 10 qu'utiliser attr_accessible est important pour prévenir le mass assignment (assignement en masse), un trou de sécurité désepérément courant et souvent sérieux dans beaucoup d'applications Rails.

Extrait 6.6. Rendre les attributs nom et email accessibles.
app/models/user.rb
class User < ActiveRecord::Base
  attr_accessible :nom, :email
end

6.1.3 Créer des objets utilisateur

Nous avons accompli un beau boulot préparatoire, et il est temps à présent d'en tirer profit et d'en apprendre plus sur Active Record en jouant avec notre nouveau modèle User créé. Comme au chapitre 4, notre outil de choix est la console Rails. Puisque nous ne voulons pas (encore) faire des changements dans notre base de données, nous allons démarrer la console dans un bac à sable (sandbox) :

$ rails console --sandbox
Loading development environment in sandbox (Rails 3.0.7)
Any modifications you make will be rolled back on exit
>> 

Comme l'indique le message « Any modifications you make will be rolled back on exit » (Toute modification que vous ferez sera oubliée en quittant le bac à sable. NdT ), en commençant dans un bac à sable, la console annulera tous les changements opérés sur la base de données à la fin de cette session bac à sable.

En travaillant à la console, il est utile de garder un œil sur le journal de développement (development log), qui enregistre les états de bas niveau de SQL renvoyés par Active Record, comme montrés à illustration 6.4. La façon d'obtenir cette sortie par une commande en ligne Unix est de tailer le journal :

$ tail -f log/development.log

Le drapeau -f assure que tail affichera les lines additionnelles comme elles sont écrites. Je recommande de garder une fenêtre de Terminal ouverte pour suivre le journal en travaillant à la console.

development_log
Illustration 6.4: Suivre le journal de développement. (taille normale)

Dans la session de console de la section 4.4.5, nous avons créé un nouvel objet utilisateur avec User.new, auquel nous n'avions accès qu'après avoir appelé le fichier de l'utilisateur exemple de l'extrait 4.8. Avec les modèles, la situation est différente ; comme vous pouvez vous en souvenir de la section 4.4.4, la console Rails charge automatiquement l'environnement Rails, ce qui inclut les modèles. Cela signifie que nous pouvons créer un nouvel objet utilisateur sans travail supplémentaire :

>> User.new
=> #<User id: nil, nom: nil, email: nil, created_at: nil, updated_at: nil>

Nous voyons ici la représentation par défaut d'un objet utilisateur, qui affiche les mêmes attributs que dans l'illustration 6.3 et l'extrait 6.5.

Appelé sans arguments, User.new retourne un objet dont tous les attributs en la valeur nulle (nil). À la section 4.4.5, nous nous sommes arrangés avec l'exemple de la classe User pour qu'elle utilise une table d'initialisation pour définir les attributs de l'objet ; ce choix était motivé par Active Record, qui permet aux objets d'être initialisés de cette façon :

>> user = User.new(:nom => "Michael Hartl", :email => "mhartl@example.com")
=> #<User id: nil, nom: "Michael Hartl", email: "mhartl@example.com",
created_at: nil, updated_at: nil>

Ici nous voyons que les attributs nom et email ont été définis comme voulu.

Si vous suivez le journal de développement, vous avez peut-être noté qu'aucune nouvelle ligne n'a été affichée. Ceci parce qu'appeler User.new n'affecte pas la base de données ; cela crée simplement un nouvel objet Ruby en mémoire. Pour sauver l'objet utilisateur dans la base de données, nous appelons la méthode save de la variable d'instance user :

>> user.save
=> true

La méthode save retourne true (vrai) si elle réussit et false (faux) dans le cas contraire (actuellement, toutes les sauvegardes doivent réussir ; nous verrons des cas à la section 6.2 où certains échouent). Dès que vous sauvez, vous devez voir une ligne du journal de développeent avec une commande SQL pour INSERT INTO "users" (INSÉRER DANS "users"). Grâce aux nombreuses méthodes fournies par Active Record, nous n'aurons jamais besoin de requête SQL dans ce livre, et j'omettrai donc de commenter les commandes SQL. Mais vous pouvez apprendre beaucoup en surveillant le journal de développement.

Vous avez peut-être noté que le nouvel objet utilisateur possédait les valeurs nil pour les attributs id et les colonnes magiques created_at et updated_at. Voyons si notre save y a changé quelque chose :

>> user
=> #<User id: 1, nom: "Michael Hartl", email: "mhartl@example.com",
created_at: "2010-01-05 00:57:46", updated_at: "2010-01-05 00:57:46">

Nous voyons qu'à l'attribut id a été assigné la valeur 1, et que les colonnes magiques se sont vues assigner le temps et la date courants.10 Pour le moment, les timestamps de création et de modification sont identiques ; nous verrons qu'ils différeront à la section 6.1.5.

Comme avec la classe User de la section 4.4.5, les instances du modèle User (« user », ici, est une instance de la classe User) permettent un accès à leurs attributs en utilisant la notation par point :11

>> user.nom
=> "Michael Hartl"
>> user.email
=> "mhartl@example.com"
>> user.updated_at
=> Tue, 05 Jan 2010 00:57:46 UTC +00:00

Comme nous le verrons au chapitre 8, il est souvent pratique de créer et de sauver un modèle en deux étapes comme nous l'avons fait ci-dessus, mais Active Record nous laisse aussi les combiner en une seule étape avec User.create (Utilisateur.créer) :

>> User.create(:nom => "A Nother", :email => "another@example.org")
=> #<User id: 2, nom: "A Nother", email: "another@example.org", created_at:
"2010-01-05 01:05:24", updated_at: "2010-01-05 01:05:24">
>> foo = User.create(:nom => "Foo", :email => "foo@bar.com")
=> #<User id: 3, nom: "Foo", email: "foo@bar.com", created_at: "2010-01-05
01:05:42", updated_at: "2010-01-05 01:05:42">

Notez que User.create, plutôt que de retourner true ou false, retourne l'objet User lui-même, que nous pouvons optionnellement assigner à une variable (comme la variable foo dans la seconde commande ci-dessus).

L'inverse de la méthode create (créer) est la méthode destroy (détruire) :

>> foo.destroy
=> #<User id: 3, nom: "Foo", email: "foo@bar.com", created_at: "2010-01-05
01:05:42", updated_at: "2010-01-05 01:05:42">

Bizarrement, destroy, comme create, retourne l'objet en question, bien que je ne puisse me souvenir d'avoir jamais utiliser le retour d'une méthode destroy. Encore plus bizarrement peut-être, l'objet pourtant détruit par destroy existe toujours en mémoire :

>> foo
=> #<User id: 3, nom: "Foo", email: "foo@bar.com", created_at: "2010-01-05
01:05:42", updated_at: "2010-01-05 01:05:42">

Comment pouvons-nous être sûrs, alors, d'avoir détruit un objet ? Et pour les objets sauvés et non détruits, comment pouvons-nous récupérer les utilisateurs de la base de données ? Il est temps d'apprendre à utiliser Active Record pour y répondre.

6.1.4 Recherche dans les objets Utilisateurs (Users)

Active Record fournit plusieurs options pour retrouver des objets. Utilisons-les pour retrouver le premier utilisateur que nous avons créé puis nous vérifierons que le troisième (foo) a été détruit. Nous allons commencer avec l'utilisateur existant :

>> User.find(1)
=> #<User id: 1, nom: "Michael Hartl", email: "mhartl@example.com",
created_at: "2010-01-05 00:57:46", updated_at: "2010-01-05 00:57:46">

Ici nous avons passé l'identifiant de l'utilisateur à User.find ; Active Record retourne l'utilisateur correspondant à cet attribut id.

Voyons si l'utilisateur avec un identifiant de 3 existe toujours dans la base de données :

>> User.find(3)
ActiveRecord::RecordNotFound: Couldn't find User with ID=3

Puisque nous avons détruit notre troisième utilisateur à la section 6.1.3, Active Record ne peut pas le retrouver dans la base de données. Ou plus exactement, find a provoqué une exception, qui est une manière d'indiquer un évènement exceptionnel dans l'exécution d'un programme — dans ce cas, un id Active Record inexistant, ce qui a conduit find à provoquer une exception ActiveRecord::RecordNotFound.12

En addition au find générique, Active Record nous permet aussi de retrouver des utilisateurs par des valeurs d'attributs spécifiques :

>> User.find_by_email("mhartl@example.com")
=> #<User id: 1, nom: "Michael Hartl", email: "mhartl@example.com",
created_at: "2010-01-05 00:57:46", updated_at: "2010-01-05 00:57:46">

Puisque nous utiliserons l'adresse email comme nom d'utilisateur (usernames), cette façon de récupérer des objets enregistrés sera utile pour permettre aux utilisateurs de s'identifier à notre site (chapitre 8).13

Nous allons terminer avec deux des manières classiques de retrouver des utilisateurs. D'abord, il y a la méthode first (premier) :

>> User.first
=> #<User id: 1, nom: "Michael Hartl", email: "mhartl@example.com",
created_at: "2010-01-05 00:57:46", updated_at: "2010-01-05 00:57:46">

Naturellement, first retourne simplement le premier utilisateur de la base de données. Il y a aussi all (tous) :

>> User.all
=> [#<User id: 1, nom: "Michael Hartl", email: "mhartl@example.com",
created_at: "2010-01-05 00:57:46", updated_at: "2010-01-05 00:57:46">,
#<User id: 2, nom: "A Nother", email: "another@example.org", created_at:
"2010-01-05 01:05:24", updated_at: "2010-01-05 01:05:24">]

all, qui signifie «  tous » en anglais, retourne comme on peut s'y attendre un tableau (section 4.3.1) de tous les utilisateurs enregistrés dans la base de données.

6.1.5 Actualisation des objets Utilisateurs (Users)

Une fois des objets créés, il est fréquent de les actualiser. Il existe deux façons classiques de le faire. D'abord, nous pouvons assigner les attributs individuellement, comme nous l'avons fait à la section 4.4.5 :

>> user           # Pour l'afficher à titre de rappel
=> #<User id: 1, nom: "Michael Hartl", email: "mhartl@example.com",
created_at: "2010-01-05 00:57:46", updated_at: "2010-01-05 00:57:46">
>> user.email = "mhartl@example.net"
=> "mhartl@example.net"
>> user.save
=> true

Notez que l'étape finale est nécessaire pour écrire les changements dans la base de donées. Nous pouvons voir ce qu'il arrive en cas d'oubli de sauvegarde en utilisant reload, qui recharge l'objet en fonction des informations contenues dans la base de données :

>> user.email
=> "mhartl@example.net"
>> user.email = "foo@bar.com"
=> "foo@bar.com"
>> user.reload.email
=> "mhartl@example.net"

Maintenant que nous avons actualisé l'utilisateur, la valeur des colonnes magiques de temps diffèrent, comme promis à la section 6.1.3 :

>> user.created_at
=> "2010-01-05 00:57:46"
>> user.updated_at
=> "2010-01-05 01:37:32"

La seconde façon d'actualiser les attributs est d'utiliser la méthode update_attributes :

>> user.update_attributes(:nom => "The Dude", :email => "dude@abides.org")
=> true
>> user.nom
=> "The Dude"
>> user.email
=> "dude@abides.org"

La méthode update_attributes accepte une table d'attributs, et en cas de succès accomplit d'un coup l'actualisation et la sauvegarde (en retournant true pour indiquer que la sauvegarde a pu se faire). Il est important de noter que, une fois que vous avez défini l'accessibilité de certains attributs avec attr_accessible (section 6.1.2.2), seuls ces attributs peuvent être modifiés en utilisant la méthode update_attributes. Si, au cours de vos développements, vous trouvez tout à coup que votre modèle commence mystérieusement à refuser d'actualiser certaines colonnes, assurez-vous que ces colonnes soient bien incluses dans l'appel à attr_accessible.

6.2 Validations de l'utilisateur

Le modèle User que nous avons créé à la section 6.1 possède maintenant des attributs nom et email qui fonctionnent, mais ils sont complètement « génériques » : n'importe quelle chaine de caractères (même l'espace) est en ce moment valide dans tous les cas. Et pourtant, les noms et les adresses email sont plus spécifiques que ça. Par exemple, le nom ne devrait pas être vierge, et l'email devrait être conforme au format caractéristique des adresses email. Plus encore, puisque nous allons utiliser l'adresse comme unique nom d'utilisateur (username) quand un nouvel utilisateur s'inscrira, nous ne devrions pas lui permettre d'entrer une adresse déjà contenue dans la base de données.

En bref, nous ne devrions pas permettre que nom et email soit n'importe quelle chaine de caractères ; nous devrions forcer certaines contraintes sur ces valeurs. Active Record nous permet d'imposer de telles contraintes en utilisant les validations. Dans cette section, nous couvrions plusieurs des cas les plus courants, en validant la présence (presence), la longueur (length), le format et l'unicité (uniqueness). Nous ajouterons à la section 7.1.1 une validation finale courante, la confirmation de l'adresse. Et nous verrons à la section 8.2 comment les validations nous renvoient des messages d'erreur utiles quand l'utilisateur soumet des données qui les violent.

Comme pour les autres fonctionnalités de notre application exemple, nous allons ajouter les validations au modèle User en suivant toujours le « Développement Dirigé par les Tests ». Puisque nous avons changé le modèle de données, c'est une bonne idée de préparer le test de la base de données avant de poursuivre :

$ rake db:test:prepare

Cela s'assure simplement que le modèle de données de la base de données en mode test, db/test.sqlite3, reflète la base de données en mode de développement, db/development.sqlite3.

6.2.1 Valider la présence

Nous allons commencer par un test de la présence de l'attribut nom attribute. Bien que la première étape en TDD consiste à écrire un test échouant (section 3.2.2), nous n'en savons pas encore assez dans ce cas à propos des validations pour écrire le test approprié, donc nous allons d'abord écrire la validation, en utilisant la console pour la comprendre. Ensuite nous commenterons la validation pour l'exclure, écrirons un test échouant, et vérifierons que décommenter les validations fait bien réussir le test. Cette procédure peut paraitre pédante pour un test aussi simple, mais j'ai trop vu14 de « simples » tests échouer ; être méticuleux à propos de la TDD est simplement la seule manière d'être confiant sur le fait que nous testons la bonne chose à tester (la technique d'exclusion par commentaire — que j'appellerai « ex-commenter ». NdT — est utile, aussi, quand on vient au secours d'une application dont le code est déjà écrit mais qu'elle ne possède — quelle horreur ! — aucun test).

La façon de valider la présence d'un attribut nom est d'utiliser la méthode validates avec l'argument :presence => true, comme montré dans l'extrait 6.7. L'argument :presence => true est une table d'options à un élément ; souvenez-vous, section 4.3.4, que les accolades sont optionnelles lorsque les tableaux sont les arguments finaux d'une méthode (comme noté à la section 5.1.1, l'utilisation de tables d'options est un théme récurrent en Rails).

Extrait 6.7. Valider la présence d'un attribut nom.
app/models/user.rb
class User < ActiveRecord::Base 
  attr_accessible :nom, :email

  validates :nom, :presence => true
end

Comme discuté brièvement à la section 2.3.2, l'utilisation de validates est caractéristique de Rails 3 (en Rails 2.3, nous aurions dû plutôt écrire validates_presence_of :nom).

L'extrait 6.7 peut sembler quelque peu magique, mais validates est juste une méthode, telle que l'est aussi attr_accessible. Une formulation équivalent de l'extrait 6.7 en utilisant les parenthèses serait :

class User < ActiveRecord::Base 
  attr_accessible(:nom, :email)

  validates(:nom, :presence => true)
end

Utilisons la console pour voir les effets de l'ajout de la validation sur notre modèle User :15

$ rails console --sandbox
>> user = User.new(:nom => "", :email => "mhartl@example.com")
>> user.save
=> false
>> user.valid?
=> false

Ici, user.save retourne false (faux), indiquant un sauvegarde défectueuse. Dans la commande finale, nous utilisons la méthode valid?, qui retourne false (false) quand l'objet rate une validations ou plus, et true (vrai) quand toutes les validations réussissent (souvenez-nou, section 4.2.3, que Ruby utilise le point d'interrogation pour signifier les méthodes booléennes). Dans ce cas, nous n'avons qu'une seule validation, donc nous savons laquelle échoue, mais il peut toujours être utile de vérifier en utilisant l'objet errors (erreurs) généré par l'échec :

>> user.errors.full_messages
=> ["Name can't be blank"]

(Le message d'erreur contient un indice qui révèle que Rails valide la présence d'un attribut en utilisant la méthode blank?, ce que nous avons vu à la fin de la section 4.4.3.)

Voilà pour les tests échouant. Pour s'assurer que notre test naissant échouera, « ex-commentons » la validation (extrait 6.8).

Extrait 6.8. Ex-commenter une validation pour s'assurer que le test échoue.
app/models/user.rb
class User < ActiveRecord::Base 
  attr_accessible :nom, :email

  # validates :nom, :presence => true
end

Comme dans le cas de la génération de contrôleur (par exemple l'extrait 5.23), la commande de génération de modèle dans l'extrait 6.1 produit une spec initiale pour tester les utilisateurs, mais dans ce cas elle est pratiquement vierge (extrait 6.9).

Extrait 6.9. Le spec User par défaut, pratiquement vierge.
spec/models/user_spec.rb
require 'spec_helper'

describe User do
  pending "add some examples to (or delete) #{__FILE__}"
end

Cette spec utilise simplement la méthode pending (en attendant) pour indiquer que nous devrions soit remplir le spec avec quelque chose de plus utile, soit le supprimer. Nous pouvons en voir les effets en jouant ce spec du modèle User :

$ rspec spec/models/user_spec.rb 
*


Finished in 0.01999 seconds
1 example, 0 failures, 1 pending

Pending:
  User add some examples to (or delete)
  /Users/mhartl/rails_projects/sample_app/spec/models/user_spec.rb
  (Not Yet Implemented)

Nous suivrons le conseil du spec par défaut en le remplissant avec des exemples RSpec, montrés dans l'extrait 6.10.

Extrait 6.10. Le spec User initial.
spec/models/user_spec.rb
require 'spec_helper'

describe User do

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

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

  it "devrait exiger un nom"
end

Nous avons vu require et describe auparavant, le plus récemment dans l'extrait 5.28. La ligne suivante est un bloc before(:each) ; ceci a été couvert brièvement par un exercice (extrait 3.33), et tout ce qu'il fait est de jouer le code à l'intérieur du bloc avant chaque exemple — dant ce cas en définissant l'instance de variable @attr comme tableau d'initialisation.

Le premier exemple est juste un « check de santé » vérifiant que le modèle User fonctionne. Il utilise User.create! (lire « create bang », « créer bang » ), qui fonctionne comme la méthode create vue à la section 6.1.3 à la différence près qu'elle provoque une exception (une erreur) ActiveRecord::RecordInvalid si la création échoue (similairement à l'exception ActiveRecord::RecordNotFound que nous avons pu voir à la section 6.1.4). Aussi longtemps que les attributs sont valides, elle ne provoque aucune erreur, et le test réussira.

La ligne finale est le test pour la présence de l'attribut nom — ou plutôt, il devrait être le test effectif, s'il y avait quelque chose dans cet attribut. Au lieu de ça, le test est juste une esquisse, mais une esquisse utile : c'est une spec de mise en attente, qui est un moyen d'écrire une description du comportement de l'application sans se soucier encore de son implémentation. L'extrait 6.9 montre un exemple spec de mise en attente utilisant un appel explicite à la méthode pending (mise en attente) ; dans ce cas, puisque nous avons inclus seulement la partie it de l'exemple…

it "devrait exiger un nom"

… RSpec déduit l'existence d'une spec de mise en attente.

Les specs de mise en attente sont bien traités par les programmes pour jouer des specs, comme vu pour Autotest dans l'illustration 6.5, et la sortie de rspec spec/ est utile de la même façon. Les specs de mise en attente sont utiles en tant qu'« emplacements réservés » pour des tests que nous savons avoir à écrire mais que nous ne voulons pas implémenter immédiatement.

autotest_pending_spec
Illustration 6.5: Autotest (via autotest) avec un spec User de mise en attente. (taille normale)

Dans le but de remplir le spec de mise en attente, nous avons besoin d'une manière de créer une table d'attributs avec un nom invalide (la table @attr est valide par construction, avec un attribut nom non vierge). La méthode Hash merge (fusion de Table de hachage) accomplit la chose, comme nous pouvons le voir dans la console rails :

>> @attr = { :nom => "Example User", :email => "user@example.com" }
=> {:nom => "Example User", :email => "user@example.com"}
>> @attr.merge(:nom => "")
=> {:nom => "", :email => "user@example.com"}

Avec merge (fusionne) en main, nous sommes prêts à faire une nouvelle spec (utilisant une astuce que j'expliquerai dans un instant) comme le montre l'extrait 6.11.

Extrait 6.11. Un test échouant pour la validation de l'attribut nom.
spec/models/user_spec.rb
describe User do

  before(:each) do
    @attr = { :nom => "Example User", :email => "user@example.com" }
  end
  .
  .
  .
  it "exige un nom" do
    bad_guy = User.new(@attr.merge(:nom => ""))
    bad_guy.should_not be_valid
  end
end

Ici nous utilisons merge pour créer un nouvel utilisateur appelée bad_guy (mauvais_garcon) avec un nom vierge. La deuxième ligne utilise alors la méthode RSpec should_not (ne_devrait_pas) pour vérifier que l'utilisateur en résultant n'est pas valide. L'astuce à laquelle je faisais allusion ci-dessus fait référence à be_valid (être_valide) : nous avons appris plus haut dans cette section qu'un objet utilisateur (user) répondait à la méthode booléenne valid?. RSpec adopte une convention utile qui nous permettait de tester n'importe quelle méthode booléenne en supprimant le point d'interrogation et en préfixant la méthode avec be_ (être). En d'autres mots…

bad_guy.should_not be_valid

… est équivalent à :

bad_guy.valid?.should_not == true

Puisque ça sonne plus proche du langage naturel (au moins pour les anglophones. NdT), écrire should_not be_valid (ne_devrait_pas etre_valide) est définitivement plus idiomatiquement correct en RSpec.

Avec ça, notre nouveau test devrait échouer, ce que nous pouvons vérifier avec Autotest ou en jouant le fichier user_spec.rb en utilisant le script spec :

$ rspec spec/models/user_spec.rb
.F

1)
'User exige un nom' FAILED
expected valid? to return false, got true
./spec/models/user_spec.rb:14:

2 examples, 1 failure

Maintenant décommentons la validation (c'est-à-dire que de l'extrait 6.8 nous allons revenir à l'extrait 6.7) pour faire réussir le test :

$ rspec spec/models/user_spec.rb
..

2 examples, 0 failures

Bien sûr, nous voulons aussi valider la présence de l'adresse email. Le test (extrait 6.12) est analogue à celui pour l'attribut nom.

Extrait 6.12. Un test de présence de l'attribut email.
spec/models/user_spec.rb
describe User do

  before(:each) do
    @attr = { :nom => "Example User", :email => "user@example.com" }
  end
  .
  .
  .
  it "exige une adresse email" do
    no_email_user = User.new(@attr.merge(:email => ""))
    no_email_user.should_not be_valid
  end
end

L'implémentation est aussi virtuellement la même, comme vu dans l'extrait 6.13.

Extrait 6.13. Valider la présence des attributs nom et email.
app/models/user.rb
class User < ActiveRecord::Base 
  attr_accessible :nom, :email

  validates :nom,  :presence => true
  validates :email, :presence => true
end

À présent, tous les tests devraient réussir, donc les validations de « présence » sont complets.

6.2.2 Validation de la longueur (length)

Nous avons contraint notre modèle utilisateur d'exiger un nom (et une adresse mail) pour chaque utilisateur, mais nous devons aller plus loin : les noms d'utilisateur seront affichés sur le site exemple, donc nous devons introduire quelques limites de longueur. Avec tout le travail que nous avons accompli à la section 6.2.1, cette étape est facile.

Nous commençons par un test. Il n'y a pas de science exacte pour choisir une longueur maximum ; nous allons déclarer que 50 caractères sont une limite maximale acceptable, ce qui signifie que les noms de 51 caractères seront trop longs (extrait 6.14).

Extrait 6.14. Un test pour la validation de longueur du nom.
spec/models/user_spec.rb
describe User do

  before(:each) do
    @attr = { :nom => "Example User", :email => "user@example.com" }
  end
  .
  .
  .
  it "devrait rejeter les noms trop longs" do
    long_nom = "a" * 51
    long_nom_user = User.new(@attr.merge(:nom => long_nom))
    long_nom_user.should_not be_valid
  end
end

De façon pratique, nous avons utilisé la « multiplication de chaine » dans l'extrait 6.14 pour construire une chaine de 51 caractères. Nous pouvons voir comment cela fonctionne en utilisant la console :

>> s = "a" * 51
=> "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
>> s.length
=> 51

Le test de l'extrait 6.14 devrait échouer. Pour le voir réussir, nous avons besoin de connaitre l'argument de validation pour contraindre la longueur, :length, qui s'utilise avec le paramètre :maximum pour placer la limite supérieure (extrait 6.15).

Extrait 6.15. Ajout d'une validation de longueur pour l'attribut nom.
app/models/user.rb
class User < ActiveRecord::Base 
  attr_accessible :nom, :email

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

Avec la réussite de notre suite de tests, nous pouvons avancer vers une validation plus difficile : le format de l'email..

6.2.3 Validation de format

Nos validations pour l'attribut nom forcent seulement des contraintes minimales — seuls les noms non vierges et inférieurs à 51 caractères seront acceptés — mais bien sûr l'attribut email doit satisfaire des exigences plus strictes. Jusqu'ici nous n'avons rejeté que l'adresse vierge ; dans cette section, nous exigerons des adresses email qu'elles soient conformes au modèle habituel user@example.com.

Ni les tests ni la validation ne seront exhaustives, mais juste assez bonne pour accepter les adresses les plus valides et rejeter les plus invalides. Nous allons commencer avec une paire de tests impliquant une collection d'adresses valides et d'adresses invalides. Pour faire ces collections, il est utile de se souvenir de la méthode pour faire des listes de strings, vue dans la session de console :

>> %w[foo bar baz]
=> ["foo", "bar", "baz"]
>> adresses = %w[user@foo.com THE_USER@foo.bar.org first.last@foo.jp]
=> ["user@foo.com", "THE_USER@foo.bar.org", "first.last@foo.jp"]
>> adresses.each do |address|
?>   puts address
>> end
user@foo.com
THE_USER@foo.bar.org
first.last@foo.jp

Ici, nous « bouclons » sur les éléments du tableau adresses en utilisant la méthode each (section 4.3.2). Cette tehnique bien en main, nous sommes prêts à écrire quelques tests de validation de format basique d'adresses (extrait 6.16).

Extrait 6.16. Tests pour la validation du format des adresses mail.
spec/models/user_spec.rb
describe User do

  before(:each) do
    @attr = { :nom => "Example User", :email => "user@example.com" }
  end
  .
  .
  .
  it "devrait accepter une adresse email valide" do
    adresses = %w[user@foo.com THE_USER@foo.bar.org first.last@foo.jp]
    adresses.each do |address|
      valid_email_user = User.new(@attr.merge(:email => address))
      valid_email_user.should be_valid
    end
  end

  it "devrait rejeter une adresse email invalide" do
    adresses = %w[user@foo,com user_at_foo.org example.user@foo.]
    adresses.each do |address|
      invalid_email_user = User.new(@attr.merge(:email => address))
      invalid_email_user.should_not be_valid
    end
  end
end

Comme noté ci-dessus, c'est loin d'être exhaustif, mais nous vérifions les formes d'adresse email valides les plus courantes user@foo.com, THE_USER@foo.bar.org (capitales, tiret plat et domaines composés), et first.last@foo.jp (le nom d'utilisateur standard de l'entreprise first.last, avec un domaine de deux lettres jp), en plus de quelques adresses de forme invalide.

Le code de l'application pour la validation du format des emails utilise une expression régulière (ou regex, pour « regular expression » en anglais) pour définir le format, aux côtés de l'argument :format pour la méthode validates (extrait 6.17).

Extrait 6.17. Valider le format de l'email avec une expression régulière.
app/models/user.rb
class User < ActiveRecord::Base
  attr_accessible :nom, :email

  email_regex = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i

  validates :nom,  :presence => true,
                    :length   => { :maximum => 50 }
  validates :email, :presence => true,
                    :format   => { :with => email_regex }
end

Ici, email_regex est une expression régulière, appelé aussi regex. Le code :

  email_regex = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
  .
  .
  .
  validates :email, :presence => true,
                    :format   => { :with => email_regex }

… s'assure que seules les adresses mail qui correspondent au pattern de l'expression régulière seront considérées comme valides.

Mais d'où vient ce pattern ?… Les expressions régulières consistent en un langage concis (certains diront illisible) pour « matcher » les modèles de texte ; apprendre à construire une expression régulière est un art, et pour commencer, j'ai mis email_regex en petites pièces (Table 6.1).16 Pour apprendre vraiment les expressions régulières, cependant, je considère que le formidable éditeur d'expression régulières Rubular (illustration 6.6) est simplement essentiel.17 Le site Rubular est une magnifique interface interactive pour fabriquer des expressions régulières, tout autant qu'un site de référence rapide et pratique aux expressions. Je vous encourage à étudier la Table 6.1 avec une fenêtre de navigateur ouverte sur Rubular — aucune lecture conséquente sur les expressions régulières ne pourra remplacer une ou deux heures passées à jouer avec Rubular.

ExpressionSens
/\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/iPattern complet
/marque le début de l'expression
\Acherche le début de la chaine
[\w+\-.]+au moins un caractère de mot, le signe « + », le signe « - » ou le point
@arobase
[a-z\d\-.]+au moins une lettre, un chiffre, un tiret ou un point
\.un point
[a-z]+au moins une lettre
\zcherche la fin de la chaine
/marque la fin de l'expression régulière
iinsensible à la casse
Table 6.1: Détail de l'expression régulière pour les emails de l'extrait 6.17.

En passant, il existe en fait une expression régulière complète pour vérifier les adresses emails en concordance avec le standard officiel, mais ça ne vaut vraiment pas la peine. Celle de l'extrait 6.17 est bien, peut-être même meilleure que l'expression officielle.18

rubular
Illustration 6.6: L'impressionnant éditeur d'expressions régulières Rubular(taille normale)

Les tests devraient tous réussir maintenant (en fait, les tests pour les adresses mails valides auraient dû toutes réussir ; puisque les expressions régulières sont notoirement sujettes à erreur, les tests de validation de l'email sont ici à titre de test de cohérence sur email_regex). Cela signifie qu'il ne reste qu'une seule contrainte à ajouter : forcer l'adresse email à être unique.

6.2.4 Validation d'unicité

Pour forcer l'unicité des adresses email (de telle sorte que nous puissons les utiliser comme username — nom d'utilisateur), nous allons utiliser l'option :unique de la méthode validates. Mais soyez averti : il existe un piège majeur, donc ne survolez pas cette section, lisez-là soigneusement.

Nous commencerons, comme d'habitude, avec nos tests. Dans nos précédents tests de modèle, nous avons principalement utilisé User.new, qui crée juste un objet Ruby en mémoire, mais pour les tests d'unicité nous avons besoin en fait de déposer un enregistrement dans la base de données.19 Le (premier) test de duplication d'email apparait dans l'extrait 6.18.

Extrait 6.18. Un test pour le rejet de la duplication d'une adresse mail.
spec/models/user_spec.rb
describe User do

  before(:each) do
    @attr = { :nom => "Example User", :email => "user@example.com" }
  end
  .
  .
  .
  it "devrait rejeter un email double" do
    # Place un utilisateur avec un email donné dans la BD.
    User.create!(@attr)
    user_with_duplicate_email = User.new(@attr)
    user_with_duplicate_email.should_not be_valid
  end
end

La méthode ici est de créer un utilisateur et alors d'essayer de faire un autre utilisateur qui possèderait la même adresse email (nous utilisons la méthode « bruyante » create!, d'abord vue à l'extrait 6.10, de telle sorte qu'elle provoquera une exception, une erreur, si quelque chose ne tournait pas rond. En utilisant create, sans le « bang » !, nous risquerions d'obtenir une erreur silencieuse dans nos tests, une source potentielle de bogue insaisissable). Nous pouvons obtenir que ce test réussisse avec le code de l'extrait 6.19.20

Extrait 6.19. Valider l'unicité de l'adresse mail.
app/models/user.rb
class User < ActiveRecord::Base
  .
  .
  .
  validates :email, :presence   => true,
                    :format     => { :with => email_regex },
                    :uniqueness => true
end

Nous n'en avons pas fini, cependant. Les adresses mails sont sensibles à la casse — foo@bar.com se rend au même endroit que FOO@BAR.COM ou FoO@BAr.coM — donc nos validations doivent aussi couvrir ce cas. Nous testons cela avec le code de l'extrait 6.20.

Extrait 6.20. Un test pour le rejet d'une adresse mail existente, insensible à la casse.
spec/models/user_spec.rb
describe User do

  before(:each) do
    @attr = { :nom => "Example User", :email => "user@example.com" }
  end
  .
  .
  .
  it "devrait rejeter une adresse email invalide jusqu'à la casse" do
    upcased_email = @attr[:email].upcase
    User.create!(@attr.merge(:email => upcased_email))
    user_with_duplicate_email = User.new(@attr)
    user_with_duplicate_email.should_not be_valid
  end
end

Ici nous utilisons la méthode upcase sur les chaines (vue brièvement à la section 4.3.2). Ce test fait la même chose que le premier, mais plutôt sur une adresse capitalisée. Si ce test vous semble un petit peu abstrait, prenez votre console et tapez :

$ rails console --sandbox
>> @attr = { :nom => "Example User", :email => "user@example.com" }
=> {:nom => "Example User", :email => "user@example.com"}
>> upcased_email = @attr[:email].upcase
=> "USER@EXAMPLE.COM"
>> User.create!(@attr.merge(:email => upcased_email))
>> user_with_duplicate_email = User.new(@attr)
>> user_with_duplicate_email.valid?
=> true

Bien entendu, pour le moment, user_with_duplicate_email.valid? est true (vrai), puisque c'est un test d'échec, mais nous voulons qu'il retourne false (faux). Heureusement, :uniqueness (unicité) accepte une option, :case_sensitive, justement dans ce but (extrait 6.21).

Extrait 6.21. Valider l'unicité de l'adresse mail, en ignorant la casse.
app/models/user.rb
class User < ActiveRecord::Base
  .
  .
  .
  validates :email, :presence   => true,
                    :format     => { :with => email_regex },
                    :uniqueness => { :case_sensitive => false }
end

Notez que nous avons simplement remplacé true par :case_sensitive => false ; Rails en déduit que :uniqueness (l'unicité ) devrait être true (vraie). À ce point, notre application fait (quelque peu) en sorte que l'adresse mail soit unique, et notre suite de tests devrait réussir.

L'avertissement d'unicité

Il y a juste un petit problème, le piège auquel je faisais allusion ci-dessus :

Utiliser validates :uniqueness ne garantit en rien l'unicité.

« Oh ?… Mais qu'est-ce qui peut bien mal tourner ?… » Cela :

  1. Alice s'inscrit à l'application exemple, avec l'adresse mail alice@wonderland.com ;
  2. Accidentellement, Alice clique deux fois sur le bouton « Soumettre », envoyant deux requêtes en succession rapide ;
  3. La séquence suivante se produit : la requête 1 crée un utilisateur en mémoire qui réussit la validation, la requête 2 fait de même, l'utilisateur de la requête 1 (Alice) est enregistré, l'utilisateur de la requête 2 (ALice) est aussi enregistré ;
  4. Résultat : deux enregistrements d'utilisateur avec la même adresse email, malgré la validation d'unicité.

Si la séquence ci-dessus ne vous semble pas plausible, croyez-moi, elle l'est. Cela survient sur n'importe quel site Rails connaissant un trafic important.21 Heureusement, la solution est simple à implémenter ; nous avons juste besoin de forcer l'unicité au niveau de la base de données elle-même. Notre méthode consiste à créer un index de base de données sur la colonne email, et ensuite d'exiger que cet index soit unique.

L'index email représente une actualisation à nos impératifs du modèle de données, ce qui (comme discuté à la section 6.1.1) est traité par Rails en utilisant les migrations. Nous avons vu à la section 6.1.1 que générer le modèle User créait automatiquement une nouvelle migration (extrait 6.2) ; dans le cas présent, nous ajoutons de la structure à un modèle existant, donc nous avons besoin de créer une migration directement en utilisant le générateur migration :

$ rails generate migration add_email_uniqueness_index

Contrairement à la migration pour les utilisateurs, la migration pour l'unicité de l'adresse mail n'est pas pré-définie, donc nous avons besoin de remplir son contenu par le code de l'extrait 6.22.22

Extrait 6.22. La migration pour forcer l'unicité de l'adresse mail.
db/migrate/<timestamp>_add_email_uniqueness_index.rb
class AddEmailUniquenessIndex < ActiveRecord::Migration
  def self.up
    add_index :users, :email, :unique => true
  end

  def self.down
    remove_index :users, :email
  end
end

Ce code utilise une méthode Rails appelée add_index pour ajouter l'index sur la colonne email de la table users. L'index par lui-même ne contraint pas l'unicité, mais c'est l'option :unique => true qui le fait.

L'étape finale est de migrer la base de données :

$ rake db:migrate

Maintenant, le scénario d'Alice fonctionnera parfaitement : la base de données sauvera l'enregistrement basé sur la première requête, et rejettera la seconde pour violation de la contrainte d'unicité de l'adresse mail (une erreur apparaitra dans le journal Rails, mais elle ne provoque aucun dommage. Vous pouvez bien entendu saisir l'exception ActiveRecord::StatementInvalid qui est provoquée — voyez Insoshi pour un exemple — mais dans ce tutoriel nous ne nous occuperons pas de cette étape). L'ajout de l'index sur l'attribut email accomplit un second but, auquel j'ai fait brièvement allusion à la section 6.1.4 : il fixe (au sens de « régler ». NdT) aussi un problème sérieux de find_by_email, comme l'explique le Box 6.2.

6.3 Affichage des utilisateurs

Nous n'en avons pas tout à fait fini avec le modèle utilisateur de base — nous avons encore besoin d'ajouter des mots de passe, tâche qui sera accomplie au chapitre 7 — mais nous en avons fait suffisamment pour faire une page minimale pour pouvoir afficher les informations de l'utilisateur. Cela permettra une introduction en douceur à l'organisation de type REST des actions utilisateur de notre site. Puisque c'est juste une démonstration grossière pour le moment, il n'y aura pas de tests dans cette section ; nous les ajouterons quand nous étofferons la vue de l'utilisateur à la section 7.3.

6.3.1 Débuggage et environnement Rails

À titre de préparation de l'ajout de pages dynamique dans notre Application Exemple, il est temps à présent d'ajouter des informations de débuggage au layout de notre site (extrait 6.23). Cela affiche des informations utiles sur chaque page en utilisant la méthode intégrée debug et la variable params (que nous étudierons plus en détail à la section 6.3.2), comme le montre l'illustration 6.7.

Extrait 6.23. Ajout d'information de débuggage au layout du site.
app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
  .
  .
  .
  <body>
    <div class="container">
      .
      .
      .
      <%= render 'layouts/footer' %>
      <%= debug(params) if Rails.env.development? %>
    </div>
  </body>
</html>

Puisque nous ne voulons pas afficher les informations de débuggage à l'utilisateur de l'application déployée online, nous utilisons :

if Rails.env.development?

… pour les restreindre à l'environnement de développement. Bien que nous ayons déjà abordé l'évidence des environnements Rails précédemment (tout récemment à la section 6.1.3), c'est la première fois que cela nous importe vraiment.

home_page_with_debug_rails_3
Illustration 6.7: La page d'accueil de l'application exemple (/) avec des informations de débuggage en bas de page. (taille normale)

Rails arrive équipé de trois environnements : test, development, et production.23 L'environnement par défaut de la console est l'environnement développement :

$ rails console 
Loading development environment (Rails 3.0.7)
>> Rails.env
=> "development"
>> Rails.env.development?
=> true
>> Rails.env.test?
=> false

Comme vous pouvez le voir, Rails fournit un objet Rails avec un attribut env et des méthodes booléennes associées. En particulier, Rails.env.development? est true (vrai) seulement dans l'environnement développement, donc le code Ruby embarqué :

<%= debug(params) if Rails.env.development? %>

… ne sera pas inséré dans les applications en mode production ou test (insérer les informations de débuggage pour les tests ne cause probablement pas de dommage, mais ça n'est probablement pas bon non plus, donc il est mieux de restreindre l'affichage des débuggage à l'environnement de développement seulement).

Quand vous avez besoin de jouer la console dans un environnement différent (pour débugguer un test par exemple), vous pouvez passer l'environnement en paramètre au script console :

$ rails console test
Loading test environment (Rails 3.0.7)
>> Rails.env
=> "test"

Comme pour la console, development (développement) est l'environnement par défaut du server Rails local, mais vous pouvez aussi le faire jouer dans un environnement différent :

$ rails server --environment production

Si vous voyez votre application jouer en mode production, elle ne fonctionnera pas sans base de données, laquelle peut être créée en jouant rake db:migrate en mode production :

$ rake db:migrate RAILS_ENV=production

(Je trouve déroutant que la console, le serveur, et les commandes de migration précisent l'environnement, quand ce n'est pas l'environnement par défaut, de trois façons mutuellement incompatibles, c'est pourquoi j'ai pris le soin de les montrer les trois.)

En passant, si vous avez déployé votre application exemple sur Heroku, vous pouvez voir son environnement en utilisant la commande heroku qui fournit sa propre console à distance :

$ heroku console
Ruby console for yourapp.heroku.com
>> Rails.env
=> "production"
>> Rails.env.production?
=> true

Naturellement, puisque Heroku est une plateforme pour les sites en mode production, il joue chaque application dans cette environnement production.

6.3.2 Modèle User, Vue, Contrôleur

Afin de faire une page pour voir un utilisateur, nous allons utiliser le modèle User pour créer un utilisateur dans la base de données, créer une vue (View) pour afficher quelques unes de ses informations, et ajouter alors une action au contrôleur (Controller) pour traiter les requêtes du navigateur. En d'autres termes, pour la première fois dans ce tutoriel, nous allons voir en un seul endroit les trois éléments de l'architecture MVC (Modèle-Vue-Contrôleur) dont nous avons parlé pour la première fois à la section 1.2.6.

Notre première étape consiste à créer un utilisateur en utilisant la console, que nous prendrons soin de ne pas démarrer en mode bac à sable puisque cette fois le but est de sauver l'enregistrement dans la base de données :

$ rails console
Loading development environment (Rails 3.0.7)
>> User.create!(:nom => "Michael Hartl", :email => "mhartl@example.com")
=> #<User id: 1, nom: "Michael Hartl", email: "mhartl@example.com",
created_at: "2010-01-07 23:05:14", updated_at: "2010-01-07 23:05:14">

Pour vérifier deux fois que ça a marché, regardons la rangée dans la base de données de développement en utilisant le Navigateur de base de données (illustration 6.8). Notez que les colonnes correspondent aux attributs du modèle de données défini à la section 6.1.

sqlite_user_row
Illustration 6.8: Une rangée utilisateur dans la base de données SQLite db/development.sqlite3(taille normale)

Ensuite vient la vue, qui est minimale pour mettre l'accent sur le fait que c'est juste une démonstration (extrait 6.24). Nous utilisons le fichier standard de Rails pour afficher un utilisateur, app/views/users/show.html.erb ; contrairement à la vue new.html.erb, que nous avons créée avec le générateur de l'extrait 5.23, le fichier show.html.erb n'existe pas encore, donc vous devez le créer à la main.

Extrait 6.24. Une vue brute pour afficher les informations de l'utilisateur.
app/views/users/show.html.erb
<%= @user.nom %>, <%= @user.email %>

Cette vue utilise du code Ruby embarqué pour afficher le nom et l'adresse mail de l'utilisateur, en assumant l'existence d'une variable d'instance appelée @user. Bien sûr, la page finale d'affichage de l'utilisateur sera très différente, et n'affichera pas publiquement l'adresse email.

Enfin, nous ajouterons l'action show au contrôleur Users (correspondant à la vue show.html.erb) avec le code de l'extrait 6.25, qui définit la variable d'instance @user nécessaire à la vue.

Extrait 6.25. Le contrôleur Users avec une action show.
app/controllers/users_controller.rb
class UsersController < ApplicationController

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

  def new
    @title = "S'inscrire"
  end
end

Ici nous avons donné un peu plus de nous-mêmes en utilisant le paramètre (params), objet standard de Rails pour récupérer l'identifiant (id) de l'utilisateur. Quand nous ferons la requête appropriée au contrôleur User, params[:id] sera l'id utilisateur 1, donc l'effet est le même que la commande find (trouver) :

User.find(1)

… que nous avons vu à la section 6.1.4.

Bien que la vue et l'action show soient maintenant toutes deux définies, nous n'avons toujours pas de moyen de voir la page elle-même. Cela nécessite de définir la règle adéquate dans le fichier de routage de Rails, comme nous allons le voir dans la section suivante.

6.3.3 Une ressource Users

Nous méthode pour afficher la page d'affichage de l'utilisateur va suivre les conventions de l'architecture REST préconisée pour les applications Rails. Ce style est basé sur les idées de transfer d'état représentationnel (representational state transfer) identifié et nommé par l'expert en informatique Roy Fielding dans sa dissertation doctorale Architectural Styles and the Design of Network-based Software Architectures (Styles architecturaux et conception des architectures de logiciels de réseau).24 Le style de conception REST met l'accent sur la représentation des données en tant que ressources qui peuvent être créées, affichées, actualisées et détruites — quatre actions correspondant au quatre opérations fondamentales POST, GET, PUT et DELETE définies par le standard HTTP (Box 3.1).

Quand on suit les principes REST, les ressources sont typiquement référencées en utlisant des noms de ressource et un identifiant unique. Qu'est-ce que ça signifie dans le contexte des utilisateurs — auxquels nous penserons en tant que « ressource Users » ? Cela signifie que nous devrions voir l'utilisateur avec un identifiant de 1 en envoyant une requête GET à l'URL /users/1. Ici, l'action show est implicite dans le type de requête — quand les fonctionnalités REST de Rails sont activées, les requêtes GET sont automatiquement traitées par l'action show action.

user_show_exception_caught
Illustration 6.9: L'effet initial de l'URL /users/1(taille normale)

Malheureusement, l'URL /users/1 ne fonctionne pas encore, à cause d'une erreur de routage (illustration 6.9). Nous pouvons faire fonctionner l'URL utilisateur de style REST en ajoutant users (utilisateurs) comme ressource au fichier config/routes.rb, comme vu dans l'extrait 6.26.

Extrait 6.26. Ajout d'une ressource Users au fichier de routage.
config/routes.rb
SampleApp::Application.routes.draw do
  resources :users

  match '/signup',  :to => 'users#new'
  .
  .
  .
end

Après avoir ajouté les routes pour la ressource Users, l'URL /users/1 fonctionne parfaitement (illustration 6.10).

user_show_rails_3
Illustration 6.10: La page d'affichage de l'utilisateur à l'URL /users/1 après avoir ajouté une ressource Users. (taille normale)

Vous avez peut-être noté que l'extrait 6.26 a effacé la ligne :

get "users/new"

… vue auparavant dans l'extrait 5.29. C'est parce que la ligne de ressource ajoutée dans l'extrait 6.26 n'ajoute pas seulement une URL /users/1 fonctionnelle ; elle confère à notre application exemple toutes les actions nécessaires à une ressource Users RESTful,25 et un grand nombre de routes nommées (section 5.2.3) pour générer les URLs utilisateur. La résultante des correspondances entre URLs, actions et routes nommées est montrée dans la Table 6.2 (à comparer avec la Table 2.2). Au cours des trois prochains chapitres, nous couvrirons toutes les autres entrées de la Table 6.2 à mesure que nous remplirons toutes les actions nécessaires pour faire de Users une ressource pleinement conforme à REST.

HTTP requestURLActionRoute nomméeBut
GET/usersindexusers_pathliste des utilisateurs
GET/users/1showuser_path(1)page affichant l'utilisateur d'id 1
GET/users/newnewnew_user_pathpage créant un nouvel utilisateur (signup)
POST/userscreateusers_pathcréer un nouvel utilisateur
GET/users/1/editeditedit_user_path(1)page d'édition de l'utilisateur d'id 1
PUT/users/1updateuser_path(1)actualiser l'utilisateur d'id 1
DELETE/users/1destroyuser_path(1)détruire l'utilisateur d'id 1
Table 6.2: Routes RESTful fournies par la ressource Users dans l'extrait 6.26.

Les params en débuggage (debug)

Avant de quitter la page d'affichage de l'utilisateur, nous allons prendre un moment pour examiner les informations de débuggage produite par l'extrait 6.23. Si vous regardez attentivement l'illustration 6.10, vous verrez qu'elle fournit des informations utiles à propos de la page rendue :26

--- !map:ActiveSupport::HashWithIndifferentAccess
action: show
id: "1"
controller: users

C'est une représentation YAML27 des params, laquelle (comme suggéré par le « Hash » du nom HashWithIndifferentAccess, qui signifie « table de hachage ») est basiquement une table de hachage. Nous voyons que son contrôleur est users, son action est show, et son attribut id est "1". Bien que vous aurez rarement l'occasion d'utiliser params[:controller] ou params[:action], utiliser params[:id] pour extraire l'identifiant de l'URL est un idiome Rails très courant. En particulier, nous utilisons le code :

User.find(params[:id])

… dans l'extrait 6.25 pour trouver l'utilisateur d'id 1 (la méthode find sait comment convertir la chaine de caractères "1" en entier 1).

L'information debug fournit souvent des retours utiles au cours du développement des applications Rails et je vous suggère d'adopter l'habitude de le consulter chaque fois que votre application se comporte de façon inattendue.

6.4 Conclusion

Ce chapitre est la première moitié du processus en deux temps de la création d'un modèle utilisateur (User) fonctionnel. Nos utilisateurs ont maintenant des attributs nom et email, chacun paré de validations contraignant leur valeur. Nous avons aussi amorcé une page d'affichage de l'utilisateur et une ressource utilisateur basée sur l'architecture REST. Au chapitre 7, nous achèverons le processus en ajoutant des mots de passe et des vues de l'utilisateur plus utiles.

Si vous utilisez Git, il serait bien de commettre un nouveau dépôt si vous ne l'avez pas fait depuis un moment :

$ git add .
$ git commit -m "Finished first cut of the User model"

6.5 Exercises

  1. Lisez l'API Rails à l'entrée pour ActiveRecord::Base pour avoir une idée de ses capacités.
  2. Étudiez l'entrée pour la méthode validates pour en apprendre plus sur ses capacités et ses options.
  3. Passer un peu de temps (une ou deux heures) à vous amuser avec Rubular.
  1. Mockingbird ne gère pas les images personnalisées comme la photo du profil de l'illustration 6.1 ; je l'ai déposée à la main en utilisant Adobe Fireworks. L'hippo ici vient de http://www.flickr.com/photos/43803060@N00/24308857/
  2. Ce nom (Active Record) vient de la « pattern active record », identifiée et nommée dans Patterns of Enterprise Application Architecture par Martin Fowler. 
  3. Prononcez « ess-quiou-elle », bien que la prononciation alternative « sé-quel » (en anglais) est aussi courante. 
  4. Dans ses toutes premières incarnations, Rails requiérait la connaissance des DLL de SQL. Même après que Rails eut ajouté les migrations, mettre en place l'ancienne base de données par défaut (MySQL) était nécessaire. Heureusement, comme noté à la section 1.2.5, Rails utilise maintenant SQLite par défaut, ce qui enregistre les données sous la forme de simples fichiers — aucun réglage n'est plus nécessaire. 
  5. Occasionnellement, il est nécessaire de comprendre la couche d'abstraction sous-jacente, mais l'une des philosophies de ce tutoriel est de rendre tout le code indépendant de la base de données (je vous assure que c'est un but qui vaut le coup, en général). Dans le cas où vous auriez à écrire le code d'une base de données spécifique pour un déploiement sur Heroku, sachez qu'Heroku utilise l'excellente base de données PostgreSQL (prononcez « poste-grèce-kiou-elle »). PostgreSQL est libre, open-source, et cross-plateforme ; si vous développez des applications spécifiquement PostgreSQL, vous pouvez l'installer localement, et configurer Rails pour l'utiliser en mode de développement en éditant le fichier config/database.yml. Une telle configuration dépasse le cadre de ce tutoriel, mais vous trouverez de nombreuses ressources sur le web ; utilisez un moteur de recherche pour chercher de l'information actualisée en fonction de votre plateforme. 
  6. En utilisant l'adresse email comme username, nous offrons la possibilité théorique de communiquer avec nos utilisateurs dans le futur. 
  7. Ne vous souciez pas de comment l'objet t s'arrange exactement pour faire ça ; la beauté des couches d'abstraction tient à ce que nous n'avons pas besoin de le savoir. Nous pouvons juste faire confiance à l'objet t pour faire son travail. 
  8. Nous verrons comment migrer sur un serveur à distance Heroku à la section 7.4.2
  9. Officiellement prononcé « ess-cue-ell-ite » (en anglais.NdT), bien que la (mauvaise)prononciation « sequel-ite » (en anglais. NdT) soit aussi courante. 
  10. Dans le cas où vous seriez surpris par "2010-01-05 00:57:46", je n'ai pas écrit ce code après minuit ; les timestamps sont enregistrés en Coordonnées de Temps Unversel (UTC), qui pour la plupart des propos est le même que le Temps de Greenwich. Tiré du NIST Time and Frequency FAQ: Question : Pourquoi UTC est utilisé comme acronyme de Coordinated Universal Time plutôt que CUT ? Réponse : En 1970 le système Coordinated Universal Time a été conçu par un groupe consultatif d'experts internationaux à l'intérieur de l'Union Internationale de Communication (International Telecommunication Union, ITU). L'ITU a pensé qu'il serait mieux d'utiliser une simple abréviation pour l'usage dans tous les langages dans le but de minimiser la confusion. Puisqu'aucun accord unanime n'a pu être trouvé sur l'utilisation soit de l'ordre des mots anglais, CUT, soit de l'ordre des mots français, TUC, le compromis de l'acronyme UTC a été choisi. 
  11. Notez la valeur de user.updated_at. Dites-vous bien que le timestamp était en UTC. 
  12. Les exceptions et la gestion des exceptions est un des sujets avancés de Ruby, et nous n'en aurons pas vraiment besoin dans ce livre. Ils sont importants cependant, et je vous suggère de les étudier en utilisant un livre sur Ruby recommandé à la section 1.1.1
  13. À ceux qui s'inquiéteraient que find_by_email serait inefficace s'il y avait une grande quantité d'utilisateurs, je dirais que vous êtes en avance sur la musique. Nous couvrirons ce problème, et sa solution, par le biais des index de base de données, à la section 6.2.4
  14. (et écrit) 
  15. J'omettrai la sortie des commandes de la console quand elles ne sont pas particulièrement instructives — par exemple, les résultats de User.new
  16. Notez que, dans la Table 6.1, « letter » (lettre) signifie en réalité « lettre minuscule », mais le i à la fin de l'expression régulière force à ne pas tenir compte de la casse (minuscule/majuscule). 
  17. Si vous le trouvez aussi utile que moi, je vous encourage à faire un don à Rubular pour remercier le développeur Michael Lovitt pour son formidable travail. 
  18. Saviez-vous que "Michael Hartl"@example.com, avec des guillemets et une espace au milieu est une adresse valide, conformément au standard ? Aussi surprenant que ça paraisse, c'est vrai — mais c'est absurde. Si vous n'avez pas une adresse mail ne contenant que des lettres, des nombres, des tirets plats et des points, alors changez-en ! N.B. L'expression régulière de l'extrait 6.17 permet plus de signes aussi, parce que Gmail (et possiblement d'autres services d'email) font quelque chose d'utile pour eux : par exemple, pour filtrer les ordres d'Amazon, vous pouvez utiliser username+amazon@gmail.com, qui se rendra vers l'adresse Gmail username@gmail.com, vous permettant de filtrer la chaine amazon
  19. Comme indiqué brièvement dans l'introduction de cette section, il existe une base de données de test dédiée, db/test.sqlite3, dans ce but. 
  20. Si vous vous demandez pourquoi la ligne create! de l'extrait 6.10 ne conduit pas à une erreur en créant une duplication d'utilisateur, c'est parce que les tests Rails sont transactionnels : chaque test est enroulé dans une transaction, qui repart de l'état précédent de la base de données après l'exécution des tests. De cette façon, chaque test s'applique à une base de données fraiche. 
  21. Oui, ça m'est arrivé. Comment croyez-vous que j'ai découvert cette explication ? 
  22. Bien sûr, nous pourrions juste éditer le fichier de migration pour la table users de l'extrait 6.2 mais ça exigerait de revenir en arrière puis à nouveau en avant. La façon Rails est d'utiliser des migrations différentes chaque fois que nous découvrons que notre modèle de data doit changer. 
  23. Vous pouvez même définir votre propre environnement personnalisé; voyez le Railscast pour ajouter un environement pour les détails. 
  24. Fielding, Roy Thomas. Architectural Styles and the Design of Network-based Software Architectures. Doctoral dissertation, University of California, Irvine, 2000. 
  25. Cela signifie que le routage fonctionne, mais que les pages correspondantes ne fonctionnent pas, elles, à ce point du développement. Par exemple, /users/1/edit sera proprement routé vers l'action edit du contrôleur Users, mais puisque l'action edit n'existe pas encore, en fait, invoquer cette URL retournera une erreur. 
  26. Certaines des copies-écran de ce tutoriel montre des informations de débuggage avec des sorties comme !map:HashWithIndifferentAccess au lieu de !map:ActiveSupport::HashWithIndifferentAccess. C'est simplement une différence mineure entre Rails 2.3 et Rails 3. Puisque les page web rendues sont en grande partie identiques entre les deux versions, cette note de bas de page me permet de ne pas avoir à les refaire toutes. 
  27. L'information debug Rails est affichée au format YAML (un acronyme récursif signifiant « YAML Ain’t Markup Language »), qui est un format de donnée convivial conçu pour être lu aussi facilement par les machines que par les humains.