QuantumGlitch / mongoose-sub-references-populate

Package useful for populating references to sub documents
MIT License
2 stars 1 forks source link

Unable to populate from within an array #1

Open mikeknerr opened 2 years ago

mikeknerr commented 2 years ago

There are many scenarios where someone would want to populate a subdocument from another model from within an array of subdocs on another model. My current use case example is this:

Meal Plan Schema (simplified)

const MealPlanUserSchema = new Schema({
  recipes: [{
    date: {
      type: Date,
      required: true,
    },
    recipe: {
      type: Schema.Types.ObjectId,
      ref: 'RecipeUser',
      required: true,
    },
  }],
  foods: [{
    date: {
      type: Date,
      required: true,
    },
    food: {
      type: Schema.Types.ObjectId,
      ref: 'FoodUser',
      required: true,
    },
  }],
}, options);

Shopping List Schema

const ShoppingListSchema = new Schema(
  {
    user: {
      type: Schema.Types.ObjectId,
      ref: 'User',
      required: true,
      unique: true,
    },
    recipes: [
      ...doesn't matter
    ],
    foods: [
      {
        adjQuantity: Number,
        name: String,
        matchingMealPlanFoods: [{
          type: Schema.Types.ObjectId,
          subRef: 'MealPlanUser.foods',
        }],
        matchingMealPlanRecipeFoods: [{
          type: Schema.Types.ObjectId,
          subRef: 'MealPlanUser.recipes.foods',
        }],
        completed: Boolean,
        cleared: Boolean,
      },
    ],
  },
  options,
);

This throws the error Cannot read property options of undefined which from digging in a bit further seems to be based on this package not anticipating any deeper nesting than top-level (as shown in the examples).

This seems like an important optimization, as the use case above is equally valid as a top-level reference to a subdoc on another model.

QuantumGlitch commented 2 years ago

Hi, watching your code it seems that

    foods: [
      {
        adjQuantity: Number,
        name: String,
        matchingMealPlanFoods: [{
          type: Schema.Types.ObjectId,
          subRef: 'MealPlanUser.foods',
        }],
        matchingMealPlanRecipeFoods: [{
          type: Schema.Types.ObjectId,
          subRef: 'MealPlanUser.recipes.foods', // <------ Here
        }],
        completed: Boolean,
        cleared: Boolean,
      },
    ],

the path referred by the subRef doesn't seem to exist. Which is the real intent here?

The model MealPlanUser provides a property 'recipes' but this one has no property 'foods'. Am i right?

mikeknerr commented 2 years ago

The recipe path is a reference to another schema, RecipeUser, which has a property of foods:

const RecipeUserSchema = new Schema({
  name: {
    type: String,
    required: true,
  },
  foods: [{
    quantity: {
      type: Number,
      default: 1,
    },
    measureUnit: String,
    name: String,
    food: {
      type: Schema.Types.ObjectId,
      ref: 'Food',
    },
  }],
},
{
  toJSON: { virtuals: true },
  toObject: { virtuals: true },
}); // options

However, at the moment I am testing the matchingMealPlanFoods, which has shallower nesting, and that one still isn't populating as expected.

mikeknerr commented 2 years ago

From what I can tell, something related to the array situation is going on here:

eachPathRecursive(schema, (path, schemaType) => {
    console.log('path', path);
    console.log('options', schemaType.options);
    if (schemaType.options.subRef) {
      populableReferencesPaths[path] = schemaType
    };
  });

Because for the relevant paths, this is what comes up for options:

path foods.matchingMealPlanFoods
 options { type: [ { type: [Function], subRef: 'MealPlanUser.foods' } ] }
path foods.matchingMealPlanRecipeFoods
options {
type: [ { type: [Function], subRef: 'MealPlanUser.recipes.foods' } ]
| }

Therefore, none of the paths will actually be truthy for schemaType.options.subRef since the subRef is buried in a type array.

EDIT: However, it seems that this populableReferencesPaths object may not actually be used anywhere...

Digging further, the issue may actually be in the execute function:

for (let option of options) {
    // The schema type of sub populate's path

    const localFieldSchemaType = model.schema.path(option.path)
    // Do we know who is the ref to the root document of the sub reference ?
    const localFieldBoundToSchemaType = localFieldSchemaType.options.boundTo
      ? model.schema.path(localFieldSchemaType.options.boundTo)
      : null;
    console.log(localFieldSchemaType.options)

It appears that the option.path here is foods.matchingMealPlanFoods, which makes sense, however, localFieldSchemaType resolves to undefined. So it seems model.schema.path may only work with top-level properties.

mikeknerr commented 2 years ago

Out of curiosity, is this something that will be resolved/investigated in a future version?