JqckB
JqckB

Reputation: 11

Mongoose sorting subdocument populated

I have a Mongoose model which have paths which need to be populated :

var adsSchema = new Schema({                                                                   
    price:      { type: Number, min: 0, required: true }, 
    author:     { type: ObjectId, ref: 'Users', required: true }, 
    title:      { type: String, required: true }, 
    date:       { type: Date, default: Date.now }, 
    offers:     [{                                
        price:      { type: Number, required: true },                                          
        date:       { type: Date, default: Date.now },                                         
        offerBy:    { type: ObjectId, ref: 'Users', required: true }                           
    }],                                                                                        
    category:   { type: ObjectId },                                                            
    images:     [{                                                                             
        type: ObjectId,                                                                        
        ref: 'Images'                                                                          
    }],                                                                                        
    videos:     [String]                                                                       
});

In some GET request, I need to populated numerous fields as I was saying, in particular offers with a sorting by 'price' by ascending.

As the documentation of Mongoose is showing there (http://mongoosejs.com/docs/api.html#query_Query-populate), you can sort by a subpath.

My problem is that it isn't the case.

There is my code which is doing it :

Ads.findOne({ _id: adId })
  .populate({ path: 'author', select: 'firstName lastName' })
  .populate('images')
  .populate({ path: 'offers', options: { sort: { 'price': -1 } })
  .populate({ path: 'category', select: 'name' })
  .exec(function (err, ad) {
     if (err)
       deferred.reject({ status: 500, message: 'Database error.' });
     else if (!ad)
       deferred.reject({ status: 500, message: 'The ad doesn\'t exist!.' });
     else {
                deferred.resolve(ad.toJSON());
     }
})

I have read as much as possible questions/answers giving here or in the Mongoose mailing-list :

I know it's not possible to sort documents results from subdocument result, OK. But I just want to sort that subdocument only, and only this one. Some responses here seems to say it's possible :

I have 2 questions around it :

Thanks for your answer ;)

Upvotes: 1

Views: 1496

Answers (2)

JqckB
JqckB

Reputation: 11

I found the solution, and it was the response of my second question.

When you are writing a subdocument in your schema like in my question, you are not creating a "relation" between one Model and an other one. Which means that the populate method doesn't work at all.

You have to make a reference to an ObjectId and the model associated to be able to use populate, and so the options which offers you Mongoose. This is an example :

var adsSchema = new Schema({
    price:      { type: Number, min: 0, required: true },
    author:     { type: ObjectId, ref: 'Users', required: true },
    offers:     [{ 
        type: ObjectId,
        ref: 'Offers'
    }],
    videos:     [String]
});

Now, the .populate method is working, because Mongoose (and so MongoDB) can perform the subquery on an other Collection and sort it. It wasn't the case before because it was in the same Model, so Mongoose doesn't perform a subquery on the same Collection (as I seems to understand it).

Upvotes: 0

Semih Gokceoglu
Semih Gokceoglu

Reputation: 1428

Personally, I do not use 'populate' as possible as I can. I had experienced many trouble in the past. So, I do not know how to sort while populating.

Instead of using populate, you can use $lookup method on aggregation and you can easily sort any field. Its almost same way with 'population'.

Ads.aggregate([
        {
          $lookup: {
            from: 'Users',
            localField: 'author',
            foreignField: '_id',
            as: 'authorName'
          }
        },
        {
          $lookup: {
            from: 'Images',
            localField: 'images',
            foreignField: '_id',
            as: 'image'
          }
        },
        {
          $lookup: {
            from: 'categories',
            localField: 'category',
            foreignField: '_id',
            as: 'category'
          }
        },
        {
          $project: {
            'authorName.firstName': 1,
            'image.imagePath': 1,
            'category.categoryName': 1,
            '_id': 0
          }
        },
        {
          $sort : { 'authorName.firstName' : -1} 
        }

      ]).exec(function(err, adss) {

      });

I did not check all fields properly. Please implement this way to your model and hope this can give you some idea. Good luck

Upvotes: 1

Related Questions