Search code examples
mongoosemongoose-populate

In Virtual Populate, how do you define foreignField?


Consider the code below:

 require("./connection");

// //----------------------------------------------------
const mongoose = require("mongoose");
const Schema = mongoose.Schema;

const PersonSchema = new Schema({
  name: String,
  band: String,
  father: String
});

const ManagerSchema = new Schema({
  name: String,
  country: String
});

const BandSchema = new Schema({
  name: String
});

BandSchema.virtual("members", {
  ref: "Person", // The model to use
  localField: "name", // Find people where `localField`
  foreignField: "band", // is equal to `foreignField`
  // If `justOne` is true, 'members' will be a single doc as opposed to
  // an array. `justOne` is false by default.
  justOne: false,
  options: { sort: { name: -1 }, limit: 5 } 
});

BandSchema.virtual("managers", {
  ref: "Manager", // The model to use
  localField: "name", // Find people where `localField`
  foreignField: "country", // is equal to `foreignField`
  // If `justOne` is true, 'members' will be a single doc as opposed to
  // an array. `justOne` is false by default.
  justOne: false,
  options: { sort: { name: 1 }, limit: 5 } 
});

//BandSchema.set("toObject", { virtuals: true });
BandSchema.set("toJSON", { virtuals: true });

const Person = mongoose.model("Person", PersonSchema);
const Manager = mongoose.model("Manager", ManagerSchema);

const Band = mongoose.model("Band", BandSchema);

/**
 * Suppose you have 2 bands: "Guns N' Roses" and "Motley Crue"
 * And 4 people: "Axl Rose" and "Slash" with "Guns N' Roses", and
 * "Vince Neil" and "Nikki Sixx" with "Motley Crue"
 */
// Person.create([
//   {
//     name: "Axl Rose",
//     band: "Guns N' Roses"
//   },
//   {
//     name: "Slash",
//     band: "Guns N' Roses"
//   },
//   {
//     name: "Vince Neil",
//     band: "Motley Crue"
//   },
//   {
//     name: "Nikki Sixx",
//     band: "Motley Crue"
//   }
// ]);

// Manager.create([
//   {
//     name: "Bibi",
//     country: "South Africa"
//   },
//   {
//     name: "Storm",
//     country: "Italy"
//   },
//   {
//     name: "Wolverine",
//     country: "Canada"
//   },
//   {
//     name: "Jorge Pires",
//     country: "Brazil"
//   }
// ]);

// Band.create([{ name: "Motley Crue" }, { name: "Guns N' Roses" }]);
/////////////////////////////////////////////////////////////////////////

const app = require("express")();

app.use("/", (req, res) => {
  Band.find({})
    .populate("members")
    .populate("managers")
    .exec(function(error, bands) {
      /* `bands.members` is now an array of instances of `Person` */
      console.log(bands);
      res.json(bands);
    });
});

app.listen(3000, () => {
  console.log("We are on port 3000");
});

/**
 *https://stackoverflow.com/questions/43882577/mongoosejs-virtual-populate
 https://stackoverflow.com/questions/60875380/populate-virtuals-does-not-seem-to-work-could-anyone-show-me-the-error
 */

Consider the related questions:

My question is: how do you define foreignField?

Members populate properly, but manager does not.

I know the problem is foreignField because if I repeat all the information from members, it will populate properly, but now we have members and manage with the same data source.


Solution

  • After some studies, trying to answer another question here on Stack Overflow (mongoose: populate in mongoose which doesn't have any ObjectId ), I realized how it works!

    Consider part of the code presented:

    BandSchema.virtual("members", {
      ref: "Person", // The model to use
      localField: "name", // Find people where `localField`
      foreignField: "band", // this field here has to match the ref path we want to populate!      
      justOne: false,
    
    });
    

    So, the trick is to make sure foreignField match the field at the ref model, and localField is the name of the field that you can find at the populated model: we must have a match between foreignField and localField, more precisely: the value of localField has to match the one of foreignField in real situation, in the database, not in the schema naming process. That is how mongoose can find and populate!

    What now I realise I was having difficult is that Virtual operates somehow in the opposite direction of populate. You can picture populate by working as a tree: it just populate from the id to the document, whereas virtual will populate the document that does not contain the key, the one that contain the key is the one to be added in the populate process: it is somehow backward, that is it was so hard to me to grasp! The document to be populated does not have the field just populated! it is mind-blowing!

    Conclusion and final remarks

    Nonetheless, I still find it limited compared to populate. You still must keep local key tracks, which does not solve for instance the story memory issue, and it is more limited according to my studies. The only advantage I see is that you do not need to use the _id, you can use any key you please. I was hoping to use to solve my problem here How to save an JSON file using GridFs, but since we still have to store local keys, I fall into de same trap!

    Correction

    I am glad to say that I was wrong! Virtuals is awsome! and solves my probem on How to save an JSON file using GridFs, I am going to update there!