Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Doesn't autopopulate with refPath when documents are autopopulated from a virtual field #96

Closed
neohotsauce opened this issue May 28, 2022 · 4 comments · Fixed by Automattic/mongoose#13758

Comments

@neohotsauce
Copy link

I am autopopulating a virtual field and the populated documents have a field with refPath. However the field with refPath is not autopopulated. The same field is autopopulated if refPath is replaced with ref.

Schema with the virtual:

 const AddressSchema = new mongoose.Schema(
    {
      name: {
        type: String
      },
      status: {
        type: String,
        enum: ["active", "inactive"],
        default: "active"
      }
    },
    {
      timestamps: true,
      toJSON: { virtuals: true },
      toObject: { virtuals: true }
    }
  );

  AddressSchema.virtual("residentials", {
    ref: "Citizen",
    localField: "_id",
    foreignField: "permanentAddress.address",
    justOne: false,
    autopopulate: true,
    match: {
      status: "active"
    },
    options: {
      select:
        "name nId"
    }
  });

  AddressSchema.plugin(require("mongoose-autopopulate"));

I am autopopulating residentials as a virtual and the populated documents have the below schema.

Schema of the virtually populated documents:

 const CitizenSchema = new mongoose.Schema(
    {
      nId: {
        type: String,
        unique: true,
        required: [true, "Please add national ID card"]
      },
      name: {
        type: String,
        required: [true, "Please add a name"],
        trim: true
      },
      permanentAddress: {
        name: {
          type: String,
          trim: true
        },
        address: {
          type: mongoose.Schema.ObjectId,
          ref: "Address"
        },
      },
      father: {
        type: mongoose.Schema.ObjectId,
        refPath: "fatherType",
        autopopulate: true
      },
      fatherType: {
        type: String,
        enum: ["Citizen", "Guest"],
        required: true
      },
      status: {
        type: String,
        enum: ["active", "inactive"],
        default: "active"
      }
    },
    {
      timestamps: true,
      toJSON: { virtuals: true },
      toObject: { virtuals: true }
    }
  );

  CitizenSchema.plugin(require("mongoose-autopopulate"));

The father field is not autopopulated with refPath but is autopopulated when replaced with ref.

@neohotsauce
Copy link
Author

It works if select option is removed from the virtual. Works if the virtual is as below:

 AddressSchema.virtual("residentials", {
    ref: "Citizen",
    localField: "_id",
    foreignField: "permanentAddress.address",
    justOne: false,
    autopopulate: true,
    match: {
      status: "active"
    }
  });

@IslandRhythms
Copy link
Contributor

The virtual is not autopopulated while the document property is.

const mongoose = require('mongoose');

const AddressSchema = new mongoose.Schema(
  {
    name: {
      type: String
    },
    status: {
      type: String,
      enum: ["active", "inactive"],
      default: "active"
    }
  },
  {
    timestamps: true,
    toJSON: { virtuals: true },
    toObject: { virtuals: true }
  }
);

AddressSchema.virtual("residentials", {
  ref: "Citizen",
  localField: "_id",
  foreignField: "permanentAddress.address",
  justOne: false,
  autopopulate: true,
  match: {
    status: "active"
  },
  options: {
    select:
      "name nId"
  }
});

AddressSchema.plugin(require("mongoose-autopopulate"));


const CitizenSchema = new mongoose.Schema(
  {
    nId: {
      type: String,
      unique: true,
      required: [true, "Please add national ID card"]
    },
    name: {
      type: String,
      required: [true, "Please add a name"],
      trim: true
    },
    permanentAddress: {
      name: {
        type: String,
        trim: true
      },
      address: {
        type: mongoose.Schema.ObjectId,
        ref: "Address"
      },
    },
    father: {
      type: mongoose.Schema.ObjectId,
      refPath: "fatherType",
      autopopulate: true
    },
    fatherType: {
      type: String,
      enum: ["Citizen", "Guest"],
      required: true
    },
    status: {
      type: String,
      enum: ["active", "inactive"],
      default: "active"
    }
  },
  {
    timestamps: true,
    toJSON: { virtuals: true },
    toObject: { virtuals: true }
  }
);

CitizenSchema.plugin(require("mongoose-autopopulate"));


const Address = mongoose.model('Address', AddressSchema);

const Citizen = mongoose.model('Citizen', CitizenSchema);

async function run() {
  await mongoose.connect('mongodb://localhost:27017');
  await mongoose.connection.dropDatabase();

  const entry = await Address.create({
    name: "Another name for The Address",
    status: "active"
  });

  const doc = await Citizen.create({
    nId: 'Hello',
    name: 'There',
    permanentAddress: {
      name: 'The Address',
      address: entry._id
    },
    fatherType: "Guest"
  });
  const res = await Citizen.create({
    nId: 'Yo',
    name: 'Test',
    permanentAddress: {
      name: "The Address",
      address: entry._id
    },
    father: doc._id,
    fatherType: "Citizen",
    status: "active"
  });
  console.log(await Citizen.find());
  console.log(await Address.findOne())
}

run();

Output:

[
  {
    permanentAddress: {
      name: 'The Address',
      address: new ObjectId("63bc67003be77fc70e511bff")
    },
    _id: new ObjectId("63bc67003be77fc70e511c02"),
    nId: 'Hello',
    name: 'There',
    fatherType: 'Guest',
    status: 'active',
    createdAt: 2023-01-09T19:12:00.278Z,
    updatedAt: 2023-01-09T19:12:00.278Z,
    __v: 0,
    id: '63bc67003be77fc70e511c02'
  },
  {
    permanentAddress: {
      name: 'The Address',
      address: new ObjectId("63bc67003be77fc70e511bff")
    },
    _id: new ObjectId("63bc67003be77fc70e511c04"),
    nId: 'Yo',
    name: 'Test',
    father: {
      permanentAddress: [Object],
      _id: new ObjectId("63bc67003be77fc70e511c02"),
      nId: 'Hello',
      name: 'There',
      fatherType: 'Guest',
      status: 'active',
      createdAt: 2023-01-09T19:12:00.278Z,
      updatedAt: 2023-01-09T19:12:00.278Z,
      __v: 0,
      id: '63bc67003be77fc70e511c02'
    },
    fatherType: 'Citizen',
    status: 'active',
    createdAt: 2023-01-09T19:12:00.305Z,
    updatedAt: 2023-01-09T19:12:00.305Z,
    __v: 0,
    id: '63bc67003be77fc70e511c04'
  }
]
{
  _id: new ObjectId("63bc67003be77fc70e511bff"),
  name: 'Another name for The Address',
  status: 'active',
  createdAt: 2023-01-09T19:12:00.233Z,
  updatedAt: 2023-01-09T19:12:00.233Z,
  __v: 0,
  residentials: [
    {
      permanentAddress: [Object],
      _id: new ObjectId("63bc67003be77fc70e511c02"),
      nId: 'Hello',
      name: 'There',
      id: '63bc67003be77fc70e511c02'
    },
    {
      permanentAddress: [Object],
      _id: new ObjectId("63bc67003be77fc70e511c04"),
      nId: 'Yo',
      name: 'Test',
      father: new ObjectId("63bc67003be77fc70e511c02"),
      id: '63bc67003be77fc70e511c04'
    }
  ],
  id: '63bc67003be77fc70e511bff'
}

@vkarpov15
Copy link
Member

I took a closer look and the issue is that Mongoose's selectPopulatedFields helper only adds the fields being populated to the projection. It also needs to add the field referenced by refPath to the projection.

As a workaround, you can add fatherType to the projection: select: "name nId fatherType"

@vkarpov15
Copy link
Member

Fix will be in Mongoose 7.4.4.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
3 participants