How can I populate array of objects in mongoose? - node.js

My schema looks like:
const mock = new Schema({
category:...,
name:....,
doneBy: [{
type:mongoose.Schema.Types.ObjectId,
ref: 'User'
}]
})
Query: 'doneBy' property is an array of particular users who have done the task. When I console.log it prints only ids as "doneBy: [id1,id2,id3......]". Now I want to populate these ids to get the particular user's info. Please share the method.

You can populate it in your query like so:
mock.find({...}).populate('doneBy')
If you want to populate the users but only select certain fields (for example, the username):
mock.find({...}).populate({ path: 'doneBy', select: { username: 1 } }) // _id will also be returned by default

Related

How to find an object inside an array inside a mongoose model?

I'm trying to query an object that's inside an item which is inside a mongoose model, when I'm trying to find that object with the find() method or _.find() method, I can't get access to the object for some reason and when I console.log() it, it gives me undefined or when I use array.filter() it gives me an empty array, which means the object that I'm trying to access does not meet the criteria that I give it in the lodash find method, but then when I look at my database I see that the object does actually have the properties to meet the criteria. So I don't know what I'm doing wrong, here's my code: as you can see I'm trying to get the information of the item that the user clicked on and want to see:
router.get("/:category/:itemId", (req, res) => {
console.log(req.params.itemId);
//gives the item id that the user clicked on
console.log(req.params.category);
//gives the name of category so I can find items inside it
Category.findOne({ name: req.params.category }, (err, category) => {
const items = category.items; //the array of items
console.log(items); //gives an array back
const item = _.find(items, { _id: req.params.itemId });
console.log(item); //gives the value of 'undefined' for whatever reason
});
});
The category Schema:
const catSchema = new mongoose.Schema({
name: {
type: String,
default: "Unlisted",
},
items: [
{
name: String,
price: Number,
description: String,
img: String,
dateAdded: Date,
lastUpdated: Date,
},
],
dateCreated: Date,
lastUpdate: Date,
});
well the answer is a little bit obvious, you are using MongoDB and in Mongo you have "_ID" you can use that "_ID" only with Mongoose! so you just have to remove the underscore and that is it! do it like this const item = _.find(items, { id: req.params.itemId });
hope you are doing better.
When I look at your Schema I see that the item field is an array of objects which doesn't have an _id inside so when you create a new instance of catShema it just generates an _id field for the new instance but not for each item inside the items array, just also enter the id of the item in question because according to my understanding, you must also have a model called items in your database
When you save these records in your database, you will generate an element with this structure
{
_id: String, // auto generated
name: String,
items: [ {"name1", "price1", "description1", "imgUrl1", "dateAdded1", "lastUpdated1"},{"name2", "price2", "description2", "imgUrl2", "dateAdded1", "lastUpdated2"}, ...],
dateCreated: Date,
lastUpdate: Date
}
Note : the code provided at the top is only an illustration of the object which will be registered in the database and not a valid code
Here you can notice that there is not a field called _id in the object sent inside the database.
My suggestion to solve this issue is
To create an additional field inside the items array called _id like :
{
...,
items: [
{
_id: {
type: String,
unique : true,
required: true // to make sure you will always have it when you create a new instance
},
...
... // the rest of fields of items
},
...
Now when you create a new instance make sure in the object you enter in the database the _id is recorded when you call the catInstance.save() so inside the catInstance object you have to add the current Id of the element to be able to filter using this field.
Hope my answer helped, if you have any additional questions, please let me know
Happy coding ...

Mongoose: How to find documents by sub-collection's document property value

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

Mongoose search for an object's value in referenced property (subdocument)

I have two schemas:
var ShelfSchema = new Schema({
...
tags: [{
type: Schema.Types.ObjectId,
ref: 'Tag'
}]
});
var TagSchema = new Schema({
name: {
type: String,
unique: true,
required: true
}
});
I would like to search for all Shelves where the tags array has a tag with a specific value.
I have tried using:
modelShelf.find({tags 'tags.name': 'mytag'})...
but it does not work. It always returns an empty array.
Any idea?
Looking at db each Shelf instance links only the objectID of the tags.
I have used references because I need to work also with Tag(s) entities.
In mongoDB you essentially can't do this directly as queries target a single collection at a time. Recently there were added new features which allow some kind of join when using the aggregation framework but for your needs that is not necessary.
From your schemas I see that the tags' names are unique so you can first fetch your desired tag with something like
modelTag.find({name: 'mytag'})
in order to get the tag's ID and then query your shelf collection for this tag ID
modelShelf.find({tags: tagId})

Is there a way to populate with a projection in Mongoose?

Say I have a collection that contains a field that references documents from another collection like follows:
ClassEnrollment
_id | student | class
---------------------
and classes in the Class collection have the following schema:
_id | className | teacher | building | time | days | classNumber | description
------------------------------------------------------------------------------
If I have a set of 3000 classes I want to populate on the server I might do something like ClassEnrollment.populate(listOfClassEnrollments, {path: 'class'});
In my situation, I don't want the majority of the class fields though, just the name. If I get the list of 3000 classes from the db with all fields, I end up taking a performance hit in the form of network latency (these 3000 classes have to be transferred from the hosted db to the server, which might be 50 MB of raw data if the descriptions are long)
Is there a way to populate the list of class enrollments with just the name through an option to populate (behind the scenes I imagine it would work like a projection, so the db just responds with the class name and _id instead of all the class information)?
You can use the select option in your populate call to do this:
ClassEnrollment.populate(listOfClassEnrollments, {path: 'class', select: 'className'});
To specify multiple fields, use a space-separated list:
ClassEnrollment.populate(
listOfClassEnrollments,
{path: 'class', select: 'className classNumber'}
);
Let's say we have a very simple user & video schemas.
1) USER SCHEMA
import mongoose from "mongoose";
const { Schema, model } = mongoose;
const UserSchema = new Schema({
name: String,
email: String,
password: String,
});
export default model("User", UserSchema);
2) VIDEOS SCHEMA
import mongoose from "mongoose";
const { Schema, model } = mongoose;
const { ObjectId } = Schema.Types;
const VideoSchema = new Schema({
videoOwnerId: { type: ObjectId, ref: "User", required: true },
title: { type: String, required: true },
desc: { type: String, required: true },
});
export default model("Video", VideoSchema);
Then I want to find in Videos Collection all videos by specific user AND at the same time all information about this user(a user document from Users Collection) and use projection on it ( select specific fields )
3) Somewhere in our code (maybe in a controller)
const videos = await Video.find({ videoOwnerId: "someId214121" }).populate("videoOwnerId", "-password");
So to populate with projection you use a populate("videoOwnerId", "-password") method, when the first argument is a field you want to populate, the second argument is a projection.
To get a document with all fields but without password for example
populate("videoOwnerId", "-password")
To get only specific fields that you want(string with fields separated by whitespace)
populate("videoOwnerId", "name email")

DB-ref in mongoose without Schema.ObjectId?

in my code people can follow other people.
So far everything is ok apart from this fact: in the userScheme I have this field.
, following: [{ type: Schema.ObjectId, ref: 'Users' }]
Since every user has an username, it's more versatile for me to use dbref with the username.
is there a way to do something like this?
, following: [{ type: Users.username, ref: 'Users' }]
Many thanks,
g
No, only ObjectId values that refer to the _id property of another collection can be used as refs.
Confirmed in the source code.
You can only reference via ObjectId to a collection like your first code snippet.
[{ type: Schema.ObjectId, ref: 'Users' }]
When making a query with populate(), you can specify the fields you want to return. In your case, it woud look something like
User.find({}).populate('following', 'username').exec(function(err,doc) {});
Each object in the following array would look like
{ _id: 'xxxxxxxxxx', username: 'xxxxxxxx' }
mongoose's populate function is mongodb's lookup in the abstract but populate only creates refs to object id but if you are sure then you will have a unique username for every user and then if you want to populate data using username then you can use lookup and result will be the same.

Resources