How to project updated values only using findOneAndUpdate in embedded array Mongoose? - mongodb

Currently my User model looks like:
{
_id: 'SomeId'
firstName: 'John',
lastName: 'Cena',
books: [
{
_id: 'xyz',
title: 'a',
author:'b',
ratings:[
{source:'source1', value:"8"},
{source:'source2', value:"9"}]
},
{
_id: 'abc',
title: 'c',
author:'d',
ratings:[
{source:'source3', value:"7"},
{source:'source4', value:"5"}]
}
]
}
After making an findOneAndUpdate query to update rating=>value of 1st book object(_id: "xyz") from 8 to 10 for a given source(say "source1"):
let up={
'books.$[book].ratings.$[rating].value':10
}
let filter={
new:true,
'books.rating':1, //I just want rating array of updated objects in it
arrayFilters:[
{ 'book._id':'xyz'},
{ 'rating.source': 'source1'}
]
}
User.findOneAndUpdate({'_id':'userId','books._id':'xyz'},up,filter).select('books.rating').exec((err,doc)=> {
if (err) throw err;
console.log(doc);
}
My code updates the books=>rating=>value correctly but I can't get that updated rating of that book.
This gives me rating of all books with both updated and non updated values in it. Looks like:-
{
books: [{ ratings:[{source:'source1', value:"10"},{source:'source2', value:"9"}] },
{ ratings:[{source:'source3', value:"7"},{source:'source4', value:"5"}] }]
}
I think the data of 2nd book shouldn't be there at all according to my code. I expect the follwing output:
{
books: [{ ratings:[{source:'source1', value:"10"}] }
}
Please help me to write findOneAndUpdate query correctly!

you can use array.find() like this:
const updatebookSource = (sourceId, userId, bookId) => {
User.findOneAndUpdate({ _id: userId, "books._id": bookId }, up, filter).exec(
(err, doc) => {
if (err) throw err;
let res = doc.books[0].ratings.find(rating => {
return rating.source === sourceId;
});
console.log(JSON.stringify(res, null, 1));
}
);
};
This returns the updated object. Let me know if it works.

Related

How to create dynamic query in mongoose for update. i want to update multiple data(Not all) with the help of Id

If I'm doing this, the field which I don't want to update is showing undefined. Any solution? (Like generating dynamic query or something)
exports.updateStudentById = async (req, res) => {
try {
const updateAllField = {
first_name: req.body.first_name,
last_name: req.body.last_name,
field_of_study: req.body.field_of_study,
age: req.body.age,
};
const data = await student_master.updateOne(
{ _id: req.body._id },
{ $set: updateAllField }
);
res.json({ message: "Student Data Updated", data: data });
} catch (error) {
throw new Error(error);
}
};
You can go for a dynamic query creation .Example
const requestBody = {
first_name: "John",
last_name: "Cena",
field_of_study: ""
}
const query={};
if(requestBody.first_name){
query["first_name"]=requestBody.first_name
}
if(requestBody.last_name){
query["last_name"]=requestBody.last_name
}
Check for the fields that are present in req.body and create a dynamic query
and when updating using mongoose use this
const data = await student_master.updateOne(
{ _id: req.body._id },
{ $set: query }
);
In this way only those fields would be updated which are present in your req.body

iterate over large mongodb collection for purpose of updating schema

I have a 300k collection of test docs. I want to update all persons firstName and lastName to be lowercase.
const person = new Schema({
firstName: { type: String},
lastName: { type: String }
})
I've added lowecase:true to the schema but how do I update the existing documents?
I tried:
CaseFile
.find({ })
.cursor()
.eachAsync(async function (doc) {
await doc.save()
})
but i get the error
Error: Collection method find is synchronous
I also tried :
CaseFile
.find({ })
.then(docs => {
docs.forEach(doc => {
doc.save()
})
})
which gives the error:
JavaScript heap out of memory
db version v5.0.2
"mongoose": "^6.0.5",
thank you Wernfried Domscheit for the pipeline 🏄 solution:
CaseFile.updateMany({}, [
{
$set:
{
firstName: { $toLower: '$firstName' },
lastName: { $toLower: '$lastName' }
}
}]
)
.then(res => res)
Why on earth "iterate", i.e. line by line?
Use an aggregation pipeline:
db.CaseFile.updateMany({}, [
{ $set:
firstName: { $toLower: "$firstName" },
lastName: { $toLower: "$lastName" }
}
])

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.

Mongoose query using if else possible?

I have this Schema:
const guestSchema = new Schema({
id: String,
cart: [
{
product: {
type: mongoose.Schema.ObjectId,
ref: "products"
},
quantity: Number
}
]
});
I have this query:
Guest.findOneAndUpdate(
{ id: req.sessionID },
{
$cond: [
{ "cart.product": { $ne: req.body.itemID } },
{ $push: { "cart": { product: req.body.itemID, quantity: 1 } } },
{ $inc: { "cart.quantity": 1 } }
]
},
{ upsert: true, new: true }
).exec(function(err, docs) {
err ? console.log(err) : res.send(docs);
});
Basically, what I'm trying to do is update based on a condition. I tried using $cond, but found out that operator isn't used for querys like I'm doing.
Based on this:
{ $cond: [ <boolean-expression>, <true-case>, <false-case> ] }
I want something similar to the functionality of this operator for my query.
Let's break down my condition:
For my boolean expression: I want to check if req.body.itemID is $ne to any of the values in my cart
If true then: $push the itemID and quantity into the cart
Else (then item already exists): $inc the quantity by 1
Question: How would achieve this result? Do I need to make two seperate querys? I'm trying to avoid doing that if possible
I went through all their Update Field Operators, and there's probably no way to do this in the way I want.
I wonder why there is no $cond for update operators. Nonetheless, I have the solution to what I wanted the functionality accomplish. Just not in the elegant fashion that I would like it.
Guest.findOneAndUpdate(
{ id: req.sessionID },
{ id: req.sessionID }, //This is here in case need to upsert new guest
{ upsert: true, new: true }
).exec(function(err, docs) {
if (err) {
console.log(err);
} else {
//Find the index of the item in my cart
//Returns (-1) if not found
const item = doc.cart.findIndex(
item => item.product == req.body.itemID
);
if (item !== -1) {
//Item found, so increment quantity by 1
doc.cart[item].quantity += 1;
} else {
//Item not found, so push into cart array
doc.cart.push({ product: req.body.itemID, quantity: 1 });
}
doc.save();
}
});
This type of logic does not belong within the database query. It should happen in the application layer. MongoDB is also very fast at retrieving and updating single records with an index so that should not be a concern.
Please try doing something like this:
try {
const guest = await Guest.findOne().where({
id: req.sessionID
}).exec();
// your cond logic, and update the object
await guest.save();
res.status(200).json(guest);
} catch (error) {
handleError(res, error.message);
}

Waterline: How to perform IN queries if attribute is a collection?

In the docs of waterline it is stated that this is the way to perform a IN query on a model:
Model.find({
name : ['Walter', 'Skyler']
});
And this the way to perform an OR query on a model:
Model.find({
or : [
{ name: 'walter' },
{ occupation: 'teacher' }
]
})
My problem now is that i need a combination of those two, and to make it even more complicated, one of the attributes i have to use is a collection.
So what i tried is this, but it doesn't seem to work:
Product.find({
or : [
{ createdBy: userIds },
{ likes: userIds }
]
})
Note: userIds is an array of id's from a user model.
The (simplified) product model looks likes this:
module.exports = {
attributes: {
name: 'string',
description: 'string',
createdBy: {
model: 'User'
},
brand: {
model: 'Brand',
},
likes: {
collection: 'User',
}
}
}
The query works when I only include createdBy, so it seems to be a problem with the collection attribute.
Is this somehow possible?
Thank you for your input.
UPDATE:
I think this is only possible with native() queries.
The way I understand it something like this should work.
Product.native(function(err, products){
if(err) return res.serverError(err);
products.find({"likes": { $elemMatch: { _id: { $in: userIds}}}}).toArray(function(err, results){
if (err){
console.log('ERROR', err);
}
else {
console.log("found products: " + results.length);
console.log(results);
return res.ok(results);
}
});
});
Unfortunately, it doesn't. The returned results is always an empty array.