DivergentArrayError in mongoose while updating array of referenced docs - node.js

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

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.

MongoDB update query in subarray

An update in the array of objects inside another array of objects.
mongodb field that I'm working on:
otherFields: values,
tasks: [
{
_id: mongodb.objectID(),
title: string,
items:[{
_id: mongodb.objectID(),
title: string,
completed: boolean //field need to be update.
}]
},
{}...
],
otherFields: value
sample mongodb document
I need to find the document using the task_id and the item_id and update a completed field in item of a task. Using the mongoose findOneAndUpdate method
const path = "tasks.$.items." + item_id + "completed";
collectionName.findOneAndUpdate(
{ _id: req.user._id, "tasks._id": taskID },
{ $set: { [path]: true }});
The above query doesn't work!!!
There is no need to use multiple query conditions, since you'd like to update a specific item that has an unique ID. Therefore you could use something along the lines:
collectionName.findOneAndUpdate(
{ 'tasks.items._id': itemID },
...
);
Keep in mind this structure is far away from optimized as it would basically look through the entire database...
Also now that I think of it, you'd also have issue with the update, as there are two nested arrays within the document. Read more here: How to Update Multiple Array Elements in mongodb

Mongoose findOneAndUpdate object inside a nested array

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)
}
})

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) {
});
})
});

$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