Mongoose: Update/Insert in nested subdocument - node.js

I've seen quite a few examples here in stackoverflow, but none of them have been useful to me. Maybe my scheme is wrong?
I need to insert new records into a nested document using Mongoose (I would like to add within the "history" array). If the document already exists, I must only update it, if it does not exist, a new document must be added. I have the following scheme:
let equipment_json = {
controls: [{
_id: con.Schema.Types.ObjectId,
name: String,
history: [{
_id: con.Schema.Types.ObjectId,
new: Boolean,
}]
}]
};
let equipment_schema = new con.Schema(equipment_json);
let Equipment = con.mongoose.model('Equipment', Equipment_schema);
This code should perform the update:
Equipment.update({
'_id': object_equipment.id_equipment,
'controls._id': object_equipment.id_control_type
},{
$set: {
'controls.$.history.$': {
new: true
}
}
},
{
upsert: true, setDefaultsOnInsert: true
},
function (err, doc ) {
console.log( doc );
})
Before using update() I used find() to check what it finds according to the criteria. Using find(), it returns the document, however, when I want to use update() it does not add to the array "controls", the "new": "true". I tried as much with $set as with $push.

It's only necessary to modify the following code:
'controls.$.history.$': {
new: true
}
by
'controls.$.history': {
new: true
}

Related

using updateOne inside a map, setting the filter to the unique id , $set is to the document and upsert is true. Even then new document is created

here is my code in the controller from where i am getting the records from my google calendar API and then passing that data to this function and the code inside the function which inserts the document (records) looks like this as below:
Holiday.bulkWrite(
holidays.map((holiday) => ({
updateOne: {
filter: { holidayId: holiday.id },
update: { $set: holiday },
upsert: true,
},
}))
)
It's hard to tell what exactly the issue is because it is not Mongo related but code related, from what it seems you are just using the wrong field for the filter.
holiday.id is null, and we can see that the "inserted" documents do not have such field. You are basically executing the following update:
db.collection.update({
holidayId: null
},
{
"$set": {
holidayId: "123"
... other fields
}
},
{
"upsert": true
})
I believe this simple fix would solve your issue, change .id To .holidayId:
Holiday.bulkWrite(
holidays.map((holiday) => ({
updateOne: {
filter: { holidayId: holiday.holidayId },
update: { $set: holiday },
upsert: true,
},
}))
)

In Mongo db I want to add an entry to a document. If the id already exists it should overwrite it, if not it should add it

I use Mongo DB and node.js. Thanks for any help :).
I want to do the following:
Search for a user in the db using the googleId.
Then if the user does not have a description field yet I want to create it. Within the description field, I want to have several objects that all have the coinId as an index.
If the coinId already exists it should overwrite the content of that particular object with the variables I pass. If it does not already exist it should add the new object to the description field.
The Mongo db document should look like this in the end. Note that 1027 or 123 are the values of the coinIds.
googleId: "PyovWaX8HERRACmeg4IzYCaMK833"
description:
1027:
_id: "ckpi7q8c60002qe9h0e4wgh3r"
coinId: 1027
description: "test1"
date: 2021-06-04T10:56:52.662+00:00
123:
_id: "woienfeiowfnaoewinfefneo"
coinId: 123
description: "test2"
date: 2021-06-04T10:56:52.662+00:00
I already tried the following:
const { result } = await db.collection('users').updateOne(
{ googleId },
{
$set: {
description: { [coinId]: { _id, coinId, description, date } },
},
},
{
returnOriginal: false,
}
);
The problem here is that the entry is always overwritten which I only want to happen if the coinId is the same.
I also tried to do it with $addToSet but then it never overwrites the object if the coinId's match.
How about findAndModify?
findAndModify
In your case, something like this:
db.collection('users').findAndModify({
query: { _id: googleId },
update: {
$setOnInsert: {
description: { [coinId]: { _id, coinId, description, date } },
}
},
new: true, // return new doc if one is upserted
upsert: true // insert the document if it does not exist
})
db.collection.findAndModify({
query: { _id: "some potentially existing id" },
update: {
$setOnInsert: { foo: "bar" }
},
new: true, // return new doc if one is upserted
upsert: true // insert the document if it does not exist
})

How to upsert a Hashmap into MongoDB Document?

I'm trying to structure a friendslist using MongoDB.
This is how I currently have it structured:
{
"_id": {
"$oid": "601c570da04b75fabcd2705d"
},
"user_id": 1,
"friendslist": {
3 : true
}
}
How can I create a query that does a put on my Hashmap (The hashmap is "friendslist") and the key is 3 for the friend's id and the true flag is for the status of the pending friend request. (True meaing they are friends and false would be pending friend request)
This is what I currently have:
const upsertFriendId = db.collection('friends')
.updateOne(
{ user_id: userId },
{ $set: { ???? : ????} },
{ upsert: true }
);
I can't figure out the syntax that I need to put in the $set object.
In-order to ensure that you do not override the rest of the keys inside the friendlist, you can do an update like
const mongoFriendKey = `friendlist.${/* FRIEND_ID_VARIABLE_GOES_HERE */}`;
const upsertFriendId = db.collection('friends')
.updateOne(
{
user_id: userId
},
{
$set:
{
[mongoFriendKey] : true
}
},
{ upsert: true }
);
In the above, replace the /* FRIEND_ID_VARIABLE_GOES_HERE */ with the friend id variable. Doing that, the query will look for a friend with that specific Id, and then it will try to set it to true if not found, without overriding the entire friendlist object of yours.
Also, I hope you know what upsert does, if the update won't find the doc, then it will end up inserting one.

findOneAndUpdate return null upon insertion?

What I want is:
return 1 if insertion succeeds even though the document doesn't exist before.
return 1 if update succeeds
But I can't seem to achieve this with findOneAndUpdate which only returns a result if the document exists and is successfully updated.
My query:
User.findOneAndUpdate(
{ email: email },
{ $set: { verified: 1 } },
{ upsert: true }
).exec(callback);
You could access the native driver to call the underlying collection's updateOne() or updateMany() method which will return the full response from Mongo in the updateWriteOpCallback callback as a updateWriteOpResult object that you can use. For example
User.collection.updateOne(
{ "email": email },
{ "$set": { "verified": 1 } },
{ "upsert": true },
function(err, result) {
// The number of documents that matched the filter.
console.log(result.matchedCount);
// The number of documents that were modified.
console.log(result.modifiedCount);
// The number of documents upserted.
console.log(result.upsertedCount);
// The total count of documents scanned.
console.log(result.result.n);
// The total count of documents modified.
console.log(result.result.nModified);
}
);
In this case you most probably need to check the result.result.nModified and result.upsertedCount properties to make the above call.
Assuming you want the result to be the updated document, you need to modify your query to look like this:
User.findOneAndUpdate(
{ email: email },
{ $set: { verified: 1 } },
{ upsert: true, returnNewDocument: true }
).exec(callback);
The returnNewDocument property will make the callback return the updated document instead of the original. In the case of an upsert on a non-existing document, it will return the new document instead of an empty set.

mongoose subdocument sorting

I have an article schema that has a subdocument comments which contains all the comments i got for this particular article.
What i want to do is select an article by id, populate its author field and also the author field in comments. Then sort the comments subdocument by date.
the article schema:
var articleSchema = new Schema({
title: { type: String, default: '', trim: true },
body: { type: String, default: '', trim: true },
author: { type: Schema.ObjectId, ref: 'User' },
comments: [{
body: { type: String, default: '' },
author: { type: Schema.ObjectId, ref: 'User' },
created_at: { type : Date, default : Date.now, get: getCreatedAtDate }
}],
tags: { type: [], get: getTags, set: setTags },
image: {
cdnUri: String,
files: []
},
created_at: { type : Date, default : Date.now, get: getCreatedAtDate }
});
static method on article schema: (i would love to sort the comments here, can i do that?)
load: function (id, cb) {
this.findOne({ _id: id })
.populate('author', 'email profile')
.populate('comments.author')
.exec(cb);
},
I have to sort it elsewhere:
exports.load = function (req, res, next, id) {
var User = require('../models/User');
Article.load(id, function (err, article) {
var sorted = article.toObject({ getters: true });
sorted.comments = _.sortBy(sorted.comments, 'created_at').reverse();
req.article = sorted;
next();
});
};
I call toObject to convert the document to javascript object, i can keep my getters / virtuals, but what about methods??
Anyways, i do the sorting logic on the plain object and done.
I am quite sure there is a lot better way of doing this, please let me know.
I could have written this out as a few things, but on consideration "getting the mongoose objects back" seems to be the main consideration.
So there are various things you "could" do. But since you are "populating references" into an Object and then wanting to alter the order of objects in an array there really is only one way to fix this once and for all.
Fix the data in order as you create it
If you want your "comments" array sorted by the date they are "created_at" this even breaks down into multiple possibilities:
It "should" have been added to in "insertion" order, so the "latest" is last as you note, but you can also "modify" this in recent ( past couple of years now ) versions of MongoDB with $position as a modifier to $push :
Article.update(
{ "_id": articleId },
{
"$push": { "comments": { "$each": [newComment], "$position": 0 } }
},
function(err,result) {
// other work in here
}
);
This "prepends" the array element to the existing array at the "first" (0) index so it is always at the front.
Failing using "positional" updates for logical reasons or just where you "want to be sure", then there has been around for an even "longer" time the $sort modifier to $push :
Article.update(
{ "_id": articleId },
{
"$push": {
"comments": {
"$each": [newComment],
"$sort": { "$created_at": -1 }
}
}
},
function(err,result) {
// other work in here
}
);
And that will "sort" on the property of the array elements documents that contains the specified value on each modification. You can even do:
Article.update(
{ },
{
"$push": {
"comments": {
"$each": [],
"$sort": { "$created_at": -1 }
}
}
},
{ "multi": true },
function(err,result) {
// other work in here
}
);
And that will sort every "comments" array in your entire collection by the specified field in one hit.
Other solutions are possible using either .aggregate() to sort the array and/or "re-casting" to mongoose objects after you have done that operation or after doing your own .sort() on the plain object.
Both of these really involve creating a separate model object and "schema" with the embedded items including the "referenced" information. So you could work upon those lines, but it seems to be unnecessary overhead when you could just sort the data to you "most needed" means in the first place.
The alternate is to make sure that fields like "virtuals" always "serialize" into an object format with .toObject() on call and just live with the fact that all the methods are gone now and work with the properties as presented.
The last is a "sane" approach, but if what you typically use is "created_at" order, then it makes much more sense to "store" your data that way with every operation so when you "retrieve" it, it stays in the order that you are going to use.
You could also use JavaScript's native Array sort method after you've retrieved and populated the results:
// Convert the mongoose doc into a 'vanilla' Array:
const articles = yourArticleDocs.toObject();
articles.comments.sort((a, b) => {
const aDate = new Date(a.updated_at);
const bDate = new Date(b.updated_at);
if (aDate < bDate) return -1;
if (aDate > bDate) return 1;
return 0;
});
As of the current release of MongoDB you must sort the array after database retrieval. But this is easy to do in one line using _.sortBy() from Lodash.
https://lodash.com/docs/4.17.15#sortBy
comments = _.sortBy(sorted.comments, 'created_at').reverse();

Resources