Update document inside array of objects in mongoose - node.js

I need to update objects inside an array so I'm trying but I get the following error:
error Plan executor error during findAndModify :: caused by :: The
positional operator did not find the match needed from the query.
This is my code:
const payment = await Purchase.findByIdAndUpdate(
{ '_id': req.body.id, 'payments._id': req.body.paymentId },
{
$set: {
'payments.$.status': false
}
}
,{ new: true });
payments object on Model:
payments: [
{
createdBy: [Object],
createdAt: '08/13/22',
paymentNumber: 0,
previousBalance: 3747.68,
paymentAmount: 3747.68,
outstandingBalance: 0,
status: true,
_id: new ObjectId("62f83f3c22e4f67dde8cb85a"),
lastModificationBy: [],
disabledBy: []
}
]

while using fineByIdAndUpdate you only need to pass id of document to be updated.
const payment = await Purchase.findByIdAndUpdate(req.body.paymentId,{status:false},{new:true} )

for using findByIdAndUpdate, you need to add this runValidators
const payment = await Purchase.findByIdAndUpdate(req.params.id,
'payments.$.status': false
}, {runValidators: true}
while same update can be done by this as well
const payment = await Purchase.findByIdAndUpdate(
req.body.paymentId,
{
status:false
}, {
new:true
}
)

Related

How to update two fields in one condition in mongodb?

What im trying to do is to update and push in two different fields when a condition is true, and to update only one field when the condition is false.
const updateQuery = historicEnable == true ? ({ $push: { locationHistoric: locationObject } }, {currentLocation: locationObject}) : ({ currentLocation: locationObject })
const theLocation = await User.findOneAndUpdate({ phone }, updateQuery, {
new: true,
});
By far when the condition is true, it pushes only in the locationHistoric, and not updating the currentLocation.
How can I make both of the methods work on the same condition?
Try this :
const updateQuery = historicEnable == true ?
({ $push: { "locationHistoric": locationObject, "currentLocation": locationObject }) :
({ $push: { "currentLocation": locationObject } })
And you're also missing operator in second statement.
Use any operator like $set or $push

Mongoose unique if not null and if state

I have a unique index like this
code: {
type: String,
index: {
unique: true,
partialFilterExpression: {
code: { $type: 'string' }
}
},
default: null
},
state: { type: Number, default: 0 },
but When the state is 2 (archived) I want to keep the code, but it should be able to reuse the code, so it cannot be unique if state is 2.
Is there any away that I could accomplish this?
This is possible, though it's through a work around documented here https://jira.mongodb.org/browse/SERVER-25023.
In MongoDB 4.7 you will be able to apply different index options to the same field but for now you can add a non-existent field to separate the two indexes.
Here's an example using the work around.
(async () => {
const ItemSchema = mongoose.Schema({
code: {
type: String,
default: null
},
state: {
type: Number,
default: 0,
},
});
// Define a unique index for active items
ItemSchema.index({code: 1}, {
name: 'code_1_unique',
partialFilterExpression: {
$and: [
{code: {$type: 'string'}},
{state: {$eq: 0}}
]
},
unique: true
})
// Defined a non-unique index for non-active items
ItemSchema.index({code: 1, nonExistantField: 1}, {
name: 'code_1_nonunique',
partialFilterExpression: {
$and: [
{code: {$type: 'string'}},
{state: {$eq: 2}}
]
},
})
const Item = mongoose.model('Item', ItemSchema)
await mongoose.connect('mongodb://localhost:27017/so-unique-compound-indexes')
// Drop the collection for test to run correctly
await Item.deleteMany({})
// Successfully create an item
console.log('\nCreating a unique item')
const itemA = await Item.create({code: 'abc'});
// Throws error when trying to create with the same code
await Item.create({code: 'abc'})
.catch(err => {console.log('\nThrowing a duplicate error when creating with the same code')})
// Change the active code
console.log('\nChanging item state to 2')
itemA.state = 2;
await itemA.save();
// Successfully created a new doc with sama code
await Item.create({code: 'abc'})
.then(() => console.log('\nSuccessfully created a new doc with sama code'))
.catch(() => console.log('\nThrowing a duplicate error'));
// Throws error when trying to create with the same code
Item.create({code: 'abc'})
.catch(err => {console.log('\nThrowing a duplicate error when creating with the same code again')})
})();
This is not possible with using indexes. Even if you use a compound index for code and state there will still be a case where
new document
{
code: 'abc',
state: 0
}
archived document
{
code: 'abc',
state: 2
}
Now although you have the same code you will not be able to archive the new document or unarchive the archived document.
You can do something like this
const checkCode = await this.Model.findOne({code:'abc', active:0})
if(checkCode){
throw new Error('Code has to be unique')
}
else{
.....do something
}

Change a single value of a subdocument in mongodb

I have a Model called Notes. It has a subdocument requests which holds various documents with values userId, reqType, accepted value( false by default) and noteId of the sender, request and note respectively. When the user hits a certain route I want to keep all the data to be as their previous values, just updating the accepted field to true.
The below code leads to no change in the data or a different iteration leads to erasing all the data other than accepted field and modifying it to true.
How should I do this?
const noteSchema = new mongoose.Schema(
requests: [
{
userId: mongoose.Schema.ObjectId,
noteId: mongoose.Schema.ObjectId,
reqType: String,
accepted: {
type: Boolean,
default: false,
},
},
],
}
)
const Note = mongoose.model('Note', noteSchema)
const note = await Note.findById(req.body.noteId)
await note.updateOne({
requests: {
$elemMatch: {
userId: req.body.userId,
reqType: req.body.reqType,
noteId: req.body.noteId,
},
$set: { "requests.$.accepted": true },
},
})
You do not need to retrieve the document and then update it. Just update it. Use this one:
await Note.updateOne(
{
"requests.userId": req.body.userId,
"requests.reqType": req.body.reqType,
"requests.noteId": req.body.noteId
},
{
$set:
{
"requests.$.accepted":true
}
}
);
I checked, it worked.
With first part mongoose will find the document, with $set it will be updated.

mongodb findOneAndUpdate replace document instead of updating specified field [duplicate]

Say, i have a document:
{
_id: 'some_mongodb_id',
name: 'john doe',
phone: '+12345678901',
}
I want to update this document:
.findOneAndUpdate({_id: 'some_mongodb_id'}, {name: 'Dan smith'})
And the result should be this:
{
_id: 'some_mongodb_id',
name: 'Dan smith',
}
The property, that is not specified, should be removed.
How do i do that?
Actually, but for the fact that mongoose is actually "messing with" the update under the covers, this is actually the default action of your submission to a regular MongoDB function.
So mongoose deems it "wise" as a convenience method to "presume" you meant to issue a $set instruction here. Since you actually do not want to do that in this case, you turn off that behavior via { overwrite: true } in the options passed to any .update() method:
As a full example:
const mongoose = require('mongoose'),
Schema = mongoose.Schema;
mongoose.Promise = global.Promise;
mongoose.set('debug',true);
const uri = 'mongodb://localhost/test',
options = { useMongoClient: true };
const testSchema = new Schema({
name: String,
phone: String
});
const Test = mongoose.model('Test', testSchema);
function log(data) {
console.log(JSON.stringify(data,undefined,2))
}
(async function() {
try {
const conn = await mongoose.connect(uri,options);
// Clean data
await Promise.all(
Object.keys(conn.models).map( m => conn.models[m].remove({}) )
);
// Create a document
let test = await Test.create({
name: 'john doe',
phone: '+12345678901'
});
log(test);
// This update will apply using $set for the name
let notover = await Test.findOneAndUpdate(
{ _id: test._id },
{ name: 'Bill S. Preston' },
{ new: true }
);
log(notover);
// This update will just use the supplied object, and overwrite
let updated = await Test.findOneAndUpdate(
{ _id: test._id },
{ name: 'Dan Smith' },
{ new: true, overwrite: true }
);
log(updated);
} catch (e) {
console.error(e);
} finally {
mongoose.disconnect();
}
})()
Produces:
Mongoose: tests.remove({}, {})
Mongoose: tests.insert({ name: 'john doe', phone: '+12345678901', _id: ObjectId("596efb0ec941ff0ec319ac1e"), __v: 0 })
{
"__v": 0,
"name": "john doe",
"phone": "+12345678901",
"_id": "596efb0ec941ff0ec319ac1e"
}
Mongoose: tests.findAndModify({ _id: ObjectId("596efb0ec941ff0ec319ac1e") }, [], { '$set': { name: 'Bill S. Preston' } }, { new: true, upsert: false, remove: false, fields: {} })
{
"_id": "596efb0ec941ff0ec319ac1e",
"name": "Bill S. Preston",
"phone": "+12345678901",
"__v": 0
}
Mongoose: tests.findAndModify({ _id: ObjectId("596efb0ec941ff0ec319ac1e") }, [], { name: 'Dan Smith' }, { new: true, overwrite: true, upsert: false, remove: false, fields: {} })
{
"_id": "596efb0ec941ff0ec319ac1e",
"name": "Dan Smith"
}
Showing the document is "overwritten" because we suppressed the $set operation that otherwise would have been interpolated. The two samples show first without the overwrite option, which applies the $set modifier, and then "with" the overwrite option, where the object you passed in for the "update" is respected and no such $set modifier is applied.
Note, this is how the MongoDB Node driver does this "by default". So the behavior of adding in the "implicit" $set is being done by mongoose, unless you tell it not to.
NOTE The true way to "replace" would actually be to use replaceOne, either as the API method of replaceOne() or through bulkWrite(). The overwrite is a legacy of how mongoose wants to apply $set as described and demonstrated above, however the MongoDB official API introduces replaceOne as a "special" king of update() operation which does not allow the usage of atomic operators like $set within the statement and will error if you try.
This is much clearer semantically since replace reads very clearly as to what the method is actually used for. Within standard API calls to the update() variants of course still allow you to omit the atomic operators and will just replace content anyway. But warnings should be expected.
You can pass upsert option, and it will replace document:
var collection = db.collection('test');
collection.findOneAndUpdate(
{'_id': 'some_mongodb_id'},
{name: 'Dan smith Only'},
{upsert: true},
function (err, doc) {
console.log(doc);
}
);
But the problem here - is that doc in callback is found document but not updated.
Hence you need perform something like this:
var collection = db.collection('test');
collection.update(
{'_id': 'some_mongodb_id'},
{name: 'Dan smith Only'},
{upsert: true},
function (err, doc) {
collection.findOne({'_id': 'some_mongodb_id'}, function (err, doc) {
console.log(doc);
});
}
);

Mongoose overwrite the document rather that `$set` fields

Say, i have a document:
{
_id: 'some_mongodb_id',
name: 'john doe',
phone: '+12345678901',
}
I want to update this document:
.findOneAndUpdate({_id: 'some_mongodb_id'}, {name: 'Dan smith'})
And the result should be this:
{
_id: 'some_mongodb_id',
name: 'Dan smith',
}
The property, that is not specified, should be removed.
How do i do that?
Actually, but for the fact that mongoose is actually "messing with" the update under the covers, this is actually the default action of your submission to a regular MongoDB function.
So mongoose deems it "wise" as a convenience method to "presume" you meant to issue a $set instruction here. Since you actually do not want to do that in this case, you turn off that behavior via { overwrite: true } in the options passed to any .update() method:
As a full example:
const mongoose = require('mongoose'),
Schema = mongoose.Schema;
mongoose.Promise = global.Promise;
mongoose.set('debug',true);
const uri = 'mongodb://localhost/test',
options = { useMongoClient: true };
const testSchema = new Schema({
name: String,
phone: String
});
const Test = mongoose.model('Test', testSchema);
function log(data) {
console.log(JSON.stringify(data,undefined,2))
}
(async function() {
try {
const conn = await mongoose.connect(uri,options);
// Clean data
await Promise.all(
Object.keys(conn.models).map( m => conn.models[m].remove({}) )
);
// Create a document
let test = await Test.create({
name: 'john doe',
phone: '+12345678901'
});
log(test);
// This update will apply using $set for the name
let notover = await Test.findOneAndUpdate(
{ _id: test._id },
{ name: 'Bill S. Preston' },
{ new: true }
);
log(notover);
// This update will just use the supplied object, and overwrite
let updated = await Test.findOneAndUpdate(
{ _id: test._id },
{ name: 'Dan Smith' },
{ new: true, overwrite: true }
);
log(updated);
} catch (e) {
console.error(e);
} finally {
mongoose.disconnect();
}
})()
Produces:
Mongoose: tests.remove({}, {})
Mongoose: tests.insert({ name: 'john doe', phone: '+12345678901', _id: ObjectId("596efb0ec941ff0ec319ac1e"), __v: 0 })
{
"__v": 0,
"name": "john doe",
"phone": "+12345678901",
"_id": "596efb0ec941ff0ec319ac1e"
}
Mongoose: tests.findAndModify({ _id: ObjectId("596efb0ec941ff0ec319ac1e") }, [], { '$set': { name: 'Bill S. Preston' } }, { new: true, upsert: false, remove: false, fields: {} })
{
"_id": "596efb0ec941ff0ec319ac1e",
"name": "Bill S. Preston",
"phone": "+12345678901",
"__v": 0
}
Mongoose: tests.findAndModify({ _id: ObjectId("596efb0ec941ff0ec319ac1e") }, [], { name: 'Dan Smith' }, { new: true, overwrite: true, upsert: false, remove: false, fields: {} })
{
"_id": "596efb0ec941ff0ec319ac1e",
"name": "Dan Smith"
}
Showing the document is "overwritten" because we suppressed the $set operation that otherwise would have been interpolated. The two samples show first without the overwrite option, which applies the $set modifier, and then "with" the overwrite option, where the object you passed in for the "update" is respected and no such $set modifier is applied.
Note, this is how the MongoDB Node driver does this "by default". So the behavior of adding in the "implicit" $set is being done by mongoose, unless you tell it not to.
NOTE The true way to "replace" would actually be to use replaceOne, either as the API method of replaceOne() or through bulkWrite(). The overwrite is a legacy of how mongoose wants to apply $set as described and demonstrated above, however the MongoDB official API introduces replaceOne as a "special" king of update() operation which does not allow the usage of atomic operators like $set within the statement and will error if you try.
This is much clearer semantically since replace reads very clearly as to what the method is actually used for. Within standard API calls to the update() variants of course still allow you to omit the atomic operators and will just replace content anyway. But warnings should be expected.
You can pass upsert option, and it will replace document:
var collection = db.collection('test');
collection.findOneAndUpdate(
{'_id': 'some_mongodb_id'},
{name: 'Dan smith Only'},
{upsert: true},
function (err, doc) {
console.log(doc);
}
);
But the problem here - is that doc in callback is found document but not updated.
Hence you need perform something like this:
var collection = db.collection('test');
collection.update(
{'_id': 'some_mongodb_id'},
{name: 'Dan smith Only'},
{upsert: true},
function (err, doc) {
collection.findOne({'_id': 'some_mongodb_id'}, function (err, doc) {
console.log(doc);
});
}
);

Resources