Skip to content

mongoose plugin for tree hierarchy using the materialized path strategy

Notifications You must be signed in to change notification settings

jczerwinski/mongoose-path-tree

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

52 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

mongoose-path-tree

Build Status

Efficient tree structures in Mongoose using materialized paths.

Features

  • One-query retrieval of ancestors
  • Automatically updates ancestors and children on change to parent
  • Reparent children to grandparent on delete
  • Get ancestors for multiple documents in a single query

Usage

Install with npm:

$ npm install mongoose-path-tree

Use it:

var tree = require('mongoose-path-tree');

var UserSchema = new Schema({
  name : String
});
UserSchema.plugin(tree);
var User = mongoose.model('User', UserSchema);

var adam = new User({ name : 'Adam' });
var bob = new User({ name : 'Bob' });
var carol = new User({ name : 'Carol' });

// Set the parent relationships
bob.parent = adam;
carol.parent = bob;

adam.save(function() {
  bob.save(function() {
    carol.save();
  });
});

At this point in mongoDB you will have documents similar to

{
  "_id" : ObjectId("50136e40c78c4b9403000001"),
  "name" : "Adam",
  "path" : "50136e40c78c4b9403000001"
}
{
  "_id" : ObjectId("50136e40c78c4b9403000002"),
  "name" : "Bob",
  "parent" : ObjectId("50136e40c78c4b9403000001"),
  "path" : "50136e40c78c4b9403000001#50136e40c78c4b9403000002"
}
{
  "_id" : ObjectId("50136e40c78c4b9403000003"),
  "name" : "Carol",
  "parent" : ObjectId("50136e40c78c4b9403000002"),
  "path" : "50136e40c78c4b9403000001#50136e40c78c4b9403000002#50136e40c78c4b9403000003"
}

The path is used for recursive methods and is kept up to date by the plugin if the parent is changed

Options

Model.plugin(tree, {
  pathSeparator: '#'              // Default path separator
  onDelete:      'REPARENT'       // Can be set to 'DELETE' or 'REPARENT'. Default: 'DELETE'
  numWorkers:     5                // Number of stream workers
  idType:         Schema.ObjectId  // Type used for _id. Can be, for example, String generated by shortid module
  parentExists:   true               // Whether `parent` property is already defined in your schema. Default `false`
  pathExists:     true               // Whether `path` property is already defined in your schema. Default `false`
  levelExists:     true               // Whether `level` property is already defined in your schema. Default `false`
})

parentExists, pathExists, and levelExists

If parentExists === true, your schema must, at minumum, define type and index on parent. The same applies to path. level requires a Number type, and an index is recommended if you plan to query on it, but is not required. This library defines an index on level.

var treeOpts = {
  parentExists: true,
  pathExists: true,
  levelExists: true
};
var schemaDef = {
  parent: {
    type: ObjectId,
    index: true
  },
  path: {
    type: String,
    index: true
  },
  level: {
    type: Number,
    index: true
  }
};

API

getChildren

Signature:

getChildren([filters], [fields], [options], [recursive], cb);

args are additional filters if needed. if recursive is supplied and true, subchildren are returned

Based on the above hierarchy:

adam.getChildren(function(err, users) {
  // users is an array of with the bob document
});

adam.getChildren(true, function(err, users) {
  // users is an array with both bob and carol documents
});

getChildrenTree

Signature as method:

getChildrenTree([args], cb);

Signature as static:

getChildrenTree([rootDoc], [args], cb);

return a recursive tree of sub-children.

args is an object you can defined with theses properties :

filters: mongoose query filter, optional, default null
  example: filters: {owner:myId}

fields: mongoose fields, optional, default null (all fields)
  example: fields: "_id name owner"

options: mongoose query option, optional, default null
  example: options:{{sort:'-name'}}

minLevel: level at which will start the search, default 1
  example: minLevel:2

recursive: boolean, default true
  make the search recursive or only fetch children for the specified level
  example: recursive:false

allowEmptyChildren: boolean, default true
  if true, every child not having children will have 'children' attribute (empty array)
  if false, every child not having children will not have 'children' attribute

Example :
var args = {
  filters: {owner:myId},
  fields: "_id name owner",
  minLevel:2,
  recursive:true,
  allowEmptyChildren:false
}

getChildrenTree(args,myCallback);

Based on the above hierarchy:

adam.getChildrenTree( function(err, users) {

    /* if you dump users, you will have something like this :
    {
      "_id" : ObjectId("50136e40c78c4b9403000001"),
      "name" : "Adam",
      "path" : "50136e40c78c4b9403000001"
      "children" : [{
          "_id" : ObjectId("50136e40c78c4b9403000002"),
          "name" : "Bob",
          "parent" : ObjectId("50136e40c78c4b9403000001"),
          "path" : "50136e40c78c4b9403000001#50136e40c78c4b9403000002"
          "children" : [{
              "_id" : ObjectId("50136e40c78c4b9403000003"),
              "name" : "Carol",
              "parent" : ObjectId("50136e40c78c4b9403000002"),
              "path" : "50136e40c78c4b9403000001#50136e40c78c4b9403000002#50136e40c78c4b9403000003"
          }]
      }]
    }
    */

});

getAncestors (instance)

Signature:

getAncestors([filters], [fields], [options], cb);

Based on the above hierarchy:

carol.getAncestors(function(err, users) {
  // users is an array of adam and bob
})

getAncestors (static)

Get all ancestors for a collection of documents. Optimized to execute in a single query, rather than one per document.

Signature:

Model.getAncestors(documents, [filters], [fields], [options], cb);

Based on the above hierarchy:

User.getAncestors([carol, bob], function(err, users) {
  console.log(users);
})
// [
//   [{name: 'Adam'}, {'name': 'Bob'}],
//   [{name: 'Adam'}]
// ]

level

The depth of the node. Root is 1

carol.level; // equals 3

Tests

Run with npm:

$ npm run test

About

mongoose plugin for tree hierarchy using the materialized path strategy

Resources

Stars

Watchers

Forks

Packages

No packages published

Languages

  • JavaScript 100.0%