How to delete an object from an array in mongodb? - node.js

MongoDB collection/doc :
{
_id:something,
name:something,
todos: [
{key:1234},
{key:5678}
]
}
I want to delete the object with key:5678 using mongoose query. I did something like this but It's not deleting the object at all and returning the User with unchanged todos array.
Node Route:
router.post('/:action', async (req, res) => {
try {
if (req.params.action == "delete") {
const pullTodo = { $pull: { todos: { key: 5678 } } }
const todo = await User.findOneAndUpdate({ _id:req.body.id} },pullTodo)
if (todo) {
res.json({ msg: "Todo Deleted", data: todo });
}
}
} catch (err) {
console.log(err)
}
})
I have allso tried findByIdAndUpdate(),update() methods but none of them deleting the object from the array. Getting User as a result without deleting the object from the array.

It is working, but you forgot give an configuration to the function call of Model.findByIdAndUpdate..
const todo = await User.findOneAndUpdate({ _id:req.body.id} },pullTodo, {new: true});
// if {new: true} is enabled, then it will give the latest & updated document from the
// result of the query. By default it gives the previous document.
Do some, research first. This isn't a type of question that should be asked. It's already been answered several times in stackoverflow.

Try using Model.findOneAndRemove() instead. It also makes only one call to the database.
Example: User.findOneAndRemove({'todos':{'$elemMatch':{key}});

can you please re-visit your JSON like below and see if this design works for you.
> db.test6.find()
{ "_id" : "mallik", "name" : "mallik-name", "todos1" : { "key1" : [ 1234, 5678 ] } }
> db.test6.update({},{$pull:{"todos1.key1":5678}},{multi:true});
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.test6.find()
{ "_id" : "mallik", "name" : "mallik-name", "todos1" : { "key1" : [ 1234 ] } }
>

I was adding a key property to every "Todo" using "mongoose.Types.ObjectId()" and I was querying with id string like : "5f439....." which was the problem. So I used:
1st Step: MongoId = require('mongodb').ObjectID;
2nd Step: const key = MongoId (**<actual id here>**);

Related

Foreach array of strings, each is upsert and increments to 1 but saves 2 on specific field

Good day,
I build api using node.js, express.js and the data is save to mongodb.
inside my route is
const { Tag } = require('../models/tag')
router.patch('/', async (req, res) => {
const { tags } = req.body
tags.forEach(async tag => {
const filter = { name: tag }
const option = { upsert: true }
const update = { $inc: { commonality: 1 } }
await Tag.findOneAndUpdate(filter, update, option)
});
return res.json({})
})
Example
tags = [ 'blood', 'test']
But saves (commonality: 2) weird happened here.
//document 1
{
"name" : "blood",
"commonality" : 2
}
//document 2
{
"name" : "test",
"commonality" : 2
}
Instead of (commonality: 1)
//document 1
{
"name" : "blood",
"commonality" : 1
}
//document 2
{
"name" : "test",
"commonality" : 1
}
When a tag by name field already exists, I will just update the commonality of that existing tag increments to 1 step. Else, I will insert new document of tag with commonality of 1.
Is there something wrong with the looping? Because without a loop and only one tag, it works well. Or looping the awaitable save is not good idea? Your help is highly appreciated. Keep safe everyone.
Ok, Problem is that array. You need to use positional operator $. Else all the elements will be updated in the array every time.
db.test.findOneAndUpdate({
"tagData.name": "blood"
},{
$inc: {"tagData.$.commonality":1}
})
You should something as above.
Input doc:
/* 1 */
{
"_id" : ObjectId("5f0db1229e001b53e3995fde"),
"tagData" : [
{
"name" : "blood",
"commonality" : 2.0
},
{
"name" : "test",
"commonality" : 1
}
]
}
It will update the respective commonality
As in the code - as per OP suggestion - adding this
const update = { $inc: { [${tag}.$.commonality]: 1 } }
//Specifying positional operator in the update part

Pull objects from array in embedded MongoDB document

I've been unable to figure out a way to to remove objects from arrays in embedded mongoDB documents.
My schema holds a user and a list of flower objects. It looks like this:
const UserSchema = new Schema({
name : {
type : String,
required : true
},
flowers : [
{
name : {
type : String,
required : true
},
water_freq : {
type : Number,
required : true
}
}
]
});
I've managed to add flower objects like so:
router.post('/:id/flowers/add', (req, res) => {
const { name, water_freq } = req.body;
User.findByIdAndUpdate(
req.params.id,
{
$push : {
flowers : { name, water_freq }
}
},
{ new: true }
).then((user) => res.json(user));
});
I want to delete flowers from users by their id but I'm unable to get it to work.
The code below is my non-working attempt at it.
router.delete('/:id/flowers/delete/:flowerid', (req, res) => {
User.findByIdAndUpdate(req.params.id, {
$pull : {
flowers : { _id: req.params.flowerid }
}
}).then((user) => res.json(user));
});
I would greatly appreciate if someone can help me get this right.
One possible cause is, in your query {$pul: xxxx}, MongoDB is expecting a BSON type object id that was auto generated for each flower entry, while you are giving a string. So you may want to convert it to the proper type before doing the query:
try:
router.delete('/:id/flowers/delete/:flowerid', (req, res) => {
User.findByIdAndUpdate(req.params.id, {
$pull : {
flowers : { _id: ObjectId(req.params.flowerid) }
}
}).then((user) => res.json(user)); });
To see more about the objectId
Thanks for the replies!
I did some more testing with postman and found out that in fact the code I posted in my question did work after all. What set me off was that the response with the user document still displayed the flower I had just deleted, which made me think it didn't work.
I still have no idea why that is, or if there's a way to get a response with the updated user. But the deletion seems to work as intended.

MongoDb Node.js Driver - findOneAndUpdate() behavior without a field given

I noticed a strange behavior with the mongodb node.js driver findOneAndUpate()...
I mistakenly gave it just a objectId string....thinking it would default to searching by _id field of a document.... so, when I used
User.prototype.updatePetArray = function(user, petElement) {
return this.collection.findOneAndUpdate(user,
{ $push: { pets: petElement } },
{ returnOriginal: false,
maxTimeMS: QUERY_TIME});
}
it pulled up and modified this document, which does not have this number at all:
{ "_id" : ObjectId("56d4e2a381c9c28b3056f792"), "username" : "bob123", "location" : "AT", ...}
Why did it modify this document when 56d4d35881c9c28b3056f78a is not in the document?
After I test it following your code with one non-exist ObjectID,
var col = db.collection('users');
col.findOneAndUpdate('56cd129222222', {fname: 'Daved'}, function(err, r) {
if (err)
console.log(err);
else
console.log(r);
db.close();
});
As a result the first document in the collection was changed .
Per this doc
findOneAndUpdate(filter, update, options, callback)
filter: Specify an empty document { } to update the first document returned in the collection.
So I think this non-exist ObjectId is consider to the same behavior with {}
After reviewing the source code of findAndModify, eventually, the
// Execute the command
self.s.db.command(queryObject
is invoked, and the queryObject is
var queryObject = {
'findandmodify': self.s.name
, 'query': query
};
So I test runCommand in mongo shell with non-exist ObjectId as below, as result, the first document is returned.
> db.users.runCommand({findandmodify: 'users', query: '123ddae344', update: true
})
{
"lastErrorObject" : {
"updatedExisting" : true,
"n" : 1
},
"value" : {
"_id" : ObjectId("56c687275d81735019263d1f"),
"fname" : "Daved"
},
"ok" : 1
}
The docs keep saying the filter parameter should be an object.
The wrong behavior is likely to some side effect of mis-interpreting the value, being a string not an object (and maybe a truthy value, non-empty string).

MongooseJS messes up pre-imported data when document is updated

A piece of my Mongo document structure is:
{ "_id": ObjectId("xxxxxx..."),
"Country" : "UNITED KINGDOM",
"Line" : "something",
"Records" : [
{"rdata" : "foo", "rtype" : "X", "name" : "John"},
{"rdata" : "bar", "rtype" : "Y", "name" : "Bill"}
], ...
I'm using Mongoose to access the data via the following model:
var Record = new Schema({
rdata: String,
rtype: String,
name: String
}, {_id: false});
var ThingSchema = new Schema({
Country: String,
Line : String,
Records : [Record],
Let's say I want to update the "Line" property of one of my documents, from being "Line" : "something" to "Line" : "way more interesting" by sending a PUT request to the appropriate API URL. I can see that the data being sent is all right. This is what the API does:
exports.update = function(req, res) {
if(req.body._id) { delete req.body._id; }
Thing.findById(req.params.id, function (err, thing) {
if (err) { return handleError(res, err); }
if(!thing) { return res.send(404); }
var updated = _.merge(thing, req.body);
updated.save(function (err) {
if (err) { return handleError(res, err); }
return res.json(200, updated);
});
});
};
The API comes back with 200/OK - but I see the following updated data:
{ "_id": ObjectId("xxxxxx..."),
"Country" : "UNITED KINGDOM",
"Line" : "way more interesting", <-- updated correctly
"Records" : [
{"rdata" : "foo", "rtype" : "X", "name" : "John"},
{"rdata" : "foo", "rtype" : "X", "name" : "John"}
], ...
Notice, how the Records array got messed up by overwriting my second record by duplicating the first one. (If I switch on the automatic addition of '_id' to the subdocument by Mongoose, then even the "_id" fields will be the same on the two records within the array).
It may be relevant, that originally the Records were not added via Mongoose - but by importing a JSON document. Any suggestion as to how to start finding out why this is happening would be fantastic.
Try changing _.merge to _.extend, then call save directly on the thing document returned by the findById() method instead of the merged object updated:
exports.update = function(req, res) {
if(req.body._id) { delete req.body._id; }
Thing.findById(req.params.id, function (err, thing) {
if (err) { return handleError(res, err); }
if(!thing) { return res.send(404); }
_.extend(thing, req.body);
thing.save(function (err) {
if (err) { return handleError(res, err); }
return res.json(200, thing);
});
});
}
Another option is using the set method on the entity i.e. thing.set(req.body) before calling the save method on the thing object.
This answer by ShitalShah highlights the differences between merge and extend that is causing duplicates in your resulting object with merge but essentially:
Here's how extend/assign works: For each property in source, copy its
value as-is to destination. if property values themselves are objects,
there is no recursive traversal of their properties. Entire object
would be taken from source and set in to destination.
Here's how merge works: For each property in source, check if that
property is object itself. If it is then go down recursively and try
to map child object properties from source to destination. So
essentially we merge object hierarchy from source to destination.
While for extend/assign, it's simple one level copy of properties from
source to destination.
JSBin to illustrate the differences

Mongoose find-by-reference returns nothing

This is data from the mongo client:
> db.projects.find({ })[1];
{
"name" : "App 276",
"slug" : "app276",
"createdByUser" : ObjectId("52f20518b66ae3622c000002"),
"_id" : ObjectId("52fc91f508e3507c19000002"),
"screens" : [ ],
"dateUpdated" : ISODate("2014-02-13T09:34:23.102Z"),
"dateCreated" : ISODate("2014-02-13T09:34:23.102Z"),
"__v" : 0
}
This is my server code:
/** List Projects */
exports.list = function(req, res) {
console.log('list', mongoose.Types.ObjectId(req.params.userId));
return Project.find({ createdByUser: mongoose.Types.ObjectId(req.params.userId) }, function (err, projects) {
if (!err) {
return res.json(projects);
}
else {
return res.send(err);
}
});
};
and this is my server output:
list 52fc9720b85bac3c1a000002
GET /api/projects 200 42ms - 2b
The JSON output is an empty array - why?
Your requested project created by user 52f **c9720b85bac3c1a** 000002.
Your database extract seems to contain a project by user 52f **20518b66ae3622c** 000002
Have you tried with the right id ?
Take a look at Mongoose Population. It's better use this feature in this case. http://mongoosejs.com/docs/populate.html

Resources