4

I am trying to update a MongoDB document with the following code:

exports.update = function (req, res) { if (req.body._id) { delete req.body._id; } Product.findById(req.params.id, function (err, product) { if (err) { return handleError(res, err); } if (!product) { return res.send(404); } var updated = _.merge(product, req.body); updated.save(function (err) { if (err) { return handleError(res, err); } return res.status(200).json(product); }); }); }; 

The code executes successfully, but the existing database array values are not updated by the .save. The content of req.body is as follows (of particular note are the values in the "start" array):

{ "_id" : ObjectId("563a95d9cc2d38622b867ecf"), "productName" : "Product Name", "productVersion" : "1", "productOverview" : "Description of product.", "productManager" : ObjectId("563a90de195e72712a197d06"), "businessPriority" : "1 Must Do", "businessRank" : 2, "businessFactors" : { "growth" : true, "diversification" : true, "architecture" : false, "riskMitigation" : false, "retention" : false }, "complete" : false, "phase" : "Discovery", "comment" : [ "Discovery phase comments", "Development phase comments", "Pilot phase comments", "Pre-launch phase comments", "Post-launch phase comments" ], "finish" : [ "2015-11-30", "2016-03-31", "2016-05-31", "2016-06-30", "2016-08-31" ], "start" : [ "2015-07-01", "2015-12-01", "2016-04-01", "2016-06-01", "2016-07-01" ] } 

The .findById successfully retrieves the existing document out of the database, which contains only the "start" array:

{ "_id" : ObjectId("563a95d9cc2d38622b867ecf"), "start" : [ "07-02", "12-01", "04-01", "06-01", "07-01" ] } 

The lodash .merge function construct a correct "updated" record (which has the same data content as req.body above).

The .save executes without error, and a 200 status is returned. However, the content of the document in the database still contains the original data for the "start" element:

{ "_id" : ObjectId("563a95d9cc2d38622b867ecf"), "start" : [ "07-02", "12-01", "04-01", "06-01", "07-01" ], "businessFactors" : { "growth" : true, "diversification" : true }, "businessPriority" : "1 Must Do", "businessRank" : 2, "comment" : [ "Discovery phase comments", "Development phase comments.", "Pilot phase comments", "Pre-launch phase comments", "Post-launch phase comments" ], "finish" : [ "2015-11-30", "2016-03-31", "2016-05-31", "2016-06-30", "2016-08-31" ], "phase" : "Discovery", "productManager" : ObjectId("563a90de195e72712a197d06"), "productName" : "New Product", "productOverview" : "Description of product.", "productVersion" : "1", "__v" : 1 } 

The Mongoose Schema is as follows:

var mongoose = require('mongoose'), Schema = mongoose.Schema; var productSchema = new Schema( { productName : String, productVersion : String, productOverview : String, productManager : Schema.Types.ObjectId, businessPriority : String, businessRank : Number, businessFactors : { retention : Boolean, growth : Boolean, diversification : Boolean, architecture : Boolean, riskMitigation : Boolean }, start : [ String ], finish : [ String ], comment : [ String ], phase : String, complete : Boolean }, { collection : 'products' } ); module.exports = mongoose.model('Product', productSchema); 

Any guidance on what might be happening here? I am using MongoDb version 3.0.6 and Mongoose version 4.1.12 on NodeJS version 4.1.1 and Express version 4.13.3.

2 Answers 2

3

you could findOneAndUpdate instead of finding the id first then saving. if the id isn't there it will create a new one. If you don't want it to save a new one set upsert to false

Product.findOneAndUpdate({_id:<your id>, {$set: <your merged JSON>}, {upsert:true}, function(err, effected, raw){}); 
Sign up to request clarification or add additional context in comments.

3 Comments

A little side note. Using findOneAndUpdate will not return the updated the document but the original document.
I'll try this as a workaround. It seems that the values that are not updating correctly are arrays (primitives seem to update just fine).
This seems to have solved the problem, the values are now getting posted to the database; thanks for the suggestion. Still a bit curious as to why the original method did not work, but I can live with the alternative. As far as Thomas's comment about the value returned, that isn't an impact in my app, as I'm done with the document once it is written ... but it did confuse me a bit when debugging the code!! Thanks for the assistance.
0

Try using _.extend or _.assign instead of _.merge:

var updated = _.assign(product, req.body); 

This answer by ShitalShah highlights the differences between merge and extend:

Here's how extend/assign works: For each property in source, copy its value as-is to destination. if property values themselves are objects, there is no recursive traversal of their properties. Entire object would be taken from source and set in to destination.

Here's how merge works: For each property in source, check if that property is object itself. If it is then go down recursively and try to map child object properties from source to destination. So essentially we merge object hierarchy from source to destination. While for extend/assign, it's simple one level copy of properties from source to destination.

JSBin to illustrate the differences.

exports.update = function (req, res) { if (req.body._id) { delete req.body._id; } Product.findById(req.params.id, function (err, product) { if (err) { return handleError(res, err); } if (!product) { return res.send(404); } var updated = _.assign(product, req.body); updated.save(function (err) { if (err) { return handleError(res, err); } return res.status(200).json(product); }); }); }; 

Check the demo below.

var dest = {	foo : {	b1 : "b1 value",	b2 : "b2 value"	},	baz : {	q1 : "q1 value"	},	mofo : "mofo value" }; var src = {	foo : {	b1: "overwritten b1",	b3: "b3 value"	},	mofo : "overwritten mofo" }; var assigned = _.clone(dest); _.assign(assigned,src); console.log("assign:", assigned); var merged = _.clone(dest); _.merge(merged,src); console.log("merge:", merged); var defaulted = _.clone(dest); _.defaults(defaulted,src); console.log("defaults:", defaulted); pre.innerHTML = "assign: " + JSON.stringify(assigned, null, 4) + "</br>merge: " + JSON.stringify(merged, null, 4) + "</br>defaults: "+ JSON.stringify(defaulted, null, 4);
<script src="//cdnjs.cloudflare.com/ajax/libs/lodash.js/3.10.1/lodash.min.js"></script> <pre id="pre"></pre>

2 Comments

Thanks for the suggestion. The code segment initially came from a framework that I had used on a prior project, so I didn't modify their approach to consolidating the two objects. At any rate, the "updated" object has the correct content, it just is not making it back into the database following the 'save'. I don't see how a change to the lodash consolidation approach would address that, but I could be missing the point.
Shital, this is informative. I probably could use an .assign in this particular case, but there are other operations for which the .merge is the right approach (where I'm sending only updated parameters) and they all use this same interface. I was a bit surprised by the behavior of the .default operation, will have to contemplate that one for a bit. Appreciate the input!

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.