I'm having some trouble querying a document by values matching inside the document after population by mongoose.
My schemas are something like this:
var EmailSchema = new mongoose.Schema({
type: String
});
var UserSchema = new mongoose.Schema({
name: String,
email: [{type:Schema.Types.ObjectId, ref:'Email'}]
});
I would like to have all users which have a email with the type = "Gmail" for example.
The following query returns empty results:
Users.find({'email.type':'Gmail').populate('email').exec( function(err, users)
{
res.json(users);
});
I have had to resort to filtering the results in JS like this:
users = users.filter(function(user)
{
for (var index = 0; index < user.email.length; index++) {
var email = user.email[index];
if(email.type === "Gmail")
{
return true;
}
}
return false;
});
Is there any way to query something like this straight from mongoose?
Mongoose's populate() method does not use MongoDB's $lookup behind the scenes. It simply makes another query to the database. Mongoose does not have functionalities that MongoDB does not have.
Mongoose Populate() Method. In MongoDB, Population is the process of replacing the specified path in the document of one collection with the actual document from the other collection.
Mongoose's populate function doesn't execute directly in Mongo. Instead after the initial find query returns a set a documents, populate will create an array of individual find queries on the referenced collection to execute and then merge the results back into the original documents.
The ref option is what tells Mongoose which model to use during population, in our case the Story model. All _id s we store here must be document _id s from the Story model. Note: ObjectId , Number , String , and Buffer are valid for use as refs.
@Jason Cust explained it pretty well already - in this situation often the best solution is to alter the schema to prevent querying Users
by properties of documents stored in separate collection.
Here's the best solution I can think of that will not force you to do that, though (because you said in the comment that you can't).
Users.find().populate({
path: 'email',
match: {
type: 'Gmail'
}
}).exec(function(err, users) {
users = users.filter(function(user) {
return user.email; // return only users with email matching 'type: "Gmail"' query
});
});
What we're doing here is populating only email
s matching additional query (match
option in .populate()
call) - otherwise email
field in Users
documents will be set to null
.
All that's left is .filter
on returned users
array, like in your original question - only with much simpler, very generic check. As you can see - either the email
is there or it isn't.
Mongoose's populate
function doesn't execute directly in Mongo. Instead after the initial find
query returns a set a documents, populate
will create an array of individual find
queries on the referenced collection to execute and then merge the results back into the original documents. So essentially your find
query is attempting to use a property of the referenced document (which hasn't been fetched yet and therefore is undefined
) to filter the original result set.
In this use case it seems more appropriate to store emails as a subdocument array rather than a separate collection to achieve what you want to do. Also, as a general document store design pattern this is one of the use cases that makes sense to store an array as a subdocument: limited size and very few modifications.
Updating your schema to:
var EmailSchema = new mongoose.Schema({
type: String
});
var UserSchema = new mongoose.Schema({
name: String,
email: [EmailSchema]
});
Then the following query should work:
Users.find({'email.type':'Gmail').exec(function(err, users) {
res.json(users);
});
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With