Multiple populates - mongoosejs - javascript

Just a simple query, for example with a double ref in the model.
Schema / Model
var OrderSchema = new Schema({
user: {
type : Schema.Types.ObjectId,
ref : 'User',
required: true
},
meal: {
type : Schema.Types.ObjectId,
ref : 'Meal',
required: true
},
});
var OrderModel = db.model('Order', OrderSchema);
Query
OrderModel.find()
.populate('user') // works
.populate('meal') // dont works
.exec(function (err, results) {
// callback
});
I already tried something like
.populate('user meal')
.populate(['user', 'meal'])
In fact only one of the populates works.
So, how do is get two populates working ?

You're already using the correct syntax of:
OrderModel.find()
.populate('user')
.populate('meal')
.exec(function (err, results) {
// callback
});
Perhaps the meal ObjectId from the order isn't in the Meals collection?

UPDATE:
This solution remains for the version 3.x of Mongoose http://mongoosejs.com/docs/3.8.x/docs/populate.html but is no longer documented for >= 4.x versions of Mongoose and so the answer from #JohnnyHK is the only valid one for now on.
ORIGINAL POST
If you're using Mongoose >= 3.6, you can pass a space delimited string of the path names to populate:
OrderModel.find()
.populate('user meal')
.exec(function (err, results) {
// callback
});
http://mongoosejs.com/docs/populate.html

This has probably been resolved already, but this is my take on multiple & deep population in Mongodb > 3.6:
OrderModel.find().populate([{
path: 'user',
model: 'User'
}, {
path: 'meal',
model: 'Meal'
}]).exec(function(err, order) {
if(err) throw err;
if(order) {
// execute on order
console.log(order.user.username); // prints user's username
console.log(order.meal.value); // you get the idea
}
});
There are probably other ways to do this, but this makes very readable code for beginners (like me)

The best solution in my opinion is arrays when you are populating more than one foreign field on the same level. My code shows that I have multiple populates for different levels.
const patients = await Patient.find({})
.populate([{
path: 'files',
populate: {
path: 'authorizations',
model: 'Authorization'
},
populate: {
path: 'claims',
model: 'Claim',
options: {
sort: { startDate: 1 }
}
}
}, {
path: 'policies',
model: 'Policy',
populate: {
path: 'vobs',
populate: [{
path: 'benefits'
}, {
path: 'eligibility',
model: 'Eligibility'
}]
}
}]);
As you can see, wherever I needed more than one field of a document populated, I encased the populate key in an array and provided an array of objects, each object having a different path. Most robust and concise way to do it, in my opinion.

You can use array syntax:
let results = await OrderModel.find().populate(['user', 'meal']);
You can also select which properties you want from each populate:
let results = await OrderModel.find().populate([{path: 'user', select: 'firstname'}, {path: 'meal', select: 'name'}]);

Latest mongoose v5.9.15
has ability to take array of populate fields
so you can do,
.populate([ 'field1', 'field2' ])

You can try:
OrderModel.find()
.populate('user')
.populate('meal')
.exec(function (err, results) {
// callback
});
or with array options
OrderModel.find()
.populate([
{
path: "path1",
select: "field",
model: Model1
},
{
path: "path2",
select: "field2",
model: Model2
}
])
.exec(function (err, results) {
// callback
});

In model file do something like:-
doctorid:{
type:Schema.Types.ObjectId, ref:'doctor'
},
clinicid:{
type:Schema.Types.ObjectId, ref:'baseClinic'
}
In js file for adding operator use Something like:-
const clinicObj = await BaseClinic.findOne({clinicId:req.body.clinicid})
const doctorObj = await Doctor.findOne({ doctorId : req.body.doctorid}) ;
**and add data as:-**
const newOperator = new Operator({
clinicid:clinicObj._id,
doctorid: doctorObj._id
});
Now, while populating
apiRoutes.post("/operator-by-id", async (req, res) => {
const id = req.body.id;
const isExist = await Operator.find({ _id: id }).populate(['doctorid','clinicid'])
if (isExist.length > 0) {
res.send(isExist)
} else {
res.send("No operator found");
}
});

i have same problem , but my mistake not in populate , i have an error in Model
if you do this
uncorrected
user: {
type: [Schema.Types.ObjectId],
ref: 'User'
}
correct
user: [{
type: Schema.Types.ObjectId,
ref: 'User'
}]
you must put array around of object like this

To populate multiple fields with array of objects in controller/action function, model of both is already referred in schema of post
post.find({}).populate('user').populate('comments').exec(function (err,posts)
{
if(err)
{
console.log("error in post");
}
return res.render('home',{
h1:"home Page",
posts:posts,
});
});

I think you are trying to the nested population you can visit official docs
User.
findOne({ name: 'Val' }).
populate({
path: 'friends',
// Get friends of friends - populate the 'friends' array for every friend
populate: { path: 'friends' }
});

Related

Mongoose auto fill data by searching in reference

const UserSchema = new Schema(
{
referrals: {
ref: 'User',
type: [mongoose.Schema.Types.ObjectId],
},
referredBy: {
ref: 'User',
type: mongoose.Schema.Types.ObjectId,
},
}
);
I want Mongoose to find users who have current user _id in referredBy reference.
In other words, eg: find all users who have '_IDOfSpecificUser' in their referredBy field and put all the found users in the array of referrals where user's _id is '_IDOfSpecificUser'.
How can I handle that in mongoose?
Simplest is using find
User.
find({ "referredBy" : "xxxxxxxxxxxx" }).
exec(function (err, users) {
if (err) return handleError(err);
console.log('The users are an array: ', users);
});
Refer to https://mongoosejs.com/docs/populate.html
If you want to convert bellow function to static method inside UserSchema, please refer to this https://mongoosejs.com/docs/api.html#schema_Schema-static and https://mongoosejs.com/docs/2.7.x/docs/methods-statics.html

mongoose extract nested arrays from multiple objects into one array

const userSchema = new Schema(
{
_id: Schema.Types.ObjectId,
name: String,
posts: [{ type: Schema.Types.ObjectId, ref: "Post" }],
following: [{ type: Schema.Types.ObjectId, ref: "User" }]
}
};
I want to extract all the posts from all the Users in the 'following' array, put them into one single array, sort them and then display the first 20. I was wondering if that is possible within the cursor or if I have to load it into memory.
function createFeed(user) {
User.findOne({ name: user })
.populate({
path: "following",
populate: {
path: "posts"
}
})
//put all the posts into one array
.sort(...) //sort by time created
.limit(...) //only get the newest n posts
.exec((err, result) => {
if (err) console.log("error", err);
console.log("result", //sorted tweets array);
});
};
(I don't want to filter all the posts in my 'Posts' collection to check if they are made by the user since that would be a lot more expensive)
You can use distinct query in mongoDB
db.User.distinct('following',{})
If you are trying to filter your populate with a condition, then you should be doing this:
User.findOne({ name: user })
.populate({
path: 'posts',
match: { user: 'XXX' }
})
Even more better would be to query the posts with the user filter condition and then populate user details.

Query and map one-way referenced children and sub-children from a parent with mongoose in Keystone.js

I got three models with one-to-many relationships. Simple tree. What I need is a simple, efficient way to query a structured relationship tree, preferably similar to mongoose's .populate() which I cant't use since I don't have id's on the parent model. I suppose keeping children ids on parent would be efficient, but Keystone doesn't provide this functionality by default and I am unable to write an update callback to control relational changes. I tried and wasted too much time, finding myself astray while maybe what I'm trying to achieve is much easier, but I just can't see it.
Here's the stripped code:
Category model
Category.add({
name: { type: String}
});
Category.relationship({ path: 'sections', ref: 'Section', refPath: 'category' });
Section model, child of a category
Section.add({
name: { type: String, unique: true, required: true}
category: { type: Types.Relationship, ref: 'Category', many: false}
});
Section.relationship({ path: 'articles', ref: 'Article', refPath: 'section'});
Article model, child of the Section
Article.add({
name: { type: String, required: true}
section: { type: Types.Relationship, ref: 'Section', many: false }
});
I want to get a structured view of a category with all children and their respective sub-children like this:
[ { _id: 57483c6bad451a1f293486a0,
name: 'Test Category',
sections: [
{ _id: 57483cbbad451a1f293486a1,
name: 'Test Section',
articles: [
{ _id: 57483c6bad451a1f293486a0,
name: 'Test Category' }
]
]
} ]
So that's how I did it. Not at all efficient but at least it's working. I didn't put anything in first-level parent since I need only one.
// Load current category
view.on('init', function (next) {
var q = keystone.list('Category').model.findOne({
key: locals.filters.category
});
q.exec(function (err, result) {
if (err || !results.length) {
return next(err);
}
locals.data.category = result;
locals.section = locals.data.category.name.toLowerCase();
next(err);
});
});
// Load sections and articles inside of them
view.on('init', function (next) {
var q = keystone.list('Section').model.find().where('category').in([locals.data.category]).sort('sortOrder').exec(function(err, results) {
if (err || !results.length) {
return next(err);
}
async.each(results, function(section, next) {
keystone.list('Article').model.find().where('section').in([section.id]).sort('sortOrder').exec(function(err, articles){
var s = section;
if (articles.length) {
s.articles = articles;
locals.data.sections.push(s);
} else {
locals.data.sections.push(s);
}
});
}, function(err) {
next(err);
});
next(err);
});
});
But now I'm getting another issue. I'm using Jade 1.11.0 for templates and sometimes it doesnt't show the data in the view.
I will post another question for this issue.

Removing one-one and one-many references - Mongoose

I have an Assignment schema which has references to Groups and Projects.
Assignment == Group [One-One Relationship]
Assignment == Projects [One-Many Relationship]
Below is my Asssignment Schema
var AssignmentSchema = new Schema({
name: String,
group: {
type: Schema.Types.ObjectId,
ref: 'Group'
},
projects: [{type: mongoose.Schema.Types.ObjectId, ref: 'Project'}],
});
If a Group/Project is removed, how can i update my Assignment Schema.
var ProjectSchema = new Schema({
name: String
});
var GroupSchema = new Schema({
name: String
});
From couple of answers in stackoverflow, i came to know about the remove middleware, but i am not sure how to implement it for one-one and one-many relationship. Can anyone show me an example of doing it.
ProjectSchema.pre('remove', function(next){
this.model('Assignment').update(
);
});
Relationships:
A one-to-one is a relationship such that a state has only one
capital city and a capital city is the capital of only one state
A one-to-many is a relationship such that a mother has many
children, and the children have only one mother
A many-to-many is a relationship such that a book can be written by
several authors or co-authors, while an author can write several
books.
one-one relationship - If a Project/Group is removed, how can i update my Assignment Schema.
Typically you will have one project mapped to one assignment and similarly one assignment mapped to one project. what you can do here is removing a project and then find the associated project in assignment model and remove their references.
delete: function(req, res) {
return Project.findById(req.params.id, function(err, project){
return project.remove(function(err){
if(!err) {
Assignment.update({_id: project.assignment}},
{$pull: {projects: project._id}},
function (err, numberAffected) {
console.log(numberAffected);
} else {
console.log(err);
}
});
});
});
}
one-many relationship - If a Project/Group is removed, how can i update my Assignment Schema.
In this scenario we are removing a project and then finding all the assignments which belongs to this project and removing its reference from them. Here the situation is, there can be many assignments for a single project.
delete: function(req, res) {
return Project.findById(req.params.id, function(err, project){
return project.remove(function(err){
if(!err) {
Assignment.update({_id: {$in: project.assingments}},
{$pull: {project: project._id}},
function (err, numberAffected) {
console.log(numberAffected);
} else {
console.log(err);
}
});
});
});
}
Remove middleware
You could achieve the same thing via middleware as pointed out by Johnny, just a correction on that..
ProjectSchema.pre('remove', function (next) {
var project = this;
project.model('Assignment').update(
{ projects: {$in: project.assignments}},
{ $pull: { project: project._id } },
{ multi: true },
next
);
});
Typically there can be many projects belonging to an assignment and many assignments belonging to the same project. You will have an assignment column in your Project Schema where one project will relate to multiple assignments.
Note: remove middleware won't work on models and it would only work on your documents. If you are going with remove middleware ensure in your delete function, you find project by id first and then on the returned document apply the remove method, so for the above to work... your delete function would look like this.
delete: function(req, res) {
return Project.findById(req.params.id, function(err, project){
return project.remove(function(err){
if(!err) {
console.log(numberAffected);
}
});
});
}
In the remove middleware, you're defining the actions to take when a document of the model for that schema is removed via Model#remove. So:
When a group is removed, you want to remove the group reference to that group's _id from all assignment docs.
When a project is removed, you want to remove the projects array element references to that project's _id from all assignment docs.
Which you can implement as:
GroupSchema.pre('remove', function(next) {
var group = this;
group.model('Assignment').update(
{ group: group._id },
{ $unset: { group: 1 } },
{ multi: true },
next);
});
ProjectSchema.pre('remove', function (next) {
var project = this;
project.model('Assignment').update(
{ projects: project._id },
{ $pull: { projects: project._id } },
{ multi: true },
next);
});

Mongoose populate sub-sub document

I have this setup in my MongoDB
Items:
title: String
comments: [] // of objectId's
Comments:
user: ObjectId()
item: ObjectId()
comment: String
Here's my Mongoose schema:
itemSchema = mongoose.Schema({
title: String,
comments: [{ type: Schema.Types.ObjectId, ref: 'comments' }],
});
Item = mongoose.model('items', itemSchema);
commentSchema = mongoose.Schema({
comment: String,
user: { type: Schema.Types.ObjectId, ref: 'users' },
});
Comment = mongoose.model('comments', commentSchema);
This is where I get my items along with the comments:
Item.find({}).populate('comments').exec(function(err, data){
if (err) return handleError(err);
res.json(data);
});
How do I populate the comments array with it's respective user? Since each comment has a user ObjectId()?
One more way (easier) to do this:
Item
.find({})
.populate({
path: 'comments',
populate: { path: 'user',
model: 'users' }
})
.exec(function(err, data){
if (err) return handleError(err);
res.json(data);
});
As a complete example calling populate on the result objects:
Item.find({}).populate("comments").exec(function(err,data) {
if (err) return handleError(err);
async.forEach(data,function(item,callback) {
User.populate(item.comments,{ "path": "user" },function(err,output) {
if (err) throw err; // or do something
callback();
});
}, function(err) {
res.json(data);
});
});
The call to .populate() in the form invoked from the model takes either a document or an array as it's first argument. So you loop through the returned results for each item and call populate this way on each "comments" array. The "path" tells the function what it is matching.
This is done using the "async" version of forEach so it is non-blocking, but generally after all the manipulation all of the items in the response are not only populated with comments but the comments themselves have the related "user" details.
Simpler
Item
.find({})
.populate({
path: 'comments.user',
model: 'users' }
})
.exec(function(err, data){
if (err) return handleError(err);
res.json(data);
});
To add one final method that people may want to use to select only particular fields from sub-documents, you can use the following 'select' syntax:
Model.findOne({ _id: 'example' })
.populate({
path: "comments", // 1st level subdoc (get comments)
populate: { // 2nd level subdoc (get users in comments)
path: "user",
select: 'avatar name _id'// space separated (selected fields only)
}
})
.exec((err, res) => {
// etc
});
I use this:
.populate({
path: 'pathName',
populate: [
{
path: 'FirstSubPathName',
model: 'CorrespondingModel',
},
{
path: 'OtherSubPathName',
model: 'CorrespondingModel',
},
{
path: 'AnotherSubPathName',
model: 'CorrespondingModel',
},
]
});
it's the more easier way that i find to do this.I expect to help. :)
To populate sub-sub document and populate from multiple schemas
ProjectMetadata.findOne({id:req.params.prjId})
.populate({
path:'tasks',
model:'task_metadata',
populate:{
path:'assigned_to',
model:'users',
select:'name employee_id -_id' // to select fields and remove _id field
}
})
.populate({
path:'client',
model:'client'
})
.populate({
path:'prjct_mgr',
model:'users'
})
.populate({
path:'acc_exec',
model:'users'
})
.populate({
path:'prj_type',
model:'project_type'
}).then ( // .. your thing
or you can do it in following manner ..
ProjectMetadata.findOne({id:req.params.prjId})
.populate(
[{
path:'tasks',
model:TaskMetadata,
populate:[{
path:'assigned_to',
model:User,
select:'name employee_id'
},
{
path:'priority',
model:Priority,
select:'id title'
}],
select:"task_name id code assign_to stage priority_id"
},
{
path:'client',
model:Client,
select:"client_name"
},
{
path:'prjct_mgr',
model:User,
select:"name"
},
{
path:'acc_exec',
model:User,
select:'name employee_id'
},
{
path:'poc',
model:User,
select:'name employee_id'
},
{
path:'prj_type',
model:ProjectType,
select:"type -_id"
}
])
I found the second method (of using array) more useful when I had to get multiple sub-sub documents of same parent.
do it try it`s working
find Project and get project related populate Task and Perticular Task User find
db.Project.find()
.populate({
path: 'task',
populate: { path: 'user_id'}
})
.exec(async(error,results)=>{
})
You can also populate subdocument by this in mongoose -
Item.find({}).populate("comments.user")
Check the screenshot below. This thing works like charm!!!
This worked for me:
i.e. no need for model
.populate({
path: 'filters',
populate: {
path: 'tags',
populate: {
path: 'votes.user'
}
}
})
.populate({
path: 'members'
})

Categories