Node.js – Mongoose sort by populated field

mongodbmongoosenode.jssorting

well i have the following shemas:


    var BrandSchema = new Schema({
      name: {
        type: String,
        required: true,
        index: {
          unique: true
        },
        lowercase: true
      },
      logo: {
        type: ObjectId,
        ref: 'Image'
      }
    });

    var FollowActionSchema = new Schema({
      'actionDate': {
        type: Date,
        'default': Date.now
      },
      'brand': {
        type: ObjectId,
        ref: 'Brand'
      },
      'performer': {
        type: ObjectId,
        ref: 'User'
      },
      'type': String // followUser, folloBrand, followMerchant
    });

What i want is getting the user following brands, sorting by brand name, so for do that i did the query to FollowAction and find all the FollowActions that user did and then i populate the brand field.

So the problem is that i can't sort the query for the brand name, the only way i know to do that is by returning all documents and sort them from nodejs app. Anyone knows how can i do that?? or if i should change the shema structure??

The query that i do is:


       async.waterfall([
        function findActions(next) {
          var options = {
            query: {
              performer: userId,
              $or: [{
                type: 'followBrand'
              }, {
                type: 'followMerchant'
              }]

            },
            projection: {
              actionDate: 1,
              brand: 1,
              merchant: 1,
              type: 1
            },
            sort: '-actionDate',
            pagination: pagination
          };
          utils.limitQuery('FollowAction', options, next);
        },
        function inflate(actions, next) {
          total = actions.count;
          var options = {
            projection: {
              name: 1,
              _id: 1,
              username: 1
            }
          };
          async.eachSeries(actions.result, function(action, done) {
            async.waterfall([
              function inflateAction(done) {
                action.inflate(options, done);
              },
              function addToArray(item, done) {
                trusted.push({
                  _id: item._id,
                  name: utils.capitalize(item.name || item.username),
                  type: item.name ? 'brand' : 'merchant'
                });
                return done(null, item);
              }
            ], done);
          }, next);
        }
      ], function(err) {
        callback(err, trusted, total);
      });

Best Solution

The Mongoose API does seem to support sorting on populated fields, but there's a bug that breaks it entirely: https://github.com/Automattic/mongoose/issues/2202. You get a result, but it's just plain wrong.

For small amounts of data, it's fine to sort the result array using Javascript Array.prototype.sort(). Keep in mind that this directly modifies the sorted array though.

What I've done in this case is add a sort key property to the schema for the model you want to sort. For your example, you could do:

var FollowActionSchema = new Schema({
  // ...
  'brandSortKey': { type: String },
  'brand': {
    type: ObjectId,
    ref: 'Brand'
  },
  // ...
});

This isn't perfect, because you'll have to explicitly set this property with the correct key yourself:

var FollowAction = Model('FollowAction', FollowActionSchema);

var aBrand = // some brand object

var f = new FollowAction({
   brand: aBrand._id,
   brandSortKey: aBrand.name
   // other properties
}); 

But, then you can sort directly via the Mongoose API (or MongoDB):

FollowAction.find({})
   .sort({ brandSortKey:1 })
   .exec(function (err, sortedResults) {
       // do something with sorted results.
   });