Virtual en action !

🌱Idée sous-jacente

Pour Mongoose : les virtuals sont utilisés pour simuler des relations entre entités, notamment avec populate et les mécanismes de lookup côté MongoDB.


  • Virtual = champ calculé, non stocké en base

  • Virtual populate = relation déclarative

🎯But

Le but est de séparer logique et persistance

- Le modèle MongoDB reste simple (sans duplication ou clé étrangère obligatoire).

-  Les entités sont reliées au niveau applicatif, via Mongoose, ce qui rend le code plus expressif et maintenable.


🆘Explication

 lecture  : → virtual


Virtual est au coeur de mongoose ! 

Autres exemples.


🆘Le coût d'une pizza : champ calculé non stocké.
  1. import mongoose from "mongoose";

  2. const pizzaSchema = new mongoose.Schema({
  3.   name: { type: String, required: true },
  4.   // `description` stores a simple recipe/notes string (renamed from `recept`)
  5.   description: { type: String, default: "" },
  6. }); 

  7. // Include virtuals when converting documents to objects/JSON
  8. pizzaSchema.set("toObject", { virtuals: true });
  9. pizzaSchema.set("toJSON", { virtuals: true });

  10. // Virtual populate: connect Pizza -> Topping via Topping.pizzas (inverse relation)
  11. pizzaSchema.virtual("toppings", {
  12.   ref: "Topping",
  13.   localField: "_id",
  14.   foreignField: "pizzas",
  15.   justOne: false,
  16. });

  17. // Virtual total price (cents) includes toppings when `toppings` is populated
  18. pizzaSchema.virtual("totalPriceCents").get(function () {
  19.   // Compute total price from populated `toppings` only (no base pizza price)
  20.   if (
  21.     !this.toppings ||
  22.     !Array.isArray(this.toppings) ||
  23.     this.toppings.length === 0
  24.   )
  25.     return 0;
  26.   return this.toppings.reduce((acc, t) => acc + (t.priceCents || 0), 0);
  27. });

  28. pizzaSchema.virtual("totalPriceEur").get(function () {
  29.   return (this.totalPriceCents || 0) / 100;
  30. });

  31. export const Pizza = mongoose.model("Pizza", pizzaSchema);
🆘relation inverse ! 

Soit un schéma topping : 

const toppingSchema = new mongoose.Schema({
  title: { },
  pizzas: [{ type: mongoose.Schema.Types.ObjectId, ref: "Pizza" }],
});

pizzas = ensemble des pizzas qui a cet ingrédients.

🛟Comment trouver les pizzas qui ont un ingredient ?

→ Dans le schéma pizza on ecrit un virtual !

🥷pizzaSchema.virtual("toppings", { ref: "Topping", localField: "_id", foreignField: "pizzas", justOne: false, });

Ici on crée une relation inverse :

  • ref: "Topping" → référence le modèle Topping.
  • localField: "_id" → utilise l’ID de la pizza.
  • foreignField: "pizzas" → cherche dans le champ pizzas des documents Topping (qui doit être un tableau d’IDs de pizzas).
  • justOne: false → une pizza peut avoir plusieurs toppings.

👉 En clair : ce virtual permet de récupérer tous les Topping qui contiennent l’ID de cette pizza dans leur champ pizzas.