1

I have the following Schema:

const userSchema = new Schema({
  local: {
    email: {type: String, unique: true, lowercase: true, trim: true, required: true},
    password: {type: String, required: true},
  },
  Items: [{
    name: {type: String, trim: true},
    createdAt: {type: Date, default: Date.now, select: false}
  }]
});

How do I query 'Items' (which contains array of objects) based on a specific _id that I will receive from the url parameter?

I have many different variations, including one shown below, but they all seem to return the Items array containing all objects, instead of just a single object matching the id?

User.findById(req.user.id, {}, {"Items._id": req.params.id}, (err, result) => { ... }
linasmnew
  • 3,907
  • 2
  • 20
  • 33
  • Use [projection](https://docs.mongodb.com/manual/reference/operator/projection/positional/). Something like `User.findOne({_id:req.user.id, "Items._id": req.params.id},{}, {"Items.$": 1}, (err, result) => { ... }` – s7vr Mar 22 '17 at 23:00
  • Still returns all of the Items instead of just a single one – linasmnew Mar 22 '17 at 23:43
  • May be I have the incorrect mongoose query. You can verify it running it in mongo shell. `db.users.findOne({_id:req.user.id, "Items._id": req.params.id},{"Items.$": 1})`. It will return one item value. I'll try to find the right mongoose query. – s7vr Mar 23 '17 at 00:35
  • Yeah it does work in the mongo shell, should i use it in mongoose? or is there more efficient way to write it? Thanks – linasmnew Mar 23 '17 at 00:45
  • Np. Yeah if you know how to convert to mongoose. I believe this is the right query in mongoose. `User.findOne({_id:req.user.id, "Items._id": req.params.id},{"Items.$": 1}, (err, result) => { ... } ` – s7vr Mar 23 '17 at 00:48
  • It is indeed, Ok thank you. – linasmnew Mar 23 '17 at 11:06
  • How would I get rid of createdAt field? The query only allows exclusion of _id, otherwise it gives an error if I try to exclude createdAt field – linasmnew Mar 23 '17 at 12:51

2 Answers2

2

You will have to use $projection to access element from embedded array. The query part finds the matching array element and replaces the placeholder $ with the array index of the matching element and projection part with $ will now use the placeholder value to project the array element as a response.

Mongo Shell Variant : db.users.findOne({_id:req.user.id, "Items._id": req.params.id},{"Items.$": 1})

Mongoose Variant: User.findOne({_id:req.user.id, "Items._id": req.params.id},{"Items.$": 1}, (err, result) => { ... }

s7vr
  • 73,656
  • 11
  • 106
  • 127
  • Can you show me your query ? Mixing of exclusion and inclusion paths are not allowed in projection with the exception of `_id` field. – s7vr Mar 23 '17 at 13:19
  • It's the same as what you provided, just the second object contains "_id":0 in front of "Items.$":1 which works fine, but how would I modify this overall query to get rid of the createdAt property from the result? I suppose I can call lean() on findOne to turn it into a regular object and then use delete user.Items[0].createdAt, but any way of doing it on the database side? – linasmnew Mar 23 '17 at 13:27
  • Nope, you can't like I said. You can't have both inclusion(`"Items.$": 1`) and exclusion path (`"Items.createdAt": 0`) in the same projection. You have to take care of that on the mongoose side. More here https://docs.mongodb.com/v3.2/tutorial/project-fields-from-query-results/#projection-document – s7vr Mar 23 '17 at 13:35
  • Sorry I'm new to mongo and mongooose, would calling lean() on findOne and using delete user.Items[0].createdAt be an efficient way to solve this problem? Or is there a more efficient way to accomplish this? – linasmnew Mar 23 '17 at 13:46
  • I'm no expert in mongoose but looks like lean is what you need if you are concerned with performance which I personally think is not a issue here at all. This gives more info about lean http://stackoverflow.com/questions/7503450/how-do-you-turn-a-mongoose-document-into-a-plain-object & http://stackoverflow.com/questions/15097375/mongoose-node-js-module-causes-high-cpu-usage . I hope this calms you a little bit. – s7vr Mar 23 '17 at 13:51
  • Thanks for the help – linasmnew Mar 23 '17 at 14:08
0

What you're trying to do is query for a subdocument. Check out http://mongoosejs.com/docs/subdocs.html

You can do something like:

User.findById(req.user.id, (err, user) => {
 let item = user.Items.id(req.params.id);
});
AJ Funk
  • 3,099
  • 1
  • 16
  • 18
  • I'm looking for a way to do it on the database side, rather then app level – linasmnew Mar 22 '17 at 23:06
  • And I'm not using a subdocument, I have a single schema, 'Items' is just a name, just like 'local' – linasmnew Mar 23 '17 at 00:06
  • @linasmnew Yes, those are subdocuments - that's why they have _id. Unless you specify that you do not want an ID, it will be there. All objects in Mongo are documents. – AJ Funk Mar 23 '17 at 00:10
  • Thanks didn't know that, what does the 'id' in user.Items.id refer to? because when i query the database inside the terminal there's only _id – linasmnew Mar 23 '17 at 00:19
  • DocumentArrays have a special id method for looking up a document by its _id. http://mongoosejs.com/docs/subdocs.html – AJ Funk Mar 23 '17 at 05:40