I want to reference to a subdocument which is defined as a schema. Here's the example:
exam.model.js:
const answerSchema = new mongoose.Schema({
text: String,
isCorrect: Boolean,
});
const questionSchema = new mongoose.Schema({
text: String,
answers: [answerSchema],
});
const examSchema = new mongoose.Schema({
title: String,
questions: [questionSchema],
})
const ExamModel = mongoose.model("Exam", examSchema);
//...export the schemas and ExamModel...
solvedExam.model.js:
const solvedExamSchema = new mongoose.Schema({
exam: {
type: mongoose.Schema.Types.ObjectId,
ref: "Exam",
}
answers: [{
question: {
type: mongoose.Schema.Types.ObjectId,
ref: //What do I put here? "question" is not a model, it's only a sub-document schema
}
answer: {
type: mongoose.Schema.Types.ObjectId,
ref: //Same problem
}
}],
});
So as it's obvious, I want to reference to the questions and answers which are only subdocuments as a schema and NOT models. How can I reference them? Thanks.
You should declare the respective Answer and Question Schema and refence those:
const answerSchema = new mongoose.Schema({
text: String,
isCorrect: Boolean,
});
const questionSchema = new mongoose.Schema({
text: String,
answers: [answerSchema],
});
const examSchema = new mongoose.Schema({
title: String,
questions: [questionSchema],
})
const AnswerModel = mongoose.model("Answer", examSchema);
const QuestionModel = mongoose.model("Question", examSchema);
const ExamModel = mongoose.model("Exam", examSchema);
...
const solvedExamSchema = new mongoose.Schema({
exam: {
type: mongoose.Schema.Types.ObjectId,
ref: "Exam",
}
answers: [{
question: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Question'
}
answer: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Answer'
}
}],
});
I'm want to join collection mongoDB but I've 2 model in project.
ADMINDETAIL and ADMINDETAIL get UID from member.model.js .
How I populate that.
queue.model.js
var mongoose = require("mongoose");
var Schema = mongoose.Schema;
var queueSchema = Schema(
{
QUEUE: String,
UID: String,
DATETIME: String,
ADMIN_ID: String,
USERDETAIL:{
type: Schema.Types.String,
ref:"MEMBER"
},
ADMINDETAIL:{
type: Schema.Types.String,
ref:"MEMBER"
},
},
{
collection: "QUEUE"
}
);
var QUEUE = mongoose.model("QUEUE", queueSchema);
module.exports = QUEUE;
member.model.js
var mongoose = require("mongoose");
var memberSchema = mongoose.Schema(
{
UID: {type: String},
NAME: {type: String},
SURNAME: {type: String},
IDNUMBER: {type: String},
PHONE: {type: String},
ADDRESS: {type: String},
},
{
collection: "MEMBER"
}
);
var MEMBER = mongoose.model("MEMBER", memberSchema);
module.exports = MEMBER;
queue.router.js
// GET QUEUE BY USER
router.get("/byuid/:UID", (req, res) => {
var {UID} = req.params;
Queue.find({UID})
.populate({Path:"USERDETAIL",model:"MEMBER"})
.populate({Path:"ADMINDETAIL",model:"MEMBER"})
.exec((err, data) => {
if (err) return res.status(400).send(err);
return res.status(200).send(data);
});
});
Error I got.
TypeError: utils.populate: invalid path. Expected string. Got typeof `object`
change the type of filed from String to ObjectId like this:
USERDETAIL:{
type: Schema.Types.ObjectId ,
ref:"MEMBER"
},
ADMINDETAIL:{
type: Schema.Types.ObjectId ,
ref:"MEMBER"
},
},
add your new data after that you can like this for population:
.populate("USERDETAIL ADMINDETAIL")
or
.populate([{
path: 'USERDETAIL ',
model: 'MEMBER'
}, {
path: 'ADMINDETAIL',
model: 'MEMBER'
}])
I think you are missing []
I have two schemas:
const categorySchema = Schema({
slug: {
type: String,
index: true
},
tags: {
type: [Schema.Types.ObjectId],
ref: 'Tag'
}
});
and:
const tagSchema = Schema({
title: String,
type: {
type: String,
enum: ['bool', 'selectable', 'int']
},
categories: [{
type: Schema.Types.ObjectId,
ref: 'Category'
}],
possibleValues: [String]
});
Now here is the problem. When I try to populate my category instance with tags, the whole field, becomes an empty array, while when there are no .populate() statements, there are some ObjectIds there. What is the problem?
Update: here is my query:
models.Category
.findOne({_id: req.params.categoryId})
.populate('tags')
.then(category => {
console.log(category);
res.send(category.tags);
});
Category Schema :
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var categorySchema = Schema({
slug: {type: String,index: true},
tags: { type: [Schema.Types.ObjectId],ref: 'Tag'}
},{
collection:'categories'
});
var Category = mongoose.model('Category', categorySchema);
Tag Schema :
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var tagSchema = Schema({
title: String,
type: {
type: String,
enum: ['bool', 'selectable', 'int']
},
categories: [{
type: Schema.Types.ObjectId,
ref: 'Category'
}],
possibleValues: [String]
},{
collection:'tags'
});
var Tag = mongoose.model('Tag', tagSchema);
Find Query :
Category.
findOne({_id: req.params.categoryId}).
populate('tags').
exec(function (err, categories) {
return res.json(categories);
});
I think you need to change the schema definition of category.tags to this:
tags: [{
type: Schema.Types.ObjectId,
ref: 'Tag'
}]
Im receiving a maximum call stack size exceeded error while working with mongoose and Nodejs. here is the error
RangeError: Maximum call stack size exceeded
at model.Document.$toObject (/Users/joncorrin/Desktop/workspace/MEAN/atlas-web/node_modules/mongoose/lib/document.js:2045:24)
at model.Document.toJSON (/Users/joncorrin/Desktop/workspace/MEAN/atlas-web/node_modules/mongoose/lib/document.js:2362:15)
at clone (/Users/joncorrin/Desktop/workspace/MEAN/atlas-web/node_modules/mongoose/lib/utils.js:252:18)
at cloneArray (/Users/joncorrin/Desktop/workspace/MEAN/atlas-web/node_modules/mongoose/lib/utils.js:362:14)
at clone (/Users/joncorrin/Desktop/workspace/MEAN/atlas-web/node_modules/mongoose/lib/utils.js:247:12)
at cloneObject (/Users/joncorrin/Desktop/workspace/MEAN/atlas-web/node_modules/mongoose/lib/utils.js:343:13)
at clone (/Users/joncorrin/Desktop/workspace/MEAN/atlas-web/node_modules/mongoose/lib/utils.js:260:16)
at model.Document.$toObject (/Users/joncorrin/Desktop/workspace/MEAN/atlas-web/node_modules/mongoose/lib/document.js:2092:13)
I believe this means that I'm causing an infinite loop somewhere, but I am unsure what is causing the error. The route worked correctly, then I added the mongoose-autopopulate plugin to the app. When sending a POST correctly with a token to my drive post route, I recieve this error. Nothing is logged and the server stops.
Here is an example of my route in drive.js
router.post('/', function(req, res, next){
var decoded = jwt.decode(req.query.token);
User.findById(decoded.user._id, function(err, user){
if (err) {
return res.status(500).json({
title: 'There was a server error',
error: err
});
}
var drive = new Drive({
startAddress: req.body.startAddress,
endAddress: req.body.endAddress,
tripDate: req.body.tripDate,
tripHour: req.body.tripHour,
price: req.body.price,
numberOfPassengers: req.body.numberOfPassengers,
user: user
});
drive.save(function (err, result) {
if(err) {
return res.status(500).json({
title: 'There was an error saving the drive collection',
error: err
});
}
user.drives.push(result);
user.save();
res.status(201).json({
message: 'Drive saved',
obj: result
});
});
});
});
And here is the related model
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var User = require('./user.model');
var Trip = require('./trip.model');
var autoPopulate = require('mongoose-autopopulate');
var driveSchema = new Schema({
startAddress: {type: String, required: true, lowercase: true},
endAddress: {type: String, required: true, lowercase: true},
tripDate: {type: Date, required: true},
tripHour: {type: String, required: true},
price: {type: Number, required: true},
numberOfPassengers: {type: Number, required: true},
trip: {type: Schema.Types.ObjectId, ref: 'Trip', autopopulate: true},
user: {type: Schema.Types.ObjectId, ref: 'User', autopopulate: true}
});
driveSchema.post('remove', function(drive) {
User.findById(drive.user, function(err, user) {
user.drives.pull(drive);
user.save();
});
});
driveSchema.plugin(autoPopulate);
module.exports = mongoose.model('Drive', driveSchema);
All of my models follow the same methods and queries. Is there anything in specific I am doing wrong? I looked it up and it seems that I could be calling an instance instead of JSON which breaks the code, but Im not experienced enough to identify where that instance is, or whats causing a reocurring call as .find() or .where() that I am using that is breaking it.
Here are my other models
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var autopopulate = require('mongoose-autopopulate');
var tripSchema = new Schema({
tripActivated: {type: Boolean},
tripCompleted: {type: Boolean},
driver: {type: Schema.Types.ObjectId, ref: 'Drive', autopopulate: true},
riders: [{type: Schema.Types.ObjectId, ref: 'Ride', autopopulate: true}],
comments: [{type: Schema.Types.ObjectId, ref: 'Comment', autopopulate: true}]
});
tripSchema.post('remove', function(trip) {
User.findById(trip.driver.user, function(err, user) {
user.trips.pull(trip);
user.save();
});
});
// tripSchema.post('remove', function(trip) {
// User.findById(trip.riders.user, function(err, user) {
// user.trips.pull(trip);
// user.save();
// });
// });
tripSchema.plugin(autopopulate);
module.exports = mongoose.model('Trip', tripSchema);
//// NEW MODEL
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var User = require('./user.model');
var Trip = require('./trip.model');
var autoPopulate = require('mongoose-autopopulate');
var rideSchema = new Schema({
startAddress: {type: String, required: true, lowercase: true},
endAddress: {type: String, required: true, lowercase: true},
tripDate: {type: Date, required: true},
tripHour: {type: String, required: true},
numberOfPassengers: {type: Number, required: true},
trip: {type: Schema.Types.ObjectId, ref: 'Trip', autopopulate: true},
user: {type: Schema.Types.ObjectId, ref: 'User', autopopulate: true}
});
rideSchema.post('remove', function(ride) {
User.findById(ride.user, function(err, user) {
user.rides.pull(ride);
user.save();
});
});
rideSchema.plugin(autoPopulate);
module.exports = mongoose.model('Ride', rideSchema);
////// NEW MODEL
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var User = require('./user.model');
var autoPopulate = require('mongoose-autopopulate');
var requestSchema = new Schema({
typeOfRequest: {type: String, required: true},
driver: {type: Schema.Types.ObjectId, ref: 'Drive', autopopulate: true},
rider: {type: Schema.Types.ObjectId, ref: 'Ride', autopopulate: true}
});
requestSchema.post('remove', function(request) {
User.findById(request.user, function(err, user) {
user.requests.pull(request);
user.save();
});
});
requestSchema.plugin(autoPopulate);
module.exports = mongoose.model('Request', requestSchema);
//// NEW MODEL
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var autoPopulate = require('mongoose-autopopulate');
var User = require('./user.model');
var messageSchema = new Schema ({
content: {type: String, required: true},
receiver: {type: Schema.Types.ObjectId, ref: 'User', autopopulate: true },
user: {type: Schema.Types.ObjectId, ref: 'User', autopopulate: true}
});
messageSchema.post('remove', function(message) {
User.findById(message.user, function(err, user) {
user.messages.pull(message);
user.save();
});
});
messageSchema.plugin(autoPopulate);
module.exports = mongoose.model('Message', messageSchema);
Upon diving deeper, my error seems to stem from this code that is starred in the mongoose source code in node_modules
Document.prototype.$toObject = function(options, json) {
***var defaultOptions = {
transform: true,
json: json,
retainKeyOrder: this.schema.options.retainKeyOrder,
flattenDecimals: true
};***
// _isNested will only be true if this is not the top level document, we
// should never depopulate
if (options && options.depopulate && options._isNested && this.$__.wasPopulated) {
// populated paths that we set to a document
return clone(this._id, options);
}
// When internally saving this document we always pass options,
// bypassing the custom schema options.
if (!(options && utils.getFunctionName(options.constructor) === 'Object') ||
(options && options._useSchemaOptions)) {
if (json) {
options = this.schema.options.toJSON ?
clone(this.schema.options.toJSON) :
{};
options.json = true;
options._useSchemaOptions = true;
} else {
options = this.schema.options.toObject ?
clone(this.schema.options.toObject) :
{};
options.json = false;
options._useSchemaOptions = true;
}
}
for (var key in defaultOptions) {
if (options[key] === undefined) {
options[key] = defaultOptions[key];
}
}
('minimize' in options) || (options.minimize = this.schema.options.minimize);
// remember the root transform function
// to save it from being overwritten by sub-transform functions
var originalTransform = options.transform;
options._isNested = true;
var ret = clone(this._doc, options) || {};
if (options.getters) {
applyGetters(this, ret, 'paths', options);
// applyGetters for paths will add nested empty objects;
// if minimize is set, we need to remove them.
if (options.minimize) {
ret = minimize(ret) || {};
}
}
if (options.virtuals || options.getters && options.virtuals !== false) {
applyGetters(this, ret, 'virtuals', options);
}
if (options.versionKey === false && this.schema.options.versionKey) {
delete ret[this.schema.options.versionKey];
}
var transform = options.transform;
// In the case where a subdocument has its own transform function, we need to
// check and see if the parent has a transform (options.transform) and if the
// child schema has a transform (this.schema.options.toObject) In this case,
// we need to adjust options.transform to be the child schema's transform and
// not the parent schema's
if (transform === true ||
(this.schema.options.toObject && transform)) {
var opts = options.json ? this.schema.options.toJSON : this.schema.options.toObject;
if (opts) {
transform = (typeof options.transform === 'function' ? options.transform : opts.transform);
}
} else {
options.transform = originalTransform;
}
if (typeof transform === 'function') {
var xformed = transform(this, ret, options);
if (typeof xformed !== 'undefined') {
ret = xformed;
}
}
return ret;
};
I set mongoose.set('debugger', true); to get a better error. When attempting to post on ride.js, the app registers the POST request, finds the userID, handles the new Ride (based off model), inserts the ride into the database, and then crashes immediately after.
here is the error with mongoose logs
Mongoose: users.ensureIndex({ email: 1 }, { unique: true, background: true })
Successfully connected to localhost:27017/atlas
Mongoose: users.findOne({ _id: ObjectId("59506e1629cdff044664f21c") }, { fields: {} })
(node:1219) DeprecationWarning: Mongoose: mpromise (mongoose's default promise library) is deprecated, plug in your own promise library instead: http://mongoosejs.com/docs/promises.html
Mongoose: rides.insert({ startAddress: 's', endAddress: 's', tripDate: new Date("Fri, 22 Feb 2222 00:00:00 GMT"), tripHour: '8', numberOfPassengers: 2, user: ObjectId("59506e1629cdff044664f21c"), _id: ObjectId("595078cf0b46e704c3091070"), __v: 0 })
events.js:160
throw er; // Unhandled 'error' event
^
RangeError: Maximum call stack size exceeded
at model.Document.$toObject (/Users/joncorrin/Desktop/workspace/MEAN/atlas-web/node_modules/mongoose/lib/document.js:2045:24)
at model.Document.toJSON (/Users/joncorrin/Desktop/workspace/MEAN/atlas-web/node_modules/mongoose/lib/document.js:2362:15)
at clone (/Users/joncorrin/Desktop/workspace/MEAN/atlas-web/node_modules/mongoose/lib/utils.js:253:18)
at cloneArray (/Users/joncorrin/Desktop/workspace/MEAN/atlas-web/node_modules/mongoose/lib/utils.js:363:14)
at clone (/Users/joncorrin/Desktop/workspace/MEAN/atlas-web/node_modules/mongoose/lib/utils.js:247:12)
at cloneObject (/Users/joncorrin/Desktop/workspace/MEAN/atlas-web/node_modules/mongoose/lib/utils.js:344:13)
at clone (/Users/joncorrin/Desktop/workspace/MEAN/atlas-web/node_modules/mongoose/lib/utils.js:261:16)
at model.Document.$toObject (/Users/joncorrin/Desktop/workspace/MEAN/atlas-web/node_modules/mongoose/lib/document.js:2092:13)
at model.Document.toJSON (/Users/joncorrin/Desktop/workspace/MEAN/atlas-web/node_modules/mongoose/lib/document.js:2362:15)
at clone (/Users/joncorrin/Desktop/workspace/MEAN/atlas-web/node_modules/mongoose/lib/utils.js:253:18)
at cloneObject (/Users/joncorrin/Desktop/workspace/MEAN/atlas-web/node_modules/mongoose/lib/utils.js:344:13)
at clone (/Users/joncorrin/Desktop/workspace/MEAN/atlas-web/node_modules/mongoose/lib/utils.js:261:16)
at model.Document.$toObject (/Users/joncorrin/Desktop/workspace/MEAN/atlas-web/node_modules/mongoose/lib/document.js:2092:13)
at model.Document.toJSON (/Users/joncorrin/Desktop/workspace/MEAN/atlas-web/node_modules/mongoose/lib/document.js:2362:15)
at clone (/Users/joncorrin/Desktop/workspace/MEAN/atlas-web/node_modules/mongoose/lib/utils.js:253:18)
at cloneArray (/Users/joncorrin/Desktop/workspace/MEAN/atlas-web/node_modules/mongoose/lib/utils.js:363:14)
**********UPDATE
After doing some digging, this is the code thats breaking the app
user.drives.push(result); <----------
user.save();
Its the inserting the data into mongodb and then when it tries to push to the user, it breaks. Any idea why?I added my user model for reference.
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var mongooseUniqueValidator = require('mongoose-unique-validator');
var autoPopulate = require('mongoose-autopopulate');
var userSchema = new Schema({
email: {type: String, required: true, unique: true, lowercase:true},
password: {type: String, required: true},
profileImgUrl: {type: String},
fName: {type: String},
lName: {type: String},
yearOfBirth: {type: String},
gender: {type: String},
ratings: [{type: Number}],
comments: [{type: Schema.Types.ObjectId, ref: 'Comment', autopopulate: true}],
messages: [{type: Schema.Types.ObjectId, ref: 'Message', autopopulate: true}],
rides: [{type: Schema.Types.ObjectId, ref: 'Ride', autopopulate: true}],
drives: [{type: Schema.Types.ObjectId, ref: 'Drive', autopopulate: true}],
requests: [{type: Schema.Types.ObjectId, ref: 'Request', autopopulate: true}],
trips: [{type: Schema.Types.ObjectId, ref: 'Trip', autopopulate: true}]
});
userSchema.plugin(mongooseUniqueValidator);
userSchema.plugin(autoPopulate);
module.exports = mongoose.model('User', userSchema);
The issue was in my user model. I was using the mongoose-autopopulate plugin and setting autopopulate to true objects that had a user instance in them.
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var mongooseUniqueValidator = require('mongoose-unique-validator');
var autoPopulate = require('mongoose-autopopulate');
var userSchema = new Schema({
email: {type: String, required: true, unique: true, lowercase:true},
password: {type: String, required: true},
profileImgUrl: {type: String},
fName: {type: String},
lName: {type: String},
yearOfBirth: {type: String},
gender: {type: String},
ratings: [{type: Number}],
comments: [{type: Schema.Types.ObjectId, ref: 'Comment', autopopulate: true}],
messages: [{type: Schema.Types.ObjectId, ref: 'Message', autopopulate: true}],<---------
rides: [{type: Schema.Types.ObjectId, ref: 'Ride', autopopulate: true}],<---------
drives: [{type: Schema.Types.ObjectId, ref: 'Drive', autopopulate: true}],<-----------
requests: [{type: Schema.Types.ObjectId, ref: 'Request', autopopulate: true}],
trips: [{type: Schema.Types.ObjectId, ref: 'Trip', autopopulate: true}]
});
userSchema.plugin(mongooseUniqueValidator);
userSchema.plugin(autoPopulate);
module.exports = mongoose.model('User', userSchema);
I was also setting autopopulate to true on those models. This was calling an infinite loop between populating the model given, and the user model.
If anyone is having this issue. Don't be like me and post a bunch of code on stack overflow. Figure out what you're calling that's recalling the call. In my case, I was calling autopopulate on two models that would communicate back and forth.
Here is my test code which I can not figure out why it isn't working, as it is very similar to test 'populating multiple children of a sub-array at a time'.
var mongoose = require('mongoose'),
Schema = mongoose.Schema,
ObjectId = Schema.ObjectId;
mongoose.connect('mongodb://localhost/testy');
var UserSchema = new Schema({
name: String
});
var MovieSchema = new Schema({
title: String,
tags: [OwnedTagSchema]
});
var TagSchema = new Schema({
name: String
});
var OwnedTagSchema = new Schema({
_name: {type: Schema.ObjectId, ref: 'Tag'},
_owner: {type: Schema.ObjectId, ref: 'User'}
});
var Tag = mongoose.model('Tag', TagSchema),
User = mongoose.model('User', UserSchema),
Movie = mongoose.model('Movie', MovieSchema);
OwnedTag = mongoose.model('OwnedTag', OwnedTagSchema);
User.create({name: 'Johnny'}, function(err, johnny) {
Tag.create({name: 'drama'}, function(err, drama) {
Movie.create({'title': 'Dracula', tags:[{_name: drama._id, _owner: johnny._id}]}, function(movie) {
// runs fine without 'populate'
Movie.find({}).populate('tags._owner').run(function(err, movies) {
console.log(movies);
});
});
})
});
Produced error is
node.js:201
throw e; // process.nextTick error, or 'error' event on first tick
^
TypeError: Cannot call method 'path' of undefined
at /Users/tema/nok/node_modules/mongoose/lib/model.js:234:44
Update
Got rid from OwnedTag and rewrote MovieSchema like this
var MovieSchema = new Schema({
title: String,
tags: [new Schema({
_name: {type: Schema.ObjectId, ref: 'Tag'},
_owner: {type: Schema.ObjectId, ref: 'User'}
})]
});
Working code https://gist.github.com/1541219
Your variable OwnedTagSchema must be defined before you use it or you'll end up doing basically this:
var MovieSchema = new Schema({
title: String,
tags: [undefined]
});
Move it above MovieSchema definition.
I would expect your code to work, too. Does it work if you put the OwnedTag right in MovieSchema, like so?
var MovieSchema = new Schema({
title: String,
tags: [{
_name: {type: Schema.ObjectId, ref: 'Tag'},
_owner: {type: Schema.ObjectId, ref: 'User'}
}]
});
edit:
var MovieSchema = new Schema({
title: String,
tags: [{ type: Schema.ObjectId, ref: 'OwnedTag' }]
});