Mongoose: Option to avoid setting property to `null` if `populate()` finds no results, leaving the original id.

Created on 20 Jan 2016  路  16Comments  路  Source: Automattic/mongoose

My code:

var blogSchema = new Schema({
    name: String,
    blog_slug: String,
    category: { type: Schema.Types.ObjectId, ref: 'category' },
    content: String
});

var categorySchema = new Schema({
    name: String,
    category_slug: String
});

var blog = mongoose.model('blog', blogSchema);
var category = mongoose.model('category', categorySchema);

blog.find().populate({
        path: 'category', 
        select: 'name category_slug', 
        match: { category_slug: req.params.category_slug },
    }).exec(function(error, data){
        console.log(data);
    });
[{
    name: "some thing",
    blog_slug: "some-thing",
    category: {
        name: "Funny",
        category_slug: "funny"
    },
    content: "Blog content"
},
{
    name: "some thing 2",
    blog_slug: "some-thing-2",
    category: null, <-- **Here**
    content: "Blog content 2"
}
]

result[1].category : null . Why?
And how to find blog by category_slug?
I'm a beginner

Most helpful comment

Is there a way to keep the _id instead of null when the match fails?

For example:

If I have the field PersonId (which is an Object Id from the model Person) in another model.

I do want to populate if this _id is not equal the current logged User, lets call id UserId.

so, my query is something like:

.populate({ path: "personId", select: "_id name avatar", match: {_id: {$ne: UserId}}, })

when the PersonId is different from UserId it returns the person data, but when it is equal it returns null. I see why is it and it makes sense.

But is there a way of avoiding the null result and keep the Object Id when the match fails?

All 16 comments

    blog.find().populate({
        path: 'category', 
        select: 'name category_slug', 
        match: { category_slug: req.params.category_slug },
    }).exec(function(error, data){
        console.log(data);
    });

The above code translates to:

  1. Find all blogs
  2. For each blog, find the category which has _id equal to the blog's category field and also has category_slug equal to req.params.category_slug.

Most likely, result.1.category is null because its category has a different category_slug than req.params.category_slug. Does that make sense?

@vkarpov15 when populate a path, if the result not match with match condition, a null will returned instead of real one ?

@isayme yep that sounds right

How to remove that implicit checking of _id matching with category? I am having similar issues. Why is not not taking only the criteria specified in the 'match'. Why taking an inplicit match on _id? Is there a way to solve this?

@abhishekjain2604 I don't understand, can you please elaborate with code samples?

@vkarpov15

let productSchema = new SCHEMA({
    price: Number
});

let pricesSchema = new SCHEMA({
    user: SCHEMA.Types.ObjectId,
    productId: SCHEMA.Types.ObjectId,
    specificPrice: Number
});

productModel = MONGOOSE.model('product', productSchema);
pricesModel = MONGOOSE.model('prices', pricesSchema);

These are my 2 models. The flow is that the productModel has a deafult price. But I can also set user specific prices, as specified in the pricesModel. What I want to be able to do it to populate 'price' field in productModel from pricesModel based on which user is making the request to get it's detail.

So something like this:

productModel.populate(productObj, {
  path: 'price',
  match: {
    user: request.user.id,
    productId: productObj.id
  },
  select: 'specificPrice',
  model: 'pricesModel'
})

@abhishekjain2604 this example should work

That's the problem. In this example, we are populating the path 'fans' which is of the type ObjectId. In my use case, I want to populate the field 'price' which is just a Number.

In that case, you should consider making price a virtual like this:

let productSchema = new SCHEMA({
    defaultPrice: Number
});
productSchema.virtual('price').get(function() {
  if (this.userPrice != null) {
    return this.userPrice.specificPrice;
  }
  return this.defaultPrice;
});
productSchema.virtual('userPrice', {
  ref: 'Price',
  localField: '_id',
  foreignField: 'productId',
  justOne: true
});

That solution would have worked but the 'specific prices' are user specific. In this 'userPrice' virtual example, localField of productSchema matches against the foreign field of priceSchema. But there's another field in priceSchema(user) that needs to be matched against a field that is not present in the productSchema. It will come from the 'request' object inside the controller. request.user.id to be specific.

I understand that my use case might be beyond the realms of what a framework needs to be able to do. But does this use case sounds like something that might be helpful enough to be introduced as a native feature? Something that might prove useful to many others as well?

Perhaps the below will work?

productSchema.virtual('userPrice', {
  ref: 'Price',
  localField: '_id',
  foreignField: 'productId',
  justOne: true
});

// In route handler
Product.findOne({ _id: request.productId }).populate({ path: 'userPrice', match: { userId: request.user.id } });

See this example. The above code will find the userPrice doc whose productId matches the product's _id and whose userId matches the request-specific user id. Does that help?

Sorry for replying so late that too on my own query. But yes, that did work and greatly reduced the overhead of computing everything manually by querying database especially when retrieving 100s of products at the same time.

Thank you so much for your help.

Is there a way to keep the _id instead of null when the match fails?

For example:

If I have the field PersonId (which is an Object Id from the model Person) in another model.

I do want to populate if this _id is not equal the current logged User, lets call id UserId.

so, my query is something like:

.populate({ path: "personId", select: "_id name avatar", match: {_id: {$ne: UserId}}, })

when the PersonId is different from UserId it returns the person data, but when it is equal it returns null. I see why is it and it makes sense.

But is there a way of avoiding the null result and keep the Object Id when the match fails?

@Scientistt no but that is a good idea. Will keep this issue open for a future release.

Can we prioritize this? It's a very common use case when implementing populate with virtuals and I wonder why it's not the default behaviour.

@DoubleHub we'll see what we can do for 5.12. The 5.11 milestone is a little crowded right now.

Was this page helpful?
0 / 5 - 0 ratings