Permalink
Browse files

Added pre.save middleware that fixes tree after new node insertion

  • Loading branch information...
1 parent acf7691 commit 31f0b7855d6c3ae6e9dc3134d38f47655a5e2d28 @luccastera luccastera committed Dec 30, 2011
Binary file not shown.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
@@ -13,6 +13,53 @@ var NestedSetPlugin = function(schema, options) {
schema.add({ rgt: {type: Number, min: 0} });
schema.add({ parentId: {type: Schema.ObjectId} });
+ schema.pre('save', function(next) {
+ var self = this;
+ var model = mongoose.model(modelName);
+ if (self.parentId) {
+ self.parent(function(err, parentNode) {
+ if (!err && parentNode && parentNode.lft && parentNode.rgt) {
+
+ // find siblings and check if they have lft and rgt values set
+ self.siblings(function(err, nodes) {
+ if (nodes.every(function(node) { return node.lft && node.rgt;})) {
+ var maxRgt = 0;
+ nodes.forEach(function(node) {
+ if (node.rgt > maxRgt) {
+ maxRgt = node.rgt;
+ }
+ });
+ if (nodes.length === 0) {
+ // if it is a leaf node, the maxRgt should be the lft value of the parent
+ maxRgt = parentNode.lft;
+ }
+ model.update({lft: { $gt: maxRgt}}, {$inc: {lft: 2}}, {multi: true}, function(err, updatedCount) {
+ model.update({rgt: { $gt: maxRgt}}, {$inc: {rgt: 2}}, {multi: true}, function(err, updatedCount2) {
+ self.lft = maxRgt + 1;
+ self.rgt = maxRgt + 2;
+ next();
+ });
+ });
+ } else {
+ // the siblings do not have lft and rgt set. This means tree was not build.
+ // warn on console and move on.
+// console.log('WARNING: tree is not built for ' + modelName + ' nodes. Siblings does not have lft/rgt');
+ next();
+ }
+ });
+ } else {
+ // parent node does not have lft and rgt set. This means tree was not built.
+ // warn on console and move on.
+// console.log('WARNING: tree is not built for ' + modelName + ' nodes. Parent does not have lft/rgt');
+ next();
+ }
+ });
+ } else {
+ // no parentId is set, so ignore
+ next();
+ }
+ });
+
// Builds the tree by populating lft and rgt using the parentIds
schema.static('rebuildTree', function(parent, left, callback) {
@@ -61,6 +108,13 @@ var NestedSetPlugin = function(schema, options) {
return self.lft < other.lft && other.lft < self.rgt;
});
+ // returns the parent node
+ schema.method('parent', function(callback) {
+ var self = this;
+ var model = mongoose.model(modelName);
+ model.findOne({_id: self.parentId}, callback);
+ });
+
// Returns the list of ancestors + current node
schema.method('selfAndAncestors', function(callback) {
var self = this;
@@ -167,6 +167,22 @@ var tests = testCase({
});
});
},
+ 'parent should return parent node': function(test) {
+ test.expect(4);
+ User.findOne({username: 'michael'}, function(err, user) {
+ User.rebuildTree(user, 1, function() {
+ User.findOne({username: 'kelly'}, function(err, kelly) {
+ test.ok(!err)
+ test.ok(kelly)
+ kelly.parent(function(err, node) {
+ test.ok(!err);
+ test.equal('meredith',node.username)
+ test.done()
+ });
+ });
+ });
+ });
+ },
'selfAndAncestors should return all ancestors higher up in tree + current node': function(test) {
test.expect(2);
User.findOne({username: 'michael'}, function(err, user) {
@@ -344,6 +360,75 @@ var tests = testCase({
});
});
});
+ },
+ 'pre save middleware should not set lft and rgt if there is no parentId': function(test) {
+ test.expect(4);
+ var user = new User({
+ username: 'joe'
+ })
+ user.save(function(err, joe) {
+ test.ok(!err);
+ test.equal('joe', joe.username);
+ test.ok(!joe.lft);
+ test.ok(!joe.rgt);
+ test.done();
+ });
+ },
+ 'adding a new leaf node the a built tree should re-arrange the tree correctly': function(test) {
+ test.expect(22)
+ User.findOne({username: 'michael'}, function(err, michael) {
+ User.rebuildTree(michael, 1, function() {
+ User.findOne({username: 'creed'}, function(err, creed) {
+ var newUser = new User({
+ username: 'joe',
+ parentId: creed._id
+ })
+ newUser.save(function(err, joe) {
+ User.find(function(err, users) {
+ // see docs/test_tree_after_leaf_insertion.png for the graphical representation of this tree
+ // with lft/rgt values after the insertion
+ users.forEach(function(person) {
+ if (person.username === 'michael') {
+ test.equal(1, person.lft);
+ test.equal(22, person.rgt);
+ } else if (person.username === 'meredith') {
+ test.equal(2, person.lft);
+ test.equal(9, person.rgt);
+ } else if (person.username === 'jim') {
+ test.equal(10, person.lft);
+ test.equal(17, person.rgt);
+ } else if (person.username === 'angela') {
+ test.equal(18, person.lft);
+ test.equal(21, person.rgt);
+ } else if (person.username === 'kelly') {
+ test.equal(3, person.lft);
+ test.equal(4, person.rgt);
+ } else if (person.username === 'creed') {
+ test.equal(5, person.lft);
+ test.equal(8, person.rgt);
+ } else if (person.username === 'phyllis') {
+ test.equal(11, person.lft);
+ test.equal(12, person.rgt);
+ } else if (person.username === 'stanley') {
+ test.equal(13, person.lft);
+ test.equal(14, person.rgt);
+ } else if (person.username === 'dwight') {
+ test.equal(15, person.lft);
+ test.equal(16, person.rgt);
+ } else if (person.username === 'oscar') {
+ test.equal(19, person.lft);
+ test.equal(20, person.rgt);
+ } else if (person.username === 'joe') {
+ test.equal(6, person.lft);
+ test.equal(7, person.rgt);
+ }
+ });
+ test.done();
+ });
+ });
+ });
+ });
+ });
}
});

0 comments on commit 31f0b78

Please sign in to comment.