Développement Web

Active Record : modèles, migrations et associations

12 min de lecture
📍 Article principal du parcours : Ruby on Rails : le guide complet pour débuter. Ce tutoriel fait partie du parcours « Ruby on Rails ». Pour la vue d’ensemble, commencez par le guide principal.

Jusqu’ici, AtelierFix affiche des chiffres écrits en dur dans le code. Le jour où Modou redémarre le serveur, tout disparaît — parce que rien n’est stocké. Une vraie application a besoin d’une base de données : des clients qu’on retrouve demain, des réparations qui s’accumulent, des liens entre les deux. C’est le rôle d’Active Record, la couche de Rails qui traduit vos objets Ruby en lignes de base de données — et l’inverse — sans que vous écriviez une seule requête SQL à la main.

Dans ce tutoriel, on dote AtelierFix de deux tables liées : les clients de l’atelier et leurs réparations. Vous allez créer les modèles, faire évoluer le schéma avec des migrations, déclarer la relation qui les unit, puis manipuler de vraies données depuis la console Rails.

🎯 Ce que vous allez apprendre

  • Expliquer ce qu’est un ORM et comment Active Record relie une classe Ruby à une table ;
  • Générer un modèle et faire évoluer la base avec une migration ;
  • Créer une relation un-à-plusieurs avec has_many et belongs_to ;
  • Créer, lire et filtrer des enregistrements depuis la console Rails ;
  • Protéger vos données avec des validations.

🛠️ Ce que vous allez construire

Le cœur de données d’AtelierFix : un modèle Client (nom, téléphone) et un modèle Reparation (appareil, panne, statut, prix en FCFA) rattaché à son client. À la fin, vous saurez créer en deux lignes un client et ses réparations, puis interroger la base — « combien de réparations en cours ? » — sans quitter le terminal.

Prérequis

  • Avoir suivi l’installation et le tutoriel MVC : l’application atelierfix démarre et vous êtes à l’aise avec les fichiers d’un projet Rails.
  • Quelques notions de base de données (table, colonne, ligne) aident, sans être indispensables.
  • ⏱️ Temps estimé : ~45 minutes.

Active Record en deux idées

Active Record est un ORM (Object-Relational Mapping). Retenez deux correspondances et tout devient simple : une classe Ruby représente une table, et un objet de cette classe représente une ligne. Quand vous écrivez Client.create(nom: "Awa"), Active Record fabrique en coulisse la requête INSERT INTO clients… et la lance. Quand vous écrivez Client.where(nom: "Awa"), il génère le SELECT correspondant. Vous pensez en objets Ruby ; le SQL est produit pour vous.

Deuxième idée, les migrations. La structure de la base (les tables, les colonnes) n’est jamais modifiée à la main : on écrit un fichier de migration qui décrit le changement, et Rails l’applique. L’intérêt ? Le schéma est versionné comme du code — votre collègue, ou votre serveur de production, rejoue exactement les mêmes migrations et obtient exactement la même base. Plus de « ça marche sur ma machine ».

Étape 1 — Créer le modèle Client

On commence par les clients de l’atelier. Le générateur de modèle crée la classe Ruby et la migration qui ajoutera la table :

bin/rails generate model Client nom:string telephone:string

Deux fichiers naissent : app/models/client.rb (la classe, vide pour l’instant) et une migration dans db/migrate/ décrivant une table clients avec deux colonnes texte. La migration n’a encore rien fait — elle décrit le changement. Pour l’appliquer à la base :

bin/rails db:migrate

Rails exécute la migration, crée la table clients (avec, en bonus, deux colonnes created_at et updated_at qu’il tient à jour automatiquement), et met à jour db/schema.rb, le portrait actuel de votre base. Vous venez de créer votre première table sans une ligne de SQL.

Étape 2 — Créer le modèle Reparation lié au client

Une réparation appartient toujours à un client. Le générateur sait exprimer ce lien grâce au type references :

bin/rails generate model Reparation appareil:string panne:text statut:string prix:integer client:references
bin/rails db:migrate

Le client:references est la pièce clé. Il ajoute à la table reparations une colonne client_id, un index pour accélérer les recherches, et une contrainte de clé étrangère qui garantit qu’une réparation pointe toujours vers un client réel. Mieux : il inscrit tout seul belongs_to :client dans app/models/reparation.rb. Notez le choix de prix:integer — on y revient plus bas, c’est volontaire pour de la monnaie.

Étape 3 — Déclarer la relation côté client

Le côté « une réparation appartient à un client » est posé. Reste le côté inverse : « un client possède plusieurs réparations ». Ce lien-là, on l’ajoute à la main. Ouvrez app/models/client.rb :

class Client < ApplicationRecord
  has_many :reparations, dependent: :destroy
end

Le has_many :reparations donne au client une méthode reparations qui renvoie toutes ses réparations. L’option dependent: :destroy dit : « si on supprime un client, supprime aussi ses réparations » — sans quoi on laisserait en base des réparations orphelines pointant vers un client disparu. Côté réparation, vérifiez que belongs_to :client est bien présent (le générateur l’a ajouté). Les deux modèles se connaissent désormais mutuellement.

Étape 4 — Manipuler de vraies données dans la console

La console Rails est un atelier interactif : un terminal Ruby où toute votre application est chargée. C’est l’endroit idéal pour créer et interroger des données sans écrire d’interface. Lancez-la :

bin/rails console

Créez un premier client, puis l’une de ses réparations à travers la relation :

awa = Client.create(nom: "Awa Ndiaye", telephone: "77 123 45 67")
awa.reparations.create(appareil: "Tecno Spark", panne: "Écran cassé", statut: "reçu", prix: 15000)

Parce qu’on passe par awa.reparations, Active Record renseigne le client_id tout seul : la réparation est automatiquement reliée à Awa. Interrogez maintenant la base :

Client.count                       # => 1
awa.reparations.count              # => 1
Reparation.where(statut: "reçu")   # => les réparations au statut "reçu"
Reparation.first.client.nom        # => "Awa Ndiaye"

Cette dernière ligne est parlante : depuis une réparation, .client.nom remonte jusqu’au nom de son propriétaire. La relation fonctionne dans les deux sens. Pour quitter la console : exit.

Point d’étape — Vous avez deux tables liées et des données réelles qui survivent au redémarrage du serveur. Relancez la console et tapez Client.first : Awa est toujours là. Si awa.reparations.create renvoie une erreur de colonne inconnue, c’est qu’une migration n’a pas été appliquée — relancez bin/rails db:migrate.

Étape 5 — Protéger les données avec des validations

Rien n’empêche encore de créer un client sans nom, ou une réparation sans statut. Les validations sont des règles que Active Record vérifie avant d’écrire en base : si une règle est violée, l’enregistrement est refusé. On les déclare dans le modèle. Dans app/models/client.rb :

class Client < ApplicationRecord
  has_many :reparations, dependent: :destroy
  validates :nom, presence: true
end

Et dans app/models/reparation.rb, on impose un statut parmi une liste fermée, pour éviter les fautes de frappe qui rendraient les filtres inutilisables :

class Reparation < ApplicationRecord
  belongs_to :client
  validates :appareil, presence: true
  validates :statut, inclusion: { in: %w[reçu en_cours prêt livré] }
end

Testez en console : Client.create(nom: "") renvoie un objet non sauvegardé. Vérifiez avec c = Client.create(nom: ""); c.errors.full_messages — Rails vous explique précisément ce qui cloche. Ces validations deviendront vos alliées dès le tutoriel suivant : c’est elles qui afficheront des messages d’erreur propres dans les formulaires.

Une validation mérite une mention à part : l’unicité. Dans un atelier, deux fiches client avec le même numéro de téléphone sèment la confusion. Une seule ligne l’empêche : validates :telephone, uniqueness: true dans le modèle Client. Rails vérifiera qu’aucun autre client ne porte ce numéro avant d’enregistrer. Attention toutefois : cette garde se joue au niveau applicatif ; pour une sécurité à toute épreuve, on double souvent l’unicité d’un index unique en base, ajouté par une migration. Pour débuter, la validation de modèle suffit largement et rend déjà l’application bien plus fiable.

Lire et filtrer : les requêtes à connaître

Créer des données, c’est la moitié du travail ; les retrouver vite en est l’autre. Active Record propose une poignée de méthodes que vous emploierez tous les jours. Toujours en console, voici celles qui couvrent l’essentiel des besoins d’AtelierFix :

Client.find(1)                       # le client d'identifiant 1 (erreur s'il n'existe pas)
Client.find_by(telephone: "77 123 45 67")  # le premier qui correspond, ou nil
Reparation.where(statut: "reçu")     # toutes les réparations reçues
Reparation.where(statut: "reçu").order(created_at: :desc)  # les plus récentes d'abord
Reparation.where("prix > ?", 20000) # les réparations à plus de 20 000 FCFA
Client.order(:nom).limit(10)         # dix clients, triés par nom

Deux nuances valent de l’or. D’abord, find lève une erreur si l’enregistrement n’existe pas, alors que find_by renvoie tranquillement nil — choisissez selon que l’absence est une anomalie ou un cas normal. Ensuite, ces requêtes sont paresseuses : Reparation.where(statut: "reçu") ne touche la base qu’au moment où vous lisez réellement le résultat (un .count, un .each, un affichage). Vous pouvez donc enchaîner .where(...).order(...).limit(...) sans déclencher plusieurs requêtes : Rails les fusionne en un seul SQL.

Un dernier réflexe pour plus tard : quand vous parcourez une liste de réparations en affichant le nom de chaque client, chargez les clients d’un coup avec Reparation.includes(:client). Sans cette précaution, Rails interroge la base une fois par réparation — le fameux problème « N+1 » qui ralentit silencieusement les applications. Ce n’est pas urgent aujourd’hui, mais le jour où une page rame, ce sera la première piste à vérifier.

🐞 Pièges fréquents

Symptôme / erreur Cause probable Correctif
no such column: reparations.client_id Migration non appliquée bin/rails db:migrate
Validation failed: Client must exist Réparation créée sans client (Rails 8 l’exige par défaut) Créer la réparation via client.reparations.create(...)
Une migration s’est trompée de colonne Erreur dans le fichier de migration bin/rails db:rollback, corriger, puis db:migrate
PendingMigrationError au démarrage Des migrations attendent d’être appliquées Lancer bin/rails db:migrate

🌍 Adaptation au contexte ouest-africain

Un détail qui compte : on a stocké le prix en integer, pas en nombre à virgule. C’est exactement ce qu’il faut pour le franc CFA, qui ne se divise pas en centimes — un montant de 15 000 FCFA est un entier, point. Cela évite au passage les erreurs d’arrondi des nombres flottants, qui sont un classique des applications de facturation. Pour des devises à décimales, on utiliserait plutôt le type decimal ; pour le FCFA, l’entier est à la fois plus juste et plus simple. Côté téléphone, restez sur du string et jamais un nombre : un numéro comme « 77 123 45 67 » contient des espaces et peut commencer par zéro, deux choses qu’un type numérique détruirait.

✅ Récapitulatif

AtelierFix a maintenant une mémoire. Vous avez créé deux modèles, fait évoluer la base par des migrations versionnées, relié clients et réparations par une association un-à-plusieurs, peuplé et interrogé la base depuis la console, et posé des validations qui garantissent la qualité des données. Surtout, vous avez vu qu’Active Record vous laisse raisonner en objets Ruby pendant qu’il écrit le SQL à votre place.

🧾 Aide-mémoire

Commande / code Rôle
bin/rails generate model Client nom:string Créer un modèle et sa migration
bin/rails db:migrate Appliquer les migrations en attente
bin/rails db:rollback Annuler la dernière migration
client:references Ajouter une clé étrangère + belongs_to
has_many :reparations, dependent: :destroy Relation un-à-plusieurs côté parent
bin/rails console Ouvrir la console interactive
validates :nom, presence: true Refuser un enregistrement incomplet

💪 À vous de jouer

Ajoutez un second client avec deux réparations, puis comptez le nombre total de réparations « en_cours » dans toute la base.

Voir une solution
modou = Client.create(nom: "Modou Fall", telephone: "70 987 65 43")
modou.reparations.create(appareil: "iPhone 11", panne: "Batterie", statut: "en_cours", prix: 25000)
modou.reparations.create(appareil: "Samsung A14", panne: "Charge", statut: "reçu", prix: 8000)
Reparation.where(statut: "en_cours").count   # => 1

Tutoriels frères

Pour aller plus loin

FAQ

Différence entre un modèle et une migration ?
Le modèle est une classe Ruby qui décrit le comportement de vos données (relations, validations). La migration est un fichier qui modifie la structure de la base (créer une table, ajouter une colonne). Le modèle vit longtemps ; une migration ne s’exécute qu’une fois.

Faut-il écrire du SQL avec Rails ?
Presque jamais pour le quotidien : Active Record couvre la création, la lecture, le filtrage et les relations. Pour des requêtes analytiques très pointues, on peut descendre au SQL, mais ce n’est pas un prérequis pour débuter.

Qu’est-ce que db/schema.rb ?
C’est le portrait à jour de votre base, regénéré à chaque migration. Ne le modifiez pas à la main : changez le schéma uniquement via des migrations, et ce fichier suivra.

Comment annuler une migration que je viens d’écrire ?
bin/rails db:rollback défait la dernière. Corrigez le fichier de migration, puis relancez bin/rails db:migrate. Tant que vous êtes en développement, c’est sans risque.

Partager