Mohammad Rayan Mohammad Rayan - 3 months ago 21
Node.js Question

Using find in a array of objects Schema using Mongoose

Am having the following Mongoose Schema in my Node Application

var expenseSchema = new Schema({
particular : String,
date : {type : Date, default: Date.now},
paid_by : String,
amount : Number,
month : String
});

var roomSchema = new Schema({
name: String,
admin: String,
roomies : [String],
expenses : [expenseSchema]
},{
collection : 'rooms'
});


I need to get expenses for a room for a particular month using find().
Here is what I was trying, however it returns me the whole room object

Room.findOne({_id : req.params._id, 'expenses.month' : 'oct'}).exec(function(err, result){
if(result == null) {
res.json({result : 'Oops! We couldn\'t find any rooms...'});
} else if(err) {
res.json({result : 'Error in getting Rooms'});
} else {
res.json({result : result});
}
});


Can someone help me with this?

Answer

You can use the positional $ operator in your projection to return the matching expenses array element in your result:

Room.findOne(
    { "_id": req.params._id, "expenses.month": "oct" }, 
    { "expenses.$": 1 }  
).exec(callback);

or for multiple matches you could use the aggregation framework's $filter operator in the $project pipeline as:

var ObjectId = mongoose.Types.ObjectId;
Room.aggregate()
    .match({ "_id": new ObjectId(req.params._id), "expenses.month": "oct" }) 
    .project({ 
        "expenses": {
            "$filter": {
                "input": "$expenses", 
                "as": "item",
                "cond": { "$eq": [ "$$item.month", "oct" ] }
            }
        }
    })
    .exec(callback);

or if you are using an older version of Mongoose driver that does not have support for the MongoDB 3.2 $filter, you could use $map alongside $setDifference, where you can "filter" array contents without using $unwind:

var ObjectId = mongoose.Types.ObjectId;
Room.aggregate([
    { "$match": { "_id": new ObjectId(req.params._id), "expenses.month": "oct" } },        
    { "$project": { 
        "expenses": {
            "$setDifference": [
                {
                    "$map": {
                        "input": "$expenses",
                        "as": "items",
                        "in": {
                            "$cond": [
                                { "$eq": [ "$$items.month", "oct" ] },
                                "$$items",
                                false
                            ]
                        }
                    }
                },
                [false]
            ]
        }
    } }
], function(err, results){          
    console.log(JSON.stringify(results[0], null, 4));
});
Comments