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.
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