Mongoose populate() returning empty array - mongodb

so I've been at it for like 4 hours, read the documentation several times, and still couldn't figure out my problem. I'm trying to do a simple populate() to my model.
I have a User model and Store model. The User has a favoriteStores array which contains the _id of stores. What I'm looking for is that this array will be populated with the Store details.
user.model
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
var UserSchema = new Schema({
username: String,
name: {first: String, last: String},
favoriteStores: [{type: Schema.Types.ObjectId, ref: 'Store'}],
modifiedOn: {type: Date, default: Date.now},
createdOn: Date,
lastLogin: Date
});
UserSchema.statics.getFavoriteStores = function (userId, callback) {
this
.findById(userId)
.populate('favoriteStores')
.exec(function (err, stores) {
callback(err, stores);
});
}
And another file:
store.model
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
var StoreSchema = new Schema({
name: String,
route: String,
tagline: String,
logo: String
});
module.exports = mongoose.model('Store', StoreSchema);
After running this what I get is:
{
"_id": "556dc40b44f14c0c252c5604",
"username": "adiv.rulez",
"__v": 0,
"modifiedOn": "2015-06-02T14:56:11.074Z",
"favoriteStores": [],
"name": {
"first": "Adiv",
"last": "Ohayon"
}
}
The favoriteStores is empty, even though when I just do a get of the stores without the populate it does display the _id of the store.
Any help is greatly appreciated! Thanks ;)
UPDATE
After using the deepPopulate plugin it magically fixed it. I guess the problem was with the nesting of the userSchema. Still not sure what the problem was exactly, but at least it's fixed.

I think this issue happens when schemas are defined across multiple files. To solve this, try call populate this way:
.populate({path: 'favoriteStores', model: 'Store'})

Related

MongoDB (Mongoose) data structure question

I'm curious about the best way to represent this kind of situation in Mongo. I have my own idea, but I'm curious on what the general consensus/best practice actually would be.
Imagine I have two collections:-
Employees
--> _id
--> FirstName
--> Surname
--> Email
Comments
--> _id
--> PersonReference
--> CommentDate
--> Comment
Now imagine that Employees can come and go and the 'Employees' collection is always up-to-date. However, in the event that an employee has ever made a comment, the full information on the comment including who made it must be available.
The way I would propose to tackle this problem, is to organise the structure like this instead:-
Employees
--> _id: _id
--> FirstName: string
--> Surname: string
--> Email: string
Comments
--> _id: _id
--> CommentDate: date
--> Comment: string
[-] --> PersonReference
[+] --> Employee: object { _id: id, FirstName: string, Surname: string, Email:string }
So essentially, I would have a list of 'Active Employees' and at a time where a comment is made, I would duplicate the employee information into the Comments collection document (rather than use a reference).
From a high level perspective, is this considered best practise?
Many thanks
Duplicating the employee info in the comments collection is really a bad idea.
When an employee info needs to be changed, it will also needs to be updated in the comments.
You have a few options:
1-) Embedding the comments inside the Employee schema:
In this method we have no separate Comments collection.
If you have no need to independently query comments, this method makes sense.
This way we can access a user and his/her comments in one db access and without needing any join (populate or lookup).
The schema for this can be like this:
const mongoose = require("mongoose");
const employeeSchema = new mongoose.Schema({
firstName: String,
username: String,
email: String,
comments: [
new mongoose.Schema({
commentDate: Date,
comment: String
})
]
});
module.exports = mongoose.model("Employee", employeeSchema);
2-) Parent referencing:
In this method we keep the reference of the comments in the Employee schema.
If you don't need to access to employee from a comment, this can an option.
Employee Schema:
const mongoose = require("mongoose");
const employeeSchema = new mongoose.Schema({
firstName: String,
username: String,
email: String,
comments: [
{
type: mongoose.Schema.Types.ObjectId,
ref: "Comment"
}
]
});
module.exports = mongoose.model("Employee", employeeSchema);
Comment Schema:
const mongoose = require("mongoose");
const commentSchema = new mongoose.Schema({
commentDate: Date,
comment: String
});
module.exports = mongoose.model("Comment", commentSchema);
3-) Child referencing
In this method we keep reference of the employee in the comments.
So if you need to access the comments from an employee we need to use Populate Virtual feature of mongoose. Becase in employee schema we don't have a reference to the comments.
Employee Schema:
const mongoose = require("mongoose");
const employeeSchema = new mongoose.Schema(
{
firstName: String,
username: String,
email: String
},
{
toJSON: { virtuals: true } // required to use populate virtual
}
);
// Populate virtual
employeeSchema.virtual("comments", {
ref: "Comment",
foreignField: "employee",
localField: "_id"
});
module.exports = mongoose.model("Employee", employeeSchema);
Comment Schema:
const mongoose = require("mongoose");
const commentSchema = new mongoose.Schema({
commentDate: Date,
comment: String,
employee: {
type: mongoose.Schema.Types.ObjectId,
ref: "Employee"
}
});
module.exports = mongoose.model("Comment", commentSchema);
4-) Both parent and child referencing:
With this method, it is possible to select comments from employee, and employee from comments. But here we have some kind of data duplication, and also when a comment is deleted, it needs to be done in both of the collections.
const mongoose = require("mongoose");
const employeeSchema = new mongoose.Schema({
firstName: String,
username: String,
email: String,
comments: [
{
type: mongoose.Schema.Types.ObjectId,
ref: "Comment"
}
]
});
module.exports = mongoose.model("Employee", employeeSchema);
Comment Schema:
const mongoose = require("mongoose");
const commentSchema = new mongoose.Schema({
commentDate: Date,
comment: String,
employee: {
type: mongoose.Schema.Types.ObjectId,
ref: "Employee"
}
});
module.exports = mongoose.model("Comment", commentSchema);
Many database implement kind of no-delete collections, implementing a delete/active flag for each document.
For example, Employees collection would become :
Employees
--> _id: _id
--> FirstName: string
--> Surname: string
--> Email: string
--> Active: boolean
This way, you keep track on employees data that has been deleted, and prevent documents duplication if you have database size restrictions.
PS: nowadays you can be tackled keeping user data if they ask deletion (RGPD)
EDIT: This solution with boolean may not work if Employees document is updated and you want to keep employees firstname,name,mail,etc at the time he made the Comment.

MongoDB Mongoose save object with nested objects

I'm new to MongoDB and I'm creating a simple db with Mongoose with the following models: User, Game and Players.
So, one user contains none or many games. Every game has to players, and each player refers to a user. Like this (I simplified the schemas for clarity):
const UserSchema = new Schema({
name: String,
games: [{
type: Schema.Types.ObjectId,
ref: 'game'
}]
});
const GameSchema = new Schema({
mode: Number,
players: {
type: [{
type: Schema.Types.ObjectId,
ref: 'player'
}],
required: true
}
});
const PlayerSchema = new Schema({
order: Number,
isWinner: Boolean,
user: {
type: Schema.Types.ObjectId,
ref: 'user',
required: true
}
});
So, now in the frontend I want to send a petition to the backend to create a new game for users Joe (_id:11111) and Bob (_id:22222) and so I send a POST to /api/games with the body { users: [ 11111, 22222 ] }
Now my question is, for the backend to create a new game, it also has to create 2 players. What's the best way to achieve this?
In the Game.create() method, shall I retrieve the data, create and save the players, create the game, assign the players, save the game, and also update the users and add the game ids?
I also read about Mongoose middleware, where you can set certain functions to be executed before or after some operations. So maybe it's better:
pre function before Game.create, to create the players
post function before Game.create, to update the users
This last one seems cleaner.
What's the best way? Maybe another one I have not considered?
Thanks
I would suggest you using the post and pre functions defined in the mongoose middleware. They're pretty straightforward and neat to use. It will probably solve your problem.
Here is a personal example of a problem we had; In our case, we had to assign a userId from a sequence in the database. We used the following code:
var UserSchema = new Schema({
username: { type: String, required: true, unique: true },
id: { type: String },
...
});
UserSchema.pre('save', function(next) {
let doc = this;
let id = 'userSeq'
Sequence.findByIdAndUpdate(id, { $inc : {nextSId : 1} }, function(error,data) {
if(error)
next(error)
doc.id = data.nextSId-1;
next();
})
});
My suggestion is that before you create the game, you can search for the users and add a reference to the game. If I were you, I would use the findAndModify query of mongodb to find the users or create if they do not exist yet.

Improve performance of query of database design sechema in mongodb

I have implemented mongodb schema design for groups where i have a query to get the groups of a specific user. I am thinking to store data as given schema.
For Example
var GroupSchema = new Schema({
name:String,
mememers: [{
userId: { Schema.ObjectId, ref: 'User'}
}]
});
and
var UserSchema = new Schema({
name:String,
email:String,
groups:[{ type:Schema.ObjectId, ref:'Group'}]
});
But i am still confused that what query will be efficient for the query.
var group = Group.find({ "memebers.userId": "***UserId***"});
group.exec(function(err, groups){ });
or
var user = User.findOne({_id:"***userId**"}).populate("groups");
user.exec(function(err, user){ });
Please give me the pro and cons of the both the queries.

mongoose schema multi ref for one property

How to write multi ref for one property of one mongoose schema, like this(but wrong):
var Schema = mongoose.Schema;
var PeopleSchema = new Schema({
peopleType:{
type: Schema.Types.ObjectId,
ref: ['A', 'B'] /*or 'A, B'*/
}
})
You should add string field to your model and store external model name in it, and refPath property - Mongoose Dynamic References
var Schema = mongoose.Schema;
var PeopleSchema = new Schema({
externalModelType:{
type: String
},
peopleType:{
type: Schema.Types.ObjectId,
refPath: 'externalModelType'
}
})
Now Mongoose will populate peopleType with object from corresponding model.
In the current version of Mongoose i still don't see that multi ref possible with syntax like you want. But you can use part of method "Populating across Databases" described here. We just need to move population logic to explicitly variant of population method:
var PeopleSchema = new Schema({
peopleType:{
//Just ObjectId here, without ref
type: mongoose.Schema.Types.ObjectId, required: true,
},
modelNameOfThePeopleType:{
type: mongoose.Schema.Types.String, required: true
}
})
//And after that
var People = mongoose.model('People', PeopleSchema);
People.findById(_id)
.then(function(person) {
return person.populate({ path: 'peopleType',
model: person.modelNameOfThePeopleType });
})
.then(populatedPerson) {
//Here peopleType populated
}
...

mongo mongoose populate subdocument returns null

I am trying to use node & mongoose's populate method to kind of 'join' 2 collections on query. The following is my schema setup:
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
var ShopSchema = new Schema({
ssss: { type: Schema.Types.ObjectId, required :true, ref: 'Stat' },
ratings: [RatingSchema]
});
var RatingSchema = new Schema({
stat: { type: Schema.Types.ObjectId, required :true, ref: 'Stat' }
}, {_id: false});
Also I have setup the Stat mongoose model so that the queries works without error (but the result is not what I expected).
I tried to perform the following queries:
ShopSchema.statics.load = function(id, cb) {
this.findOne({
_id: id
}).populate('ssss', '_id stat_id').exec(cb);
};
mongoose.model('Shop', ShopSchema);
This gives me the correct result and the ssss is correctly referenced.
The result is something like this .
"ssss":{"_id":"5406839ad5c5d9c5d47091f0","stat_id":1}
However, the following query gives me the wrong result.
ShopSchema.statics.load = function(id, cb) {
this.findOne({
_id: id
}).populate('ratings.stat', '_id stat_id').exec(cb);
};
mongoose.model('Shop', ShopSchema);
This gives me ratings.stat = null for all results. Could someone tell me what I did wrong? Thanks.
I just found the answer by trial and error.....
in the last example ShopSchema is declared before the RatingSchema. So I am guessing Mongoose doesn't know exactly what is happening inside RatingSchema and making the populate returns an error. So if you declare RatingSchema before the ShopSchema and the populate method is working like a charm..