mongoose-materialized

Materialized path hierarchy for mongoose

Mongoose Materialized

A mongoose plugin for the materialized paths.


var mongoose = require('mongoose'),
    materializedPlugin = require('mongoose-materialized'),
    Schema = mongoose.Schema;
 
mongoose.connect('mongodb://localhost/materialized');
 
var CatSchema = new Schema({
  name: {type: String}
});
 
 
CatSchema.plugin(materializedPlugin);
 
var Cat= mongoose.model('Cat', CatSchema); // Category 

Go to contents


Adding root and child element.

Important: The model verifies the existence of the parent category before they would save. Except for the root element or change the parent id not touch.

// if you have predefined datas with parent id 
Cat.Building(function(){
    // building materialized path 
});
 
// add root element 
cat = new Cat({name: "Foods"});
cat.save(function(errfoods){
    // append new sub category 
    foods.appendChild({name: "Vegetables"}, function(errvega){
        // vega: { name: "Vegetables", parentId: [foods ID], path: ',[foods ID]' } 
    });
    // or make new 
    var vega = new Cat({name: "Vegetables"});
    // saving with append 
    foods.appendChild(vega, function(errdata){ ... });
    // or save traditional way 
    vega.parentId = foods._id;
    vega.save(function(errdata){ ... });
});
 

Find element and checking the relationship

Cat.findOne({parentId: null}, function(errdoc){
    // access to the children 
    doc.getChildren(function(errdocs){
        // ... 
    });
 
    // access to the children with condition and sort 
    doc.getChildren({
        condition: { name: /^a/ },
        sort: { name: 1 }
    },function(errdocs){
        // ... 
    });
 
    // access to the siblings 
    doc.getSiblings(function(errdocs){
        // ... 
    });
 
    // access to the ancestors 
    doc.getAncestors(function(errdocs){
        // ... 
    });
 
    // check element is root 
    doc.isRoot(function(errisOk){ ... });
 
    // check element is leaf 
    doc.isLeaf(function(errisLeaf){ ... });
 
    // depth virtual attributes 
    doc.depth
 
    // use promise 
    doc.getChildren().then(function(docs){
        // ... 
    });
 
    // get doc array tree 
    doc.getArrayTree(function(errtree){
        // ... [ {"_id": "...", "children": [ {...} ]}] 
    });
 
    // get doc tree 
    doc.getTree(function(errtree){
        // ... { "doc ID": { ..., children: { ... } } 
    });
 
    // or get tree with condition and sorting 
    doc.getTree({
        condition: { name: /^[a-zA-Z]+$/ },
        sort: { name: 1 }
    }, function(errtree){
        // ... 
    });
});
 
Cat.GetTree('elemt ID', function (errtree) {
    // ... 
});
 
Cat.GetArrayTree('elemt ID', function (errtree) {
    // ... 
});
 
// access for full tree in array 
Cat.GetFullArrayTree(function (errtree) {
 
});
 
// access for full tree object 
Cat.GetFullTree(function (errtree) {
 
});

The different arrayTree and simple Tree methods: arrayTree result:

    { _id: 53ee2db76f2d838a07a04e6a,
    path: '',
    name: 'Foods',
    __v: 0,
    _w: 0,
    parentId: null,
    depth: 0,
    id: '53ee2db76f2d838a07a04e6a',
    children: [ [Object], [Object] ] 
    }
]

and the Tree result:

{ '53ee2db76f2d838a07a04e6a': 
   { _id: 53ee2db76f2d838a07a04e6a,
     path: '',
     name: 'Foods',
     __v: 0,
     _w: 0,
     parentId: null,
     depth: 0,
     id: '53ee2db76f2d838a07a04e6a',
     children: { 
        '53ee2db76f2d838a07a04e6b': [Object] 
        } 
    } 
}

Manipulate child element with static method mongoose-materialized it is possible to use more than one root.

Cat.AppendChild('ID', { 'name': 'Meats'}, function(errdoc){ ... });
Cat.getChildren('ID', function(errchilds){ ... });
Cat.getRoots(function(errroots){
    // root elements 
});
 
// Format tree, sub element stored in children field 
Cat.getRoots({ name: "" }).then(function (errroot) {
    root.getChildren().then(function (errchildren) {
        console.log( Cat.toTree(children) );
        // or only shown name 
        console.log( Cat.toTree(children, { name: 1 }) );
    });
});
 

Hierarchical builder for the existing data. Important: This operation is relatively slow. Use only the conversion.

Cat.Building(function(){
    // builded materialized path sturcture 
});
 
// This example convert nested set to materialized path. Use this function to migration. 
 
Cat.Building({
    remove: { lt: 1, gt: 1, children: 1 } // remove nested fields from existsing data 
}, function(){
    // building is competted 
});

Go to contents


The following methods must be used with a callback. The callback method have two arguments. The first error and the second data object. If all goes well then the error is null.

model.calledFunction( function (errordata) {
    if (error)
        // handle error 
});

The methods with work callback return promise. Mongoose Promise

model.calledFunction().then( function (data) {
 
}, function (err) {
    // handle error 
});

Imprtant! Do not use the following methods:

  • Model.findByIdAndUpdate()
  • Model.findByOneAndUpdate()
  • Model.findByIdAndRemove()
  • Model.findByOneAndRemove()
  • Model.update() - static version
  • instance.update()
  • Model.remove() - static version

These functions are not triggered by the removal and saving events.

Instead, the following are recommended:

  • instance.save() - saving and update (before use findOne, findById)
  • instance.remove() - remove document (before use findOne, findById)
  • Model.Remove(condition, callback)

The my query object is special object for mongo query. This parameter available for functions.

var query = {
    // mongo condition 
    condition: {
        name: /^a/
    },
    // selected fields 
    fields: {
        _id: 1,
        name: 1
    },
    // sorting 
    sort: {
        name: -1
    }
};
 
// Example get chidls with query 
doc.getChilds(query, function(errdocs){ ... });

To run the tests:

npm test

Go to contents


Added attributes:

  • parentId: Parent item id.
  • path: materialized path. Auto generated
  • _w: weight for sort
  • depth: (virtual) element depth

Go to contents


Similar method has the static begins with the first letter capitalized. (IsLeaft is static and isLeaf non static)

  • IsLeaf(ModelOrId, callback)

  • IsRoot(ModelOrId, callback)

  • GetChildren(ModelOrId, [query,] callback)

  • GetRoots([query,] callback)

  • GetTree(root condition, [children query,] callback) - get elemets tree with children

  • GetFullTree(callback)

  • GetArrayTree(root condition, [children query,] callback) - get elemets tree with children

  • GetFullArrayTree(callback)

  • Remove(condition, callback) - use this instead of remove.

  • AppendChild(ModelOrId, callback)

  • ToTree(documentArray, selected fields) Return object, no mongoose document (toObject()). Fields: { name: 1, _id: 1 }

  • ToArrayTree(documentArray, selected fields) Return objects in array, no mongoose document (toObject()). Fields: { name: 1, _id: 1 }

  • Building([prepare,] callback) - rebuild material path (good for extisting collections - parentId is needed)

Go to contents


  • isRoot(callback)

  • isLeaf(callback)

  • isDescendant(callback)

  • isParent(ModelOrId, callback)

  • isSibling(ModelOrID, callback)

  • getParent(callback)

  • getDescendants([query,] callback)

  • getChildren([query,] callback) alias for getDescendants

  • getAncestors([query,] callback)

  • getSiblings([query,] callback)

  • getTree([query,] callback) - get elemets tree with children

  • getArrayTree([query,] callback) - get elemets tree with children, array version

  • appendChild(model, callback)

  • setParent(ModelOrId) - if parameter is ID then check parent existence and set parentId (the model parameter to avoid the query)

  • getChildCondition()

  • getAncestorsCondition()

  • getSiblingsCondition()

Go to contents


Inspired by seamless data management.

Go to contents


  • added new static methods: ToArrayTree(), GetArrayTree(), GetFullArrayTree()
  • added new methods: getArrayTree()
  • added new tests
  • enhancements (toTree, parentId type inherits from _id)
  • updated README.md and package dependencies
  • fixed remove parent with parentId=null bug
  • added new tests
  • updated README.md
  • added requested function: skip, limit for getDescendants, getChildren, getAncestors, getSiblings
  • in tree construction (getTree, buildTree) skip, limit methods is not recommended for use
  • fixed typo in 153 line.
  • ToTree use virtuals
  • better depth solution
  • added GetFullTree static method
  • added prepare for Building - use: Building({ remove: { lt: 1, gt: 1, level: 1 }, function () { });
  • added GetFullTree test
  • added getTree method and GetTree static method
  • added Remove static method for remove with condition
  • fixed: getChildren now return promise
  • fixed: GetRoots function call
  • fixed: GetChildren function call
  • Building method already work
  • Building tests
  • updated README.md
  • added ToTree test
  • tempory removed Building static method (thown not implemented error if use)
  • fixed: ToTree now return json document. (Not mongoose document)
  • updated README.md
  • currently under construction
  • added test
  • static methods
  • before save verifies the existence of parent element
  • Query supported methods
  • added Travis CI build status
  • updated README.md

Go to contents


Go to contents