Duplicated entries in referenced array - MongoDB - Mongoose - node.js

I'm new to MongoDb and I met this problem days ago and I can't resolve it. Basically, my user is allowed to create new Post with a bunch of Images. When I create the Post, then I create also the Images but when I check on mongo shell the entries in the array of the Post, one image can be present two or three times. (All the images are saved with an url)
These are my Models:
var postSchema = new mongoose.Schema({
Name: String,
Background: String,
Description: String,
posted: {type:Date,default: Date.now() },
images: [{type: mongoose.Schema.Types.ObjectId, ref: "image"}]
});
var imageSchema = new mongoose.Schema({
src: String,
caption: String
});
(These Schema are in separeted files and then exported as model)
This is my code for saving Post:
app.post("/post",isLoggedIn,function(req,res){
var post= {Name: req.body.name,
Background: req.body.backg,
Description: req.body.desc};
Posts.create(post, function(err, newPost){
if(err){
console.log(err);
} else {
var allImages = req.body.img;
allImages.forEach(function(singleImg){
Images.create(singleImg, function(err, newImg){
if(err){
console.log(err);
} else {
newPost.images.push(newImg);
newPost.save(function(err){
if(err){
return res.send(err);
}
});
}
});
});
}
});
return res.redirect("/posts");
});
Edit
This is my code with $addToSet
app.post("/post",isLoggedIn,function(req,res){
var post= {Name: req.body.name,
Background: req.body.backg,
Description: req.body.desc};
Posts.create(post, function(err, newPost){
if(err){
console.log(err);
} else {
Posts.findByIdAndUpdate(newPost._id, {$addToSet:{images: {$each: req.body.img}}}, function(err, updatedPost){
return res.redirect("/posts");
});
}
});
});
It gives me CastError Cast to ObjectId failed

Don't forget hanlde errors
Edit your code: (With Mongoose + nodejs - I suggest use indexOf, It runs very well with me, My DB have about 10M records)
From
Posts.create(post, function(err, newPost){
if(err){
console.log(err);
} else {
var allImages = req.body.img;
allImages.forEach(function(singleImg){
Images.create(singleImg, function(err, newImg){
if(err){
console.log(err);
} else {
newPost.images.push(newImg);
newPost.save(function(err){
if(err){
return res.send(err);
}
});
}
});
});
}
});
to
Posts.create(post, function(err, newPost){
if(err){
console.log(err);
} else {
var allImages = req.body.img;
allImages.forEach(function(singleImg){
Images.create(singleImg, function(err, newImg){
if(err){
console.log(err);
} else {
// Check exist
if (newPost.images.indexOf(newImg._id) == -1) {
newPost.images.push(newImg._id);
newPost.save(function(err){
if(err) {
return res.send(err);
}
});
} else {
console.log(newImg);
// do something
}
}
});
});
}
});
OR
Use $addToSet if you use MongoDb or Mongoose
Hope it will help you.
Thank you

Related

How to use findByIdAndUpdate on mongodb?

I am a noobie in coding and I am having an issue with how to use properly MongoDB. I have a parent object classroom containing an array of objects - comments. I am trying to update the content of 1 selected comment.
originally I updated the state of the whole "classroom" in the react and passed all the data and $set {req.body} in findByIdAndUpdate.
I want to achieve the same result if I only pass to my axios request classId, commentId and comment data and not whole classroom / all comments
I tried to filter selected comment out of the array of comments and concat updated comment, but that did not work. Clearly, I have any idea what is going on and docs don't make it any easier for me to understand.
my classroom schema:
var ClassroomSchema = new Schema({
title: String,
teacher: String,
info: String,
image_url: String,
comments: [Comment.schema]
});
comment schema:
var CommentSchema = new Schema()
CommentSchema.add({
content: String,
comments: [CommentSchema],
created_at: {
type: Date,
default: Date.now
}
});
original solution:
function update(req, res){
Comment.findById(req.params.comment_id, function(err, comment) {
if(err) res.send(err)
comment.content = req.body.content;
comment.save();
console.log(req.body.comments)
Classroom.findByIdAndUpdate(req.params.classroom_id,
{$set: req.body}, function(err, classroom){
if (err) {
console.log(err);
res.send(err);
} else {
commentToUpdate = req.body.commentData;
res.json(classroom);
}
});
});
}
my current failing atempt:
function update(req, res){
console.log('update => req.body: ', req.body);
console.log('req.params', req.params)
Comment.findById(req.params.comment_id, function(err, comment) {
if(err) res.send(err)
comment.content = req.body.content;
comment.save();
console.log('comment: ', comment);
Classroom.findById(req.params.classroom_id, function(err, classroom) {
console.log('CLASSROOM findByIdAndUpdate classroom: ', classroom)
// console.log('reg.body: ', req.body)
if (err) {
console.warn('Error updating comment', err);
res.send(err);
} else {
// commentToUpdate = req.body.commentData;
old_comments = classroom.comments;
console.log('comments: ', old_comments);
Classroom.findByIdAndUpdate(req.params.classroom_id,
{$set:
{ comments: old_comments.filter(comt._id !== comment._id).concat(comment)}
}, function(err, updatedClassroom) {
if (err) {
console.warn(err);
} else {
res.json(updatedClassroom);
}
});
}
});
});
}
haven't tested, but try this.
function update(req, res) {
Classroom.update(
{ _id: req.params.classroom_id, "comments._id": req.params.comment_id },
{ $set: { "comments.$.content": req.body.content } },
function(err) {
..
}
);
}

search queries in nodejs and mongodb and populated data

i have a web app that's written in nodejs and mongodb, i have the following two models
var TeacherSchema = new Schema({
school_id:[{type: Schema.Types.ObjectId, ref: 'School'}],
name: String,
subjects: [{type: Schema.Types.ObjectId, ref: 'Subject'}],
});
var SubjectSchema = new Schema({
title : String,
school_id:[{type: Schema.Types.ObjectId, ref: 'School'}]
});
i wrote an api that searches throw the teacher or subjects
router.get("/field-teacher-subject", function (req, res) {
var school_id= req.query.schoolId;
Subject.find(school_id:'school_id,function (err, subjects) {
if (err) {
console.log(err);
res.json({status: "error", message: err.message});
} else {
var sub_array=[];
for(var q in subjects){
sub_array.push(subjects[q]._id);
}
Teacher.find({subjects:{$in :sub_array }},{first_name:true, father_name:true, last_name : true, subjects:true}).populate('subjects')
.exec(function(tech) {
console.log("hello: ");
var subjeto = [];
if(tech){
for(var p in tech){
subjeto.push(tech[p].subjects);
}
}
res.json({status: "success", message: "subjects returned",
items: tech});
}).catch(function(err){
if(err){
res.json({status:"error",
message:"error occurred"+err.message});
return;
}
});
}
}).limit(parseInt(req.query.max));
});
THIS RETURNS null when i search for a name,
what is the best way to solve this
Hard to know what you are asking but your code has few errors. Let's clean up your code, shall we?
router.get("/field-teacher-subject", function (req, res) {
// get subjects
Subject
.find({ school_id: req.query.schoolId }) // 1st argument is an object
.limit(parseInt(req.query.max)) // should go before
.exec(function (err, subjects) { // use .exec()
if (err) {
console.log(err);
return res.json({ status: "error", message: err.message });
}
// get subject IDs
var sub_array = subjects.map(function (subject) { return subject._id; });
// get teachers assigned to subjects
Teacher
.find({ subjects: { $in: sub_array }})
.select('first_name father_name last_name subjects')
.populate('subjects')
.exec(function(err, teachers) { // 1st argument is an error
if (err) {
console.log(err);
return res.json({status: "error", message: err.message });
}
var subjeto = teachers.map(function (teacher) { return teacher.subjects; });
res.json({status: "success", message: "subjects returned", items: teachers });
});
});
});
Useful links:
See 3rd example in doc on how to use .limit() and .exec().
.map()
You tried to use .exec() like .then() and .catch() in your second query

MongoDB: Updating arrays of sub documents after document deletion

I have an API in which I am deleting 'course' documents in the following way:
module.exports.deleteCourse = function(req, res){
var courseid = req.params.courseid;
if(courseid){
Course.findByIdAndRemove(courseid, function(err, course){
if(err){
sendJSONResponse(res, 404, err);
return;
}
sendJSONResponse(res, 204, null)
});
} else{
sendJSONResponse(res, 404, {"message":"NoId"});
}
};
This is successful in deleting the course from the database, as is shown when attempting to find it by id.
The issue is that in user documents:
var instructorSchema = new mongoose.Schema({
name: {type: String,
unique: true,
required: true},
password: {type: String,
required: true},
courses: [course.schema]
});
If the document was pushed to the courses array it remains after the deletion method.
So my question. Is there a relatively painless way to keep this document updated after deletes?
Thanks.
Add a class method for course using statics, where you delete both the course and its dependencies.
Assuming you are storing ids in courses array:
var Instructor = require('./instructor');
courseSchema.statics = {
removeOneWithDependencies : function(id, done){
this.findByIdAndRemove(id, function(err, course){
if(err){
return done(err);
}
else{
//Removes the course id from courses array of all instructor docs
Instructor.update({courses: course._id}, { $pullAll: {courses: [course._id] } }, {multi: true}, function(err){ //http://stackoverflow.com/a/27917378/
if(err){
return done(err);
}
else{
return done();
}
})
}
});
}
}
In case you are storing course documents in courses array you need to change the update query to:
Instructor.update({"courses._id": course._id}, { $pull: {courses:{_id: course._id} } }, {multi: true}, function(err){ //http://stackoverflow.com/a/15122017/
Finally use the above method in your API:
module.exports.deleteCourse = function(req, res){
var courseid = req.params.courseid;
if(courseid){
Course.removeOneWithDependencies(courseid, function(err){
if(err){
return sendJSONResponse(res, 500, err);
}
else{
return sendJSONResponse(res, 204, null);
}
});
} else{
sendJSONResponse(res, 404, {"message":"NoId"});
}
};

mongoose save if new refs

If you want to know what's the problem in this thread in two words: I'm trying to accomplish a mongoose function findOrAdd(): if the Id I'm searching for isn't present, I should add a new document. After that (that's why I need some sync functions) I need to do another query based on the new ObjectIds.
This is my Post Schema
var com_post_schema = new Schema({
content: { type: String, required: true },
postedBy: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'com_user'
}]
});
and my User Schema
var com_user_schema = new Schema({
name: { type: String, required: true },
age: {type:Number}
});
So a post can have more than one author.
My problem: an author can be an existent user (chosen in bootstrap-tokenfield) or a new user, see this json example:
{
"content":"post content",
"postedBy":[
{
"_id":"56a60a972b70225014753d1a",
"name":"Paul",
"age":20,
"__v":0,
"value":"Paul",
"label":"Paul"
},
{
"value":"John",
"label":"John"
}
]
}
The user Paul is already present in 'com_user' collection, I have to save the user John in 'com_user' and then save the post with both user ObjectIds refs (the fields 'value' and 'label' are sent by bootstrap-tokenfield).
I'm not clear how can I do that.
EDIT this is my current code I still have sync problems. I made some tests and I see randomly in console "New post added" and then "User not found.."
Try with 3 users and see..
app.post('/api/community/posts', function(req,res){
var arr=[],i=0;
req.body.postedBy.forEach(function(el){
com_user.findById(el._id, function (err, user) {
if(user) {
console.log("User found!");
console.log(user);
arr.push(mongoose.Types.ObjectId(user._id ));
i++;
if(i==req.body.postedBy.length-1) {
console.log('UFi'+i)
console.log(arr)
var com_post1= new com_post({
content:req.body.content,
postedBy:arr,
});
com_post1.save(function(err){
if(!err){
console.log("New post added!");
res.json({"New post added! ":req.body.content});
}
else {
res.json({"Error adding post":'error'});
error(err)
}
});
}
}
else {
var com_user1= new com_user({
name:el.label,
age: 20
});
com_user1.save(function(err,newuser){
if(err)
console.log(err)
else {
console.log('User not found and just added!');
console.log(newuser)
arr.push(mongoose.Types.ObjectId(newuser._id));
console.log(arr)
i++;
if(i==req.body.postedBy.length-1) {
console.log('NUFi'+i)
console.log(arr)
var com_post1= new com_post({
content:req.body.content,
postedBy:arr,
});
com_post1.save(function(err){
if(!err){
console.log("New post added!");
res.json({"New post added! ":req.body.content});
}
else {
res.json({"Error adding post":'error'});
error(err)
}
});
}
}
});
}
});
});
});
its because the code after forEach gets executed before the forEach is completed. Try it like this
app.post('/api/community/posts', function(req,res){
var arr=[],i=0;
req.body.postedBy.forEach(function(el){
com_user.findById(el._id, function (err, user) {
if(user) {
console.log("User found!");
console.log(user);
arr.push(mongoose.Types.ObjectId(user._id ));
i++;
if(i==req.body.postedBy.length-1) { //to ensure forEach is complete
console.log(arr)
var com_post1= new com_post({
content:req.body.content,
postedBy:arr,
});
com_post1.save(function(err){
if(!err)
res.json({"New post added! ":req.body.content});
else {
res.json({"Error adding post":'error'});
error(err)
}
});
}
}
else {
var com_user1= new com_user({
name:el.label,
age: 20
});
com_user1.save(function(err,newuser){
if(err)
console.log(err)
else {
console.log('User not found and just added!');
console.log(newuser)
arr.push(mongoose.Types.ObjectId(newuser._id));
i++;
if(i==req.body.postedBy.length-1) {
console.log(arr)
var com_post1= new com_post({
content:req.body.content,
postedBy:arr,
});
com_post1.save(function(err){
if(!err)
res.json({"New post added! ":req.body.content});
else {
res.json({"Error adding post":'error'});
error(err)
}
});
}
}
});
}
});
});

How to model and save in mongoose multi-to-multi relationship?

I want to 2 model, one is user that can belongs to multiple groups, and another is group that can has multiple users.
This is my schemas and models, i don't know whether they the correct:
var Schema = mongoose.Schema;
var UserSchema = new Schema({
joinedGroups:[{type:Schema.Types.ObjectId, ref: 'Group'}]
}
);
var GroupSchema = new Schema({
createdBy: {type: Schema.Types.ObjectId, ref: 'User'},
joinedUsers:[{ type: Schema.Types.ObjectId, ref: 'User' }]
});
var User = mongoose.model('User',UserSchema);
var Group = mongoose.model('Group',GroupSchema);
when receive POST of url:/api/groups with the body of user._id, I want to join this user to new create group, besides, i want to join this new created group to user's joinedGroups and finally i want to response the client of the new group with users in it. Follow is my code of doing this:
app.post('/api/groups', function(req, res){
console.log(req.body);
var userId = req.body.user_id;
var group = {
createdBy : userId
};
Group.create(group, function(err,group){
if(err){
console.log('create group err');
res.send(err);
}
else{
console.log('create group success');
User.update({_id: userId},
{$push: {joinedGroups: group._id}},
function(err,user){
if(err){
console.log('update user err');
res.send(err);
}
else{
Group.update({_id: group._id},
{$push: {joinedUsers: user}},
function(err,group){
if(err){
console.log('update group err:' + err);
res.send(err);
}
else{
group.populate({path:'joinedUsers'},
function(err, group){
if(err){
console.log('populate group err');
res.send(err);
}
else{
console.log('populate group success');
res.json(group);
}
}
);
}
});
}
});
}
});
});
I feel it's really complex, and it occur error :
update group err:CastError: Cast to ObjectId failed for value "1" at path "joinedUsers"
So i want somebody help me with right solution to do this, thanks!
edit:
I also want to support join user in to existed group in PUT /api/group/:group_id like below:
var userId = req.body.user_id;
var groupId = req.params.group_id;
how to do that? thanks!
First of all, your realization is really complex and it can be simplified as this:
var userId = req.body.user_id; // this is an ObjectId
var group = {
createdBy: userId,
joinedUsers: userId
};
Group.create(group, function (err, group) {
if (err || !group) {
return res.send(err);
}
User.findById(userId, function (err, user) {
if (err || !user) {
return res.send(err);
}
user.joinedGroups.push(group._id);
user.save(function (err) {
if (err) {
return res.send(err);
}
group.populate('joinedUsers', function (err, group) {
if (err || group) {
return res.send(err);
}
res.json(group);
});
});
});
});
And the reason why you getting CastError error is: the update method returns 1 as second argument of callback if successfully updated. But your Group#joinedUsers filed expecting User reference.

Resources