RE: http://stackoverflow.com/questions/24190478/cast-plain-object-to-mongoose-document
When mongoose loads data from the database, it converts (casts) that data into instances of mongoose Document. It would be nice if this functionality was exposed to the outside world, or if mongoose would provide some sort of best practices recommendation. I am posting this request because other users on SO voted for it.
There's a Model.hydrate()
function in 4.0.0-rc1 that should be helpful. See unstable docs. Does this meet your use case?
That is exactly what I would expect. Thank you!
I prefer the way you have it implemented: no fields are marked as modified initially. It would be handy to have a second parameter which would flip that logic. For example, my front end code keeps track of dirty fields and posts only those fields to the update service. My backend assumes that any fields passed to it are modified and should be persisted to the database. I know not all services work this way, but it is certainly a use case and would save me (and others) from looping over all the fields.
Can you give any insight on how validation would work. What if I hydrate a document with only modified fields per my use case above? Will validation fail on non-hydrated fields? Thanks.
Well, there's no way to mark fields as modified originally, because you don't know what was modified before you hydrated the raw data. Every item is in the init
state, so if you run validate()
on a document you just hydrated validation will run normally. I don't understand what you mean by non-hydrated fields though - hydrate works pretty much the same as new Model(data)
AFAIK
Thanks for the quick response. Imagine a basic Person model: _id, first_name, last_name, email - all fields are required and email is validated as a real email. A user of my app updates the email address for an existing record and clicks Save. Here is the data which is posted to the server:
{ "_id": "7qhjf...89s", "email": "[email protected]" }
My service then hydrates a Person document with _id
and email
fields only. At this point I would manually mark the email field as modified and then call save()
. I would expect the new email address to be validated and persisted to the database without triggering validation errors on the other fields. It would be analogous to the following:
Person.findById("7qhjf...89s", "email", function(err, person) {
if(err) // throw error
person.email = "[email protected]";
person.save();
});
The "second" parameter I am suggesting is for hydrate to assume that everything is modified and save us the step of marking fields as dirty. Here's a simplified implementation - extending code you referenced earlier:
Model.hydrate = function (obj, allModified) {
var model = require('./queryhelpers').createModel(this, obj);
model.init(obj);
if(allModified) {
for(var prop in obj) {
model.markModified(prop);
}
}
return model;
};
Why don't you just use new Person(req.body)
? Hydrate is mostly meant to take raw data from the database.
Correct me if I'm wrong, but mongoose would treat that object as a "new" person. Not only is isNew === true
, but the version starts at 1, the doc will be inserted vs. updated, and [who knows what else mongoose does internally with new documents]. The use case in my last example is simple and I would really use Model.update
in that scenario - so please don't get bogged down on the implementation details. Here's the broader use case which drove me to create the SO question I referenced earlier:
I have data: a plain 'ol javascript object. At one point in time this object came from a mongo database - this I know for sure. The data has been to the front end, around the world, and back to me for persisting into the originating database. Before I update the original record, I need to instantiate a mongoose document, call some custom methods, trigger some custom validation, and [who knows what else], and finally call save()
.
As it stands, the hydrate()
method will allow for this... but hydrate seems to assume that the data is coming directly from the database: none of the fields have been modified yet, all paths will undergo validation on save, etc. What about data that came from [anywhere _not_ the database] but we know originated in the database? The data may or may not have all fields with it.
Ahhhh I see your point. So how hydrate works is that all fields are in "init" state when the model is hydrated, which means validation runs as shown in the commit I just pushed, but no fields are marked as dirty to be saved to the db initially, unless you make changes. IMO this makes sense with how hydrate() is supposed to work: its assumed the data is saved in the db and you got it using the node driver or some other tool.
I think a better tool for your use case would be to simply look up the document from the db by its id, no?
Yes, which is currently what happens: look up a document, update some fields, save(). I guess this is the best pattern. Another test you might consider is if the "food" field is required and you hydrate a document without it. I would expect a validation failure there too, right?
If I can imagine a use case where I wouldn't want the error (because the document in the database already contains a valid "food" property), I will post it here. Otherwise I'm good to close this one out. Thanks for the discussion, and thanks again for a wonderful open source project.
IMO that's the best pattern. If you really want to avoid the overhead of loading the doc, I'd recommend you just use new MyModel(req.body)
and manually set isNew
to false. There's also the alternative of using 4.0's update validators.
As for not wanting the error, that's really a case where you should load the doc from the database. You're going to have to ask the database at some point whether there's a valid food
property or not, and right now the best way to do that with mongoose is to load the doc. You can get more fine-grained control by asking the node driver to do a count()
if you really only want to know if one doc has one property, but this only gives you a performance benefit in a really limited use case.