Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

toObject/toJSON option are taken from parent for populated models #2035

Closed
wclr opened this issue Apr 21, 2014 · 2 comments
Closed

toObject/toJSON option are taken from parent for populated models #2035

wclr opened this issue Apr 21, 2014 · 2 comments
Milestone

Comments

@wclr
Copy link

wclr commented Apr 21, 2014

Nested schmema toJSON: {getters: true, virtuals: true}

model {
  nested: {type: Shema.Types.ObjectId, Ref: 'Nested'}
}

Model.findById(id).populate('nested').exec().then(function(model){
   var raw = model.toObject()

   //raw.nested.id -> undefined
  // will get raw.nested.id only if set toJSON getters: true of Model Schema
})
@bojand
Copy link

bojand commented Jul 3, 2014

Getting this too. Here's a full code to reproduce:

var mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/test');

var Schema = mongoose.Schema;

var userSchema = new Schema({
  firstName: String,
  lastName: String,
  password: String
});

userSchema.virtual('fullName').get(function () {
  return this.firstName + ' ' + this.lastName;
});

userSchema.set('toObject', { virtuals: false });

var postSchema = new Schema({
  owner: {type: Schema.Types.ObjectId, ref: 'User'},
  content: String
});

postSchema.virtual('capContent').get(function () {
  return this.content.toUpperCase();
});

postSchema.set('toObject', { virtuals: true });

var User = mongoose.model('User', userSchema);
var Post = mongoose.model('Post', postSchema);

var user = new User({ firstName: 'Joe', lastName: 'Smith', password: 'password' });

user.save(function (err, savedUser) {
  var post = new Post({ owner: savedUser._id, content: "lorem ipsum"});

  post.save(function (err, savedPost) {

    Post.findById(savedPost._id).populate('owner').exec(function (err, newPost) {
      var obj = newPost.toObject();
      console.dir(obj);
    });
  });
});

Output:

{ owner: 
   { firstName: 'Joe',
     lastName: 'Smith',
     password: 'password',
     _id: 53b4c4e2a835486d39fa5c97,
     __v: 0,
     fullName: 'Joe Smith',
     id: '53b4c4e2a835486d39fa5c97' },
  content: 'lorem ipsum',
  _id: 53b4c4e2a835486d39fa5c98,
  __v: 0,
  capContent: 'LOREM IPSUM',
  id: '53b4c4e2a835486d39fa5c98' }

fullName is still returned in the object, when that schema's toObject option is set to {virtuals: false}.

I believe this happens because of code in Document.prototype.toObject(). We create the options object from the current docs schema. Then the clone function is called with the very same options. This iteratively calls toObject() on the (user) sub document instance with the (post) parent document's toOjbect options. Then in the user's instance toObject function the options are actually {virtual: true}.

Also happens if we reverse the options. Then none of the virtuals are returned.

Not really sure what the best solution would be. Probably to somehow maybe keep wether we are setting options inline vs. schema (if inline takes presedence or something), and to save the old option before doing recursion and then reset it. Might play around with this some more.

@bojand
Copy link

bojand commented Jul 3, 2014

Was able to fix this by modifying Document.prototype.toObject to keep track of inline params (which would take precedence) and then determine what to do based on that:

Document.prototype.toObject = function (options) {
  if (options && options.depopulate && 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 && 'Object' == options.constructor.name)) {
    options = this.schema.options.toObject
      ? clone(this.schema.options.toObject)
      : {};

    options.$_inline = false;
  }
  else if (options && options.$_inline === undefined) {
    options.$_inline = clone(options); // original inline options
  }

  ;('minimize' in options) || (options.minimize = this.schema.options.minimize);

  var ret = clone(this._doc, options);

  var doVirtuals = options.virtuals;
  if (options.$_inline) {
    doVirtuals = options.$_inline.virtuals;
  }
  else if(options.$_inline == false) {
    doVirtuals = (this.schema.options.toObject && this.schema.options.toObject.virtuals === true);
  }

  if (doVirtuals) {
    applyGetters(this, ret, 'virtuals', options);
  }

 ...

Not sure if this is the best approach, but seems to work for me. I think getters are effected by similar behaviour.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants