mongoose model refuses to update array of objects inside array of objects - node.js

I've a schema for a store inventory. The location has locationcategories array in the schema and inside that array, I have items[] array.
My issue is updating this items[] inside the locationcategories[]. I use the mongodb shell to update that array (using updateOne()) and it works (update my items[] inside locationcategories[]) but when I do it using mongoose model ("Locations") it doesn't update, in my console, it just shows { n: 1, nModified: 0, ok: 1 } which indicate that everything was found but didn't modify anything. When I check the db, it doesn't have any new items added.
Here is code I use in mongoshell which updates items[] inside locationcategories[]
db.locations.updateOne({"locationname" :"My Town", "locationcategories":{"$elemMatch":{"categoryname": "Media"}}},{$push:{"locationcategories.$.items": {"test":"test"}}})
In my application server route (expressjs) I enter the following:
Location.updateOne(
{ "locationname": "My Town", "locationcategories": {"$elemMatch":{"categoryname": "Media"}}},
{$push: {"locationcategories.$.items": {"test":"test"}}},
{new : true, upsert: true },
function (error, results) {
console.log("findByIdAndUpdate results :", results);
}
);
my mongooose schema-model:
var Schema = mongoose.Schema;
var locationSchema = new Schema({
locationname: String,
locationdescription: String,
locationcategories: [{ categoryorder: Number, categoryname: String,
categorydescription: String, items[{itemname: String }]}],
items: [{categoryid : String, itemorder: Number, itemname: String, itemdescription: String, itemprice: Number }],
created_at: Date
});
var Location = mongoose.model('Location', locationSchema);
export default Location;
I'm using mognodb version 4.02
Model.updateOne should work but it is not adding anything to my db. I read many of the stackoverflow.com issues but I couldn't find anything that indicate an issue except this old question from years ago which he had an issue updating array inside array. I find it hard to believe that there is an issue and no one addressed it after all those years but I might be wrong.
If the mongo shell execute and add to db correctly, it leads me to believe that the mongoose model or schema are responsible for the issue or my updateOne() code is wrong.
Any idea why?

Got it. So I changed items inside the locationcategories[] to categoryItems[] and it works.
The .$ operator looks for the first array with the name items which was items outside but I was posting the data to the model's first array.
Make sure you name the arrays in your model with unique names and point the $push to the correct array.
Also upgrded mongodb from version 4.0 to the latest one 4.41.

Related

How to insert Array of objects in mongoDB?

I am very new to MONGO DB so please bear with me.I am having a problem my array of objects is not working properly .
Here is my schema
const playerSchema = new mongoose.Schema({
name: String,
stats :{
wins:Number,
losses:Number,
xp:Number
},
achievement:[
{
name:String,
date: String
}
] });
Here is my document
const fluffy = new playerModel({
"name":"nic raboy",
"stats":{
"wins":5,
"losses":10,
"xp":300
},
"achievements":[
{"name":"Massive XP","date" :"25-08-21"},
{"name":"instant loss","date":"24-08-21"}
]
});
however in mongodb atlas its only showing array...and i cant see the objects inside...
SCREENSHOT
Your schema is correct, it seems your input is wrong,
In schema definition you named it achievement, whereas in input document it is achievements. Correct this everything will work as you expected.
Explanation
The schema is expecting achievement and you inserted achievements, that is why it is shown as an empty array in the database. To avoids this kind of typos in the future, use the required flag.
const playerSchema = new mongoose.Schema({
name: String,
stats: {
wins: Number,
losses: Number,
xp: Number
},
achievements: [
{
name: {
type: String,
required : true,
},
date: {
type: String,
required : true, // required informs for missing fields
}
}
]
})
Refer this link for more on validation
You can use insertMany see the doc here.
Of course, a while loop should work find calling multiple times insertOne, though I advise you to use the insertMany() method.
If you're new to MongoDB, I strongly encourage you to have a look at MongoDB University's MongoDB basics course as well as the MongoDB for JavaScript Developers course.

Non-existing field in Mongodb document appears in mongoose findById() result

I'm somewhat new in what is related to Mongoose and I came to this behaviour I consider as strange. The document returned by Mongoose has fields that are not present in the actual MongoDb document, and seem to be added by Mongoose based on the schema.
I use a schema similar to this (this one is simplified) :
const ProfessionalSchema = new mongoose.Schema({
product: {
details: [{
_id: false,
id: String, // UUID
name: String,
prestations: [{
_id: false,
id: String, // UUID
name: String,
price: Number,
}],
}],
},
[...]
My document as shown in Mongodb with mongo CLI utility doesn't have a product field.
What I don't understand is why the result of Professional.findById().exec() returns a document with a product:{details[]} field. I expect not to have that field in the Mongoose returned result, since it is not present in the original MongoDb document.
The Mongoose documentation found https://mongoosejs.com/docs/guide.html (Schema and Model paragraph) didn't help.
My business logic would require that field not to be present, instead of being forced by the schema. Is this achievable ?
Try taking a look at the default option. You could e.g. default your product to null and then, in your business logic, handle the "product is null" case rather than the "product field does not exist" case.
As for why this is happening, it's because you're dealing with a schema. If the field doesn't exist on the document, it's going to be auto-populated. The whole point of a schema is to ensure consistency of your document structure.

Mongoose - get length of array in model

I have this Mongoose schema:
var postSchema = mongoose.Schema({
postId: {
type: Number,
unique: true
},
upvotes: [
{
type: Number,
unique: true
}
]
});
what the best query to use to get the length of the upvotes array? I don't believe I need to use aggregation because I only want to query for one model, just need the length of the upvotes array for a given model.
Really struggling to find this info online - everything I search for mentions the aggregation methodology which I don't believe I need.
Also, as a side note, the unique schema property of the upvotes array doesn't work, perhaps I am doing that wrong.
find results can only include content from the docs themselves1, while aggregate can project new values that are derived from the doc's content (like an array's length). That's why you need to use aggregate for this, even though you're getting just a single doc.
Post.aggregate([{$match: {postId: 5}}, {$project: {upvotes: {$size: '$upvotes'}}}])
1Single exception is the $meta projection operator to project a $text query result's score.
I'm not normally a fan of caching values, but it might be an option (and after finding this stackoverflow answer is what I'm going to do for my use case) to calculate the length of the field when the record is updated in the pre('validate') hook. For example:
var schema = new mongoose.Schema({
name: String,
upvoteCount: Number,
upvotes: [{}]
});
schema.pre('validate', function (next) {
this.upvoteCount = this.upvotes.length
next();
});
Just note that you need to do your updates the mongoose way by loading the object using find and then saving changes using object.save() - don't use findOneAndUpdate
postSchema.virtual('upvoteCount').get(function () {
return this.upvotes.length
});
let doc = await Post.findById('foobar123')
doc.upvoteCount // length of upvotes
My suggestion would be to pull the entire upvotes fields data and use .length property of returned array in node.js code
//logic only, not a functional code
post.find( filterexpression, {upvote: 1}, function(err, res){
console.log(res.upvotes.length);
});
EDIT:
Other way of doing would be stored Javascript. You can query the
upvote and count the same in mongodb side stored Javascript using
.length

Mongoose searching on array subdocument ObjectId not working

This is honestly driving me crazy. Here's the problem:
I'm running the following query in Mongoose:
s.findSubdocument=function(uid, cb)
{
this.model('User').find({"flwng._id":uid.toString()}).select('flwng').exec(cb);
}
on the following User Schema:
var userSchema= new mongoose.Schema(
{
uname:{type:String, index:{sparse:true, unique:true, dropDups:true}}, //the username
email:String,
pwd:{type:String},
flwng:[{_id : {type : mongoose.Schema.ObjectId},uname : String}], //_id of users I am following
flwrsz:Number,
flwngsz:Number,
feedsz:Number,
}, {j: 1});//j:1 means guarantee it is written to the journal.
userSchema.set('autoIndex', true);
userSchema.index({"fid":1}, {sparse:true, unique:true, dropDups:true});
userSchema.index({"flwng._id" : 1});
Where uid="53c4f16c431247694f0000a3" ==> but I get an empty array back :(
When I run the same exact query in the mongo shell:
db.users.find({"flwng._id":"53c4f16c431247694f0000a3"});
I get the right set of results back. I tried with and without an index and schema on "flwng._id", I tried to drop the index, reIndex and I'm now running out of ideas. Am I doing something wrong with Mongoose?
Any help would be appreciated - thanks!
Henri
There's a mismatch between your existing documents in mongodb and your schema. Your data has records where flwng._id is a String, which is why you get results in the mongo shell. But your mongoose schema defines that as an ObjectId, so when you query with mongoose, mongoose casts your string value to an ObjectId and the query doesn't match. Either write a migration to fix your existing data or update your schema to match your data in terms of String vs ObjectId data type and things should start working through mongoose.

$unset is not working in mongoose

My schema is as follows : -
var Schema1 = new mongoose.Schema({
name: String,
description: String,
version: [{
id: String,
status: Number
}]
});
I want to unset the version field. I try the following code :-
Schema1.update({}, {
$unset: {
version: 1
}
}, {
multi: true
}).exec(function(err, count) {
console.log(err, count)
});
It gives me the following output :-
null 10
But the output contain the version field :-
{
name : 'a',
description : 'sdmhf',
version : []
}
The above code remove the data but I want to remove the version field from my collection as well. Can you tell me how to do that?
There's nothing wrong with your code. Mongoose is actually deleting those fields in the documents (which I assume is what you expected). You can see by opening a mongo shell into your database and searching all your documents before and after the update (use db.yourcollection.find({}))
Why does an empty array still appear even when it's removed from every document in the collection? Mongoose will ensure the documents returned will obey the schema that you define. So even if Mongoose finds no version property pointing to an Array in the actual document, it will still present an empty array when the matching documents are returned.
You can verify this yourself by adding some arbitrary property (pointing to an Array) to your schema and running a .find({}) again. You'll see that Mongoose will return these properties in every document even though you never saved them to the database. Similarly, if you add non-Array properties like Strings, Booleans, etc, Mongoose will return those as long as you specify a default value.
If you want to drop version for good (as you mentioned in your comment) you can drop it from your Mongoose schema after you've completed the $unset.
This worked for me
doc.field = undefined
await doc.save()

Resources