Mongoose, update a nested object inside a nested object - node.js

i'm trying to update an object that is inside an array that is nested inside an array my schema is the following:
const quizzerSchema = mongoose.Schema(
{
title: {
type: String,
},
imgUrl: {
type: String,
},
SKU: {
type: String,
},
topics: [
{
topicTitle: { type: String },
questions: [
{
section: { type: String },
setup: { type: String },
question: { type: String },
correct: { type: String },
answer: { type: String },
note: { type: String },
questionNumber: { type: String },
a: { type: String },
b: { type: String },
c: { type: String },
d: { type: String },
e: { type: String },
},
],
},
],
},
{
timestamps: true,
}
);
I want to be able to update the values of a specific object that inside the questions array.
I was able to update the object that is inside the topics array, but now I need to go a level deeper
This is how I am updating the first nested level (Object inside topics)
let updateTopic = await Quizzer.findOneAndUpdate(
{ _id: bookid, 'topics._id': topicid },
{
$set: {
'topics.$.topicTitle': topicTitle,
},
}
);
But I don't know how to access a level deeper.
Any help would be super appreciated

You can update specific nested value using arrayFilters as below:
db.collection.update({},
{
$set: {
"topics.$[x].questions.$[y].d": "New_Value"
}
},
{
arrayFilters: [
{
"x.questions": {
$exists: true
}
},
{
"y.c": "2"
}
]
})
Explained:
Define 2x arrayFilters x & y in the arrayFilter section to identify the nested element
Use in the $set part the filters to update the nested field with key "d" in the example
Playground

Related

MongoDB remove all elements from the array inside one document

I want to delete all elements from an array in my document..
I have an array of object id's
My model looks like this:
const TypeSchema = new mongoose.Schema({
ISOklasifikacija: { type: String, requred: false },
profilUIC: { type: String, requred: false },
certificateId: [
{
type: Schema.Types.ObjectId,
ref: "Certificates",
required: false,
},
],
});
My query for clearing the array is:
let type = await Type.findOneAndUpdate(
{
_id: req.params.typeId,
},
{
$set: { certificateId: [] },
},
{ multi: true }
);
console.log(type);
It nevers clear the array. In console I get: null and this id exists which I am passing in
Thanks for help

Node/Mongoose Error - Can't use $expr with String

My goal is to create a query that searches for a partial string in a particular field - from the last index of a nested array in a MongoDB database. My constraint is that I must use a .find() query.
I'm using Mongoose 6.2.9 and MongoDB 5.0.4 - I've developed a query that works perfectly in MongoDB Compass, but Mongoose seems to be having issues with:
(node:48632) UnhandledPromiseRejectionWarning: Error: Can't use $expr with String.
The call I'm trying to make:
findQuery['statuses.schema'] = {
$expr: {
$regexMatch: {
input: {
$arrayElemAt: ['$statuses.schema', -1]
},
regex: "sample|regex|here",
options: 'i'
}
}
};
The issue seems to be placing $statuses.schema inside quotation marks (cast as a string) - but without these quotation marks, I seem to get an error in my GraphQL response (this query is part of a resolver for a GraphQL API) that would lead me further away from a solution:
{
"message": "$statuses is not defined",
"locations": [
{
"line": 2,
"column": 3
}
],
Edit: Added Schema
const statusSchema = mongoose.Schema({
timestamp: { type: String },
status: { type: String },
test: { type: String },
symptom: [symptomSchema],
code: { type: String },
});
const serverSchema = mongoose.Schema({
ipAddress: { type: String },
index: { type: String },
serialNumber: { type: String },
model: { type: String },
configCode: { type: String },
operator: { type: String },
onlineStatus: {type: Boolean, default: false},
repairCount: { type: String },
lastRepair: { type: String },
customer: { type: String },
location: [locationSchema],
inRepair: { type: String },
rack: { type: String },
statuses: [statusSchema],
schema_version: { type: String },
}, { _id: true});
So in short, MongoDB seems to require "$myArray.myField" be a string, but Mongoose cannot handle it being a string when nested under $expr. What am I doing wrong?

"$pull" doesn't delete the embedded documents of mongoose array

My mongo schema is as below:
resourceId: {
type: String,
},
resourceName: {
type: String,
},
dateAndValue: [
{
date: { type: Date },
value: { type: Number },
},
],
Once data is added to the "dateAndValue" array, I want to remove all objects in the array that includes '0' as the value. This is the code I used for it but it doesn't seem to work:
await QuantumResourcesManpowerAdmin.updateMany(
{ resourceId: qrmaRecord.resourceId },
{
$pull: {
dateAndValue: {
$elemMatch: { value: 0 },
},
},
},
{ multi: true }
);
Found the answer, $elemMatch should be removed from the above code. So,
await QuantumResourcesManpowerAdmin.update(
{ resourceId: qrmaRecord.resourceId },
{
$pull: {
dateAndValue: {
value: 0,
},
},
},
{ safe: true, multi: true }
);
would work without any issue

removing a string within nested array mongoose

const AllPostsSchema = new Schema({
user: {
type: Schema.Types.ObjectId,
ref: 'users'
},
posts: [{
postid: {
type: String
},
title: {
type: String
},
category: {
type: String
},
subcategory: {
type: String
}, category: {
type: String
},
description: {
type: String
},
name: {
type: String
},
price: {
type: Number
},
email: {
type: String
},
phonenumber: {
type: Number
},
language: {
type: String
},
make: {
type: String
},
model: {
type: Number
},
odometer: {
type: Number
},
condition: {
type: String
},
state: {
type: String
},
town: {
type: String
},
city: {
type: String
},
links: [{ type: String }],
date: {
type: Date,
default: Date.now
}
}]
})
AllPosts.findOneAndUpdate({ 'user': req.query.userid },
{ $pull: { 'posts': { 'links': req.query.link } } }
)
.then(post => console.log(post))
i need to find a specific user and within that user match the post id then remove one of the links in links array. when I do it like above it removes the whole array instead i want it to remove specific link within links array in posts arrayy.
Each user has one or more than one posts. I need to update a single post of a specific user. if a user wants to delete an image i delete that from amazon s3 then, i need to remove the link from that post link array so it doesnt create broken img tags in the front end.
AllPosts.findOneAndUpdate({ 'user': req.query.userid, 'posts.postid': req.query.postid },
{ $pull: { 'links': req.query.link } }
)
.then(post => console.log(post))
this also didnt work.
Solved. For future reference :
AllPosts.findOneAndUpdate({ 'user': req.query.userid, 'posts.postid': req.query.postid },
{ $pull: { 'posts.$.links': req.query.link } }
)
.then(post => console.log(post))

how to insert autoincrement number with my mongoose collection

I am newbie to mongoose, I have a mongoose schema like this:
var user = mongoose.Schema({
userID: {
type: String,
required:true
},
seq: {
type: Number,
default: 0
},
firstname: {
type: String
},
lastname: {
type: String
},
dob: {
type: String
},
email: {
type: String,
required: true
},
username: {
type: String,
required: true
},
displayname: {
type: String
},
password: {
type: String,
required: true
},
mobile: {
type: String
},
profilePic: {
type: String
},
city: {
type: String
},
gender: {
type: String
},
profileType: {
type: String,
required: true
},
profileId: {
type: String
},
isActive: {
type: Number
},
ageVerified: {
type: String
},
ipAddress: {
type: String
},
key: {
type: String
},
osType: {
type: String
},
osVersion: {
type: String
},
deviceName: {
type: String
},
joinedDate: {
type: String
},
connectedAccounts: [{
profileType: {
type: String
},
profileId: {
type: String
},
email: {
type: String
}
}]
}, {collection: 'user'});
Please note the userID is an auto increment number field, for inserting value am using the mongoose query like:
new user(contents).save(function (err,doc){};
'contents' is a object, which contain data for all the field except userID, here my question is how to insert value for the userID(autoincrement number) while inserting records for other fields? And I refer this linkto set the auto increment value... But I don't know how to use this in mongoose?
Following the MongoDB tutorial, Create an Auto-Incrementing Sequence Field, you need to first create a separate counters collection to track the last number sequence used. The _id field contains the sequence name i.e. the userID field in the user collection and the seq field contains the last value of the sequence.
To start with, insert into the counters collection the initial value for the userID:
db.counter.insert(
{
"_id": "userID",
"seq": 0
}
)
Having populated the counters collection, generate its schema in Mongoose:
var counterSchema = mongoose.Schema({
"_id": { "type": String, "required": true },
"seq": { "type": Number, "default": 0 }
});
var counter = mongoose.model('counter', counterSchema);
Then redefine your user schema so that when you save a user model it first calls the counter model's findByIdAndUpdate() method to atomically increment the seq value and return this new value which can then be used as the next userID value:
var userSchema = mongoose.Schema({
"userID": { "type": String, "required": true },
"firstname": { "type": String },
"lastname": { "type": String },
// other properties ...
}, { "collection": "user" }
);
userSchema.pre("save", function (next) {
var doc = this;
counter.findByIdAndUpdate(
{ "_id": "userID" },
{ "$inc": { "seq": 1 } }
, function(error, counter) {
if(error) return next(error);
doc.userID = counter.seq.toString();
next();
});
});
If you want an autoincrement prop based on the length of your collection, you can do something like this:
UserSchema.pre("save", function (next) {
if (this.isNew) {
this.constructor.find({}).then((users) => {
this.autoIncrementProp = users.length + 1;
next();
});
}
});
isNew is a reserved Schema name (Boolean flag specifying if the document is new.)
Considering that the logic of schema.findByIdAndUpdate is "return the current value and THEN increment it", you can also use #chridam's solution with the following edit:
var counter = mongoose.model('counter', counterSchema);
userSchema.pre("save", function (next) {
var doc = this;
counter.findByIdAndUpdate(
{ "_id": "userID" },
{ "$inc": { "seq": 1 } }
, function(error, c /*counter*/) {
if(error)
return next(error);
else if(!c) {
c = new counter({ _id: "userID" }, { $inc: { seq: 1 } };
c.save(function() {
doc.userID = (c.seq - 1) + '';
next();
});
} else {
doc.userID = counter.seq.toString();
next();
}
});
});
Please, note that this solutions makes your code function from scratch WITHOUT forcing you to initialize the DB.
The tricks lies in the first round of the loop. counter is undefined, so you need to initialize it, but if you initialize it equal to 0, next tick the code tries to assign userID=0 again!
This is because on one hand there is the logic of schema.findByIdAndUpdate that reads-first the value and then increments it, on the other hand you need a logic that assigns-first the value though.
You can join these two logics by making the algorithm skip the first step and setting variable values as above.
Notes: counterSchema is the same as chridam's:
counterSchema = mongoose.Schema({
"_id": { "type": String, "required": true },
"seq": { "type": Number, "default": 0 }
});

Resources