'Mongoose populating refs inside refs

I'm trying to save and retrieve a collection of game matches with associated games, and who played in it. My schema looks like this,

const TournamentSchema = new mongoose.Schema({
  matches: [{
    games: [{
      type: mongoose.Schema.Types.Mixed,
      ref: 'Game',
      players: [{
        type: mongoose.Schema.Types.ObjectId,
        ref: 'Player',
      }],
    }],
  }],
});

This is what the object in the database looks like,

{
    "__v": 0,
    "_id": "5a50ed6b267ddd32c4523327",
    "matches": [
        {
            "_id": "5a50ed6b267ddd32c4523328",
            "games": [
                {
                    "players": [
                        { "_id": "5a4fa908d9d55465ac4fdbe6" },
                        { "_id": "5a50cf3d09176c2bb0f98fe1" }
                    ],
                    "_id": "5a498918ffc6220edbe8a403"
                },
                {
                    "players": [
                        { "_id": "5a50cf5609176c2bb0f98fe2" },
                        { "_id": "5a50cf6009176c2bb0f98fe3" }
                    ],
                    "_id": "5a50cf9007c2bb0c73f3783a"
                }
            ]
        }
    ],
}

I'm trying to retrieve it like this,

async function list(req, res, next) {
  logger.log('info', 'Incoming request to retrieve all tournaments');

  const tournaments = await Tournament.find().populate('matches.games.players');
  return res.json(tournaments);
}

However, what I get from the database is the same as what was saved. i.e the refs don't get resolved. If I change type: Mixed from games to type: ObjectId it wont persist players, but populate will resolve games. How do I work with refs inside refs?

As requested this is what my Game schema looks like,

const GameSchema = new mongoose.Schema({
  name: {
    type: String,
    unique: true,
    required: true,
    lowercase: true,
  },
  scoring: {
    type: Object,
    required: true,
    rank: {
      first_place: Number,
      second_place: Number,
      third_place: Number,
    },
  },
  max_num_players: Number,
  min_num_players: Number,
}, { runSettersOnQuery: true });

Each Game can have different scoring percentage per rank. For example for Counter Strike if you were first place, you would get 100% of points, second 80%, third 50%. However, for League of Legends first place would be 85%, second 60%, and third 50%.



Solution 1:[1]

I think the problem is that you name "game" both the game definition in the game collection and the tournament game (which actually is game + players).

I would write the schema like this (not tested):

const TournamentSchema = new mongoose.Schema({
  matches: [{
    games: [{
      game: {
         type: mongoose.Schema.Types.ObjectId,
         ref: 'Game',
      },
      players: [{
        type: mongoose.Schema.Types.ObjectId,
        ref: 'Player',
      }],
    }],
  }],
});

And you would query like this:
Tournament.find().populate('matches.games.game matches.games.players')

I still find unclear what the Game schema contains and why the Game itself does not have a list of players.

Solution 2:[2]

Maybe it's a bit late, but I'll share what I've understood through researching of a similar case of nested populating (as the title says). In the documentation it says that you could solve population across multiple levels in this way: We have an Schema:

const CustomerSchema = new Schema({
  orders: [
    {
      type: Schema.Types.ObjectId || null,
      ref: "Order",
    },
  ],
(...)

})

But the orders at the same time have products refs:

const OrderSchema = new Schema({
products: {
    type: [{ type: Schema.Types.ObjectId, ref: "Product" }],
  },
(...)
});

Now you could do a deep population like this:

const user = await CustomerModel.findById(id)
      .populate("user", "-password")
      .populate({ path: "orders", populate: { path: "products" } }) <---
      .exec();

Hope this might help someone with a similar case.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 XCS
Solution 2 Luciano Cavallo