Unable to remove item from array using Mongoose / UpdateOne - javascript

I have a Node.js application in which I am trying to remove an object from an array when an API endpoint is hit. I so far have been unable to get it to update/remove the object. Currently, the below query returns with no error but upon checking into my DB I am still seeing it. Below is my query and basic response (I will be adding more but that is outside the scope of this question). I have also included a sample of my data model.
In the below data model I am trying to remove the whole object from the foo array as it is no longer needed.
Code
const ID = req.params.id
await FooBar.updateOne({foo: {$elemMatch: {v_code: ID}}}, { $pull: {v_code: ID}}, (err) => {
if(err) return res.json({success: false, err})
return res.json({success: true, id: ID})
})
Data model
{
bar: [
{
foo: [
{
v_code: <>
_id: <>
}
]
}
]
}
I'm sure this has been asked for in other questions but none specific to my data model. I've tried piecing together multiple SO posts and that is how I got the $elemmatch and the $pull portions of my query and so far I've had zero luck

give the following command a try:
db.collection.updateOne(
{
"bar.foo.v_code": ID
},
{
$pull: { bar: { foo: { $elemMatch: { v_code: ID } } } }
}
)
https://mongoplayground.net/p/iqJki-mnHSJ

Related

Cannot search data in Express JS with MongoDB $or

I am trying to make API for my invoices data in which there are some boolean values on that basis I want to search those invoices. Like there are some invoice marked as 'payment received' and I want to search those invoice in mongoDB
I am using:
Backend - ExpressJS
Data Base - MongoDB
Route:
app.get("/api/invoice/search", async (req, res) => {
try {
const invoice = await Invoice.find({
isDispatched: true, // This is dynamic data I want to search this data from req.body, this is // for testing
});
res.status(200).json({
success: true,
totalInvoices: invoice.length,
invoice,
});
} catch (err) {
console.log(err);
}
});
Output, I am getting all the data instead of the those filtered with query.
I want to search multiple fields here I am using $or for this but its not working.
app.get("/api/invoice/search", async (req, res) => {
try {
const invoice = await Invoice.find({
$or: [
{
isDispatched: { $exists: true }, //This has to be dynamic data from req.data
},
{
paymentPending: { $exists: true }, //This has to be dynamic data from req.data
},
],
});
res.status(200).json({
success: true,
totalInvoices: invoice.length,
invoice,
});
} catch (err) {
console.log(err);
}
});
Output is random like sometime I get few data which has both true and false data.
I have tried removing $exists, adding $regex but didn't worked.
I have tried removing $exists, adding $regex but didn't worked.
My understanding of the question:
The result of the query sometimes returns values where isDispatched data exists and paymentPending does not. Likewise, with paymentPending data existing and isDispatched data not.
Based on this understanding there might be two problems.
First Possible Issue:
If the above is correct it looks like you aren't really trying to use an $or. Try using the following as your query to see if your results are as expected.
{
isDispatched: { $exists: true },
paymentPending: { $exists: true }
}
Explanation: $or is going to return any values that match either case.
Second Possible Issue:
The $exists only checks that the value is in the document; it will match a null value. So, you may be expecting null fields to not exist.
{
isDispatched: { $exists: true, $ne: null },
paymentPending: { $exists: true, $ne: null }
}

Mongoose search by array entries ($in) behavior not aligned with MongoDB Atlas

I'm running a Node.js server, connecting to a MongoDB database with mongoose.
Inside my controller, I have several methods that make operations to the database. One of them is this one:
async findMultiple(req, res) {
const [baseSkillsArray] = Array(req.body);
try {
// if there is not baseSkillsArray, skip
if (!baseSkillsArray) {
return res.status(200).send([]);
}
// find all baseSkills using the ids in the baseSkillsArray
const allBaseSkills = await BaseSkill.find({
_id: { $in: [baseSkillsArray.baseSkillArray] } //
});
console.log('test ' + allBaseSkills);
res.status(200).send(allBaseSkills);
} catch (error) {
console.error(error.message);
res.status(500).send('Server error find BaseSkills');
}
}
However, this returns me nothing. I did some debugging and I found the reason is the find id $in the array. So I tried hard coding a value, like '2', for instance.
// find all baseSkills using the ids in the baseSkillsArray
const allBaseSkills = await BaseSkill.find({ _id: { $in: ['2'] } });
No success. So I went to MongoDB Atlas, where my DB is stored. I tried filtering using the same line of code in my collections.
{ _id: { $in: ['2'] } }
Surprisingly, it returns my document as I wanted!
The issue is that I need to make it work with mongoose. Any ideas? Is this a known bug?
There is nothing wrong with the query, nor a bug regarding $in.
In fact, what's wrong is the actual collection name. I manually created a collection in MongoDB Atlas, called "baseSkills". However, mongoose by default transforms your collection name into lowercase and adds an "s" if your collection's name is not in the plural.
So every time I started my server, I noticed that there was a new collection called "baseskills". I assumed it was a bug and deleted it. Only after making this post that I realized the collection was there again.
So I exported the documents to this collection and my query was working fine.
FYI, there is a way to enforce the collection's name in mongoose. When you declare you model, add a second parameter to the Schema function called "collection". Here is an example:
const BaseSkillSchema = new mongoose.Schema({
_id: {
type: String,
required: true
}, ...
}, { collection: 'baseSkills' })
That's it! Sorry for the mess and thank you for your help!
you want to query over mongo db object ids. So you should create a new ObjectId to do that.
import {Types} from 'mongoose';
{ _id: { $in: [new Types.Object("2")] } }
Or if you have 2 ids one generated and one custom created as id then you can query without creating a new object.
{ id: { $in: ['2'] } }

Can I edit a MongoDB object and preverse it's previous value?

lets suppose I have a model with a field called draftFields. It is an object(but it can be an array).
I will create a PUT request to add data into draftFields. My question is: can I add data to draftFields and preserve the previous vale?
Lets say that I have added the first data to draftFields. E.g:
draftFields = {
someRandomValue: 'hi'
}
and after that I'm going to make another PUT request and it should look like this:
draftFields = {
someRandomValue: "hi",
anotherRandomValue: "hey"
}
How can I do that? Everytime I updated my draftFields obj it will remove the previous value. I had to save it in my frontend state to be able to save the previous value. Is there any workaround or method to preserve the values from the backend?
This is my code atm:
app.put('/api/save-draft/:id', function (req, res) {
User.findOneAndUpdate(
{ _id: req.params.id },
{ $set: { draftFields: req.body.draftFields } },
{ new: true },
(err, doc) => {
if (err) {
console.log('Something wrong when updating data!');
res.status(400).send('Error');
}
res.status(200).send('All good!');
console.log(doc);
},
);
});
I'm using Javascript(ReactJS) and NodeJS if this is relevant.
I can use the $push method and change from object to array.
https://docs.mongodb.com/manual/reference/operator/update/push/
I have failed searching, sorry.

Meteor MongoDB Setting field of array per document

I have a Documents in a Collection that have a field that is an Array (foo). This is an Array of other subdocuments. I want to set the same field (bar) for each subdocument in each document to the same value. This value comes from a checkbox.
So..my client-side code is something like
'click #checkAll'(e, template) {
const target = e.target;
const checked = $(target).prop('checked');
//Call Server Method to update list of Docs
const docIds = getIds();
Meteor.call('updateAllSubDocs', docIds, checked);
}
I tried using https://docs.mongodb.com/manual/reference/operator/update/positional-all/#positional-update-all
And came up with the following for my Server helper method.
'updateAllSubDocs'(ids, checked) {
Items.update({ _id: { $in: ids } }, { $set: { "foo.$[].bar": bar } },
{ multi: true }, function (err, result) {
if (err) {
throw new Meteor.Error('error updating');
}
});
}
But that throws an error 'foo.$[].bar is not allowed by the Schema'. Any ideas?
I'm using SimpleSchema for both the parent and subdocument
Thanks!
Try passing an option to bypass Simple Schema. It might be lacking support for this (somewhat) newer Mongo feature.
bypassCollection2
Example:
Items.update({ _id: { $in: ids } }, { $set: { "foo.$[].bar": bar } },
{ multi: true, bypassCollection2: true }, function (err, result) {
if (err) {
throw new Meteor.Error('error updating');
}
});
Old answer:
Since you say you need to make a unique update for each document it sounds like bulk updating is the way to go in this case. Here's an example of how to do this in Meteor.
if (docsToUpdate.length < 1) return
const bulk = MyCollection.rawCollection().initializeUnorderedBulkOp()
for (const myDoc of docsToUpdate) {
bulk.find({ _id: myDoc._id }).updateOne({ $set: update })
}
Promise.await(bulk.execute()) // or use regular await if you want...
Note we exit the function early if there's no docs because bulk.execute() throws an exception if there's no operations to process.
If your data have different data in the $set for each entry on array, I think you need a loop in server side.
Mongo has Bulk operations, but I don't know if you can call them using Collection.rawCollection().XXXXX
I've used rawCollection() to access aggregate and it works fine to me. Maybe work with bulk operations.

Mongoose update previously fetched collection

I would like to update a collection. Docs seem unclear on this.
I am wondering how to achieve the following:
Order.find({ _id: { $in: ids }}).exec(function(err, items, count) {
// Following gives error - same with save()
items.update({ status: 'processed'}, function(err, docs) {
});
});
I know how to batch save like this:
Model.update({ _id: id }, { $set: { size: 'large' }}, { multi: true }, callback);
But that requires setting my query again.
I've also tried:
Order.collection.update(items...
But that throws a max call stack error.
In mongoose, model.find(callback), return an Array of Document via callback. You can call save on a Document but not on an Array. So you can use for loop or forEach on the Array.
Order
.find({ _id: { $in: ids}})
.exec(function(err, items, count) {
items.forEach(function (it) {
it.save(function () {
console.log('you have saved ', it)
});
})
});

Categories