Mongo / Mongoose - Push object into document property array on condition - javascript

I have a document stored on mongo that, amongst others, contains a property type of an array with a bunch of objects inside. I am trying to push an object to that array. However each object that gets pushed to into the array contains a date object. How I need it to work is if an object with that date already exists then dont push the new object into the array.
I'm been playing with it for a while, trying to run update and elemMatch queries etc, but I can't figure it out. I have a long draw out version below, as you can see I'm making 2 separate requests to the database, can someone help me reduce this to something simpler?
UserModel.findById(req.user._id, (err, doc) => {
if (err) {
return res.sendStatus(401);
}
const dateExists = doc.entries.find((entry) => entry.date === date);
if (dateExists) {
res.sendStatus(401);
} else {
UserModel.findByIdAndUpdate(
req.user._id,
{
$push: {
entries: {
date,
memo,
},
},
},
{ safe: true, upsert: true, new: true },
(err, doc) => (err ? res.sendStatus(401) : res.sendStatus(200))
);
}
});
Thanks.

db.users.updateOne({
_id: req.user._id,
"entries.date": date,
},
{
$push: {
entries: {date,memo},
}
})
https://docs.mongodb.com/manual/reference/method/db.collection.updateOne/index.html

Related

How can I add to this schema array with mongoose?

Here's the user schema and the part I want to update is ToDo under User.js (further down). I am attempting to add new data to an array within the db.
data.js
app.post("/data", loggedIn, async (req, res) => {
console.log(req.body.content);
let content = { content: req.body.content };
User.update({ _id: req.user._id }, { $set: req.body }, function (err, user) {
if (err) console.log(err);
if (!content) {
req.flash("error", "One or more fields are empty");
return res.redirect("/");
}
user.ToDo.push(content);
res.redirect("/main");
});
});
User.js
new mongoose.Schema({
email: String,
passwordHash: String,
ToDo: {
type: [],
},
date: {
type: Date,
default: Date.now,
},
})
Originally I was trying the .push() attribute, but I get the error:
user.ToDo.push(content);
^
TypeError: Cannot read property 'push' of undefined
First of all, your problem is the callback is not the user. When you use update the callback is something like this:
{ n: 1, nModified: 1, ok: 1 }
This is why the error is thrown.
Also I recommend specify the array value, something like this:
ToDo: {
type: [String],
}
The second recommendation is to do all you can into mongo query. If you can use a query to push the object, do this instead of store the object into memory, push using JS function and save again the object into DB.
Of course you can do that, but I think is worse.
Now, knowing this, if you only want to add a value into an array, try this query:
var update = await model.updateOne({
"email": "email"
},
{
"$push": {
"ToDo": "new value"
}
})
Check the example here
You are using $set to your object, so you are creating a new object with new values.
Check here how $set works.
If fields no exists, will be added, otherwise are updated. If you only want to add an element into an array from a specified field, you should $push into the field.
Following your code, maybe you wanted to do something similar to this:
model.findOne({ "email": "email" }, async function (err, user) {
//Here, user is the object user
user.ToDo.push("value")
user.save()
})
As I said before, that works, but is better do in a query.

Mongoose : Add an element to an array of Objects under an Object field

I'm trying to add an element to an array of Objects under an Object field
This in my mongoose "Utilisateur" schema:
So what i'm trying to do here is to add an element to the "lien_vers" array
this is the code that i wrote
Utilisateur.findOneAndUpdate({ _id: mongoose.Types.ObjectId(auteur._id) },
{
$push: {
'relations.lien_vers': {
metadonnees: { nom_societe: societePersonalId },
identite: auteur._id.toString(),
canons: [""]
}
}
}, { upsert: true }
, function (error, doc) {
if (err) {
console.log("Something wrong when updating data!", err);
}
console.log("updated", doc.relations);
});
i've got no errors but there is no changes ! it seems like the update is not working correctly
Any help plz
By default, findOneAndUpdate() returns the document as it was before update was applied. If you set new: true, findOneAndUpdate() will instead give you the object after update was applied.
Your code looks fine, the record is updated in your db, it just return the document before update in your callback. To get the updated document, you just need to add new: true to the options.
Eventually this solution worked for me
I insist on adding markModified() so that the save() method can detect changes
Utilisateur.findOne({ _id: mongoose.Types.ObjectId(auteur._id) }, function (err, sal) {
if (err) return handleError(err);
else {
sal.relations.lien_vers.push({
metadonnees: { nom_societe: societePersonalId },
identite: auteur._id.toString(),
canons: [""]
});
console.log("before saving", sal.relations.lien_vers)
sal.markModified('relations');
sal.save(function (err) {
if (err) {
console.log(err)
}
console.log("doc saved")
});
}
});
}

MongoDB - check if $addToSet was a duplicate or not

I'm creating a like functionality for my app using MongoDB. I recently stumbled upon a problem.
My code for the liking:
async function like(articleId, userId) {
const db = await makeDb();
return new Promise((resolve, reject) => {
const result = db.collection(collectionName).findOneAndUpdate(
{ articleId: ObjectID(articleId) },
{
$setOnInsert: {
articleId: ObjectID(articleId),
creationDate: new Date()
},
$addToSet: { likes: ObjectID(userId) }
},
{ upsert: true }
);
resolve(result);
});
What it does:
If there is no document in the collection create one and fill in all the fields
If a document exists update only the likes array with a new userID
This works as expected and there are no problems, thanks to $addToSet I don't have any duplicates etc.
However after the above code gets executed and the result is returned I need to do some additional stuff and for it to work I need to know if Mongos $addToSet did change anything.
So in short:
if a new userId has been added to the likes array I want to do something
if the userId is already in the likes array and the $addToSet didnt change anything I don't want to take any action.
Is there a way to distinguish if the $addToSet did something?
The current console.log() of the result looks like this:
{
lastErrorObject: { n: 1, updatedExisting: true },
value: { _id: 5e2c47c57bb5183d80dce14f,
articleId: 5da4d1365217baf52fbcd76a,
creationDate: 2020-01-25T13:51:01.928Z,
likes: [ 5d750b677d8edfc08af8a527 ]
},
ok: 1
}
The one thing that comes to my mind (but is very bad performance-wise) is to compare the likes array before the update if it contains the userID with javascript includes method.
Any better ideas?
I found the solution!
99% of the credit goes to Neil Lunn for this answer:
https://stackoverflow.com/a/44994382/2175228
It brought me on the right track. I ditched the findOneAndUpdate method in favor of the updateOne method. The query parameters etc. everything stayed the same, only the method name changed and obviously the result type of this method.
The problem with the updateOne method is that it does not return the object that was updated (or the old one). But it does return the upsertedId which is the only thing I need in my situation.
So in the end what You get is a very long CommandResult object which starts with:
CommandResult {
result: { n: 1, nModified: 0, ok: 1 },
(...)
}
but when I looked very closely I noticed that the object has exactly those fields that I needed the whole time:
modifiedCount: 0,
upsertedId: null, // if object was inserted, this field will contain it's ID
upsertedCount: 0,
matchedCount: 1
So what You need to do is just take the parts that You need from the CommandResult object and just proceed ;)
My code after the changes:
async function like(articleId, userId) {
const db = await makeDb();
return new Promise((resolve, reject) => {
db.collection(collectionName)
.updateOne(
{ articleId: ObjectID(articleId) },
{
$setOnInsert: {
articleId: ObjectID(articleId),
creationDate: new Date()
},
$addToSet: { likes: ObjectID(userId) }
},
{ upsert: true }
)
.then(data => {
// wrap the content that You need
const result = {
upsertedId: data.upsertedId,
upsertedCount: data.upsertedCount,
modifiedCount: data.modifiedCount
};
resolve(result);
});
});
}
Hopefully this answer will help someone with a similar problem in the future :)

Mongoose findOneAndUpdate on array of subdocuments

I'm trying to replace an array of sub-documents with a new copy of the array.
Something like...
var products = productUrlsData; //new array of documents
var srid = the_correct_id;
StoreRequest.findOneAndUpdate({_id: srid}, {$set: {products: products}}, {returnNewDocument : true}).then(function(sr) {
return res.json({ sr: sr}); //is not modified
}).catch(function(err) {
return res.json({err: err});
})
The products var has the correct modifications, but the returned object, as well as the document in the db, are not being modified. Is this not the correct way to replace a field which is an array of subdocuments? If not, what is?
I am a bit late to the party plus I am really in a hurry -- but should be:
StoreRequest.updateOne(
{ _id: srid },
{ $set: { 'products.$': products }},
{ new: true });
I couldn't make it work with findOneAndUpdate but the above does work.

Mongodb save into nested object?

I can't get my req.body inserted into my mongodb collection.
I have this route that triggers the add method and inside I am trying to figure out a query to save the req.body into a nested collection array
router.post('/api/teams/:tid/players', player.add);
add: function(req, res) {
var newPlayer = new models.Team({ _id: req.params.tid }, req.body);
newPlayer.save(function(err, player) {
if (err) {
res.json({error: 'Error adding player.'});
} else {
console.log(req.body)
res.json(req.body);
}
});
}
Here is an example document
[
{
"team_name":"Bulls",
"_id":"5367bf0135635eb82d4ccf49",
"__v":0,
"players":[
{
"player_name":"Taj Gibson",
"_id":"5367bf0135635eb82d4ccf4b"
},
{
"player_name":"Kirk Hinrich",
"_id":"5367bf0135635eb82d4ccf4a"
}
]
}
]
I can't figure out how to insert/save the POST req.body which is something like
{
"player_name":"Derrick"
}
So that that the new req.body is now added into the players object array.
My question is how do I set the mongodb/mongoose query to handle this?
P.S I am obviously getting the error message because I don't think the query is valid, but it's just kind of an idea what I am trying to do.
Something like this is more suitable, still doesn't work but its a better example I guess
var newPlayer = new models.Team({ _id: req.params.tid }, { players: req.body });
If you created a Team model in Mongoose then you could call the in-built method findOneAndUpdate:
Team.findOneAndUpdate({ _id: req.params.tid },
{ $addToSet: { players: req.body} },
function(err, doc){
console.log(doc);
});
You could do findOne, update, and then save, but the above is more straightforward. $addToSet will only add if the particular update in question doesn't already exist in the array. You can also use $push.
The above does depend to an extent on how you have configured your model and if indeed you are using Mongoose (but obviously you asked how it could be done in Mongoose so I've provided that as a possible solution).
The document for $addToSet is at http://docs.mongodb.org/manual/reference/operator/update/addToSet/ with the relevant operation as follows:
db.collection.update( <query>, { $addToSet: { <field>: <value> } } );

Categories

Resources