Populating Mongoose objects from id to new field
Asked Answered
S

3

15

I was working with mongoose to populate field of ids with their respective documents to a new field.my question is assuming my cart model is -

let CartSchema = new mongoose.Schema({
    userId: {
        type: mongoose.Schema.Types.ObjectId,
        ref: 'User'
    },
    productIds: [
        {
            type: mongoose.Schema.Types.ObjectId,
            ref: 'Product'
        }
    ]
});

i want to populate the products so i used

Cart.find({}).populate("products").exec(function (err, cart) {
    console.log(cart)
}

but this populates the documents in the same field name productIds and i want to populate those fields in a new field name called "products" so i tried this

let CartSchema = new mongoose.Schema({
        userId: {
            type: mongoose.Schema.Types.ObjectId,
            ref: 'User'
        },
        productIds: [
            {
                type: String
            }
        ]
    }, { toJSON: { virtuals: true } });

CartSchema.virtual('products', {
    ref: 'Product',
    localField: 'productIds',
    foreignField: '_id',
});

Cart.find({}).populate("products").exec(function (err, cart) {
    console.log(cart)
}

but returned empty array named products.so how can i populate the productIds array to a new field name products with their respective document array.

Thanks.

Sympathy answered 29/1, 2019 at 20:21 Comment(0)
P
2

the approch is correct you should see your data poulated in the products field. make sure you have the correct data and model n

Prevocalic answered 30/1, 2019 at 9:4 Comment(0)
A
20

There's a way to do this - it's called Virtuals (see docs). The idea is to create a "virtual property" which is not actually saved to the DB and acts as a computed property. As per the example provided by qinshenxue on the related github issue:

// declare your ID field as a regular string
var countrySchema = new mongoose.Schema({
    capitalId: {type:String}
});

// create a virtual field which links between the field you've just declared 
// and the related collection. 
// localField is the name of the connecting field, 
// foreign field is a corresponding field in the connected collection
// justOne says that it'll populate a single connected object, 
// set it to false if you need to get an array
countrySchema.virtual('capital',{
    ref: 'City',
    localField: 'capitalId',
    foreignField: '_id',
    justOne: true
});

// tell Mongoose to retreive the virtual fields
countrySchema.set('toObject', { virtuals: true });
countrySchema.set('toJSON', { virtuals: true });

// now you can populate your virtual field like it actually exists
// the following will return a City object in the 'capital' field
Country.find().populate('capital') 
Avisavitaminosis answered 3/9, 2020 at 23:38 Comment(5)
I guess this solves the issue, however it bastarizes the Mongoose models. If OP had used a loosely typed tech stack entirely (e.g. NodeJS) then this never would have been a problem. If you have a strongly typed front-end, then the proper solution here is to find a solution on the strongly typed front-end rather than bastardizing the Mongoose models on the loosely typed backend.Cyanic
@Cyanic please define "bastardizes". In my opinion, no matter if you use typings or not, having an object with a surprise property (one that you never know what does it contain), is a tremendously error-prone solution that should never be used.Avisavitaminosis
Mongoose populate would be called intentionally, not randomly. E.g. if you have an API endpoint then that endpoint would always return DB data either populated or not populated, not both randomly. The front-end is then coded against that.Cyanic
@Cyanic so if I have, let's say, 3 populatable properties, I should have 9 types for such object? For each combination of populated/unpopulated properties? This indeed does look like bastardization.Avisavitaminosis
i think it's clearer this way, capitalId contains always an ObjectID, no matter the use case. the virtual field is a convenient way to resolve the id into an object.Payload
P
2

the approch is correct you should see your data poulated in the products field. make sure you have the correct data and model n

Prevocalic answered 30/1, 2019 at 9:4 Comment(0)
C
-3

Doing what you want to do technically goes against the convention with regards to using Mongoose. You can keep things simple by just renaming the "productIds" field to "products":

If you think about it, an array of products can be an array of the product id values, or it can be the actual documents. The property name "products" apply correctly to all scenarios, "productIds" do not.

Considering populated documents also has the "_id" property on each of them, there is no need to bloat the JSON with new virtual properties just for the id values - you already have them!

It's highly unlikely that you will get ids when expecting documents or get documents when expecting ids because you are always aware of the times you choose to populate the property and when not. Example: If you are talking to an API endpoint, then that API endpoint will always either return the products populated or not populated, not both randomly. Your front-end is then coded against that!

Cyanic answered 29/1, 2019 at 20:43 Comment(4)
yes you are right but if i want to add products to my cart i would put the ids of the product in the id and when i get my cart i would like to get the ids populated as product and while programing my model in ios i am going to need to create 2 different models to send and recive cart info if i use that one field for bothSympathy
@NatnaelGetachew I assumed you have a loosely typed tech stack (e.g. NodeJS) entirely. If you do not then you cannot always fully re-use your loosely typed models in your strongly typed front-end. I would suggest you find a solution on the strongly typed front-end rather than bastardizing your Mongoose models on the loosely typed backend.Cyanic
Do you ever used Prisma? if not I recommend it. hen I guess you will understand the point of using a separate field. Besides let me to share my experience with you, long story short, we used to use Mongoose like what you said in a project with TS, It was awful since we have to do this in our schema: city: ObjectId | string | City which city was referencing the city collection. You can guess what a tough time we had with type checking.Fyn
Makes sense @KasirBarati. It was a long time ago, but in my case, we did that in a loosely typed tech stack and it worked nicely. We embraced the benefits of loosely typed languages to move fast. With that said, I can fully understand the complications when part of the stack is statically typed and has to work with the data.Cyanic

© 2022 - 2024 — McMap. All rights reserved.