Mongoose findOneAndUpdate object inside a nested array - node.js

This is my schema
var tlSchema = new mongoose.Schema({
_id:String,
name:String,
cost:Number,
rows:Number,
spr:Number,
screen:[{
_id:String,
date:String,
time:String,
layout:[{
_id:String,
seatno:String,
booked:Boolean
}]
}]
})
var tl = mongoose.model("tl",tlSchema);
I want to update booked field inside of layout array inside of
screen array, this is my query
tl.findOneAndUpdate({ "screen._id":"5e9385432bdc30062e22694c","screen.layout._id":"5e9385432bdc30062e226974"},
{"$set":{"screen.0.layout.$.booked":false}}
,function(err,v){
if(err){console.log(err)}else{
console.log(v)
}
})
I'm not getting any error and also my data is not getting updated
please help

What you're trying to do is update a specific element in an array that is contained in a specific element of another array.
You can use arrayFilters to achieve what you want.
The options object here defines 2 filters, one for each array, and the $set in the update uses these filters to only affect matching elements from each array:
tl.findOneAndUpdate({ "screen._id":"5e9385432bdc30062e22694c","screen.layout._id":"5e9385432bdc30062e226974"},
{"$set":{"screen.$[screenFilter].layout.$[layoutFilter].booked":false}},
{"arrayFilters":[
{"screenFilter._id":"5e9385432bdc30062e22694c"},
{"layoutFilter._id":"5e9385432bdc30062e226974"}
]},
function(err,v){
if(err){console.log(err)}else{
console.log(v)
}
})

Related

How to add new fields to the last object in an array in MongoDB?

I have an Array of objects in mongoDB as follow
Initially there is only the heartRate field inside the object. Now I want to add new fields to this object along with the existing heartRate field.
Also there can be multiple objects inside the dailyReadings array. Therefore, I need to add new fields only to the last object using nodejs and expressjs
I tried using the $push method but ended up adding new object intead of adding the fields to the last object. Is there a way to achieve this? Thanks in advance!
Why I am doing this (For further understanding):-
I have developed a mobile app to read the heart rate. Initially it will save the heart rate in the database as a new object (As in the image). Then, there are several other data sent through a desktop application which needs to add to the same object (Which is the last object in the dailyReadings array)
There is no straight way to do this, you can try update with aggregation pipeline starting from MongoDB 4.2,
$size to get total elements in dailyReadings array
$subtract to minus 1 from above total elements
$slice to get elements other than the last object element
$slice to get last object element by -1 from dailyReadings
$arrayElemAt to select first object element from array
$mergeObjects to merge existing object fields of the last object and new fields that you want to insert
$concatArrays to concat first slice array and second updated object
db.collection.update(
{}, // put your query condition
[{
$set: {
dailyReadings: {
$concatArrays: [
{
$slice: [
"$dailyReadings",
0,
{ $subtract: [{ $size: "$dailyReadings" }, 1] }
]
},
[
{
$mergeObjects: [
{ $arrayElemAt: [{ $slice: ["$dailyReadings", -1] }, 0] },
{
newField: "1"
}
]
}
]
]
}
}
}]
)
Playground
In order for you to add fields to the last object, the heartRate should be an object with a schema containing the following
Array (for you to add to)
any other necessary data type you'd want the object to have
you must define a complex schema using mongoose, perform the following changes to your file of model
const mongoose = require('mongoose');
const childSchema = mongoose.Schema({
heartRate: {type: Array, required: true}
array: {type: Array, required: false}, //change the required parameter based on your requirement
});
const parentSchema = mongoose.Schema({
dailyReadings: {
type: childSchema,
required: false //change the required parameter based on your requirement
},
});
module.exports = mongoose.model('modelCollection', parentSchema);
So basically you need to define the child schema, and change the type of dailyReadings to that schema and add to the array of different objects.

mongoose model refuses to update array of objects inside array of objects

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.

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

DivergentArrayError in mongoose while updating array of referenced docs

I have a schema defined in mongoose as follow:
var VolunteerSchema = new Schema ({
......
other fields
.....
preferLocations:[{
type: Schema.ObjectId,
ref: 'Location'
}]
.....
});
I am using volunteer.save() method to update the model.
While updating to volunteer model i get the error as follow:
{ [DivergentArrayError: For your own good, using `document.save()` to update an
array which was selected using an $elemMatch projection OR populated using skip,
limit, query conditions, or exclusion of the _id field when the operation resul
ts in a $pop or $set of the entire array is not supported. The following path(s)
would have been modified unsafely:
preferLocations
Use Model.update() to update these arrays instead.]
message: 'For your own good, using `document.save()` to update an array which
was selected using an $elemMatch projection OR populated using skip, limit, quer
y conditions, or exclusion of the _id field when the operation results in a $pop
or $set of the entire array is not supported. The following path(s) would have
been modified unsafely:\n preferLocations\nUse Model.update() to update these a
rrays instead.',
name: 'DivergentArrayError' }
While updating the location I collect the _ids field in array and asigned to preferLocations as given below:
volunteer.preferLocations = locationIdsArray;
I don't get the error when I remove this line.What am I doing wrong?
When using $elemMatch in a projection, do not use document.save(). Instead, manually update your document using Model.update(). In your case you should try
volunteer.findOneAndUpdate(
{
_id: ObjectId("567452bae5b25d6e6c1a0f7e"),
localization: { '$elemMatch': { language: 'de' } }
},
{
$set: { 'localization.$.name' : "Neuer Name" }
}).exec(//...
});
click here more details

Mongoose $push cannot push object into correct document

I have a mongoose schema like this:
A = {
_id: Schema.Types.ObjectId,
arrayA:[{
_id,
nestedArray: [Schema.Types.ObjectId]
}],
arrayB: [Schema.Types.ObjectId]
}
I would like to push an Object Id into nestedArray in specific arrayA object AND
arrayB should contains an specific Object Id by following code:
A.update({'arrayA._id': arrayAId, arrayB: {$in: [arrayContainsSomeArrayBIds]}},
{$push: {'arrayA.$.nestedArray': nestedArrayId}}, function(err) {
});
However, the Object Id is pushed into nestedArray of the last object in arrayA.
If arrayB: {$in: [arrayContainsSomeArrayBIds]} is removed, the Object Id can be pushed into correct object in arrayA.
mongoose version: 3.8.21
Can anyone help me to find out the problem?
Currently it is not possible in MongoDB to update an a array element with the positional operator, when the query document contains references to other arrays apart from the one being updated.
The below code, contains reference to two arrays fields: arrayA and arrayB, when
the update is issued on arrayA. This is invalid and would lead to undesired behavior.
A.update({'arrayA._id': arrayAId, arrayB: {$in: [arrayContainsSomeArrayBIds]}},
{$push: {'arrayA.$.nestedArray': nestedArrayId}}, function(err) {
});
From the docs,
Only one array field may appear in the query document.
The query document should only contain a single condition on the array field
being projected.
Multiple conditions may override each other
internally and lead to undefined behavior.
Under these requirements,
the following query is incorrect:
db.collection.find( { <array>: <value>, <someOtherArray>: <value2> },
{ "<array>.$": 1 } )
The solution is to modify your code to fire two queries:
Get the _ids of the documents, which match our condition.
Then perform the update.
Sample Code flow:
A.find({'arrayA._id': arrayAId, arrayB: {$in: [arrayContainsSomeArrayBIds]}},
function(err,data){
data.forEach(function(doc){
A.update({'arrayA._id': arrayAId,
"_id":doc._id},
{$push: {'arrayA.$.nestedArray': nestedArrayId}},
function(err) {
});
})
});

Resources