1

Not sure if this would be considered a dup, but I've searched around and implemented similar queries to what I find online but can't seem to get my nested reference to work. I am simply testing to understand the mongoose syntax for populating both nested references and nested docs as discussed here: Mongoose nested schema vs nested models

However, I must be missing something because it seems to work but returns an empty array of nested references. I know that my query should return two results for the nested reference.

  • What am I overlooking or doing wrong?
  • How would I run my query differently if I wanted to use nested documents?

Data:

Result Collection:

{
    "_id" : ObjectId("5a4dcbe4ab9a793d888c9396"),
    "event_id" : ObjectId("5a482302a469a068edc004e3"),
    "event_name" : "Sample Event",
    "score" : "3-2",
    "winner" : "player1"
},

{
    "_id" : ObjectId("5a59791379cc1c321c1918f0"),
    "event_id" : ObjectId("5a482302a469a068edc004e3"),
    "event_name" : "Sample Event",
    "score" : "2-1",
    "winner" : "player2"
}

Event Collection:

{
    "_id" : ObjectId("5a482302a469a068edc004e3"),
    "type" : "Tournament",
    "name" : "Sample Event"
}

My code is as follows:

var mongoose = require("mongoose");
mongoose.connect("MongoDB://localhost/devDB");

var ResultSchema = mongoose.Schema({
    _id: mongoose.Schema.Types.ObjectId,
    event_id: {type: mongoose.Schema.Types.ObjectId, ref: "EventModel"},
    event_name: String,
    score: String,
    winner: String
});

var ResultModel = mongoose.model("results", ResultSchema);

var EventSchema = mongoose.Schema({
    _id: mongoose.Schema.Types.ObjectId,
    name: String,
    type: String,
    results: [{type: mongoose.Schema.Types.ObjectId, ref: "ResultModel"}] 
});

var EventModel = mongoose.model("events", EventSchema);


function GetEvent(eventid){
    // EventModel.findById(eventid)
    EventModel.findOne({_id: eventid})
        .populate("results","score winner", ResultModel)
        //.select("results") to extract only the nested references
        .exec(function(err, event){
            if (err){
                console.log("ERROR fetching doc: ", err.name + ":  " + err.message)
            } else{
                console.log(event);
            }
    });
}

GetEvent("5a482302a469a068edc004e3");

My output when I run it:

{ 
  results: [],
  _id: 5a482302a469a068edc004e3,
  type: 'Tournament',
  name: 'Test Tournament'
}
mo_maat
  • 2,110
  • 12
  • 44
  • 72
  • Weird. I'm certain my data is in a collection called `results`. In my schemas, am I doing the right thing by passing the Model object as the `ref:` value and not the name of the collection? I find the conventions I see in different solutions very confusing where the name of the collection is the same as the name of the model object. To make it clearer I append model to the name of the model and make all my collections lower case. – mo_maat Jan 14 '18 at 22:56
  • Sorry but I'm totally confused... I looked at your pastebin, but it does not include the query you actually ran to get the results. If my schemas are properly set up, then the problem must be in my query... Can you confirm that I've properly set up my schemas and the nesting? That will at least narrow down where I need to search. Thanks. – mo_maat Jan 14 '18 at 23:22

1 Answers1

5

The problem with your code is that your results array is missing the ObjectIds that are necessary to populate the array. You are currently connecting the collections together with the event_id in the results documents. They refer to the documents in the event collection. However, when you are doing a populate on the events collection it expects the reference to be in the results array.


Take these values for instance:

Results:

{
        "_id" : ObjectId("5a5be9a4669365067f984acb"),
        "event_name" : "Game 1",
        "score" : "1-2",
        "winner" : "ManU",
        "event_id" : ObjectId("5a5be9d9669365067f984acd")
}
{
        "_id" : ObjectId("5a5be9b5669365067f984acc"),
        "event_name" : "Game 2",
        "score" : "3-2",
        "winner" : "Bayern Munich",
        "event_id" : ObjectId("5a5be9d9669365067f984acd")
}

Events:

{
        "_id" : ObjectId("5a5be9d9669365067f984acd"),
        "name" : "Champions League",
        "type" : "Cup",
        "results" : [
                ObjectId("5a5be9a4669365067f984acb"),
                ObjectId("5a5be9b5669365067f984acc")
        ]
}

I've manually inserted the documents with the MongoDB shell. Notice that the objectIds of the result documents are stored in the results array. If I now run this code:

const mongoose = require("mongoose");
mongoose.connect("mongodb://localhost/devDB");

const ResultSchema = mongoose.Schema({
    event_id: {type: mongoose.Schema.Types.ObjectId, ref: "events"},
    event_name: String,
    score: String,
    winner: String
});

const Results = mongoose.model("results", ResultSchema);

const EventSchema = mongoose.Schema({
    name: String,
    type: String,
    results: [{type: mongoose.Schema.Types.ObjectId, ref: "results"}] 
});

const Events = mongoose.model("events", EventSchema);

function GetEvent(eventid){
    Events.findOne({_id: eventid})
        .populate("results", "score winner")
        .exec(function(err, event){
            if (err){
                console.log("ERROR fetching doc: ", err.name + ":  " + err.message)
            } else{
                console.log(event);
            }
    });
}

GetEvent("5a5be9d9669365067f984acd");

I get the following output:

{ _id: 5a5be9d9669365067f984acd,
  name: 'Champions League',
  type: 'Cup',
  results:
   [ { _id: 5a5be9a4669365067f984acb, score: '1-2', winner: 'ManU' },
     { _id: 5a5be9b5669365067f984acc,
       score: '3-2',
       winner: 'Bayern Munich' } ] }

The populated results showed up because the results array actually contained references to the result objects. The collection that the populate method looks for documents in is given in ref. The value there is the name of the registered model, i.e. the name you give as the first argument to mongoose.model().

You are also refering to the event in the result documents with the event_id. It's not really needed with the code you have in your question, but you can leave it if you want to have a connection both ways (result <--> event). You can then create a function like this:

function GetResult(resultid){
    Results.findOne({_id: resultid})
        .populate("event_id", "name type")
        .exec(function(err, result){
            if (err){
                console.log("ERROR fetching doc: ", err.name + ":  " + err.message)
            } else{
                console.log(result);
            }
    });
}

When executed like this

GetResult("5a5be9b5669365067f984acc");

it will give us this result:

{ _id: 5a5be9b5669365067f984acc,
  event_name: 'Game 2',
  score: '3-2',
  winner: 'Bayern Munich',
  event_id:
   { _id: 5a5be9d9669365067f984acd,
     name: 'Champions League',
     type: 'Cup' } }

For nested models (embedding) you no longer store objectIds to other collections in a document. You store the entire document as a subdocument instead. Take for instance this code:

const mongoose = require("mongoose");
mongoose.connect("mongodb://localhost/devDB");

const ResultSchema = mongoose.Schema({
    event_name: String,
    score: String,
    winner: String
});

// Don't register subdocuments!
// const Results = mongoose.model("results", ResultSchema);

const EventSchema = mongoose.Schema({
    name: String,
    type: String,
    results: [ResultSchema] 
});

const Events = mongoose.model("events", EventSchema);

function GetEvent(eventid){
    Events.findOne({_id: eventid})
        // We no longer use populate.
        // .populate("results", "score winner")
        .exec(function(err, event){
            if (err){
                console.log("ERROR fetching doc: ", err.name + ":  " + err.message)
            } else{
                console.log(event);
            }
    });
}

GetEvent("5a5bf133669365067f984ace");

We now store the entire result schema inside the event schema. In this particular case it's an array, but it doesn't have to be. The results collection will no longer exist. The results are store in the event documents themselves. Make sure you don't register subdocument schemas. There is also no point in having event_id so I removed that.

I reinsert the data with the MongoDB shell:

{
        "_id" : ObjectId("5a5bf133669365067f984ace"),
        "name" : "Champions League",
        "type" : "Cup",
        "results" : [
                {
                        "event_name" : "Game 1",
                        "score" : "3-2",
                        "winner" : "ManU"
                },
                {
                        "event_name" : "Game 2",
                        "score" : "1-2",
                        "winner" : "Real Madrid"
                }
        ]
}

And when I use GetEvents("5a5bf133669365067f984ace") I get:

{ _id: 5a5bf133669365067f984ace,
  name: 'Champions League',
  type: 'Cup',
  results:
   [ { event_name: 'Game 1', score: '3-2', winner: 'ManU' },
     { event_name: 'Game 2', score: '1-2', winner: 'Real Madrid' } ] }
Mika Sundland
  • 18,120
  • 16
  • 38
  • 50
  • Awesome!! I started heading down the route you pointed out since I figured the issue had to be related to the set up of my collections. I had a gap in my understanding of mongoose references. Basically, if Collection A wants to reference docs in Collection B, then Collection A must have a field that holds the Objectids of Collection B. What I had thought and wanted to do was keep my events collection from being too bloated with result references and instead get all the results that reference an event. Any downsides to having a potentially high count of references in my events collection? – mo_maat Jan 15 '18 at 00:30
  • By the way, nice choice of sample data. Except I don't like ManU and Real Madrid winning. I would have preferred Chelsea and Barcelona... :) – mo_maat Jan 15 '18 at 00:30
  • @mo_maat Whether there are any downsides in having a high count of references depends on what you are comparing it against. There is a pretty good answer [here](https://stackoverflow.com/questions/24096546/mongoose-populate-vs-object-nesting/24096822#24096822) on data modeling with MongoDB/Mongoose, which explains some of it. – Mika Sundland Jan 15 '18 at 01:16
  • Excellent! This is exactly what I've been looking for. – mo_maat Jan 15 '18 at 01:24