mongoose overwrite field on update - node.js

I have a problem trying to update a user with a new profile, mongoose overwrites the previous ones, so it is only allowing them to have 1 profile.
I have in a user document with its associated profiles, buyer or seller with some data of each profile such as id and reputation.
At the beginning the user has no profiles, when I add his buyer or seller profile, there is no drama, but when I try to add the other profile, he writes the one he was on.
At this moment I have this to add the profile (the seller is the same, just change buyer for seller):
const user = await User.findOne({_id: 'userId'});
const updatedUser = await user.updateOne({
profiles: {
buyer: {
_id: buyerId,
reputation: 50
}
}
});
What he does is write to me the one who is already there, and I understand that I am stupidly telling him that by passing that object, but I tried anyway.
I was checking and I saw that they put a $ set, so I tried it like this:
const user = await User.findOne({_id: 'userId'});
const updatedUser = await user.updateOne({
profiles: {
$set: {
buyer: {
_id: buyerId,
reputation: 50
}
}
}
});
that is not supposed to write me over, and only add the field if it does not exist and leave the other there, quiet (it is what I want).
But the only thing I achieved was that I was writing profiles with an empty object, without any profile.
What I am looking for is that the user is left with the profiles placed there, in profiles, like this:
profiles: {
buyer: {
_id: 'fuckinid1',
reputation: 50
},
seller: {
_id: 'fuckinid2',
reputation: 50
},
}
my schema is this:
const schema = new Schema({
_id: { type: String },
name : { type: String },
surname : { type: String },
email : { type: String, lowercase: true },
profiles : {
buyer: {
_id : String,
reputation: Number
},
seller: {
_id : String,
reputation: Number
}
}
});
How can i make this?

You should try like below:
let updateRecord = Test.findOneAndUpdate(
{ _id: test._id },
{ name: 'Test name' },
{ new: true, overwrite: true }
);

As for the more, you should check out the mongoose docs for findOneAndUpdate options. I also hardly recommend you to use findByIdAndUpdate because even if you need to check document on existence, it won't be overwrited without the correct options. And instead, it will create new, if it isn't exist. Also MongoDB by default creates an index for _id field, so your queries will be executed much faster with index, then w/o them.
Also, I noticed that you are using embedded document structure. Well, it's always for you to decide, but I don't sure that you really needed that _id field for sub-documents. Anyway, if you need more info about it, just tag me in the comment below, and I'll try to provide more information about it.

Related

MongoDB: add object to subarray if not exists

I searched many questions here and other articles on the web, but they all seem to describe somehow different cases from what I have at hand.
I have User schema:
{
username: { type: String },
lessons: [
{
lesson: { type: String },
result: { type: String }
}
]
}
I want to add new element into lessons or skip, if there is already one with same values, therefore I use addToSet:
const dbUser = await User.findOne({ username })
dbUser.lessons.addToSet({ lesson, result: JSON.stringify(result) })
await dbUser.save()
However it makes what seems to be duplicates:
// first run
[
{
_id: 60c80418f2bcfe5fb8f501c1,
lesson: '60c79d81cf1f57221c05fdac',
result: '{"correct":2,"total":2}'
}
]
// second run
[
{
_id: 60c80418f2bcfe5fb8f501c1,
lesson: '60c79d81cf1f57221c05fdac',
result: '{"correct":2,"total":2}'
},
{
_id: 60c80470f2bcfe5fb8f501c2,
lesson: '60c79d81cf1f57221c05fdac',
result: '{"correct":2,"total":2}'
}
]
At this point I see that it adds _id and thus treats them as different entries (while they are identical).
What is my mistake and what should I do in order to fix it? I can change lessons structure or change query - whatever is easier to implement.
You can create sub-documents avoid _id. Just add _id: false to your subdocument declaration.
const userSchema = new Schema({
username: { type: String },
lessons: [
{
_id: false,
lesson: { type: String },
result: { type: String }
}
]
});
This will prevent the creation of an _id field in your subdoc, and you can add a new element to the lesson or skip it with the addToSet operator as you did.

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

Mongoose - query doc if element not in array

I am dealing with an issue while querying a notification schema
receiver: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Profile' }],
readBy: [{
readerId: { type: mongoose.Schema.Types.ObjectId,
ref: 'Profile', default: [] },
readAt: { type: Date }
}]
In order to query latest notifications, this is the query I have written:
GOAL is to check if the "profile.id" DOES NOT exist in the readBy array (which means its unread by that user)
const notifications = await Notification.find({
receiver: profile.id, // this works
readBy: { // but, adding this returns an empty array
$elemMatch: {
readerId: { $ne: profile.id }
}
}
})
Would really appreciate the help, stuck here for days.
I think is easier than we are trying to do.
If you want to know if the element is into the array, then, query looking for it. If the query is empty, it implies that not exists, otherwise yes.
Look this example.
Is a simple query, only check if one document has been received and readed by user profile.id.
db.collection.find([
{
"receiver": profile.id,
"readBy.readerId": profile.id
}
])
Please check if the output is as expected or I misunderstood your petition.
In mongoose you can use this:
var find = await model.find({"receiver":1,"readBy.readerId":1}).countDocuments()
It will return how many documents match the query.
Edit to get documents where readerId is not present in the array:
Here, you only need to add $ne operator
db.collection.find([
{
"receiver": profile.id,
"readBy.readerId": {
"$ne": profile.id
}
}
])
You can check this here

Conditional update, depending on field matched

Say I have a collection of documents, each one managing a discussion between a teacher and a student:
{
_id,
teacherId,
studentId,
teacherLastMessage,
studentLastMessage
}
I will get queries with 3 parameters: an _id, a userId and a message.
I'm looking for a way to update the teacherLastMessage field or studentLastMessage field depending on which one the user is.
At the moment, I have this:
return Promise.all([
// if user is teacher, set teacherLastMessage
db.collection('discussions').findOneAndUpdate({
teacherId: userId,
_id
}, {
$set: {
teacherLastMessage: message
}
}, {
returnOriginal: false
}),
// if user is student, set studentLastMessage
db.collection('discussions').findOneAndUpdate({
studentId: userId,
_id
}, {
$set: {
studentLastMessage: message
}
}, {
returnOriginal: false
})
]).then((results) => {
results = results.filter((result) => result.value);
if (!results.length) {
throw new Error('No matching document');
}
return results[0].value;
});
Is there a way to tell mongo to make a conditional update, based on the field matched? Something like this:
db.collection('discussions').findOneAndUpdate({
$or: [{
teacherId: userId
}, {
studentId: userId
}],
_id
}, {
$set: {
// if field matched was studentId, set studentLastMessage
// if field matched was teacherId, set teacherLastMessage
}
});
Surely it must be possible with mongo 3.2?
What you want would require referencing other fields inside of $set. This is currently impossible. Refer to this ticket as an example.
First of all, your current approach with two update queries looks just fine to me. You can continue using that, just make sure that you have the right indexes in place. Namely, to get the best performance for these updates, you should have two compound indexes:
{ _id: 1, teacherId: 1 }
{ _id: 1, studentId: 1 }.
To look at this from another perspective, you should probably restructure your data. For example:
{
_id: '...',
users: [
{
userId: '...',
userType: 'student',
lastMessage: 'lorem ipsum'
},
{
userId: '...',
userType: 'teacher',
lastMessage: 'dolor sit amet'
}
]
}
This would allow you to perform your update with a single query.
Your data structure is a bit weird, unless you have a specific business case which requires the data the be molded that way i would suggest creating a usertype unless a user can both be a teacher and a student then keep your structure.
The $set{} param can take a object, my suggestion is to do your business logic prior. You should already know prior to your update if the update is going to be for a teacher or student - some sort of variable should be set / authentication level to distinguish teachers from students. Perhaps on a successful login in the callback you could set a cookie/local storage. Regardless - if you have the current type of user, then you could build your object earlier, so make an object literal with the properties you need based on the user type.
So
if(student)
{
var updateObj = { studentLastMsg: msg }
}
else
{
var updateObj = { teacherLastMsg: msg }
}
Then pass in your update for the $set{updateObj} I'll make this a snippet - on mobile

How to insert data into subdocument using $push in query, instead of retrieving doc and saving it back

Edit: this was actually working
As the Mongoose - Subdocs: "Adding subdocs" documentation says, we can add a subdoc using the push method (i.e. parent.children.push({ name: 'Liesl' });)
But I want to go further, and would like to use the $push operator to insert subdocuments.
I have two Schemas: the ThingSchema:
var ThingSchema = mongoose.Schema({
name: {
type: String,
required: true
},
description: {
type: String
}
});
and the BoxSchema, the main document that has an array of subdocuments (things) of ThingSchema:
var BoxSchema = new mongoose.Schema({
name: {
type: String,
required: true
},
description: {
type: String
},
things: {
type: [ThingSchema]
}
});
var BoxModel = mongoose.model('Box', BoxSchema);
I need every subdocument in things to have unique names - that is, that it would be impossible to insert a new document into this array that has a name value that already exists in the subdocs.
I'm trying to do something like:
var thingObj = ... // the 'thing' object to be inserted
BoxModel.update({
_id: some_box_id, // a valid 'box' ObjectId
"things.name": { "$ne": thingObj.name }
},
{
$push: { things: thingObj}
},
function(err) {
if (err) // handle err
...
});
but not getting any desired results.
What would be the correct way to add a ThingSchema subdocument into BoxSchema's thing array using the $push operator to do so in the query (must not add the subdoc if there's another subdoc named the same), instead of the Mongoose Docs way?
Edit: this is actually the issue
I made a mistake, the code above works as expected but now the problem I have is that when thingObj does not match the ThingSchema, an empty object is inserted into the things array:
// now thingObj is trash
var thingObj = { some: "trash", more: "trash" };
When executing the query given the above trash object, the following empty object is inserted into the subdocs array:
{ _id: ObjectId("an_obj_id") }
What I want this case, when the thingObj doesn't match the ThingSchema, is nothing to be added.
$addToSet adds something unique to the array (as in it checks for duplicates). But it only works for primitives.
What you should do is put things into their own collection and make a unique index on name. Then, make this change
things: {
type: [{type: ObjectId, ref: 'thingscollection'}]
}
this way you can do
BoxModel.update({
_id: some_box_id, // a valid 'box' ObjectId
"things": { "$ne": thingObj._id }
},
{
$addToSet: { things: thingObj._id}
},
function(err) {
if (err) // handle err
...
});
And when you fetch use .populate on things to get the full documents in there.
It's not exactly how you want it, but that's a design that might achieve what you're aiming for.

Resources