NicolasMoise NicolasMoise - 5 months ago 99
Node.js Question

Mongoose Query: Find an element inside an array

Mongoose/Mongo noob here:

My Data

Here is my simplified data, each user has his own document

{ "__v" : 1,
"_id" : ObjectId( "53440e94c02b3cae81eb0065" ),
"email" : "test@test.com",
"firstName" : "testFirstName",
"inventories" : [
{ "_id" : "active",
"tags" : [
"inventory",
"active",
"vehicles" ],
"title" : "activeInventory",
"vehicles" : [
{ "_id" : ObjectId( "53440e94c02b3cae81eb0069" ),
"tags" : [
"vehicle" ],
"details" : [
{ "_id" : ObjectId( "53440e94c02b3cae81eb0066" ),
"year" : 2007,
"transmission" : "Manual",
"price" : 1000,
"model" : "Firecar",
"mileageReading" : 50000,
"make" : "Bentley",
"interiorColor" : "blue",
"history" : "CarProof",
"exteriorColor" : "blue",
"driveTrain" : "SWD",
"description" : "test vehicle",
"cylinders" : 4,
"mileageType" : "kms" } ] } ] },
{ "title" : "soldInventory",
"_id" : "sold",
"vehicles" : [],
"tags" : [
"inventory",
"sold",
"vehicles" ] },
{ "title" : "deletedInventory",
"_id" : "deleted",
"vehicles" : [],
"tags" : [
"inventory",
"sold",
"vehicles" ] } ] }


As you can see, each user has an
inventories
property that is an array that contains 3 inventories (activeInventory, soldInventory and deletedInventory)

My Query

Given an user's email a a vehicle ID, i would like my query to go through find the user's
activeInventory
and return just the vehicle that matches the ID. Here is what I have so far:

user = api.mongodb.userModel;
ObjectId = require('mongoose').Types.ObjectId;
return user
.findOne({email : params.username})
.select('inventories')
.find({'title': 'activeInventory'})
//also tried
//.where('title')
//.equals('activeInventory')
.exec(function(err, result){
console.log(err);
console.log(result);
});


With this, result comes out as an empty array. I've also tried
.find('inventories.title': 'activeInventory')
which strangely returns the entire inventories array. If possible, I'd like to keep the chaining query format as I find it much more readable.

My Ideal Query

return user
.findOne({email : params.username})
.select('inventories')
.where('title')
.equals('activeInventory')
.select('vehicles')
.id(vehicleID)
.exec(cb)


Obviously it does not work but it can give you an idea what I'm trying to do.

Answer

Using the $ positional operator, you can get the results. However, if you have multiple elements in the vehicles array all of them will be returned in the result, as you can only use one positional operator in the projection and you are working with 2 arrays (one inside another).

I would suggest you take a look at the aggregation framework, as you'll get a lot more flexibility. Here's an example query for your question that runs in the shell. I'm not familiar with mongoose, but I guess this will still help you and you'd be able to translate it:

db.collection.aggregate([
    // Get only the documents where "email" equals "test@test.com" -- REPLACE with params.username
    {"$match" : {email : "test@test.com"}}, 
    // Unwind the "inventories" array
    {"$unwind" : "$inventories"}, 
    // Get only elements where "inventories.title" equals "activeInventory"
    {"$match" : {"inventories.title":"activeInventory"}}, 
    // Unwind the "vehicles" array
    {"$unwind" : "$inventories.vehicles"}, 
    // Filter by vehicle ID -- REPLACE with vehicleID 
    {"$match" : {"inventories.vehicles._id":ObjectId("53440e94c02b3cae81eb0069")}}, 
    // Tidy up the output
    {"$project" : {_id:0, vehicle:"$inventories.vehicles"}}
])

This is the output you'll get:

{
        "result" : [
                {
                        "vehicle" : {
                                "_id" : ObjectId("53440e94c02b3cae81eb0069"),
                                "tags" : [
                                        "vehicle"
                                ],
                                "details" : [
                                        {
                                                "_id" : ObjectId("53440e94c02b3cae81eb0066"),
                                                "year" : 2007,
                                                "transmission" : "Manual",
                                                "price" : 1000,
                                                "model" : "Firecar",
                                                "mileageReading" : 50000,
                                                "make" : "Bentley",
                                                "interiorColor" : "blue",
                                                "history" : "CarProof",
                                                "exteriorColor" : "blue",
                                                "driveTrain" : "SWD",
                                                "description" : "test vehicle",
                                                "cylinders" : 4,
                                                "mileageType" : "kms"
                                        }
                                ]
                        }
                }
        ],
        "ok" : 1
}