I have such schema:
mongoose.model "Ticket", {
title: String
body: String
likes: Number
comments: [{
body: String
upvotes: Number
downvotes: Number
}]
}
My code to query
q = @model.findOne {"comments._id": oid}, {"comments.$": 1}
q.select "comments.upvotes"
q.exec (err, result) =>
console.log(result.comment[0].downvotes) # 6
As you see, the select doesn't work for the subdocs, it also returns not selected fields. How to fix that?
This is how MongoDB handles basic projection with array elements. While you can do something like this:
Model.findOne({}, { "comments.upvotes": 1 },function(err,doc) {
})
And that would just return the "upvotes" field from within the sub-documents of the comments array for all documents matching the condition and all array elements of course, you cannot combine this with a selected positional projection using the positional $ operator. This basically stems from the "theory" that generally you actually want to return the whole array. So this is how it has always worked and is not likely to change soon.
In order to get what you want, you need the extended capabilities for document manipulation offered by the aggregation framework. This gives you more control over how the documents are returned:
Model.aggregate(
[
// Match the document containing the array element
{ "$match": { "comments._id" : oid } },
// Unwind to "de-normalize" the array content
{ "$unwind": "$comments" },
// Match the specific array element
{ "$match": { "comments._id" : oid } },
// Group back and just return the "upvotes" field
{ "$group": {
"_id": "$_id",
"comments": { "$push": { "upvotes": "$comments.upvotes" } }
}}
],
function(err,docs) {
}
);
Or in modern versions of MongoDB since 2.6 you can even do this:
Model.aggregate(
[
{ "$match": { "comments._id" : oid } },
{ "$project": {
"comments": {
"$setDifference": [
{ "$map": {
"input": "$comments",
"as": "el",
"in": {
"$cond": [
{ "$eq": [ "$$el._id", oid ] },
{ "upvotes": "$$el.upvotes" },
false
]
}
}},
[false]
]
}}
}}
],
function(err,docs) {
}
)
And that uses the $map and $setDifference operators to do an "in-line" filtering of the array content without first processing an $unwind stage.
So if you want more control over how the document is returned, then the aggregation framework is the way to do it when working with embedded documents.