To learn the MEAN stack (using Mongoose), I'm creating a StackOverflow type application. I have Questions that are stored in Mongo(v3.0.7) and they have Answer sub-documents.
I am trying to increment the Vote of an Answer, but when the question is returned it is null. I'm pretty sure there's something wrong with the query, specifically where I'm trying to get the answer with the ID I need to modify.
Question Schema:
var questionsSchema = new mongoose.Schema({
answers: [ answerSchema ],
});
Answer Schema:
var answerSchema = new mongoose.Schema({
votes: { type: Number, default: 0 },
});
Querying for _id returns null:
Question.findOneAndUpdate(
{_id: req.params.questionId, 'answers._id': req.params.answerId },
{ $inc: { 'answers.$.votes': 1 } },
{ new: true },
function(err, question){
if (err) { return next(err); }
//question is returned as NULL
res.json(question);
});
Querying for 0 votes works:
Question.findOneAndUpdate(
{_id: req.params.questionId, 'answers.votes': 0 },
{ $inc: { 'answers.$.votes': 1 } },
{ new: true },
function(err, question){
if (err) { return next(err); }
//question is returned as NULL
res.json(question);
});
UPDATE:
Query through Mongo the result is returned:
db.questions.find({_id: ObjectId('562e635b9f4d61ec1e0ed953'), 'answers._id': ObjectId('562e63719f4d61ec1e0ed954') })
BUT, through Mongoose, NULL is returned:
Question.find(
{_id: Schema.ObjectId('562e635b9f4d61ec1e0ed953'), 'answers._id': Schema.ObjectId('562e63719f4d61ec1e0ed954') },
Try to use mongoose Types ObjectID
http://mongoosejs.com/docs/api.html#types-objectid-js:
var ObjectId = mongoose.Types.ObjectId;
Question.find({
_id: '562e635b9f4d61ec1e0ed953',
'answers._id': new ObjectId('562e63719f4d61ec1e0ed954')
})
Final answer to the original update question:
Question.findOneAndUpdate(
{_id: req.params.questionId,
'answers._id': new ObjectId(req.params.answerId) },
{ $inc: { 'answers.$.votes': 1 } },
{ new: true },
function(err, question){
if (err) { return next(err); }
res.json(question);
});
You don't need ObjectId at all:
Question.findOne({_id: "562e635b9f4d61ec1e0ed953"}, callback)
Mongoose handles the string for you.
In addition, using find() and querying by _id will result in an array of length 0 or 1. Using findOne() will return the document object.
Related
Here is my code, it searches the word 'test' through all documents in 'subs' collection and return them.
The thing is I just need two specific fields (id and name).
app.get('/', (req, res) => {
db.collection('subs')
.find({
$text: { $search: 'test' },
})
.toArray((err, result) => {
if (err) {
throw new err();
}
res.json({
length: result.length,
body: { result },
});
});
});
So you can use a projection:
db.collection('subs').find({$text: { $search: 'test' }}, {name: 1 } ).
Read more about it here: https://docs.mongodb.com/manual/tutorial/project-fields-from-query-results/#return-the-specified-fields-and-the-_id-field-only
you can set the fields you need in additional argument to the find method :
db.collection('subs').find({
$text: { $search: 'test' }
},
{
name: 1,
otherColumn: 1
}); // select only the "name" & the "otherColumn" column
The _id column is always returned by default, but you could disable it by adding _id: 0.
Hope this solve your question.
Finally I found the answer! :
.find(
{
name: { $in: ['Prison Break', 'Dexter'] },
$text: { $search: 'kill' },
},
{
projection: { name: 1 },
}
)
I have a document that has 3 nested arrays. I'm updating the second nested array with this function:
// Append $set key
for(var key in u)
updates["scopes.$[].sections.$." + key] = u[key];
Proposal.findOneAndUpdate({
"scopes.sections._id": req.body.id // sectionId
}, {
$set: updates
}, { new: true })
.then(response => {
console.log(response);
})
.catch(error => {
console.log(error);
});
I use a similar function to update the first nested array- scopes. That is working properly and updates the scope that matches. But for the second nested array only the first element of the array is being updated. I logged the id and the correct param is being passed in the req.body.
Is there something I'm missing in the update key- scopes.$[].sections.$.key ?
Edit with sample document and logs-
_id: 6079c199c5464b6296b113f6
name: ""
status: "outstanding"
hasAutomaticThreshold:false
isDiscount:true
discount: 0
discountPercentage: 0
taxRate: 9
companyId: 606f5e179cc0382ad6aacd84
clientId: 6070fa06dd505146ccfac9ec
projectId: 60736ed48fb2c869e0c9b33d
author: 606f5e259cc0382ad6aacd86
scopes: Array
0:Object
title: ""
isExpanded: true
_id: 6079c199c5464b6296b113f7
sections:Array
0:Object
title:"Section One"
description:""
isExpanded:false
_id: 6079c199c5464b6296b113f8
items: Array
1:Object
title:""
description:""
isExpanded:false
_id: 6079c1f8d3176462c0840388
items: Array
And this is what the logged req.body.id and updates object looks like:
6079c1f8d3176462c0840388 // ID
{ 'scopes.$[].sections.$.title': 'Section One' }
The positional $ operator will update single position, you need to use arrayFilters $[<identifier>],
// Append $set key
for(var key in u)
updates["scopes.$[].sections.$[s]." + key] = u[key];
Proposal.findOneAndUpdate(
{ "scopes.sections._id": req.body.id },
{ $set: updates },
{
arrayFilters: [{ "s._id": req.body.id }],
new: true
}
)
.then(response => {
console.log(response);
})
.catch(error => {
console.log(error);
});
Playground
i'm trying to do a pretty simple operation, pull an item from an array with Mongoose on a Mongo database like so:
User.update({ _id: fromUserId }, { $pull: { linkedUsers: [idToDelete] } });
fromUserId & idToDelete are both Objects Ids.
The schema for Users goes like this:
var UserSchema = new Schema({
groups: [],
linkedUsers: [],
name: { type: String, required: true, index: { unique: true } }
});
linkedUsers is an array that only receives Ids of other users.
I've tried this as well:
User.findOne({ _id: fromUserId }, function(err, user) {
user.linkedUsers.pull(idToDelete);
user.save();
});
But with no luck.
The second option seem to almost work when i console the lenghts of the array at different positions but after calling save and checking, the length is still at 36:
User.findOne({ _id: fromUserId }, function(err, user) {
console.log(user.linkedUsers.length); // returns 36
user.linkedUsers.pull(idToDelete);
console.log(user.linkedUsers.length); // returns 35
user.save();
});
So it looks like i'm close but still, no luck. Both Ids are sent via the frontend side of the app.
I'm running those versions:
"mongodb": "^2.2.29",
"mongoose": "^5.0.7",
Thanks in advance.
You need to explicitly define the types in your schema definition i.e.
groups: [{ type: Schema.Types.ObjectId, ref: 'Group' }],
linkedUsers: [{ type: Schema.Types.ObjectId, ref: 'User' }]
and then use either
User.findOneAndUpdate(
{ _id: fromUserId },
{ $pullAll: { linkedUsers: [idToDelete] } },
{ new: true },
function(err, data) {}
);
or
User.findByIdAndUpdate(fromUserId,
{ $pullAll: { linkedUsers: [idToDelete] } },
{ new: true },
function(err, data) {}
);
I had a similar issue. I wanted to delete an object from an array, using the default _id from mongo, but my query was wrong:
const update = { $pull: { cities: cityId }};
It should be:
const update = { $pull: { cities: {_id: cityId} }};
This question already has answers here:
Update MongoDB field using value of another field
(12 answers)
Closed 5 years ago.
I know I can do an update using $set:
Contact.update({
_id: request.id
}, {
$set: { name: newNameValue }
}, {
upsert: false
}, function(err) { ... });
But in this case, instead of passing newNameValue, I'd like to use the previous name value to compute the new one. Let's say I want to capitalize the old name, something like:
Contact.update({
_id: request.id
}, {
$set: { name: $old.name.toUpperCase() }
}, {
upsert: false
}, function(err) { ... });
I think this has already been answered here: How to add new data to current string in MongoDB?, so please, check that for a more detailed answer, but anyway, in short, you can't do that with a single query.
The way to do it using Mongoose, as shown in this official Mongoose example:
Contact.findById(request.id, (err, contract) => {
if (err) return handleError(err);
contract.name = contract.name.toUpperCase();
contract.save((err, contractContract) => {
if (err) return handleError(err);
...
});
});
as far as I know it's not possible. You would need to find and then update
Contact
.find({
_id: request.id
})
.exec(function(err, data) {
if (err) {
return ...;
}
Contact.findByIdAndUpdate(request.id, {
$set: {
name: data.name.toUpperCase()
}
}, {
new: true
}, function(err, doc) {
if (err) return ...;
console.log(doc)
});
}
I've looked through the mongoose API, and many questions on SO and on the google group, and still can't figure out updating embedded documents.
I'm trying to update this particular userListings object with the contents of args.
for (var i = 0; i < req.user.userListings.length; i++) {
if (req.user.userListings[i].listingId == req.params.listingId) {
User.update({
_id: req.user._id,
'userListings._id': req.user.userListings[i]._id
}, {
'userListings.isRead': args.isRead,
'userListings.isFavorite': args.isFavorite,
'userListings.isArchived': args.isArchived
}, function(err, user) {
res.send(user);
});
}
}
Here are the schemas:
var userListingSchema = new mongoose.Schema({
listingId: ObjectId,
isRead: {
type: Boolean,
default: true
},
isFavorite: {
type: Boolean,
default: false
},
isArchived: {
type: Boolean,
default: false
}
});
var userSchema = new mongoose.Schema({
userListings: [userListingSchema]
});
This find also doesn't work, which is probably the first issue:
User.find({
'_id': req.user._id,
'userListings._id': req.user.userListings[i]._id
}, function(err, user) {
console.log(err ? err : user);
});
which returns:
{ stack: [Getter/Setter],
arguments: [ 'path', undefined ],
type: 'non_object_property_call',
message: [Getter/Setter] }
That should be the equivalent of this mongo client call:
db.users.find({'userListings._id': ObjectId("4e44850101fde3a3f3000002"), _id: ObjectId("4e4483912bb87f8ef2000212")})
Running:
mongoose v1.8.1
mongoose-auth v0.0.11
node v0.4.10
when you already have the user, you can just do something like this:
var listing = req.user.userListings.id(req.params.listingId);
listing.isRead = args.isRead;
listing.isFavorite = args.isFavorite;
listing.isArchived = args.isArchived;
req.user.save(function (err) {
// ...
});
as found here: http://mongoosejs.com/docs/subdocs.html
Finding a sub-document
Each document has an _id. DocumentArrays have a special id method for looking up a document by its _id.
var doc = parent.children.id(id);
* * warning * *
as #zach pointed out, you have to declare the sub-document's schema before the actual document 's schema to be able to use the id() method.
Is this just a mismatch on variables names?
You have user.userListings[i].listingId in the for loop but user.userListings[i]._id in the find.
Are you looking for listingId or _id?
You have to save the parent object, and markModified the nested document.
That´s the way we do it
exports.update = function(req, res) {
if(req.body._id) { delete req.body._id; }
Profile.findById(req.params.id, function (err, profile) {
if (err) { return handleError(res, err); }
if(!profile) { return res.send(404); }
var updated = _.merge(profile, req.body);
updated.markModified('NestedObj');
updated.save(function (err) {
if (err) { return handleError(res, err); }
return res.json(200, profile);
});
});
};