I’m using Mongoose version 4.6.8 and MongoLab (MLab). I have a Mongoose schema called “Group” that has a collection of User subdocuments called “teachers”:
var GroupSchema = new Schema({
//…more properties here…//
teachers: [{
type: Schema.ObjectId,
ref: 'User'
}]
});
This is a document from the “groups” collection on MongoLab:
{
//…more properties here…//
"teachers": [
{
"$oid": "5799a9c759feea9c208c004c"
}
]
}
And this is a document from the “users” collection on MongoLab:
{
//…more properties here…//
"username": "bob"
}
But if I want to get a list of Groups that have a particular teacher (User) with the username of “bob”, this doesn’t work (the list of groups is empty):
Group.find({"teachers.username": "bob"}).exec(callback);
This also returns no items:
Group.find().where('teachers.username').equals('bob').exec(callback);
How can I achieve this?
Without some more knowledge of your set up (specifically whether you want anybody named Bob or a specific Bob whose id you could pick up first) - this might be some help although I think it would require you to flatten your teachers array to just their ID's, not single-key objects.
User.findById(<Id of Bob>, function(err, user){
Group.find({}, function(err, groups){
var t = groups.map(function(g){
if(g['teachers'].indexOf(user.id))
return g
})
// Do something with t
})
})
You can use populate to do that.
Try this:
Group.find({})
.populate({
path : 'teachers' ,
match : { username : "bob" }
})
.exec(callback);
populate will populate based on the teachers field (given path) and match will return only those who have username bob.
For more information on mongoose populate options, Please read Mongoose populate documentation.
I think the solution in this case is to get a teacher’s groups through the User module instead of my first inclination which was to go through the Groups module. This makes sense because it is in line with how modern APIs represent a one-to-many relationship.
As an example, in Behance’s API, an endpoint for a user’s projects is:
GET /v2/users/user/projects
And a request to this endpoint (where the User’s username is “matiascorea”) would look like this:
https://api.behance.net/v2/users/matiascorea/projects?client_id=1234567890
So in my case, instead of finding the groups by teacher, I would need to simply find the User (teacher) by username, populate the teacher’s groups, and use them:
User.findOne({username: 'bob'})
.populate('groups')
.exec(callback);
And the API call for this would be:
GET /api/users/user/groups
And a request to this endpoint would look like this:
https://example.com/api/users/bob/groups
Related
I need to find and join another collection to get the businesses data from businesses collection and the profile description which is saved in the profiles collection. Latest version of nodejs and mongoose.
businesses = await Business.find({}, "business_id name industry")
.limit(limit * 1)
.skip((page - 1) * limit)
.exec();
That is the code, which I need later also for the pagination.
Now I found a solution with $Lookup in Mongoose. My code looks like
Business.aggregate([{
$lookup: {
from: "profiles", // collection name in db
localField: "business_id",
foreignField: "business_id",
as: "profile"
}
}]).exec(function(err, profile) {
console.log(profile[0]);
});
The Business and Profile is saved with the business_id in a field. So I can't work with _id from Mongoose. I never before work with mongoose and two collections.
The issue is now that the profile[0] is not connected to the correct business. So the profile is a another one as from the business find above.
I need to find the latest 10 Businesses and join to another collection and grap also the profile details. What I make wrong here, has anyone a example for this behauivor ?
Use https://mongoosejs.com/docs/populate.html
As in your case you don't have ObjectId here you can use populate-virtuals
So far you've only populated based on the _id field. However, that's sometimes not the right choice. In particular, arrays that grow without bound are a MongoDB anti-pattern. Using mongoose virtuals, you can define more sophisticated relationships between documents.
const BusinessSchema = new Schema({
name: String
});
BusinessSchema.virtual('profile', {
ref: 'Profile', // The model to use
localField: 'business_id', // Find people where `localField`
foreignField: 'business_id', // is equal to `foreignField`
// If `justOne` is true, 'members' will be a single doc as opposed to
// an array. `justOne` is false by default.
justOne: false,
options: { sort: { name: -1 }, limit: 5 } // Query options, see "bit.ly/mongoose-query-options"
});
I am configuring Mongoose to work on an existing MongoDB, that has these two collections:
Users - with fields:
_id: ObjectId
name: String
org_id: ObjectId
Organizations - with fields:
_id: ObjectId
name: String
I want to be able to populate a User document by Organization data.
So I've created these two Models:
const userSchema = new Schema({
name: String,
org_id: {
type: Schema.Types.ObjectId,
ref: 'Organization',
},
});
const User = mongoose.model('User', userSchema);
const organizationSchema = new Schema({
name: String,
code: String,
});
const Organization = mongoose.model('Organization', organizationSchema);
Since historically the ref field from User to Organization is called org_id (instead of just organization) the population of a user by the organization code is:
const user = await User.findById('5b213a69acef4ac0f886cdbc')
.populate('org_id')
.exec();
where user.org_id will be populated by Organization data. Of course I would be happier to have organization instead of org_id in both - populate method and the path (i.e. user.organizationd).
What is the proper way to achieve it without changing the existing documents?
I could create my Schema methods (instead of populate) and aliases, but I am looking for a more generic and elegant solution.
I understood that you don't want to change the existent documents, but for me, if this name of field doesn't make more sense you need to refactor.
Change the name of the field, organization instead of org_id.
For this you can use the $rename command: MongoDB $rename
db.getCollection('users').updateMany({},{$rename: { "org_id": "organization" }});
After this you will can call .populate('organization').
If it is impossible, I believe that you will not find a solution better than aliases.
Mongoose Documentation: Aliases
I will follow along your code.looks like you applied this: mongoose.Schema=Schema
you embedded Organization model into User. first lets extract organization details for each user.
//import User and Organization models
const main=async ()=>{
const user=await User.findById("placeUserId")//we get the user
const populated=await user.populate("org_id").execPopulate()//we populated organization with all properties
console.log(populated.org_id) }
in the above code, org_id was already referenced in the userSchema. we just reached org_id property and extracted. this was simple. next without changing any code in userSchema and organizationSchema i will find which user is in which organization with virtual property.
virtual property allows us to create virtual fields in the database. it is called virtual because we do not change anything. it is just a way that to see how two models are related.
for this we are gonna add a little code on the page where you defined you defined your organizationSchema file which i assume in models/organization.js. this code will describe the virtual field. it is kinda schema of the virtual field.
//models/organization.js
organizationSchema.virtual('anyNameForField',{
ref:"User", //Organization is in relation with User
localField:"_id"//field that Organization holds as proof of relation
foreignField:"org_id"//field that User holds as proof of relation
})
now time to write the function to find the user inside the organization.
const reverse=async ()=>{
const organization=await Organization.findById("")
const populated=await organization.populate("anyNameForField").execPopulate()
console.log(populated.anyNameForField) //i gave a stupid name to bring your attention.
}
very simple and elegant!
Here is my Mongoose Schema:
var SchemaA = new Schema({
field1: String,
.......
fieldB : { type: Schema.Types.ObjectId, ref: 'SchemaB' }
});
var SchemaB = new Schema({
field1: String,
.......
fieldC : { type: Schema.Types.ObjectId, ref: 'SchemaC' }
});
var SchemaC = new Schema({
field1: String,
.......
.......
.......
});
While i access schemaA using find query, i want to have fields/property
of SchemaA along with SchemaB and SchemaC in the same way as we apply join operation in SQL database.
This is my approach:
SchemaA.find({})
.populate('fieldB')
.exec(function (err, result){
SchemaB.populate(result.fieldC,{path:'fieldB'},function(err, result){
.............................
});
});
The above code is working perfectly, but the problem is:
I want to have information/properties/fields of SchemaC through SchemaA, and i don't want to populate fields/properties of SchemaB.
The reason for not wanting to get the properties of SchemaB is, extra population will slows the query unnecessary.
Long story short:
I want to populate SchemaC through SchemaA without populating SchemaB.
Can you please suggest any way/approach?
As an avid mongodb fan, I suggest you use a relational database for highly relational data - that's what it's built for. You are losing all the benefits of mongodb when you have to perform 3+ queries to get a single object.
Buuuuuut, I know that comment will fall on deaf ears. Your best bet is to be as conscious as you can about performance. Your first step is to limit the fields to the minimum required. This is just good practice even with basic queries and any database engine - only get the fields you need (eg. SELECT * FROM === bad... just stop doing it!). You can also try doing lean queries to help save a lot of post-processing work mongoose does with the data. I didn't test this, but it should work...
SchemaA.find({}, 'field1 fieldB', { lean: true })
.populate({
name: 'fieldB',
select: 'fieldC',
options: { lean: true }
}).exec(function (err, result) {
// not sure how you are populating "result" in your example, as it should be an array,
// but you said your code works... so I'll let you figure out what goes here.
});
Also, a very "mongo" way of doing what you want is to save a reference in SchemaC back to SchemaA. When I say "mongo" way of doing it, you have to break away from your years of thinking about relational data queries. Do whatever it takes to perform fewer queries on the database, even if it requires two-way references and/or data duplication.
For example, if I had a Book schema and Author schema, I would likely save the authors first and last name in the Books collection, along with an _id reference to the full profile in the Authors collection. That way I can load my Books in a single query, still display the author's name, and then generate a hyperlink to the author's profile: /author/{_id}. This is known as "data denormalization", and it has been known to give people heartburn. I try and use it on data that doesn't change very often - like people's names. In the occasion that a name does change, it's trivial to write a function to update all the names in multiple places.
SchemaA.find({})
.populate({
path: "fieldB",
populate:{path:"fieldC"}
}).exec(function (err, result) {
//this is how you can get all key value pair of SchemaA, SchemaB and SchemaC
//example: result.fieldB.fieldC._id(key of SchemaC)
});
why not add a ref to SchemaC on SchemaA? there will be no way to bridge to SchemaC from SchemaA if there is no SchemaB the way you currently have it unless you populate SchemaB with no other data than a ref to SchemaC
As explained in the docs under Field Selection, you can restrict what fields are returned.
.populate('fieldB') becomes populate('fieldB', 'fieldC -_id'). The -_id is required to omit the _id field just like when using select().
I think this is not possible.Because,when a document in A referring a document in B and that document is referring another document in C, how can document in A know which document to refer from C without any help from B.
Working on a project in KeystoneJS and I'm having trouble figuring out the mongoose relationship bit.
According to the keystone docs, let's say we have the following models: User and Post. Now a post has a relationship to a user, so I'll write:
Post.add({
author: { type: Types.Relationship, ref: 'User' }
});
and then:
User.relationship({ path: 'posts', ref: 'Post', refPath: 'author' });
Now, I want to be able to see all posts related to that User without having to query for both a User and Posts. For example, if I queried for a user object I would like to be able to do user.posts and have access to those related posts. Can you do this with mongoose/keystone?
As far as I understand, keystone's List Relationship has nothing to do with mongoose and querying. Instead, it is used by keystone's admin UI to build out the relationship queries before rendering them in the view. This said I would forget User.relationship(...); solving your problem, although you want it for what I just mentioned.
The following should work fine based on your schema, but only populates the relationship on the one side:
var keystone = require('keystone');
keystone.list('Post').model.findOne().populate('author', function (err, doc) {
console.log(doc.author.name); // Joe
console.log(doc.populated('author')); // '1234af9876b024c680d111a1' -> _id
});
You could also try this the other way, however...
keystone.list('User').model.findOne().populate('posts', function (err, doc) {
doc.posts.forEach(function (post) {
console.log(post);
});
});
...mongoose expects that this definition is added to the Schema. This relationship is added by including this line in your User list file:
User.schema.add({ posts: { type: Types.Relationship, ref: 'Post', many: true } })
After reading the keystone docs, this seems to be logically equivalent the mongoose pure way, User.schema.add({ posts: [{ type: Schema.Types.ObjectId, ref: 'Post' }] });. And now you are now maintaining the relationship on both lists. Instead, you may want to add a method to your keystone list.
User.schema.methods.posts = function(done){
return keystone.list('Post').model.find()
.where('author', this.id )
.exec(done);
};
By adding a method to your User list, it saves you from persisting the array of ObjectIds relating the MongoDB document back to the Post documents. I know this requires a second query, but one of these two options look to be your best bet.
I found this on their github https://github.com/Automattic/mongoose/issues/1888, check it for context, but basically says to use the keystone populateRelated() method. I tested it and does work
// if you've got a single document you want to populate a relationship on, it's neater
Category.model.findOne().exec(function(err, category) {
category.populateRelated('posts', function(err) {
// posts is populated
});
});
I'm aware the question is old but this has to be out there for further reference
I'm working with Mongoose for the first time and I'm trying to accomplish what seems to be a simple task. I have a users document that contains a clients property, which consists of an array of client id's. As an example, my document looks like this:
{
email: "nick#movementstrategy.com",
password: "$2a$10$xZVzMYgyoyT.biOMDrBlRe3HNHY5A6lXga6uc8b/cnIAX/khQ7ep2",
modified: ISODate("2013-01-16T00:13:56.894Z"),
created: ISODate("2013-01-16T00:13:56.894Z"),
_id: ObjectId("50f5f0c4d6bbbcc6ce000002"),
clients: [
"50f6e118e0ccf9a1e9000001",
"50f6e12be0ccf9a1e9000002"
],
__v: 0
}
I've created a middleware that removes dependencies for a client when I call remove();
clientSchema.pre('remove', function(next) {
Sweepstakes.remove({client_id: this._id}).exec();
Submission.remove({client_id: this._id}).exec();
// find users with this._id present in clients, and remove from array
next();
});
Now, I simply need to locate all users who have the client id present in clients, remove the id, and update the user.
I know that I could easily query for all users with the id present, and then loop through and save out each user individually... But that seems inefficient, and my gut tells me that there is a better way to accomplish what I am trying to do -- just having a hard time locating it in the documentation.
What is the most efficient way to do this using Mongoose?
Probably something like this:
Users.update({condition}, {$pull : { clients: this._id} }, function(err, numAffected) {
//handle errors and whatever
next();
});
You can add clients : {$in : [this._id]} as condition to limit the update.