Mongoose: updating array in document not working - javascript

I'm trying to update an array in document by adding object if it doesn't exist, and replacing the object in array otherwise. But nothing ($push, $addToSet) except the $set parameter does anything, and $set works as expected - overwrites the whole array.
My mongoose schema:
var cartSchema = mongoose.Schema({
mail: String,
items: Array
});
The post request handler:
app.post('/addToCart', function(req, res) {
var request = req.body;
Cart.findOneAndUpdate({
"mail": request.mail
}, {
$addToSet: {
"items": request.item
}
}, {
upsert: true
},
function(err, result) {
console.log(result);
}
);
res.send(true);
});
The data that I'm sending from the client:
{
"mail":"test#gmail.com",
"item":{
"_id":"59da78db7e9e0433280578ec",
"manufacturer":"Schecter",
"referenceNo":"Daemon-412",
"type":"Gitare",
"image":"images/ba9727909d6c3c26412341907e7e12041507489988265.jpeg",
"__v":0,
"subcategories":[
"Elektricne"
]
}
}
EDIT:
I also get this log when I trigger 'addToCart' request:
{ MongoError: The field 'items' must be an array but is of type object in
document {_id: ObjectId('5a19ae2884d236048c8c91e2')}

The comparison in $addToSet would succeeded only if the existing document has the exact same fields and values, and the fields are in the same order. Otherwise the operator will fail.
So in your case, request.item always need to be exactly the same.
I would recommend creating a model of "item". Then, your cart schema would be like:
var cartSchema = mongoose.Schema({
mail: String,
items: [{
type: ObjectId,
ref: 'item',
}],
});
And let MongoDB determine if the item exist.

this should work you just need to implement objectExits function that test if the item is that one you're looking for :
Cart.findOne({ "mail": request.mail })
.exec()
.then(cart => {
var replaced = cart.items.some((item, i) => {
if (item._id == request.item._id)) {
cart.items[i] = request.item;
return true;
}
})
if (!replaced) {
cart.items.push(request.item);
}
cart.save();
return cart;
})
.catch(err => {
console.log(err)
});

Related

Mongodb, Delete one object from an array of objects using javascript

I am trying to remove one object from an array of my collection, which looks like this. It s a collection in Mongodb
Before deleting a specific object based on chartId, I need to check the userId and the name of the array. Then I need to delete the object.
I have written this code, but its not working. someone will tell me what exactly I am missing in this code.
delChartObj.updateOne(
{ 'userId': userId },
{ $pull: { "Color": { "chartId": req_chart_id } } },
{ safe: true, multi: true}, function (err, obj) {
if (err) { res.send.err }
res.status(200).send({ msg: "Deleted Sucessfully" });
});
In my case, userId = ADAM, array = "Color" and chartID = time
I am using mongoose for performing action
delChartObj is an object of model
const UserSchema = mongoose.Schema({
userId: { type: String, required: true, unique: true },
charts: { type: Object },
});
You should do findOneAndUpdate, the syntax will be something like:
Model.findOneAndUpdate(
< condition>,
{ $pull: { "Color.$.chartId": req_chart_id } } }, // The actual Query
{ new: true }
)
try this in pull
{ $pull: { "Chart.Color.$.chartId": req_chart_id } } },

Using findById to find the id of a schema in an array

Hey I was wondering how do I use findById for a schema inside an array? For example, I have the following Schema:
const GameSchema = new mongoose.Schema({
users: [
{
user: { type: mongoose.Schema.ObjectId, ref: 'User' },
role: {
type: String,
required: true,
enum: ['user', 'moderator', 'creator'],
default: 'user',
},
},
]
}]
I want to find the user with a mongoose function like findById, such as the following:
const user = await game.users.findById({ user: req.user.id })
It doesn't seem to work since users is not a mongodb model. I know I can find the user by using find() like the following:
const user = await game.users.find(
(gameUser) => gameUser.user == req.user.id
)
The only problem is that the type of gameUser and req.user.id is not the same and I can't use '==='. Is there some way to go through the array and use the mongoose function findById?
As docs explains, findById method:
Finds a single document by its _id field
So you have to use findOne() instead of findById().
Also, to return only one field from the entire array you can use projection into find.
Check this example. This query find an object by its id (i.e. user field) and return only the object, not the whole array.
db.collection.find({
"users": { "$elemMatch": { "user": 1 } }
},
{
"users.$": 1
})
Using mongoose you can do:
yourModel.findOne(({
"users": { "$elemMatch": { "user": 1 } }
},
{
"users.$": 1
})).then(result => {
console.log(result)
}).catch(e => {
// error
})

mongoose check if id exists but that id is nested inside an array

When i fetch new alerts, i want to check if the ID of the new alert was already recorded. The issue is that that ID is nested inside an array. There's the alertsDetails array, which contains objects and those objects have an _ID filed which is what i want to check. I am not sure how to achieve that. I got the code below but then i have to iterate over the result to check the exists value. Im sure there must be a better way.
const mongoose = require('mongoose');
const { Schema } = mongoose;
const G2AlertsSchema = new Schema(
{
status: { type: String, required: true },
openDate: { type: Date, required: true },
alertType: { type: Array, required: true },
severity: { type: Array, required: true },
locationName: { type: Array, required: true },
history: { type: Array, required: true },
alertDetails: { type: Array, required: false },
assignedTo: { type: Schema.Types.ObjectId, ref: 'user' },
},
{
timestamps: true,
},
);
const G2Alerts = mongoose.model('G2Alert', G2AlertsSchema);
module.exports = G2Alerts;
This is the code i found on mongodb's website. I just want to see if the ID exists only. Basically when i fetch the new alerts i get an array and i iterate over it, i want to check each item's ID against what's inside the Database. If it's there, skip and go to the next. If it's new, then create a new alert and save it.
const exists = await G2Alerts.aggregate([
{
$project: {
exists: {
$in: ['5f0b4f508bda3805754ab343', '$alertDetails._id'],
},
},
},
]);
EDIT: Another thing. I am getting a eslint warning saying i should use array iteration instead of a for loop. The issue is, i need to use await when looking up the Alert ID. If i use, reduce or filter, i can't use await. If i use async inside the reduce or filter function, then it will return promises in or just an empty array.
This below works, based on the answer provided by Tom Slabbaert
const newAlertsData = [];
for (let item of alertData.data.items) {
const exists = await G2Alerts.find({ 'alertDetails._id': `${item._id}` });
if (exists.length === 0) {
newAlertsData.push(item);
}
}
if (newAlertsData.length !== 0) {......
But this does not
const filteredAlerts = alertData.data.items.reduce((filtered, item) => {
const exists = await G2Alerts.find({ 'alertDetails._id': `${item._id}` });
if (exists.length === 0) {
filtered.push(item);
}
return filtered;
}, []);
You're not far off, here is an example using the correct syntax:
const exists = await G2Alerts.findOne({"alertDetails._id": '5f0b4f508bda3805754ab343'}});
if (!exists) {
... do something
}
This can also be achieve using aggregate with a $match stage instead of a $project stage or even better countDocuments which just returns the count instead of the entire object if you do not require it.
One more thing I'd like to add is that make sure alertDetails._id is string type as you're using string in you're $in. otherwise you'll need to cast them to ObjectId type in mongoose like so:
new mongoose.Types.ObjectId('5f0b4f508bda3805754ab343')
And for Mongo:
import {ObjectId} from "mongodb"
...
new ObjectId('5f0b4f508bda3805754ab343')
EDIT
Try something like this?
let ids = alertData.data.items.map(item => item._id.toString());
let existing = await G2Alerts.distinct("alertsDetails._id", {"alertsDetails._id": {$in: ids}});
const filteredAlerts = alertData.data.items.reduce((filtered, item) => {
if (!existing.includes(item._id.toString())) {
return [item].concat(filtered)
}
return filtered;
}, []);
This way you only need to call the db once and not multiple times.
Final code based on the provided answer.
const ids = alertData.data.items.map(item => item._id);
const existing = await G2Alerts.find({ 'alertDetails._id': { $in: ids } }).distinct(
'alertDetails._id',
(err, alerts) => {
if (err) {
res.send(err);
}
return alerts;
},
);
const filteredAlerts = alertData.data.items.reduce((filtered, item) => {
if (!existing.includes(item._id.toString()) && item.openDate > dateLimit) {
return [item].concat(filtered);
}
return filtered;
}, []);

Mongo - Update nested array of objects by _id

How can I access an array of object with their own _id and update it with Mongo/Mongoose?
Take a look to my update query and check if there's something wrong, because this code doesn't return any error, but it doesn't really update the field
modelUser.findOneAndUpdate(
{ userName: body.author, "portfolio._id": body.id },
{ new: true },
{
$set: { //I thing the problem it's over here
"portfolio.$.profitLoss": profitLoss,
"portfolio.$.percentage": percentage
}
},
(err, user) => {
if (err) {
console.log(err);
}
console.log(`Done`);
}
);
This is my User Schema:
const userSchema = new Schema({
...stuff,
portfolio: [
{
coin: String,
amount: String,
price: String,
bought: Date,
profitLoss: String,
percentage: String
}
],
});
Basically i think mongo just don't know which of these sub documents should update, I don't know if there's something like another findOneAndUpdate for sub object/document by id.
Just changed findOneAndUpdate to updateOne and everything works.

delete object from document array in mongodb collection using mongoose

I try to remove an element from an array attribute of my object.
This is my schema :
const userSchema = new mongoose.Schema({
userID: {
type: Number
},
name: {
type: String
},
names: [
{
text: { type: String, required: true },
order: {
type: Number,
required: true
}
}
]
});
this is my mongoose function :
User.findOne({ userID: Number(req.params.id) })
.then((user) => {
user.names.remove({text: "john", order: 3});
recipe.save(() => {
res.json(recipe);
});
})
I don't understand why it's not good :/
As per documentation of mongoose remove method remove operation is only executed when a callback is passed. To force execution without a callback, you must first call remove() and then execute it by using the exec() method.
Since you are trying to delete from array of objects then better would be to use pull operator. You don't have to do find and remove, you can simply use update method.
As per documentation of $pull operator you can either specify a value or a condition
i.e.
{ $pull: { <field1>: <value|condition>, <field2>: <value|condition>, ... } }
In your scenario you need to either specify complete value of one or more names item object or an condition that matches one or more names item
Add the condition where you match id of names item or if you don't know that then you can use elemMatch to match on few fields i.e.
Use following pull condition to solve the issue:
User.update(
{ _id: Number(req.params.id) },
{ $pull: { 'names': { $elemMatch: { 'text': "john", 'order': 3 }} } },
(error, success) => {
if (error) console.log(error);
console.log(success);
}
);
To Remove Element from array in document please follow as below
User.update(
{
userID: Number(req.params.id),
},
{
$pull: { names: { $elemMatch: { text: "john", order: 3 } } }
},
{
multi: false
}
).lean().then((Status) => {
console.log("Status-->", Status);
res.json('Removed Successfully');
})
Refer $pull operator at link

Categories