LearnBoost/mongoose — Issue #734: Using find, setting a field, save with large nested field
Author: roblevintennis
Created: 2012-02-14 01:23:16+00:00
State: open

Essentially, this bug shows up when I call say findOne, then call a setter on the document returned, then save. If I later query in the CLI repl I don't see the change. Details...

Reproduce the problem?

1. Find a document via findOne, etc.
2. Set a deeply nested field like a tree on the doc returned and save.

Here's an example. The commented code that calls doc.save doesn't work properly for me whereas the Model.update one does. Interestingly, the stringify logging shows my newly inserted node in the tree, etc., as expected. It's not until I go to the repl or query later to verify that I find the record does not reflect my changes.

Taxonomy.findTaxonomyByName('site_sections', function(doc) { // uses findOne
if(doc) {
// Grab the tree and add the section node
tree = _.extend(tree, doc.data); // uses underscore to clone
tax.setTree(tree);
newNode = tax.createNode(null, req.body.name);
tax.insert(newNode, req.body.parent);
tree = tax.getTree();
// This way works
Taxonomy.update({name:'site_sections'}, {$set: { data: tree }}, {upsert: true, multi: true}, function(err) {
if (err) {
console.log('error')
res.send("Error saving section", 500);
} else {
console.log('Successfully saved section taxonomy.');
res.json({sections: doc.data}, 200);
}
});
/*
This was does not work .. later if I query in repl etc., I do NOT see my added node
doc.save(function(err) {
if (err) {
console.log('error')
res.send("Error saving section", 500);
} else {
console.log('Successfully saved section taxonomy.');
console.log('after saving doc is:');
console.log(JSON.stringify(doc, null, ' ')); // I see the new node in tree, etc., but later in repl I don't
res.json({sections: doc.data}, 200);
}

});
*/
}
});
}

Expected result:
Given that there's tons of documentation and examples using the nested find .. save idiom, I would expect that it would work fine and I should see my updated document in repl. It feels a bit clumsy to find a document, use some of it's data to create new data, call a setter, but then have to use a Model static to update. I already have the document and should be able to call save. However, I can live with this fact if the documentation is clearer about this requirement. (I should note that when I did this with a simple setter like doc.field = 'new data' it worked fine), and it was only for the deeply nested tree that this happened.

My env:
npm - mongoose@2.5.7
Software Mac OS X Lion 10.7.2 (11C74)
15-inch Early 2008

I would consider this fixed if the documentation did not use this idiom all over the place (find, set, save) and called out the fact that update is required for more complex structures.

Note that I'm using a small library I created to create the tree and it's an npm available here:
https://github.com/roblevintennis/Taxonomy
http://search.npmjs.org/#/taxonomy
tax = require('taxonomy');

But here is the tree data as I have before inserting a node in case you'd prefer to manipulate yourself:

{"data":[{"children":[{"children":[{"children":[],"data":"Adults","_id":"tax_41329060776702","attr":{"id":"tax_41329060776702","data-slug":"adults"},"isLeaf":true}],"data":"Wilson","_id":"tax_11329060776701","attr":{"id":"tax_11329060776701","data-slug":"wilson"},"isLeaf":false},{"children":[{"children":[],"data":"Seniors","_id":"tax_51329060776702","attr":{"id":"tax_51329060776702","data-slug":"seniors"},"isLeaf":true}],"data":"Penn","_id":"tax_21329060776701","attr":{"id":"tax_21329060776701","data-slug":"penn"},"isLeaf":false},{"children":[{"children":[],"data":"Juniors","_id":"tax_61329060776702","attr":{"id":"tax_61329060776702","data-slug":"juniors"},"isLeaf":true}],"data":"Dunlop","_id":"tax_31329060776702","attr":{"id":"tax_31329060776702","data-slug":"dunlop"},"isLeaf":false},{"_id":"tax_71329060776702","attr":{"id":"tax_71329060776702","data-slug":"penn"},"children":[{"_id":"tax_81329060776702","attr":{"id":"tax_81329060776702","data-slug":"juniors"},"children":[],"isLeaf":true}],"isLeaf":false}],"data":"Tennis","state":"open","attr":{"class":"root","data-slug":"tennis","id":"tax_01329060776701"},"_id":"tax_01329060776701","isLeaf":false},{"children":[{"children":[],"data":"Nike","attr":{"class":"soccer-brands","id":"tax_111329060776702","data-slug":"nike"},"_id":"tax_111329060776702","isLeaf":true},{"children":[],"data":"Adidas","attr":{"class":"soccer-brands","id":"tax_101329060776702","data-slug":"adidas"},"_id":"tax_101329060776702","isLeaf":true}],"data":"Soccer","state":"open","_id":"tax_91329060776702","attr":{"id":"tax_91329060776702","data-slug":"soccer"},"isLeaf":false}]}

Also, if you must see findTaxonomyByName that's being called above, that code is:

Taxonomy.statics.findTaxonomyByName = function (name, cb) {
var query = mongoose.model('Taxonomy').findOne({'name': name});
query.exec(function (err, doc) {
if(noError(err, 'Taxonomy.findByName .. error trying to find taxonomy by name')) {
if(doc) {
cb(doc);
} else {
cb(null);
}
} else {
cb(null);
}
});
};