Mongoose: updateMany() is not working as expected - node.js

I'm using mongoose to handle my DB queries. I'm trying to update a set of records entirely using this method. Mode code looks like this:
// prepare database query
const filter = { type: 'company' };
const update = req.body.payload; // payload contains the array of objects (i.e. updated records)
const options = { new: true, runValidators: true }
// find and update the taxonomy record
await Taxonomy.updateMany(filter, update, options);
But whenever I run this query I'm getting following error in the console:
Error [MongooseError]: Invalid update pipeline operator: "_id"
I suppose there is something wrong in my update payload. The req.body.payload looks like this:
[
{
_id: '5ef3d08c745428001d92f896',
type: 'company',
name: 'Company Size',
__v: 0
},
{
_id: '5ef3cdc5745428001d92f893',
type: 'company',
name: 'Company Industry',
__v: 0
}
]
Can you please tell me what actually is wrong here?

This is not the right usage of updateMany() - it is aimed to update many documents with a single change.
To update many documents use bulkwrite() (docs) :
async function myUpdateMany(Model, objectsArray) {
try {
let ops = []
for (let obj of (objectsArray || [])) {
ops.push({
updateOne: {
filter: { platformId: obj.platformId },
update: obj,
upsert: false, // set "true" if you want to add a new document if it doesn't exist
}
})
}
Model.bulkWrite(ops, { ordered: false });
} catch (err) {
throw Error("myUpdateMany error: " + err)
}
}
Regarding runValidators, according to this, it seems to work by default.

Related

How to fix 'Updated field become undefined' issue in MongoDb query

I'm currently writing a mongoDB query to update set of queries in the db, The requirement is to fetch the db entries that has mediaOrigin = 'Kaltura' and the mediaType= 'YOUTUBE', then update the entries as mediaOrigin= 'AWS' and the mediaUrl.dataUrl and mediaUrl.downloadUrl with the value of mediaUrl.originalUrl. So I have completed a script to update the relevent queries but the value that the mediaUrl.dataUrl and mediaUrl.downloadUrl taking is undefined. So how can I solve that, I need to fill that two fields with the value of mediaUrl.originalUrl.
Here is the query I have written,
try {
db.getCollection('media').find({mediaOrigin: { $eq: 'KALTURA' }, mediaType: {$eq: 'YOUTUBE' }, delete: false
})
.forEach(function(media) {
var youtubeUrl = media.mediaUrl.originalUrl;
var Url = youtubeUrl;
db.getCollection('media').update(
{
_id: media._id
},
{
$set: {
'mediaUrl.downloadUrl': Url,
'mediaUrl.dataUrl': Url,
mediaOrigin: 'AWS'
}
}
);
});} catch (e) {
print(e);}
So how can I solve that.
Here I have attached the example entry in the db that I need to update.
You are attaching .forEach end of the .find() method get results from your collection.
You have to wait to get results before sending the result into foreach.
So use it like this:
const medias = await db.getCollection('media').find({
mediaOrigin: { $eq: 'KALTURA' },
mediaType: {$eq: 'YOUTUBE' },
delete: false
}).toArray();
medias.forEach(async(media) => {
var youtubeUrl = media.mediaUrl.originalUrl;
var Url = youtubeUrl;
await db.getCollection('media').update(
{
_id: media._id
},
{
$set: {
'mediaUrl.downloadUrl': Url,
'mediaUrl.dataUrl': Url,
mediaOrigin: 'AWS'
}
}
);
});

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. :)

Mongoose findOneAndReplace replacement doc empty

I want to replace a document in one of my models(configurations) and for that I am using findOneAndReplace method.
However, mongoose replaces the document with an empty one.
I invoke the method as:
let updateData = { _id: '5ecba01dbac0c68120535f40', data: 'newData' };
Configuration.findOneAndReplace({ uuid : req.params.uid }, updateData, (err, conf) => {
if ( err ) {
sendErrorResponse ( res, err );
} else {
res.json ( {
status : 1,
data : conf
} );
}
} );
In the logs, I can see this:
configurations.findOneAndReplace({ uuid: 'default' }, {}, { _id: '5ecba01dbac0c68120535f40', data: 'newData' }, projection: {}})
As per the documentation, the second parameter should be the replacement document but mongoose is passing the replacement document as 3rd parameter and second parameter is empty. I think that's the reason that it sets it empty in the db.
Now instead of this, if I use findOneAndUpdate, it works completely fine. I get the following in the logs:
configurations.findOneAndUpdate({ uuid: 'default' }, { '$set': { _id: '5ecba01dbac0c68120535f40', data: 'newData' }}, { upsert: false, projection: {}, returnOriginal: true })
But I want to replace the document instead of updating it. Is there something that I am missing or is this probably a bug in mongoose?
We've had a similar issue recently - setting the options to a an empty object and enabling useFindAndModify in the connection settings resolved the issue for us:
// connection-setup
mongoose.connect('mongodb://...', { useNewUrlParser: true, useUnifiedTopology: true, useFindAndModify:true });
// findOneAndReplace call
Configuration.findOneAndReplace({ uuid : req.params.uid }, updateData, {}, (err, conf) => {
...
});
Also there's this github issue which might be of help.

FindOneAndUpdate not updating nested field with passed in parameters

I am trying to create a service that can be used to update nested fields in a Mongoose model. In the following example I am trying to set the field 'meta.status' to the value 2. This is the service:
angular.module('rooms').factory('UpdateSvc',['$http', function($http)
{
return function(model, id, args)
{
var url = '/roomieUpdate/' + id;
$http.put(url, args).then(function(response)
{
response = response.data;
console.log(response.data);
});
}
}]);
This is how it is called in the controller:
var newStatus = {'meta.$.status' : 2};
var update = UpdateSvc("roomie", sessionStorage.getItem('userID'), newStatus);
And this is the model:
var RoomieSchema = new Schema(
{
meta:
{
created:
{
type: Date,
default: Date.now
},
status:
{
type: Number,
default: '1',
}
}
}
And this is the route:
app.put('/roomieUpdate/:id', function(req,res)
{
var id = req.params.id;
Roomie.findOneAndUpdate(
{_id: mongoose.Types.ObjectId(id)},
req.body,
{ new : true },
function(err, doc)
{
if(err)
{
console.log(err);
}
res.json(doc);
console.log(doc);
});
});
The argument is received correctly, but I can't seem to get this to work. I am not even getting an error message. console.log(doc) simply prints out the object and the field meta.status remains '1'. I have done a direct Mongo search on the target object to make sure that I wasn't just reading the old document. I've tried a great many things like separating the key and value of req.body and use {$set:{key:value}}, but result is the same.
findOneAndUpdate() by default will return the old document, not the new (updated) document.
For that, you need to set the new option:
Roomie.findOneAndUpdate({
_id : mongoose.Types.ObjectId(id)
}, req.body, { new : true }, function(err, doc) {
...
});
As it turns out, var newStatus = {'meta.$.status' : 2}; should have been var newStatus = {'meta.status' : 2}; The document now updates correctly.
The reason the $ was there in the first place was probably based on this thread:
findOneAndUpdate - Update the first object in array that has specific attribute
or another of the many threads I read through about this issue. I had tried several solutions with and without it, but couldn't get anything to go right.

Sails.js - Querying array size

// Item.js
schema: true,
attributes: {
testArray: {
type: 'array',
required: true,
array: true
}
}
I would like to find all items where the testArray attribute have a specific length.
I tried with this code below, but it doesn't work.
Item.find({testArray: {length: 2}}).exec(function (err, items) {
console.log(items);
});
I also tried with minLength, maxLength, size, but still no results.
Is there is a way to do that?
I'm using MongoDB via Sails.js/Waterline.
Actually this is in documentation
Model.where({ property: { '>': 100 }})
http://sailsjs.org/#!documentation/models
Another solution:
var query = { testArray: { $size: { $gt:2 } } };
Item.native(function(err, collection) {
collection.find(query).toArray(function(err, items) {
//items contains objects where testArray.length is greater than 2
});
});
You might run into problems depending on your mongodb version, then read http://www.mkyong.com/mongodb/mongodb-find-all-documents-where-an-array-list-size-is-greater-than-n/

Resources