$pull/$pop on embedded array mongodb - node.js

I have a simple collection with the following schema
{
name:"John",
brands:[
{
name:"some",
email:"asdf#some.com"
},
{
name:"blah"
email:"blah#blah.com"
}
]
}
i'm using the following query to remove the embedded object inside my brands array field:
var args = {
'query':{name:"John",brands.email:"asdf#some.com"}
,update:{
'$pull':{
'brands.$.email:"asdf#some.com"
}
}
}
i'm using nodejs driver for mongodb and when i run the above using following:
collectionName.findAndModify(args,function(req,res){
})
I get the following error:
MongoError: Cannot apply $pull/$pullAll modifier to non-array
I guess i'm doing correct but still getting this error. Any help appreciated.

Your $pull is targeting email which isn't an array. If you're trying to remove the matching element, you can do it like this:
var args = {
query: {name: "John"},
update: {
'$pull': {
brands: {email: "asdf#some.com"}
}
}
}
or if you're trying to remove the email field, use $unset instead:
var args = {
query: {name: "John", "brands.email": "asdf#some.com"},
update: {
'$unset': {
'brands.$.email': 1
}
}
}

Related

Using updateOne method to update an object field inside array - throws error "Cannot create field 'url' in element"

I have MongoDB database (with Mongoose) containing a collection of Products (among others), which looks like this:
[
{
name: 'Product A',
url: 'product-a',
category: 'accesory',
price: 12,
shortDescription: ['example description'],
technicalSpecs: [{ speed: 10, weight: 20 }],
images: [],
reviews: [],
relatedProducts: [
{
url: 'product-b',
name: 'Product B',
// to be added in Update query
//id: id_of_related_product
}
]
} /* other Product objects */
]
As every MongoDB document is provided with _id property by default, but within the relatedProducts array i only have url and name properties, i want to add the id property (associated with corresponding Product) for each object in the relatedProducts array, so i will be able to conveniently query and process those related products.
I came up with an idea to query all Products to get only those, which have non-empty relatedProducts array. Then i loop them and i search for Product model, which has specific url and name properties - this let's me get it's true (added by MongoDB) _id. At the end i want to add this _id to matching object inside relatedProducts array.
My code:
async function assignIDsToRelatedProducts(/* Model constructor */ Product) {
const productsWithRelatedOnes = await Product.find(
{ relatedProducts: { $ne: [] }}, ['relatedProducts', 'name', 'url']
);
for (const productItem of productsWithRelatedOnes) {
for (const relatedProduct of productItem.relatedProducts) {
const product = await Product.findOne(
{ url: relatedProduct.url, name: relatedProduct.name },
'_id'
);
// throws error
await productItem.updateOne(
{ 'relatedProducts.url': relatedProduct.url },
{ $set: { 'relatedProducts.$.id': product._id } }
);
}
}
}
However it throws the following error:
MongoError: Cannot create field 'url' in element {relatedProducts: [ /* array's objects here */ ]}
I don't know why MongoDB tries to create field 'url', as i use it to project/query url field (not create it) in updateOne method. How to fix this?
And - as i am newbie to MongoDB - is there a simpler way of achieving my goal? I feel that those two nested for..of loops are unnecessary, or even preceding creation of productsWithRelatedOnes variable is.
Is it possible to do with Mongoose Virtuals? I have tried it, but i couldn't match virtual property within the same Product Model - attach it to each object in relatedProducts array - after calling .execPopulate i received either an empty array or undefined (i am aware i should post at-the-time code of using Virtual, but for now i switched to above solution).
Although i didn't find solution or even reason of my problem, i solved it with a slightly other approach:
async function assignIDsToRelatedProducts(Product) {
const productsHavingRelatedProducts = Product.find({ relatedProducts: { $ne: [] }});
for await (const withRelated of productsHavingRelatedProducts) {
for (const relatedProductToUpdate of withRelated.relatedProducts) {
const relatedProduct = await Product
.findOne(
{ url: relatedProductToUpdate.url, name: relatedProductToUpdate.name },
['url', '_id']
);
await Product.updateMany(
{ 'relatedProducts.url': relatedProduct.url },
{ $set: { 'relatedProducts.$.id': relatedProduct._id } }
);
}
}
const amountOfAllProducts = await Product.find({}).countDocuments();
const amountOfRelatedProductsWithID = await Product
.find({ 'relatedProducts.id': { $exists: true } }).countDocuments();
console.log('All done?', amountOfAllProducts === amountOfRelatedProductsWithID);
}
Yet, i still suppose it can be done more concisely, without the initial looping. Hopefully somebody will suggest better solution. :)

How to delete an object from an array in mongodb?

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>**);

Find and update child of child in MongoDB

Let's take an exemple :
listCollection = {
id_list:"111",
child_list:[{
id_list:"222",
child_list:[{
id_list:"333",
child_list:[{
// and it can be more
}]
}]
}]
}
As you see, it is always the same object inside the same object. The initial object is :
var list = {
id_list: "string",
child_list:[]
};
Using mongoDB, I would like to Find, Update or Push list anywhere I want inside the collection, only by knowing the id_list.
This is the most far I was :
db.collection("listCollection").findOneAndUpdate({
"child_list.id_list": "listId"
}, {
"$push": {
"child_list.$.child_list": newList
}
}, function(err, success) {
if (err) {
console.log(err);
} else {
console.log(success);
}
})
It works fine for the first level of child, but not more.
I am pretty new to Node JS + mongoDB, can you help me a little bit with this ? Thank you :)
I have found my way through this. Not a really beautiful one, but a working one.
Let's retake my exemple :
listCollection = {
child_list:[{
child_list:[{
child_list:[
]
}]
}]
}
I want to add a new child_list at the end of the hierarchy. At the end, I want my collection to look like that :
listCollection = {
child_list:[{
child_list:[{
child_list:[{
child_list:[
]
}]
}]
}]
}
Yeah, it is kind of a dumb exemple but it is a simple one :)
So here is the object I want to insert :
var theNewChildList= {
child_list:[]
};
Then, I will need a kind of "route" of where I want to put theNewChildList.
Here, I want to push it at listCollection.child_list[0].child_list[0].child_list
So I just use that string :
var myRoute = "child_list.0.child_list.0.child_list";
Then, I prepare your mongoDB query like that :
var query = {};
query[myRoute] = theNewChildList;
and finally, I do the query :
db.collection("myCollectionOfList").findOneAndUpdate(
{}, // or any condition to find listCollection
{"$push": query},
function(err, success) {
if (err) {
console.log(err);
} else {
console.log(success);
}
})
And it works.
Note that you have many ways to prepare the var myRoute dynamically.
I you have other ideas, please share :)

How to delete an element from an array using mongoose

I'm a little bit stuck. I'm trying to delete an element from an array using mongoose.
I used :
my_collection.update({
user: req.query.user
}, {
$pullAll: { //or $pull
my_array: array[index] //= "elem1"
}
});
Unfortunately it really doesn't work...
Here is my document, if it could help :
{
"_id":"5a997cde9872f41085391f51",
"my_array":
["elem1",
"elem2",
"elem3",
"elem4"],
"user":"rodolphe",
"__v":0
}
Thank you for your help!
See $pullAll, it requires an array argument, you passed a string.
This is the error I get when I run your code:
MongoError: $pullAll requires an array argument but was given a string
Make sure you console.log your errors with .catch()
// mock data
const req = { query: { user: "rodolphe" } }
const array = ["elem1"];
const index = 0;
// update record
Collection.update({
user: req.query.user
}, {
$pullAll: { //or $pull
my_array: [array[index]] // WRAP WITH AN ARRAY
}
})
.then(res => console.log(res))
.catch(err => console.log(err));

$ne query not working with mongoose but working in mongoshell

When I execute this mongoose query
FinancedProject.find({_id:{$ne:fb.financedProjects.financedProjectId}).exec( callback);
where fb is an object like this
{
_id: ObjectId("54das4da9dsa9d4ad4a9");
name: "some",
financedProjects: [
{registry:"147", financedProjectId:ObjectId("13da4sd4sa48da4dsa")},
{registry:"189", financedProjectId:ObjectId("5d5asd5a4sd5ada5sd")}
]
{
the result is undefined and when I execute it in the mongoshell the results are the expected
Because financedProjects is an array you have to address the element with [] like:
FinancedProject.find({
_id: {
$ne: fb.financedProjects[0|.financedProjectId
}
}).exec( callback );
EDIT:
mongoose ist JavaScript, so it follows the rules of JavaScript. fb.financedProjects is an array. So if you use the expression fb.financedProjects.financedProjectId this is evaluated to undefined by the JavaScript interpreter, because there is no financedProjectId property within that array (arrays have 0,1,2,3,... as properties). So mongoose does get { $ne: undefined } and has no chance to recognize that you meant the property financedProjectId of the array elements.
To achieve what you want, you can do this:
var arr = [];
for( var i=0; i<fb.financedProjects.length; i+=1 ) {
arr.push( fb.financedProjects[i|.financedProjectId );
}
FinancedProject.find({
$not: {
_id: {
$in: arr
}
}
}).exec( callback );

Resources