0

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);
6
  • What do you mean "recursive API call"? Do you think you could highlight that particular code? Have you tried a debugger? The thing that immediately bothers me is I see a "post remove" hook defined twice and I also wonder if your managed to do this in a circular way by doing the same thing on the other schema not shown here. Please try to narrow down where the problem occurs first, and also look at those other schema in case you are doing what I think you are, Commented Jun 25, 2017 at 22:42
  • Where should I place the debugger statement? And the removal of the second post remove hook did not resolve the issue. Commented Jun 26, 2017 at 2:03
  • @NeilLunn I narrowed it to just this POST and model, still working on narrowing more. Commented Jun 26, 2017 at 2:08
  • Well if you use a debugger and simply stop execution on error then it will show you where the error occurs and you can walk back to see where the actual call is.And if you know you have a recursive call, then set a breakpoint there and step through it. But I also said it seems very suspicious that you have post remove hooks here and you probably also defined them on the related model which is being affected. And that is a sure fire way to trigger an infinite loop. But you have not updated your question with the code of those models like you were asked to. Commented Jun 26, 2017 at 2:08
  • @NeilLunn I added all my other models. Commented Jun 26, 2017 at 2:20

1 Answer 1

0

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.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.