Node.js - Mongoose - Update nested array with all values in req.body - javascript

I have an object that looks like this.
{
_id: '577fe7a842c9b447',
name: 'Jacob\'s Bronze Badges',
competitors: [
{
_id: '577fe7a842c9bd6d',
name: 'Peter\'s Silver Badges',
sites: [
{
_id: '577fe7a842c9bd6d',
name: 'Facebook',
url: 'fb.com/peter'
},
{
_id: '577fe7a842c9bd6d'
name: 'Google',
url: 'google.com/peter'
}
]
},
{
_id: '599fe7a842c9bd6d',
name: 'Paul\'s Gold Badges',
sites: [
{
'_id': '577fe7a842c9bd6d',
name: 'Facebook',
url: 'fb.com/paul'
},
{
_id: '577fe7a842c9bd6d',
name: 'Google',
url: 'google.com/paul'
}
]
}
]
}
My goal is to reference the competitors array and update items inside with all of the values from req.body. I based this code off of this answer, as well as this other one.
Location.update(
{ 'competitors._id': req.params.competitorId, },
{ $set: { 'competitors.$': req.body, }, },
(err, result) => {
if (err) {
res.status(500)
.json({ error: 'Unable to update competitor.', });
} else {
res.status(200)
.json(result);
}
}
);
I send my HTTP PUT to localhost:3000/competitors/577fe7a842c9bd6d to update Peter's Silver Badges. The request body is:
{
"name": "McDonald's"
}
The problem is that when I use $set to set the competitor with _id: req.params.competitorId, I don't know what is in req.body. I want to use the entire req.body to update the object in the array, but when I do, that object is overwritten, so instead of getting a new name, Peter's Silver Badges becomes:
{
name: 'McDonald\'s',
sites: []
}
How can I update an object within an array when I know the object's _id with all of the fields from req.body without removing fields that I want to keep?
I believe that the sites array is empty because the object was reinitialized. In my schema I have sites: [sitesSchema] to initialize it. So I am assuming that the whole competitors[_id] object is getting overwritten with the new name and then the sites: [sitesSchema] from myschema.

You would need to use the $ positional operator in your $set. In order to assign those properties dynamically, based on what is in your req.body, you would need to build up your $set programmatically.
If you want to update the name you would do the following:
Location.update(
{ 'competitors._id': req.params.competitorId },
{ $set: { 'competitors.$.name': req.body.name }},
(err, result) => {
if (err) {
res.status(500)
.json({ error: 'Unable to update competitor.', });
} else {
res.status(200)
.json(result);
}
}
);
One way you might programatically build up the $set using req.body is by doing the following:
let updateObj = {$set: {}};
for(var param in req.body) {
updateObj.$set['competitors.$.'+param] = req.body[param];
}
See this answer for more details.

To update embedded document with $ operator, in most of the cases, you have to use dot notation on the $ operator.
Location.update(
{ _id: '577fe7a842c9b447', 'competitors._id': req.params.competitorId, },
{ $set: { 'competitors.$.name': req.body.name, }, },
(err, result) => {
if (err) {
res.status(500)
.json({ error: 'Unable to update competitor.', });
} else {
res.status(200)
.json(result);
}
}
);

Related

change values of nested objects by id with mongoose

I'm new to MongoDB, and I'm trying to do a very simple task, but however I can't get it right.
What I want is to change the process status but I tried "FindAndUpdate", "UpdateOne" and "FindByIdAndUpdate" but it won't work.
Maybe it has to do with my Schema. Should I create a new Schema for the Process?
My Database entry inside a MongoDB Collection:
_id: 622c98cfc872bcb2578b97a5
username:"foo"
__v:0
process:Array
0: Object
processname:"bar"
process_status:"stopped"
_id: 6230c1a401c66fc025d3cb88
My current Schema:
const User = new mongoose.Schema(
{
username: { type: String, required: true },
process: [
{
processname: {
type: String,
},
process_status: {
type: String,
},
},
],
},
{ collection: "user-data" }
);
My current code:
const startstopprocess = await User.findByIdAndUpdate(
{ _id: "6230c1a401c66fc025d3cb88" },
{ process_status: "started" }
).then(function (error, result) {
console.log(error);
console.log(result);
});
You can use positional operator $ in this way:
db.collection.update({
"process._id": "6230c1a401c66fc025d3cb88"
},
{
"$set": {
"process.$.process_status": "started"
}
})
Note how using positional operator you can say mongo "from the object you have found in find stage, update the process_status variable to started"
Example here

How to delete items out of a nested array by their _id in Mongoose

I have a Model, Users, and each user has an array of objects called habits.
{
_id: 606f1d67aa1d5734c494bf0a,
name: 'Courtney',
email: 'c#gmail.com',
password: '$2b$10$WQ22pIiwD8yDvRhdQ0olBe6JnnFqV2WOsC0cD/FkV4g7LPtUOpx1C',
__v: 35,
habits: [
{
_id: 6081d32580bfac579446eb81,
name: 'first',
type: 'good',
days: 0,
checked: false
},
{
_id: 6081d32f80bfac579446eb82,
name: 'seconds',
type: 'bad',
days: 0,
checked: false
},
]
}
From my client side, I send over a list of ids of the habits I want to delete out of the array, that looks like this..
[
'6081d32580bfac579446eb81',
'6081d32f80bfac579446eb82',
]
I am trying to find a way to delete the IDs in the habits array, deleting only the habits whose IDs are sent in the array above.
Here is what I have tried....
router.post('/delete', validate, async (req, res) =>{
const user = await User.findById(req.user._id)
const idList = req.body.ids.checkedItems
const updatedList = user['habits'].filter(habit=> {
return !idList.includes(`${habit._id}`)
})
user['habits'] = updatedList;
try {
await user.save()
res.send(updatedList)
} catch (err) {
res.status(400).send(err)
}
})
-idList is the array of ids as strings.
-user['habits'] accesses the list from the document, user.
-my filter method only returns the habits that are NOT included in the idList array. Because the Ids in the array are the ones to be deleted.
This solution is obviously just vanilla javascript, what I am looking for is if anyone knows how to achieve this using mongoose.js syntax.
I think you could do this by using deleteMany or deleteOne on the Model, but I am not sure how to achieve this.
Thank you for taking the time to help or give suggestions.
The solution that worked for me in the end is to use the Model method 'findByIdAndUpdate'.
const { itemsToDelete } = req.body
User.findByIdAndUpdate(req.user._id,
{ $pull: { habits: { _id: itemsToDelete } } },
{ new: true , useFindAndModify: false},
function (err, data) {
if (err) {
res.send(err)
} else {
res.send(data.habits)
}
}
)
You can use a combination of $pull and $in for this.
User.update({_id: userId},
{
$pull: {
habits: {
_id: {
$in: [
ObjectId("6081d32580bfac579446eb81"),
ObjectId("6081d32f80bfac579446eb82")
]
}
}
}
})

Is there a way to find documents in nested array in mongoDB

const userSchema = new Schema({
username: {
type: String,
required: true
},
email: {
type: String,
required: true
},
password: {
type: String,
required: true
},
posts: [{
type: Schema.Types.ObjectId,
ref: 'Post'
}],
friends: [{
type: Schema.Types.ObjectId,
ref: 'User'
}],
});
// Exporting the schema so it can be accessed by requiring it.
module.exports = mongoose.model('User', userSchema);
As you can see I got this user schema that has a friends array and a posts array.
User.findById(userId).then(result => {
Post.find(query).then(posts => {
res.status(200).json(posts)
}).catch(err => {
if (!err.statusCode) {
err.statusCode = 500;
}
next(err);
})
});
Is there any query that can fit in the find() above in order to get all the posts of the user's friends?
If in the post model you have a link to the user model, that is, some field that identifies who wrote the post, you could use a for loop to search for posts made by the user's friends.
I don't know if this is the best solution but I hope it helps.
As a tip, you should use asynchronous syntax instead of promises, this helps when correcting errors.
async function getFriendsPosts(req,res){
/*in this array we will store the
posts of the user's friends */
let posts = [];
try{
//we check if the user exists
let user = User.findById(req.params.id);
//if it doesn't exist we will send a message
if(!user) res.status(404).send("User not Found");
else{
/* here we compare the id of the friends with the id of
the friends with the "creator" field in the post model*/
for await(let friend of user.friends){
for await(let creator of Post.find()){
/* if there is a match we send
it to the post array*/
if(friend._id.equals(creator._id)){
posts.push(creator);
}
}
}
/*finally we send the array with the posts*/
res.send(posts);
}
}catch(err){
res.status(500).send("Internal Server Error");
}
}
If I suppose that the Post Schema is something like that
{
title: String,
content: String,
owner: { type: Schema.Types.ObjectId, ref: 'User'}
}
then we can use aggregate pipeline to get the friends posts of some user
something like that
db.users.aggregate([
{
$match: {
_id: "userId1" // this should be of type ObjectId, you need to convert req.params.id to ObjectId (something like: mongoose.Types.ObjectId(req.params.id) instead of 'userId1')
}
},
{
$lookup: {
from: "posts",
let: {
friendsIDs: "$friends"
},
pipeline: [
{
$match: {
$expr: {
$in: ["$owner", "$$friendsIDs"]
}
}
}
],
as: "friendsPosts"
}
}
])
you can test it here Mongo Playground
feel free to replace these 'userId1', 'userId2', ..., 'postId1, 'postId2', .. in this link with your real users and posts Ids
by this way, you got the friends posts of some user in one query rather than two queries
then the function will be something like that
User.aggregate([
{
$match: {
_id: mongoose.Types.ObjectId(req.params.id)
}
},
{
$lookup: {
from: "posts", // this should be the posts collection name, It may be 'Post' not 'posts', check it
let: {
friendsIDs: "$friends"
},
pipeline: [
{
$match: {
$expr: {
$in: ["$owner", "$$friendsIDs"]
}
}
}
],
as: "friendsPosts"
}
}
]).then(result => {
// the aggregate pipeline is returning an array
// but we are sure it will be an array of only one element as we are searching for only one user, so we can use result[0]
result = result || []; // double check the result array
result[0] = result[0] || {}; // double check the user object
var posts = result[0].friendsPosts; // here is the friends posts array
// return the posts array
res.json(posts);
})
hope it helps
Update
If we need to sort the firendsPosts, and then limit them
we can use the following
db.users.aggregate([
{
$match: {
_id: "userId1"
}
},
{
$lookup: {
from: "posts",
let: {
friendsIDs: "$friends"
},
pipeline: [
{
$match: {
$expr: {
$in: [
"$owner",
"$$friendsIDs"
]
}
}
}
],
as: "friendsPosts"
}
},
{
$unwind: "$friendsPosts" // unwind the array to get a stream of documents
},
{
$sort: {
"friendsPosts.createdAt": 1 // then sort the posts by the createdAt Date in ascending order
}
},
{
$group: { // then group the posts again after sorting
_id: "$_id",
friendsPosts: {
$push: "$friendsPosts"
}
}
},
{
$project: {
friendsPosts: {
$slice: ["$friendsPosts", 2] // this is to limit the posts
}
}
}
])
you can test it here Mongo Playground 2

How can I use MongoDB to find documents that match with custom field?

I am trying to find all documents that match with custom field in node.js.
node.js code:
req.app.db.models.Property.find({
user: {
id: req.params.id
}
}).exec(function(err, user) {
if (err) {
return next(err);
}
console.log("id:" + req.params.id);
console.log("user:" + user);
res.status(200).json(user);
});
But, console shows like this
id:5941cfc42df14b2fe811d531
user:
And the schema is like below
user: {
id: { type: mongoose.Schema.Types.ObjectId, ref: 'User' },
name: { type: String, default: '' },
email: { type: String, defaul: ''}
},
propertyType: { type: String, default: '' },
..........
}
It seems that it can't find documents.
What's wrong with this? Several documents exist on Property Collection.
The problem is in your query, its finding a user having an object id only.
So what you are actually equating is "user" with "{ id: req.params.id }" which return only id parameter. So for your solution do following:
var query = {'user.id':req.params.id}
Property.find(query).exec(function(err, user) {
if (err) {
return next(err);
}
if you want to specify the type of object returned, if found.
You can mention it as follows:
Property.find(query,'user.id user.name').exec(...)
this will return an object with user id and email only and not the name.

Push object to array in an update with node's mongodb package

I'm trying to update a document adding an object to an array field called subscription_history.
Object is like this:
{
memberId: int,
subscription_history: array
}
The code:
db.collection('users').update(
{
memberId: data.memberid
}, {
$push: {
subscription_history: {
joined: new Date(data.joined*1000),
expires: new Date(data.expires*1000)
}
}
}, {
//upsert: true,
multi: true
}, function (err, updated) {
if (err) {
console.log(err)
}
});
});
The query is working in mongodb client 'robomongo', but with the node.js client package is not working and I don't know why. Any ideas?

Categories

Resources