Mongodb Increment value inside nested array not working - mongodb

I'm building a app where you can vote on user submitted polls. Here's the schema for my polls:
const pollSchema = new Schema({
userID: String,
pollQuestion: String,
pollOptions: [
{
name: String,
quantity: Number
}
]
})
This is the express route that handles incrementing the quantity of a poll option:
app.put('/vote', (req, res) => {
Poll.update(
{
id: req.body.id,
'pollOptions.name': req.body.selection
},
{ $inc: { 'pollOptions.$.quantity': 1 } }
).then(updatedPoll => {
console.log('poll', updatedPoll)
res.send(updatedPoll)
})
})
here req.body.selection is the option that the user has chosen to vote for.
I basically want the quantity of the user's chosen option to increment by one. This way of updating the database I derived from the answer to this question
When I send a request to this route I get some very unexpected behaviour. For one thing, the database doesn't get updated or changed in any way. The second thing is the updatedPoll is a really bizarre object. Here's what i get from the console.log:
poll { n: 0,
nModified: 0,
opTime:
{ ts: Timestamp { _bsontype: 'Timestamp', low_: 1, high_: 1510783687 },
t: 1 },
electionId: 7fffffff0000000000000001,
ok: 1 }
The one thing I've tried is include { new: true } as a third parameter after the object with the $inc operator. I doesn't change anything. Can someone please help with this issue?

Thanks to Neil Lunn in the comments, changing my route to the following worked for me:
poll.put('/vote', (req, res) => {
Poll.findOneAndUpdate(
{
_id: req.body.id,
pollOptions: {
$elemMatch: { name: req.body.selection }
}
},
{ $inc: { 'pollOptions.$.quantity': 1 } },
{ new: true }
).then(poll => {
res.send(poll)
})
})

Related

Mongoose add or update values in an array inside an object inside an array

This is my schema,
const courseSchema = new mongoose.Schema({
name: String,
code: String,
class: String,
credit: Number,
overview: String,
prerequisite: String,
syllabus: [
{
moduleName: String,
moduleDetails: String,
},
],
materials: [
{
moduleNo: Number,
moduleMaterials: [String],
},
],
teacher: String,
students: [String],
});
I want to add new materials in which each time an update operation is called I receive a
moduleNo and a materialURL.
I want to add this to the materials array in my existing course which is filtered by courseID. So each time I receive a moduleNo and a materialURL, I want to check if moduleNo already exists in the materials array. If yes, then add materialURL into the moduleMaterials array without deleting the existing urls in moduleMaterials. If no, then add a new object with moduleNo and moduleMaterials and materialURL pushed into moduleMaterials. I've heard about upsert and think that could be used but I'm not sure what the correct queries are to do this operation.
What I've currently come up with even though it's wrong,
Course.updateOne(
{ _id: courseID },
{
$push: {
materials: { moduleNo, moduleMaterials: { $push: { materialURL } } },
},
},
{ upsert: true },
(err, result) => {
if (err) {
console.error(err);
} else {
console.log(result);
}
}
);
How do I do execute this query?

Editing specific element of an array of a particular collection - mongoDb

I am new to mongoDb. I have created a collection named task that has comments field which is array along with other fields. I need to edit specific comment of the task. There is a edit button in each comment. Both task id and comment id are available. Now how to edit specific comment of the task?
Thanks in advance
task api
{
"status":true,
"task":[
{
"_id":"61dfef323a6ee474c4eba926",
"description":"hello there",
"title":"hello",
"comments":[
{
"comment_id":1,
"username":"test",
"comment":"abcd",
"status":true,
},
{
"comment_id":2,
"username":"test",
"comment":"abcdsdfsdf",
"status":true,
}
],
"createdAt":"2022-01-13T09:21:54.795Z",
"updatedAt":"2022-01-13T09:21:54.795Z",
"__v":0
}
]
}
Task model schema
const taskSchema = new Schema({
title: { type: String, required: true },
description: { type: String, required: true },
comments: [Object],
}, {
timestamps: true,
});
I tried using $set but I don't know how to use it in the inner array.
router.route('./comments/edit').post((req, res) => {
const commentId = req.body.commentId;
const taskId = req.body.postId;
const comment = req.body.editedComment;
const updatedAt = new Date();
Task.updateOne(
{ _id: taskId},
{
//what to do here?
// $set: { comments: [ {'comment_id': commentId} ]},
}
)
.then((response) => res.json({ status: true, msg: 'Comment Edited!' }))
.catch(err => res.json({ status: false, msg: err }));
});
Thanks in advance.
This is how to do best:
db.collection.update({
status: true
},
{
$set: {
"task.$[x].comments.$[y].username": "New Name"
}
},
{
arrayFilters: [
{
"x._id": "61dfef323a6ee474c4eba926"
},
{
"y.comment_id": 2
}
]
})
Explained:
Define x and y as arrayFIlters in the update statement.
In the $set statement provide the x & y filters to identify the specific comment for update.
In the example I update the username , but you can update any other value from the targeted array subelement addressed by x & y.
playground
And here is how to update two values at same time in the same nested array element.

Mongoose findOneAndUpdate with $addToSet pushes duplicate

I have a schema such as
listSchema = new Schema({
...,
arts: [
{
...,
art: { type: Schema.Types.ObjectId, ref: 'Art', required: true },
note: Number
}
]
})
My goal is to find this document, push an object but without duplicate
The object look like
var art = { art: req.body.art, note: req.body.note }
The code I tried to use is
List.findOneAndUpdate({ _id: listId, user: req.myUser._id },
{ $addToSet: { arts: art} },
(err, list) => {
if (err) {
console.error(err);
return res.status(400).send()
} else {
if (list) {
console.log(list)
return res.status(200).json(list)
} else {
return res.status(404).send()
}
}
})
And yet there are multiple entries with the same Art id in my Arts array.
Also, the documentation isn't clear at all on which method to use to update something. Is this the correct way ? Or should I retrieve and then modify my object and .save() it ?
Found a recent link that came from this
List.findOneAndUpdate({ _id: listId, user: req.user._id, 'arts.art': artId }, { $set: { 'arts.$[elem]': artEntry } }, { arrayFilters: [{ 'elem.art': mongoose.Types.ObjectId(artId) }] })
artworkEntry being my modifications/push.
But the more I'm using Mongoose, the more it feels they want you to use .save() and modify the entries yourself using direct modification.
This might cause some concurrency but they introduced recently a, option to use on the schema { optimisticConcurrency: true } which might solve this problem.

The method findOneAndUpdate change the id of my element in a array

I'm working with mongoDB, mongoose and graphQL. I'm trying to make an update in my DB.
I'm doing an update in an array called phones, the changes work perfectly, the only problem is that when the update ends, the value of the objectId changes.
// Models -> Schema Organization
const organizationSchema = new mongoose.Schema({
name: String,
address: String,
phones: [
{
number: Number,
prefix: Number
}
],
email: String
})
// Types -> Organization
type Response {
success: Boolean!
token: String
errors: [Error]
}
type Error {
path: String!
message: String!
}
input iOrganization {
_id: ID
arrID: ID
address: String
email: String
number: Int
prefix: Int
name: String
}
type Mutation {
updateOrgGeneric(iOrg: iOrganization): Response!
}
// Resolvers -> Organization (1st way)
Mutation: {
updateOrgGeneric: (parent, args, {models}) => {
return models.Organization.findOneAndUpdate(
{ "_id": args.iOrg._id, "phones._id": args.iOrg.arrID },
{ $set: { "phones.$": { number: args.iOrg.number, prefix: args.iOrg.prefix }} },
{new: true}
)
.then((resp) => {
console.log(resp);
return {
success: true,
errors: []
}
})
.catch((error) => {
return {
success: false,
errors: error
};
})
},
}
// Resolvers -> Organization (2nd way)
Mutation: {
updateOrgGeneric: (parent, args, {models}) => {
return models.Organization.findOneAndUpdate(
{ "_id": args.iOrg._id },
{ $set: { "phones.$[arr]": { number: args.iOrg.number, prefix: args.iOrg.prefix }} },
{new: true}
{ arrayFilters:[{ "arr._id": mongoose.Types.ObjectId(args.iOrg.arrID) }], new: true}
)
.then((resp) => {
console.log(resp);
return {
success: true,
errors: []
}
})
.catch((error) => {
return {
success: false,
errors: error
};
})
}
}
// Playground (http://localhost:5000/graphql)
mutation {
updateOrgGeneric(
iOrg: {
_id: "5bdbee1b794b972bc8562aeb"
arrID: "5bdcea7cae88be098c020b19"
number: 85239,
prefix: 862
}
){
success
errors {
path
message
}
}
}
Both _id, as arrID, exist in the BD.
In the playground example the initial arrID was: _id:ObjectId("5bdcea7cae88be098c020b19"), but after the update is another, example: _id:ObjectId("5bdcec0a2ab78533b4bd1d98"). What am I doing wrong?
Thank you!
Mongodb is a nosql database which means that every object in the database should consist of an Id and revision values. Once an update occurs the revision value changes as part of the update process to implement the changes made to the data object. Since your data object don't have the revision value then the id value changes. Because it is unique. Now I'm no expert with mongo but you should check the docs on how to persist data objects and change accordingly
In case anyone lands here (despite this being old post), the problem probably lies in trying to update the entire phones object, of which the overwritten _id is a part. Since there's a model defined for phonesin mongoose, it will try to create a new _id any time an entire new phones object is created.
Someone who wanted to keep the same id would need to $set only the fields they want to change, rather than the entire object. So
{ $set: { "phones.$[arr]": { number: args.iOrg.number, prefix: args.iOrg.prefix }} }
could be changed to
{ $set: { "phones.$[arr].number": args.iOrg.number, "phones.$[arr].prefix": args.iOrg.prefix } }

$inc has no effect on the document (does not work at all) [duplicate]

This question already has answers here:
Update field in exact element array in MongoDB
(5 answers)
Closed 4 years ago.
I've got a simple poll trying to update the vote counter every time someone upvte it.
I tried using $inc but it has not effect. therefore it does return which supposed to be the updated poll/ poll after the vote counter is updated, but it just returns the same one without increasing anything at all.
What am i doing wrong?
app.patch('/voting/:id', (req, res) => {
let userVote = req.body.votes;
Poll_Schema_Model.findByIdAndUpdate({ "_id": '5b070f512a28d70eb0abaa51' }, { $inc: { "poll[0].votes":userVote } }, { new: true }, (err, newPoll) => {
res.status(200).send(newPoll);
})
.catch(() => {
res.status(400).send();
});
});
the newPoll results in :- (note that votes is defaulted to 0)
{
"_id": "5b070f512a28d70eb0abaa51",
"_creator": "5b04aba0ee81bb26182b2267",
"poll": [
{
"votes": 0,
"_id": "5b070f512a28d70eb0abaa52",
"option1": "FIRST OPTIONNN",
"option2": "SECOND OPTIONN"
}
],
"__v": 0
}
My schema :-
const Poll_Schema = new mongoose.Schema({
_creator: {
type: mongoose.Schema.Types.ObjectId
},
poll: [{
option1: {
type: String,
maxlength: 20,
minlength: 3
},
option2: {
type: String,
maxlength: 20,
minlength: 3
},
votes: {
type: Number,
minlength: 1,
default:0
}
}]
});
The syntax for referencing array item is different, you should specify position after dot like poll.0.votes instead of [0]. So your code should look like this:
app.patch('/voting/:id', (req, res) => {
let userVote = req.body.votes;
Poll_Schema_Model.findByIdAndUpdate({ "_id": '5b070f512a28d70eb0abaa51' }, { $inc: { "poll.0.votes":userVote } }, { new: true }, (err, newPoll) => {
res.status(200).send(newPoll);
})
.catch(() => {
res.status(400).send();
});
});