Mongoose populate within an object? - node.js

I'm not sure how to populate the sample schema below or if it is even possible. Can a reference be within an object like below? If you can, how would you populate it? E.g. .populate('map_data.location');?
var sampleSchema = new Schema({
name: String,
map_data: [{
location: {type: Schema.Types.ObjectId, ref: 'location'},
count: Number
}]
});
Or will I have to have two separate arrays for location and count like so:
// Locations and counts should act as one object. They should
// Be synced together perfectly. E.g. locations[i] correlates to counts[i]
locations: [{ type: Schema.Types.ObjectId, ref: 'location'}],
counts: [Number]
I feel like the first solution would be the best, but I'm not entirely sure how to get it working within Mongoose.
Thank you very much for any help!

The first solution is possible.
Mongoose currently has limitations (see this ticket here) populating multiple levels of embedded documents, however is very good at understanding nested paths within a single document - what you're after in this case.
Example syntax would be:
YourSchema.find().populate('map_data.location').exec(...)
Other features, such as specifying getters / setters on paths, orderBy and where clauses, etc. also accept a nested paths, like this example from the docs:
personSchema.virtual('name.full').get(function () {
return this.name.first + ' ' + this.name.last;
});
Internally Mongoose splits the string at the dots and sorts everything out for you.

First option is ok, but if someone would have problems with that query map_data.location - Mongoose returns empty array instead of object - I found that this will work:
.populate({
path: 'map_data.location',
model: 'Location'
})

YourSchema.find()
.populate({
path: 'map_data',
populate: {
path: 'location'
}
}).exec(...)
The above statement will populate array of map_data and also the location object of each map_data.
hope this helps someone.

i think you will need this:
if you schema is this:
var sampleSchema = new Schema({
name: String,
map_data: [{
location: {type: Schema.Types.ObjectId, ref: 'location'},
count: Number
}]
});
you monggosse query have to be:
const responseMap=await YourSchema.find()
.populate(
path: 'map_data._id',
model: 'location'
select:['nameLocation','geoPoints','count'],
).exec();
console.log(responseMap);
but you count variable have to be in location schema, and map_data have to containt only id location.

If someone couldn't solve his/her problem I got a solution It works for me.
My Schema:
const categorySchema = new Schema(
{
name: {
type: String,
unique: true
},
products: [{
type: Schema.Types.ObjectId,
ref: 'Product'
}]
})
And then to get a category with an specific name and with their products:
.get('/:name', async (req, res) => {
const { name } = req.params;
const responseMap = await Category.find({name})
.populate({
path: 'products',
model: 'Product',
})
.exec();
res.send(responseMap);})

The first option is the best. "count" is part of object "map_data".

Related

Self referencing an array element in a Mongoose populate query

My parent Mongoose schema, Study, looks like this:
const StudySchema = new mongoose.Schema({
series: [{
type: {
name: String,
states: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'State'
}]
}
}]
})
And the nested child schema, State , looks like this:
const State = new mongoose.Schema({
name: String,
data: number,
seriesId: mongoose.Schema.Types.ObjectId,
studyId: mongoose.Schema.Types.ObjectId,
})
I want to perform a populate like this when retrieving a study:
this.studyModel
.findById(studyId)
.select('series')
.populate([
{
path: 'series.state',
match: {
studyId: studyId,
seriesId: 'series._id', // HERE'S THE ISSUE
}
}
])
But the problem is that, in the line I've specified, I have no access to series._id since Mongoose does not understand what series is in the match context (cannot use series._id ), although it does in the path context (can use series.state).
I want to populate the state element of the state array inside series that matches the studyId (no problem) and the seriesId, but I don't know how to self reference the series in the match of the populate query.

How to populate a document inside an array inside another document?

Sorry if title looks complicated... I couldn't think of a better way to describing it.
My real case situation matches the following Schemes:
Collection1:
const mongoose = require('mongoose');
const itemSchema = mongoose.Schema({
_id: mongoose.Schema.Types.ObjectId,
name: { type: String, required: [true, 'Name is required.'] },
quantity: { type: Number, required: [true, 'Quantity is required.'] },
collection2: { type: mongoose.Schema.Types.ObjectId, ref: 'Collection2' }
}, { _id : false });
const collection1Schema = mongoose.Schema({
_id: mongoose.Schema.Types.ObjectId,
name: { type: String, required: [true, 'Name is required.'] },
imagePath: { type: String, required: [true, 'Image is required.'] },
items: [itemSchema]
});
module.exports = mongoose.model('Collection1', collection1Schema);
Note: itemsSchema is inside the collection1 file (and having no declared _id's) because they only exist for the Collection1 model (considering "quantity" and other fields I removed for simplification). This itemsScheme is not needed elsewhere as another collection.
Collection2:
const mongoose = require('mongoose');
const collection2Schema = mongoose.Schema({
_id: mongoose.Schema.Types.ObjectId,
name: { type: String, required: [true, 'Name is required.'], unique: true }
});
module.exports = mongoose.model('Collection2', collection2Schema );
Note: Other properties (such as 'imagePath') were removed for simplification.
Now, this is the query I am trying to run:
Collection1.find()
.populate({
path: 'items',
populate: {
path: 'collection2', model: 'Collection2'
}
})
.then(...)
.catch(...);
And this is the error message I am getting when I run it:
Error fetching collection1: Cast to ObjectId failed for value "{
name: 'an item name',
quantity: 750
}" at path "_id" for model "Collection1"
The exact same error happens if I just run:
Collection1.find()
.populate('items')
.then(...)
.catch(...);
Maybe I cannot run .populate('items') because it has no declared model. If this is the case, how can I populate collection2 while querying collection1? Again, I cannot consider storing items in a separated collection.
But if I run:
Collection1.find()
.populate('collection2')
.then(...)
.catch(...);
I get the items, no errors, but it doesn't populate collection2. Well, it makes sense for the items because they're just an array of a block of properties inside collection1. But what about populating collection2?
Collection2 already has a few documents added, all with their _ids and other fields well filled. In the controller, I set _id: new mongoose.Types.ObjectId(), while creating a new document for both cases, Collection1 and Collection2.
In the front-end, I create a new document for Collection1, I add items, each item with a document from Collection2, and I save everything with no errors. I also confirmed everything is been properly saved (collection1 has list of items and each item an _id reference to collection2). The only problem is populating collection2 inside this array.
I have already tried restructuring everything with _ids (including itemScheme) and dropping all collections to test it again but no success.
I have been stuck with this problem for about three days now.
Is there any special property I should be setting for populate to make it work for this specific structure?
Thanks in advance...
populate('items')
This will not work as item is not a model.
What you want is following:
Collection1.find()
.populate('items.collection2')
.then(...)
.catch(...);
This will populate collection2 in all the array elements

Mongoose populate ObjectID from multiple possible collections

I have a mongoose model that looks something like this
var LogSchema = new Schema({
item: {
type: ObjectId,
ref: 'article',
index:true,
},
});
But 'item' could be referenced from multiple collections. Is it possible to do something like this?
var LogSchema = new Schema({
item: {
type: ObjectId,
ref: ['article','image'],
index:true,
},
});
The idea being that 'item' could be a document from the 'article' collection OR the 'image' collection.
Is this possible or do i need to manually populate?
Question is old, but maybe someone else still looks for similar issues :)
I found in Mongoose Github issues this:
mongoose 4.x supports using refPath instead of ref:
var schema = new Schema({
name:String,
others: [{ value: {type:mongoose.Types.ObjectId, refPath: 'others.kind' } }, kind: String }]
})
In #CadeEmbery case it would be:
var logSchema = new Schema({
item: {type: mongoose.Types.ObjectId, refPath: 'kind' } },
kind: String
})
But I did't try it yet...
First of all some basics
The ref option says mongoose which collection to get data for when you use populate().
The ref option is not mandatory, when you do not set it up, populate() require you to give dynamically a ref to him using the model option.
#example
populate({ path: 'conversation', model: Conversation }).
Here you say to mongoose that the collection behind the ObjectId is Conversation.
It is not possible to gives populate or Schema an array of refs.
Some others Stackoverflow people asked about it.
Soluce 1: Populate both (Manual)
Try to populate one, if you have no data, populate the second.
Soluce 2: Change your schema
Create two link, and set one of them.
var LogSchema = new Schema({
itemLink1: {
type: ObjectId,
ref: 'image',
index: true,
},
itemLink2: {
type: ObjectId,
ref: 'article',
index: true,
},
});
LogSchema.find({})
.populate('itemLink1')
.populate('itemLink2')
.exec()
Dynamic References via refPath
Mongoose can also populate from multiple collections based on the value of a property in the document. Let's say you're building a schema for storing comments. A user may comment on either a blog post or a product.
body: { type: String, required: true },
on: {
type: Schema.Types.ObjectId,
required: true,
// Instead of a hardcoded model name in `ref`, `refPath` means Mongoose
// will look at the `onModel` property to find the right model.
refPath: 'onModel'
},
onModel: {
type: String,
required: true,
enum: ['BlogPost', 'Product']
}
});
const Product = mongoose.model('Product', new Schema({ name: String }));
const BlogPost = mongoose.model('BlogPost', new Schema({ title: String }));
const Comment = mongoose.model('Comment', commentSchema);

Mongoose query populate match id of find elements

I'm trying to populate a model with data from another model. Those two models looks like this:
var postSchema = mongoose.Schema({
_comments: { type: mongoose.Schema.Types.ObjectId, ref: 'Comment' },
type: String,
body: String,
});
var commentSchema = mongoose.Schema({
id_post: mongoose.Schema.Types.ObjectId,
body: String,
});
I want to find all posts and populate them with comments that have id_post == _id from founded Posts. Something like this:
Post.find({}).populate({
path: '_comments',
select: 'body',
match: { post_id: Post._id }
options: { limit: 5 }
})
.exec(function (err, posts){...});
First of all, There are few problems in the code you wrote.
If each post may have many comments you should implement one-to-many relationship between your schemas, you can do it by surrounding the comment ref with []
var postSchema = mongoose.Schema({
_comments: [ {type: mongoose.Schema.Types.ObjectId, ref: 'Comment'} ] ,
type: String,
body: String,
});
id_post is not just a field of type ObjectId, it should be written like this:
var commentSchema = mongoose.Schema({
post: { type: mongoose.Schema.Types.ObjectId, ref: 'Post' },
body: String,
});
When saving a new comment make sure you connect it to its post:
var comment = new Comment({
body: "Hello",
post: post._id // assign the _id from the post
});
comment.save(function (err) {
if (err) return handleError(err);
// thats it!
});
Now when you want to find a post and populate its comments you should write something like this:
Post
.find(...)
.populate({
path: '_comments',
select: 'body',
options: { limit: 5 }
})
.exec()
The reason I dropped the match is that match should be used when you want to filter according to a specific field, in your case you can use match to get only comments with type='something'.
populate should work because when you inserted the comment you made the bond to its post.
More information on the right way of using populate can be found here - Mongoose Query Population
Post data should be persisted the following way:
{
body: "some body",
type: "some type",
_comments: [12346789, 234567890, ...]
}
More information about the way the ref will be persisted here - One-to-Many Relationships with Document References

Using Mongoose to findbyid and update sibling

I'm trying to locate a reference to another schema and update a sibling field. Specifically, I'm trying to manipulate the hasResponded field below based on a particular 'survey' ObjectId.
My schema looks like this:
var userSchema = new mongoose.Schema({
// some other stuff
surveys: [{
survey: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Survey'
},
hasResponded: {
type: Boolean,
default: false
}
}]
});
If you have a survey id, simply search for all the users that have this particular id in the array.
Something like that:
Users.find({surveys: { "$elemMatch": { type: <ID> } } });
Then, iterate through the users and their corresponding survey array to find the ones that match the id you gave.
Got to say I would structure this db a little different if this query takes place often.
Make a new Schema - UserSurveys that holds the id of the user and the survey + hasResponded. Like this:
var UserSurveySchema = new mongoose.Schema({
user_id: {type: mongoose.Schema.Types.ObjectId, ref: 'User'},
survey_id: {type: mongoose.Schema.Types.ObjectId, ref: 'Survey'}
hasResponded: {type:Boolean, 'default':false}
...
});
You might also want to keep an index on the user and survey ids.
Then, it will be much easier to update the field, requests will take much shorter times.
Hope this helps.

Resources