Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Find after populate mongoose

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?

like image 404
monokh Avatar asked Jul 11 '15 13:07

monokh


People also ask

Does Mongoose populate use lookup?

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.

How does populate work in Mongoose?

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.

How do you populate with find?

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.

What does REF do in Mongoose?

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.


2 Answers

@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 emails 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.

like image 194
bardzusny Avatar answered Sep 24 '22 10:09

bardzusny


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);
});
like image 43
Jason Cust Avatar answered Sep 23 '22 10:09

Jason Cust