Devanshu Singh
Devanshu Singh

Reputation: 1

How can i populate a sub Schema in different model in mongoose

I want to populate a model which has an ObjectId of a document which is present as sub schema on another model

Sub Schema

var addressSchema = new Schema({
    address: String,
    phoneNumber: Number,
    name: String,
    pincode: Number,
    augmontAddressId: String,
});

Main Schema - address is an array that can have multiples addresses and each object has its own objecId which i want to refer to

var userSchema = new Schema(
    {
      name: String,
        address: [addressSchema],
    },
    {
        timestamps: true,
    }
);

Ledger Schema referencing to Main Schema

var ledgerSchema = new Schema(
    {
        txnId: String,
        paymentId: String,
        addressId:{
            type: mongoose.Schema.Types.ObjectId,
            ref: "User",
        },
    },
    {
        timestamps: true,
    }
);

I tried

LedgerSchema.findById().populate("addressId").then(res=>{
    console.log(res);
})

But its not populating the addressId instead sending null value

{
  _id: new ObjectId("650c49a20483f1ec8eb7aecd"),        
  addressId: null,
  createdAt: 2023-09-21T13:48:18.667Z,
  updatedAt: 2023-09-21T13:48:18.667Z,
  __v: 0
}

Also i tried setting ref in ledgerSchema as 'User.address' But it gives an error

MissingSchemaError: Schema hasn't been registered for model "User.Address".

Upvotes: 0

Views: 81

Answers (1)

jQueeny
jQueeny

Reputation: 8082

Oh how nice it would be if you could just do User.address to reference things. Unfortunately, this can't be done with populate due to the way you have set up your subdocuments. In your userSchema each address is essentially a subdocument that is embedded in the top-level document. In contrast, within in your ledgerSchema, each addressId is a referenced document and are separate top-level documents.

My advice would be to change your userSchema so that each address is a referenced document but that will create another collection in your database (addresses). You need that to do references.

If for whatever reason you cant modify your schema and need to keep each address as an embedded subdocument then you can achieve the desired result with aggregate. It is not a trivial task and it is not pretty but it does work:

const ledger = await ledgerModel.aggregate([
    { $match: { _id: new mongoose.Types.ObjectId(id) } }, //< Your ledger _id
    {
        $lookup: //< Do a lookup to match addressId with userSchema.address._id
            {
                from: "users",
                localField: "addressId",
                foreignField: "address._id",
                as: "address_details" //< This creates a new array
            }
    },
    { $unwind: '$address_details'}, //< Now you need to unwind the new array
    { $unwind: '$address_details.address'}, //< And unwind each address
    {
        $project: //< This stage only outputs the original ledger properties
            {
                txnId: 1,
                paymentId: 1,
                address_details: 1,
                compare_address_ids: { $cmp: [ "$addressId", "$address_details.address._id" ] },
               //^ Compare addressId against ones done via lookup to find a match
            }
    },
    {
        $match: {
            compare_address_ids: 0 //< 0 means they matched
        }
    },
    {
        $project: //< Now get rid of these two made up properties from the output
            {
                compare_address_ids: 0,
                'address_details.name': 0,
            }
    }
]);

Upvotes: 0

Related Questions