Updating a double nested array MongoDB - node.js

Consider this schema:
let userSchema = new mongoose.Schema({
id: String,
displayName: String,
displayImage: String,
posts: [
{
url: String,
description: String,
likes: [String],
comments: [
{ content: String, date: String, author: { id: String, displayName: String, displayImage: String } }
]
}
]
});
I am able to delete a certain item from the comments array using this query
controller.deleteComment = (req, res, next) => {
User.findOneAndUpdate(
{ id: req.query.userid, 'posts._id': req.params.postid, },
{
$pull: {
'posts.$.comments': { _id: req.body.commentID },
}
}
)
.exec()
.then(() => {
res.send('deleted');
})
.catch(next);
};
Is there anyway that I can UPDATE an element inside the comments array by using the $set operator? I need to changed the contents of a comment based on its comment ID.. something like this:
controller.editComment = (req, res, next) => {
User.findOneAndUpdate(
{ id: req.query.userid, 'posts._id': req.params.postid, 'comments._id':req.body.commentID },
{
$set: {
'posts.$.comments': { content: req.body.edited },
}
}
)
.exec()
.then(() => {
res.send('deleted');
})
.catch(next);
};
This ^ is obviously not working but I'm wondering if there is a way I can do this?
UPDATE
As per suggestions below I am doing the following to manage only one Schema. This works, however only the first post's comments get updated regardless of which posts comments I am editing. I have checked and the return docs are always correct. There must be a problems with the doc.save() method.
controller.editComment = (req, res, next) => {
User.findOne(
{ id: req.query.userid, 'posts._id': req.params.postid },
{ 'posts.$.comments._id': req.body.commentID }
)
.exec()
.then((doc) => {
let thisComment = doc.posts[0].comments.filter((comment) => { return comment._id == req.body.commentID; });
thisComment[0].content = req.body.edited;
doc.save((err) => { if (err) throw err; });
res.send('edited');
})
.catch(next);
};

I don't know a simple (or even tough :P) way to achieve what are trying to do. In mongo, manipulation is comparatively tough in doubly nested arrays and hence, best to be avoided.
If you are still open for schema changes, I would suggest you create a different schema for comments and refer that schema inside user schema.
So your comment schema will look like this:
let commentSchema = new mongoose.Schema({
content: String,
date: String,
author: {
id: String,
displayName: String,
displayImage: String
}
});
And you user schema should look like this:
let userSchema = new mongoose.Schema({
id: String,
displayName: String,
displayImage: String,
posts: [{
url: String,
description: String,
likes: [String],
comments: [{
type: Schema.Types.ObjectId,
ref: 'comment' //reference to comment schema
}]
}]
});
This way your data manipulation will be a lot easier. You can populate comments while fetching user document. And, notice how easy update/delete operations are, given that you already know that _id of the comments you want to update.
Hope you find this answer helpful!

controller.editComment = (req, res, next) => {
User.findOneAndUpdate(
{ id: req.query.userid, 'posts._id': req.params.postid, 'comments._id':req.body.commentID },
{
$push: {
'posts.$.comments': { content: req.body.edited },
}
}
)
.exec()
.then(() => {
res.send('deleted');
})
.catch(next);
};

Related

MongoDb - Update array of json objects

I would like to update an object from a JSON objects array. Here is the schema
qualifications:[ {
Experience: [{
title: String,
companyName: String,
location: String,
years: Number
}],
Education:[ {
school: String,
years: Number,
}],
Licences: [String],
Honnors: [String],
}],
For example how can I push an object to the Education array? This is what i have tried so far.
const updateEducation = async (req, res) => {
try {
const user = await User.findOneAndUpdate(
{ _id: req.body.userid },
{
$push: {
qualifications:{
Education: {
school: req.body.educationSchool,
years: req.body.educationYearText
}
}
},
},
{ new: true }
);
And then i use this to remove an object
const deleteEducation = async (req, res) => {
try {
const user = await User.findOneAndUpdate(
{ _id: req.body.userid },
{
$pull: {
"qualifications.Education": {
school: req.body.school
}
},
}
);
But unfortunately in the update function i get "error": "Plan executor error during findAndModify :: caused by :: The field 'qualifications' must be an array but is of type object in document
what is wrong?
The qualifications property of the User is an array of arrays, in other words, 2D array. And subarray receives data in an object. You are trying to access Education by object keys. But Education is in index 1 of qualifications array.
const updateEducation = async (req, res) => {
try {
const user = await User.findOneAndUpdate({ _id: req.body.userid });
user.qualifications[1].push({
school: req.body.educationSchool,
years: req.body.educationYearText
});
await user.save();
} catch (err) {
// handle error here...
}
);

Mongoose to update MongoDB collection of array of number ids

I have the following user Schema
const UserSchema = new mongoose.Schema({
name: String,
username: String,
email: String,
picture: String,
id: { type: String, unique: true },
accessCount: Number,
appliedIds: [Number],
general: {
citizenship_code: String,
gender: String,
currentLocation: String,
phone: String,
}
});
And I wanna update the appliedIds field. The appliedIds field is an array of numbers that will have an indeterminate number of indexes.
My function is as follow:
router.route('/myCustomRoute/:id/apply')
.post(async (req, res) => {
const { nModified } = await Job.findOne({ id: req.params.id }).updateOne({
$addToSet: { applicants: req.body.applicantId },
});
const test = await User.findOneAndUpdate({ id: req.body.applicantId}, { appliedIds: req.params.id })
Obviously, the applicantId is getting replaced entirely here, how can I just add a new item in this array instead of replacing it entirely?
You'll want to push to the appliedIds fields in your findOneAndUpdate call.
const test = await User.findOneAndUpdate(
{ id: req.body.applicantId},
{ $push: { appliedIds: req.params.id } }
)
MongoDB $push

My mongoose populate() function isnt working, I dont know why

I'm trying to populate the content key of my posts model with the contents of my content collection but it isnt working. The content collection is getting populated but the data isnt getting passed to posts collection's content key.
Schemas:
const postsSchema = new mongoose.Schema({
_id: {
type: String,
required: true
},
title: {
type: String,
required: true
},
content: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Content' }]
})
const contentSchema = new mongoose.Schema({
_id: {
type: String,
required: true
},
subHeading: String,
subHeadingContent: String,
image: {
data: Buffer,
contentTyle: String
}
})
const content = mongoose.model("Content", contentSchema)
const posts = mongoose.model("Posts", postsSchema)
Post request to populate the collections:
// Write a new post
app.post("/create", function (req, res) {
let Posts = {
title: req.body.newTitle,
subheading: req.body.newSubHeading,
subheadingcontent: req.body.newSubHeadingContent
}
postsArray = [];
postsArray.push(Posts)
console.log(postsArray);
postsArray.forEach((post) => {
content.create({
_id: uuidv4(),
subHeading: post.subheading,
subHeadingContent: post.subheadingcontent
})
posts.create({
_id: uuidv4(),
title: post.title,
})
posts.find().populate('content').exec((err, posts) => console.log("populated: ", posts))
})
res.redirect("/overview");
})
You do not need to have a postsArray & a for loop since your only trying to create a post from a single object.
Anyways, the reason it's not showing up is because you need to push the content object to your posts array.
First, you give a variable to the content.create,
then you push the variable to the array when you're creating the post
app.post("/create", function (req, res) {
let post = {
title: req.body.newTitle,
subheading: req.body.newSubHeading,
subheadingcontent: req.body.newSubHeadingContent
}
const postContent = content.create({
_id: uuidv4(),
subHeading: post.subheading,
subHeadingContent: post.subheadingcontent
})
posts.create({
_id: uuidv4(),
title: post.title,
{ $push: { content: postContent } }
})
posts.find().populate('content').exec((err, posts) => console.log("populated: ", posts))
res.redirect("/overview");
})

Delete all nested items in database when deleting a top item using MongoDB and Express

I have three collections in my mongodb: clients, programs, and data. Data is nested in Programs and Programs are nested in Clients. That is a single client can have multiple programs and every such program can have a number of Data sets. Here is how clients' schema looks like:
const ClientSchema = new Schema({
fullName: { type: String, required: true },
dateEnrolled: { type: Date, required: true },
isCurrent: { type: String, required: true },
Programs: [{ type: Schema.Types.ObjectId, ref: 'Program' }]
});
// and my Programs schema looks like:
const ProgramSchema = new Schema({
programName: { type: String, required: true },
dateStarted: { type: Date, required: true },
dateFinished: { type: Date },
Data: [{ type: Schema.Types.ObjectId, ref: 'Answer' }]
});
Now, when I delete a client from my database I want to delete all the programs that belong to the clients and all the data sets that were created for those programs. Please, help me.
Try something like this:
router.delete('/:clientId', (req, res, next) => {
const id = req.params.id;
Client.findById(id).then((client) => {
client.Programs.forEach((programId) => {
Program.findById(programId).then((program) => {
Answer.deleteMany({ _id: { $in: program.Data } });
});
Program.deleteOne({ _id: programId })
});
Client.deleteOne({ _id: client._id })
})
});
You can register a middleware function for remove in ClientSchema.
ClientSchema.pre('remove', { query: true, document: true }, async function() {
// call deleteMany on ProgramModel
await ProgramModel.deleteMany({ _id: { $in: this.Programs } });
});
You can do the same thing for ProgramSchema if you want to cascade delete Answer.
Nenad, thank you very much, again! You gave me an idea to loop through first program ids, then through programs. My solution comes in two parts:
Part 1:
`router.delete('/:id', (async (req, res, next) => {
const { id } = req.params;
const client = await Client.findById(id);
await client.Programs.forEach(async (element) => {
const programs = [];
const program = await Program.findById(element);
programs.push(program);
programs.forEach(async (program) => {
await Answer.deleteMany({ _id: { $in: program.Data } });
})
});
await Client.findByIdAndDelete(id);
res.redirect('/clients');
}))`
part 2 that goes to Client schema file and makes sure that all the programs are deleted as well:
`ClientSchema.post('findOneAndDelete', async function (doc) {
if (doc) { await Program.deleteMany({ _id: { $in: doc.Programs } }) }
})`
Now, when I delete a client all instances of related programs are deleted and all instances of data-sets related to each program are deleted, too. Thank you, guys, are all awesome!

add a item inside a nested schema mongoose with addToSet

I know populating schemas is not a new question but I am having a little trouble following the logic on this in regards to multiple schemas. I am working with
"mongoose": "^4.8.5",
"express": "^4.15.0",
I have a schema with a collection of caffeine drinks. When a user selects a drink i would like for that drink to be assigned to the user.
** If at any point I am missing something simple in the architecture please let me know. This project has been my intro to mongodb.
I am reading through populating on the mongoose documentation http://mongoosejs.com/docs/populate.html.
Essentially, if I am to assign the drinks to the list it looks like I want to add them as a reference in an array. This was my approach with caffeine_list
const SelectedDrinks = require('./userDrinks');
const UserSchema = mongoose.Schema({
name: {
type: String
},
email: {
type: String,
required: true
},
username: {
type: String,
required: true
},
password: {
type: String,
required: true
},
caffeine_list: caffeine_list: [ // attempting to reference selected drinks
{
type: mongoose.Schema.Types.ObjectId,
ref: 'SelectedDrinks'
}
]
})
SelectedDrinks comes from the schema below. I added a reference to the user as the creator below
const User = require('./user');
let userDrinkSchema = new mongoose.Schema({
creator : {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
},
caffeine: Number,
mgFloz: Number,
name: String,
size: Number,
updated_at: {
type: Date,
default: Date.now()
}
});
This is where I start to get confused. I initially tried populate but could not get it going. If that was correct please let me know.
In regards to my task of adding a selected drink to the user I used addToSet. I was hoping that this would give me the drink info. I did my set up like so....
const User = require('../../models/user');
const UserDrinks = require('../../models/userDrinks');
router.post('/addDrink', (req, res, next) => {
let newDrink = new UserDrinks({
creator: req.body.creator,
caffeine: req.body.caffeine,
mgFloz: req.body.mgFloz,
name: req.body.name,
size: req.body.size,
updated_at: req.body.updated_at
});
newDrink.save( (err) => {
if(err) {
res.send(err);
} else {
User.findOne({ _id: newDrink.creator}, (err, user) => {
user.caffeine_list.addToSet(newDrink)
user.save( function (err) {
if(err) {
console.log(err);
}else {
res.status(201).json(newDrink);
}
})
})
}
})
});
However, after i do a post in postman I check caffeine_list and the result is
"caffeine_list" : [
ObjectId("58d82a5ff2f85e3f21822ab5"),
ObjectId("58d82c15bfdaf03f853f3864")
],
Ideally I would like to have an array of objects being passed with the caffeine info like so
"caffeine_list" : [
{
"creator": "58d6245cc02b0a0e6db8d257",
"caffeine": 412,
"mgFloz": 218.7,
"name": "1.95 Perfect Drink!",
"size": 42.93,
"updated_at": "2017-03-24T18:04:06.357Z"
}
]
Change your else part with below code instead of findOne and save use update
User.update(
{ _id: newDrink.creator},
{ $addToSet:{
caffeine_list: newDrink
}}).exec(function (err, updatedrink){
if(err) {
console.log(err);
}else {
res.status(201).json(updatedrink);
}
})
Although I am not sure this is the best approach I did find this to be give me the result that I was desiring. I had to make two small changes and I was able to get the caffeine_list to give me the desired response
I had to access the schema for selected drinks
const SelectedDrinks = require('./userDrinks').schema; //** need schema
Afterwards I was able to change
caffeine_list: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'UserDrinks' // name of the file
}
]
to
caffeine_list: [SelectedDrinks]
Now that I have the schema I am able to add the drinks directly into the caffeine_list on the UserSchema.

Resources