0

I want to return a restaurant with given shortName and filter its array menuEntries to only contain menu entries with field isAvailable set to true.

Here is my schema:

var restaurantSchema = new mongoose.Schema({
    shortName: String,
    fullName: String,
    address: String,
    logoImageUrl: String,
    description: String,
    location: {
        type: { type: String },
        coordinates: [Number]
    },
    menuEntries: [{
        name: String,
        description: String,
        isAvailable: Boolean
    }],
    updatedAt: {type : Date}
});

restaurantSchema.index({ 'location': '2dsphere' });

mongoose.model('Restaurant', restaurantSchema, 'Restaurants');

I am using the following query but it still returns menu entries that have isAvailable set to false:

Restaurant
    .findOne({
        shortName: shortName,
        menuEntries: { $elemMatch: { isAvailable: { $eq: true } } }
    }, function(error, restaurant) {
        if (error) {
            returnJsonResponse(response, 500, {
                'message': error
            });
        } else if (!restaurant) {
            returnJsonResponse(response, 404, {
                'message': 'Restaurant with name ' + shortName + ' doesn\'t exist'
            });
        } else {
            returnJsonResponse(response, 200, restaurant);
        }
    });

EDIT

It doesn't work with the following code either:

Restaurant
    .findOne({
        shortName: shortName
    })
    .elemMatch('menuEntries', {'isAvailable': true})
    .exec(function(error, restaurant) {
        if (error) {
            returnJsonResponse(response, 500, {
                'message': error
            });
        } else if (!restaurant) {
            returnJsonResponse(response, 404, {
                'message': 'Restaurant with name ' + shortName + ' doesn\'t exist'
            });
        } else {
            returnJsonResponse(response, 200, restaurant);
        }
    });

I am using mongoose ^5.6.2 and MongoDB 3.6.9. What am I doing wrong?

Bill
  • 507
  • 1
  • 5
  • 11

2 Answers2

2
viewRestaurant: (request, callback) => {
        let aggrQuery = [
            {
                '$match': { "shortName": request }
            },
            { $unwind: "$menuEntries" },
            { '$match': { 'menuEntries.isAvailable': true } },
        ]
        restaurantQuery.aggregate(aggrQuery).exec((err, data) => {
            if (err) {
                callback(err, null)
            }
            else {
                callback(null, data)
            }
        })
    },

router.post('/view-restaurant',(req,res)=>{
    var request = req.body.shortName;
    restaurantController.viewRestaurant(request,(err,data)=>{
        if(err) throw err;
        res.json(data);
    })
})
Sourav De
  • 99
  • 4
0

$elemMatch is not what you're looking for. You're trying to filter an array inside a document not a list of documents based on an array. In order to accomplish what you want to achieve you'll have to use aggregations and the $filter operator.

Please take a look at the official example but here's what I think your code should look like:

Restaurant.aggregate([
   {
      $project: {
         menuEntries: {
            $filter: {
               input: "$menuEntries",
               as: "entry",
               cond: { $$eq: [ "$$entry.price", true ] }
            }
         },
        shortName: 1,
        fullName: 1,
        address: 1,
        logoImageUrl: 1,
        description: 1,
        location: 1,
        updatedAt: 1
      }
   }
])

Note that $project requires you to manually include all the other fields.

Niklas Higi
  • 2,188
  • 1
  • 14
  • 30
  • This deletes all fields other than `menuEntries`. How can I preserve them? – Bill Jul 02 '19 at 16:37
  • Sorry for the late reply but have to [manually specify the other fields to be included](https://docs.mongodb.com/master/reference/operator/aggregation/project/#include-existing-fields). I'll edit my answer to include them. – Niklas Higi Jul 03 '19 at 15:39