Skip to content

Commit c522bdb

Browse files
committed
Initial commit of mongoose-tree
0 parents  commit c522bdb

File tree

6 files changed

+424
-0
lines changed

6 files changed

+424
-0
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
.DS_Store
2+
node_modules/

README.md

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
## mongoose-tree
2+
3+
Implements the materialized path strategy for storing a hierarchy of documents with mongoose
4+
5+
# Usage
6+
7+
Install via NPM
8+
9+
$ npm install mongoose-tree
10+
11+
Then you can use the plugin on your schemas
12+
13+
```javascript
14+
var tree = require('mongoose-tree');
15+
16+
var UserSchema = new Schema({
17+
name : String
18+
});
19+
UserSchema.plugin(tree);
20+
var User = mongoose.model('User', UserSchema);
21+
22+
var adam = new User({ name : 'Adam' });
23+
var bob = new User({ name : 'Bob' });
24+
var carol = new User({ name : 'Carol' });
25+
26+
// Set the parent relationships
27+
bob.parent = adam;
28+
carol.parent = bob;
29+
30+
adam.save(function() {
31+
bob.save(function() {
32+
carol.save();
33+
});
34+
});
35+
```
36+
37+
At this point in mongoDB you will have documents similar to
38+
39+
{
40+
"_id" : ObjectId("50136e40c78c4b9403000001"),
41+
"name" : "Adam",
42+
"path" : "50136e40c78c4b9403000001"
43+
}
44+
{
45+
"_id" : ObjectId("50136e40c78c4b9403000002"),
46+
"name" : "Bob",
47+
"parent" : ObjectId("50136e40c78c4b9403000001"),
48+
"path" : "50136e40c78c4b9403000001.50136e40c78c4b9403000002"
49+
}
50+
{
51+
"_id" : ObjectId("50136e40c78c4b9403000003"),
52+
"name" : "Carol",
53+
"parent" : ObjectId("50136e40c78c4b9403000002"),
54+
"path" : "50136e40c78c4b9403000001.50136e40c78c4b9403000002.50136e40c78c4b9403000003"
55+
}
56+
57+
The path is used for recursive methods and is kept up to date by the plugin if the parent is changed
58+
59+
# API
60+
61+
### getChildren
62+
63+
Signature:
64+
65+
getChildren([recursive], cb);
66+
67+
if recursive is supplied and true subchildren are returned
68+
69+
Based on the above hierarchy:
70+
71+
```javascript
72+
adam.getChildren(function(err, users) {
73+
// users is an array of with the bob document
74+
});
75+
76+
adam.getChildren(true, function(err, users) {
77+
// users is an array with both bob and carol documents
78+
});
79+
```
80+
81+
### getAnsestors
82+
83+
Signature:
84+
85+
getAnsestors(cb);
86+
87+
Based on the above hierarchy:
88+
89+
```javascript
90+
carol.getAnsestors(function(err, users) {
91+
// users is an array of adam and bob
92+
})
93+
```
94+
95+
### level
96+
97+
Equal to the level of the hierarchy
98+
99+
```javascript
100+
carol.level; // equals 3
101+
```
102+
103+
# Tests
104+
105+
To run the tests install mocha
106+
107+
npm install mocha -g
108+
109+
and then run
110+
111+
mocha
112+
113+

index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
module.exports = require('./lib/tree');

lib/tree.js

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
2+
var Schema = require('mongoose').Schema;
3+
4+
module.exports = exports = tree;
5+
6+
function tree(schema, options) {
7+
schema.add({
8+
parent : {
9+
type : Schema.ObjectId,
10+
set : function(val) {
11+
if(typeof(val) === "object" && val._id) {
12+
return val._id;
13+
}
14+
return val;
15+
},
16+
index: true
17+
},
18+
path : {
19+
type : String,
20+
index: true
21+
}
22+
});
23+
24+
schema.pre('save', function(next) {
25+
var isParentChange = this.modifiedPaths.indexOf('parent') !== -1;
26+
27+
if(this.isNew || isParentChange) {
28+
if(!this.parent) {
29+
this.path = this._id.toString();
30+
return next();
31+
}
32+
33+
var self = this;
34+
this.collection.findOne({ _id : this.parent }, function(err, doc) {
35+
if(err) return next(err);
36+
37+
var previousPath = self.path;
38+
self.path = doc.path + '.' + self._id.toString();
39+
40+
if(isParentChange) {
41+
// When the parent is changed we must rewrite all children paths as well
42+
self.collection.find({ path : { '$regex' : '^' + previousPath + '.' } }, function(err, cursor) {
43+
if(err) return next(err);
44+
45+
var stream = cursor.stream();
46+
stream.on('data', function (doc) {
47+
var newPath = self.path+doc.path.substr(previousPath.length);
48+
self.collection.update({ _id : doc._id }, { $set : { path : newPath } });
49+
});
50+
stream.on('close', function() {
51+
next();
52+
});
53+
stream.on('error', function(err) {
54+
next(err);
55+
});
56+
});
57+
} else {
58+
next();
59+
}
60+
});
61+
} else {
62+
next();
63+
}
64+
});
65+
66+
schema.pre('remove', function(next) {
67+
if(!this.path) {
68+
return next();
69+
}
70+
this.collection.remove({ path : { '$regex' : '^' + this.path + '.' } }, next);
71+
});
72+
73+
schema.method('getChildren', function(recursive, cb) {
74+
if(typeof(recursive) === "function") {
75+
cb = recursive;
76+
recursive = false;
77+
}
78+
var filter = recursive ? { path : { $regex : '^' + this.path + '.' } } : { parent : this._id };
79+
return this.model(this.constructor.modelName).find(filter, cb);
80+
});
81+
82+
schema.method('getParent', function(cb) {
83+
return this.model(this.constructor.modelName).findOne({ _id : this.parent }, cb);
84+
});
85+
86+
schema.method('getAnsestors', function(cb) {
87+
if(this.path) {
88+
var ids = this.path.split(".");
89+
ids.pop();
90+
} else {
91+
var ids = [];
92+
}
93+
var filter = { _id : { $in : ids } };
94+
return this.model(this.constructor.modelName).find(filter, cb);
95+
});
96+
97+
schema.virtual('level').get(function() {
98+
return this.path ? this.path.split(".").length : 0;
99+
});
100+
}

package.json

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"author": "Brian Kirchoff <briankircho@gmail.com>",
3+
"name": "mongoose-tree",
4+
"description": "Materialized path hierarchy for mongoose",
5+
"version": "0.0.1",
6+
"engine": "node >= 0.8.0",
7+
"dependencies": {
8+
"mongoose": "~2.7.1"
9+
},
10+
"devDependencies": {
11+
"async": "~0.1.22",
12+
"should": "~1.0.0",
13+
"underscore": "~1.3.3"
14+
}
15+
}

0 commit comments

Comments
 (0)