How to model and save in mongoose multi-to-multi relationship? - node.js

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.

Related

Get a list of inner properties with mongoose

I have a mongoose schema having the structure of
const videoProjectsSchema = new Schema({
projectname:String,
projectmeta:String,
username:String,
createdat:{ type: Date, default: Date.now }
});
I need to retrieve a list of projectnames which belongs to a particular user. the array returned should only contain the names of the projects or else a list of projectname. This is my code(which returns all the projects objects)
videoProjects.find({ username: req.query.username }, function(err, proj) {
if (err) {
console.log(err);
}
res.json(proj);
});
You can use fields as second parameter to limit fields returned in object.
videoProjects.find({ username: req.query.username },{projectmeta:0,username:0,createdat:0,_id:0},
function(err, proj) {
if (err) {
console.log(err);
}else
res.json(proj);
});
OR
videoProjects.find({ username: req.query.username },{projectname:1,_id:0},
function(err, proj) {
if (err) {
console.log(err);
}else
res.json(proj);
});

Duplicated entries in referenced array - MongoDB - Mongoose

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

Sorting up updatedAt mongoDB

So on my webpage www.groupwrites.com I am showing an Index of stories in the "Read" page. These stories currently show in the order of which they were created (i.e the newest ones on bottom). I am trying to figure out how to display them with the most recently created/updated one first. I am using mongoDB, node JS on cloud9. I have been trying to research and know that I should use updatedAt but I am not sure how to plug everything in. I am not sure how to update the timestamp for updatedAt in the put routes.
This is my code for the index:
// INDEX - show all stories
router.get("/browse", function(req, res, next){
// Get all stories from DB
Story.find({}, function(err, allStories){
if (err) {
return next(err);
} else {
// if user is logged in then render stories and any alerts
if(req.user) {
User.findById(req.user._id).populate({
path: 'alerts',
model: 'Alert',
match: { 'isRead': { $eq: false }}
}).exec(function(err, user) {
if(err) {
return next(err);
}
res.render("stories/index", {stories:allStories, alerts: user.alerts.length, page: 'browse'});
});
} else {
res.render("stories/index", {stories:allStories})
}
}
})
})
// CREATE - add new story to DB
router.post("/browse", middleware.isLoggedIn, function(req, res, next){
// get data from form and add to stories array
var title = req.body.title
var image = req.body.image
var desc = req.body.description
var category = req.body.category
var author = {
id: req.user._id,
username: req.user.username
}
var newStory = {title: title, image: image, description: desc, author: author, category: category}
// Create a new story and save to database
Story.create(newStory, function(err, newlyCreated){
if (err) {
return next(err);
} else {
// redirect back to stories page
req.flash("success", "Successfully published story!")
res.redirect("/browse")
}
})
})
This is the code for the content of the stories, (i.e when adding a chapter to the story):
// New Content
router.get("/stories/:id/content/new", middleware.isLoggedIn, function(req, res, next){
// Find story by id
Story.findById(req.params.id, function(err, story){
if (err) {
return next(err);
} else {
res.render("content/new", {story: story})
}
})
})
// Create Content
router.post("/stories/:id/content", middleware.isLoggedIn, function(req, res, next){
// Look up story using ID
Story.findById(req.params.id).populate({path: 'subscribors', model: 'User'}).exec(function(err, story){
if (err) {
return next(err);
} else {
Content.create(req.body.content, function(err, content){
if (err) {
return next(err);
} else {
if(story.subscribors.length) {
var count = 0;
story.subscribors.forEach(function(subscribor) {
// create alert for each subscribor and add to subscribor's alerts
Alert.create({follower: story.author.id, followed: subscribor, story: story, isUpdated: true}, function(err, newAlert) {
if(err) {
return next(err);
}
// console.log(newAlert);
subscribor.alerts.push(newAlert);
subscribor.save();
count+=1;
if(count === story.subscribors.length) {
// Add username and ID to content
content.author.id = req.user._id;
content.author.username = req.user.username;
// Save content
content.save();
story.content.push(content);
story.save();
req.flash("success", "Successfully added chapter!");
return res.redirect("/stories/" + story._id);
}
});
});
} else {
// Add username and ID to content
content.author.id = req.user._id;
content.author.username = req.user.username;
// Save content
content.save();
story.content.push(content);
story.save();
req.flash("success", "Successfully added chapter!");
return res.redirect("/stories/" + story._id);
}
}
});
}
});
});
// Content Edit Route
router.get("/stories/:id/content/:content_id/edit", middleware.checkContentOwnership, function(req, res){
Content.findById(req.params.content_id, function(err, foundContent){
if(err){
res.redirect("back")
} else{
res.render("content/edit", {story_id: req.params.id, content: foundContent})
}
})
})
// Content Update
router.put("/stories/:id/content/:content_id", middleware.checkContentOwnership, function(req, res){
Content.findByIdAndUpdate(req.params.content_id, req.body.content, function(err, updatedContent){
if(err){
res.redirect("back")
} else {
req.flash("success", "Successfully edited chapter!")
res.redirect("/stories/" + req.params.id)
}
})
})
While defining a Mongoose Schema,
1 for ascending and -1 for descending
Example:
"use strict";
var mongoose = require('mongoose');
var db= require('mongoose').models;
let findOrCreate = require('findorcreate-promise');
var abc= new mongoose.Schema({
name: String,
updated_At: { type: Date, default: Date.now } // like this you can define
});
mongoose.model('abc', abc);
and you can use this by :
db.abc.find({})
.sort({'updated_At':1}) //1 for ascending and -1 for descending
.exec(Your callback function)
this will make sorting from smallest updated_At date to largest.
Thanks

Removing references in one-to-many and many-to-many relationships in MongoDB using Mongoose and Node.js

I need to mention that I am totally aware of the fact that MongoDB is not a relational database in the first place. However it supports referencing other documents, hence some functionality should be supported, imo. Anyways, I have this relationship: a Company has many Departments and one Department belongs to one Company.
company.js
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
var CompanySchema = new Schema({
name: {
type: String,
unique: true,
required: true
},
departments: [{
type: Schema.Types.ObjectId,
ref: 'Department'
}],
dateCreated: {
type: Date,
default: Date.now
},
dateUpdated: {
type: Date,
default: Date.now
}
});
module.exports = mongoose.model('Company', CompanySchema);
department.js
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
var DepartmentSchema = new Schema({
name: {
type: String,
required: true
},
company: {
type: Schema.Types.ObjectId,
ref: 'Company'
},
dateCreated: {
type: Date,
default: Date.now
},
dateUpdated: {
type: Date,
default: Date.now
}
});
module.exports = mongoose.model('Department', DepartmentSchema);
Now, I am writing Node.js logic to manipulate this data using API. I get that if I create a new Department, I should add a reference to Company and I should create its reference in this Company's departments array. Simple. But what if a user changes the Company property of a Department? Say, the HR Department used to belong to Company A, but a user now moves it to Company B? We need to remove the reference to this department from Company A's array and push it to Company B. The same is when we want to delete a department. We need to find a company it belongs to and dis-associate it. My solution is working ATM, but seems rather clumsy.
routes.js
var Department = require('../../models/department'),
Company = require('../../models/company');
module.exports = function(express) {
var router = express.Router();
router.route('/')
.get(function(req, res) {
// ...
})
.post(function(req, res) {
// ...
});
router.route('/:id')
.get(function(req, res) {
// ...
})
.put(function(req, res) {
// First we need to find the department with the request parameter id
Department.findOne({ _id: req.params.id }, function(err, data) {
if (err) return res.send(err);
var department = data;
// department.name = req.body.name || department.name; Not relevant
// If the company to which the department belongs is changed
if (department.company != req.body.company._id) {
// We should find the previous company
Company.findOne({ _id: department.company }, function(err, data) {
if (err) return res.send(err);
var company = data;
// Loop through its departments
for (var i = 0; i < company.departments.length; i++) {
if (company.departments[i].equals(department._id)) {
// And splice this array to remove the outdated reference
company.departments.splice(i, 1);
break;
}
}
company.save(function(err) {
if (err) return res.send(err);
});
});
// Now we find this new company which now holds the department in question
// and add our department as a reference
Company.findOne({ _id: req.body.company._id }, function(err, data) {
if (err) return res.send(err);
var company = data;
company.departments.push(department._id);
company.save(function(err) {
if (err) return res.send(err);
});
});
}
// department.company = req.body.company._id || department.company; Not relevant
// department.dateUpdated = undefined; Not relevant
// And finally save the department
department.save(function(err) {
if (err) return res.send(err);
return res.json({ success: true, message: 'Department updated successfully.' });
});
});
})
.delete(function(req, res) {
// Since we only have id of the department being deleted, we need to find it first
Department.findOne({ _id: req.params.id}, function(err, data) {
if (err) return res.send(err);
var department = data;
// Now we know the company it belongs to and should dis-associate them
// by removing the company's reference to this department
Company.findOne({ _id: department.company }, function(err, data) {
if (err) return res.send(err);
var company = data;
// Again we loop through the company's departments array to remove the ref
for (var i = 0; i < company.departments.length; i++) {
if (company.departments[i].equals(department._id)) {
company.departments.splice(i, 1);
break;
}
}
company.save(function(err) {
if (err) return res.send(err);
});
// I guess it should be synchronously AFTER everything is done,
// since if it is done in parallel with Department.findOne(..)
// piece, the remove part can happen BEFORE the dep is found
Department.remove({ _id: req.params.id }, function(err, data) {
if (err) return res.send(err);
return res.json({ success: true, message: 'Department deleted successfully.' });
});
});
});
});
return router;
};
Is there any elegant solution to this case or it is just as it should be?
I see you have not yet captured the essence of the async nature of node.js ... for example you have a comment prior to department.save which says : and finally ... well the earlier logic may very will be still executing at that time ... also I strongly suggest you avoid your callback approach and learn how to do this using promises

can't populate the array with mongoose in node.js

This is my schema on the course
var CourseSchema = mongoose.Schema({
students:[{ type: ObjectId, ref: 'User' }]
});
var CourseModel = mongoose.model('Course',CourseSchema);
var UserSchema = mongoose.Schema({ name:String})
var UserModel = mongoose.model('User',UserSchema);
In the mongodb, I have created the existing courses and users, and when the user want to participate the course, the user reference will be added to the students array in the course model.
Here is how I try to add the user reference to the students
function joinCourse(cid,uid,callback){
var options = { new: false };
var uid = mongoose.Types.ObjectId(uid);
CourseModel.findOneAndUpdate({'_id':cid},{'$addToSet':{'students':uid}},options,function(err,ref){
if(err) {
console.log('update joinCourse'.red,err);
callback(err, null);
}else{
console.log('update joinCourse '.green+ref);
callback(null,ref);
}
})
}
when the above function is executed, the students array has the objectID or reference of the user. However, when I want to populate the students from the course model, it doesn't work.
var id = mongoose.Types.ObjectId(id);
CourseModel.findById(id).populate('students').exec(function(err, users) {
if(err){callback(err, null);}
else{
//// users.length == undefined
console.log("findCourseStudentsById".green,users.length);
callback(null, users);
}
})
I didn't find any problem on populate function, so I wonder is there something wrong with joinCourse function? so I change the function as
courseModel.findCourseById(cid,function(err,course){
if(err) next(err);
else{
course.students.push({'_id': mongoose.Types.ObjectId(uid)});
course.save(function (err) {
if (err) next(err);
});
}
})
but still the populate doesn't work. Note, I am using mongoose 3.6
populate populates the model instance, but the callback is passed the model instance on which you call populate, and not the populated data itself:
CourseModel.findById(id).populate('students').exec(function(err, course) {
if(err){callback(err, null);}
else{
console.log("findCourseStudentsById".green, course.students.length);
callback(null, course);
}
});

Resources