Mongoose: Allow schema defaults to be excluded for Document creation during hydration

Created on 25 Jan 2016  路  7Comments  路  Source: Automattic/mongoose

The Problem

I have been working on a scenario which has thrown up an idea which could be useful to myself and others.

I added a created field to a schema, which has {type: Date, default: Date.now} and when I save a new document, this feature is awesome.

The issue arises because I have documents in the collection already which did not have the created field when I updated the schema. Now, whenever I do a Model.find or Model.findOne the documents which do not have the created field, they receive a default of Date.now which is not only incorrect, but a vast misrepresentation of the state of the data within my collection.

The Solution
What I would like to see can be represented by a specific case. Let's just look at the find() method for simplicity.

For reference, my Mongoose version number is 4.1.12

What I would like to be able to do, is to specify the following Schema options: {type: Date, default: Date.now, ignoreDefaultOnHydrate: true }

This in combination with a new field willBeNew on the Document(obj, fields, skipId, willBeNew) constructor will allow the default to be determined whether it's inclusion is required during $__buildDoc by allowing SchemaType.prototype.getDefault access to both the document.isNew flag along with this.options.ignoreDefaultOnHydrate.

I realise that this may be unnecessarily complicated for something which most people do not require, and maybe there is a better way for me to go about implementing this for myself, and if so, I would love to hear any suggestions, however, I found it strange, that there lies an inherent assumption when building a Document, that it will be new, when in the very next line of code, by calling init() (I am referring to the query.completeMany() ) the Document is marked isNew = false. This is the scenario I am talking about when I think we can pass a flag up to the Document constructor because we know that the document is going to be marked as not new immediately after this call.

confirmed-bug

All 7 comments

The timestamps option does this in a slightly smarter way where it gets the createdAt from the _id field if its an ObjectId.

Beyond that, I think the better way to handle this would be to improve schema.queue() so that methods can be run after doc.init() is called, so you can access isNew. The trouble, as you noticed, is that init() sets isNew() and it gets run pretty late in the game as far as document hydration goes.

@vkarpov15 Thanks for pointing me to the timestamps option, this will be really useful.

I guess the only thing which I still wonder about, is why the decision to make an assumption about Document creation isNew = true in the constructor? I am sure there is a case for a) needing to know the future use of the Document at creation and b) being able to tell and inform the document creation of it's future required use.

Surely those two things are not impossible. Anyway, thanks again for the info.

Shouldn't be impossible, just behavior that's deeply ingrained in mongoose for many years. The split between the document constructor and init() pre-dates my involvement in the project so I'm not certain of the reasoning.

So I'm going to close this one. There's several ways to work around this:

1) use the timestamps option
2) set createdAt in pre save middleware rather than as a default
3) check for isNew in the default:

default: function() {
  if (this.isNew) {
    return Date.now();
  }
  return void 0;
}

Thanks @vkarpov15 I hadn't seen the #3 option before. I think this will be how I alter future implementations.

Yeah I hadn't thought of it myself either. But when you want to use your ignoreDefaultOnHydrate, an isNew check in the default function should do the same thing. Could even write a plugin to do that.

Option 3 doesn't work @vkarpov15

Was this page helpful?
0 / 5 - 0 ratings