Giter Site home page Giter Site logo

vikpe / mongoose-mpath Goto Github PK

View Code? Open in Web Editor NEW
67.0 3.0 16.0 2.01 MB

Mongoose plugin for tree hierarchy using the materialized path pattern.

Home Page: https://docs.mongodb.com/manual/tutorial/model-tree-structures-with-materialized-paths/

License: MIT License

JavaScript 100.00%
mongoose mongoose-plugin materialized-path mongodb hierarchical-data node

mongoose-mpath's Introduction

Mongoose Materialized Path npm version test codecov

Mongoose plugin for tree hierarchy using the materialized path pattern.

Requirements

  • Node.js >= 14
  • MongoDB >= 4
  • Mongoose >= 6

Installation

npm install mongoose-mpath

Setup

Important note

This plugins adds parent, path and children fields to the schema. You should not define them in the schema which the plugin is enabled on.

Semantics

MySchema.plugin(MpathPlugin, [PLUGIN OPTIONS]);

Plugin options

const options = {
  modelName:     'MyModel',        // Name of model
  pathSeparator: '#',              // (optional) String used to separate ids in path
  onDelete:      'REPARENT',       // (optional) 'REPARENT' or 'DELETE'
  idType:        Schema.ObjectId   // (optional) Type used for model id
}

Example setup

import Mongoose from 'mongoose';
import MpathPlugin from 'mongoose-mpath';

const LocationSchema = new Mongoose.Schema({name: String});
LocationSchema.plugin(MpathPlugin, {modelName: 'Location'});

const LocationModel = Mongoose.model('Location', LocationSchema);

const europe = new LocationModel({name: 'europe'});
const sweden = new LocationModel({name: 'sweden', parent: europe});
const stockholm = new LocationModel({name: 'stockholm', parent: sweden});

await europe.save();
await sweden.save();
await stockholm.save();

At this point in mongoDB you will have documents similar to

{
  "_id" : ObjectId("50136e40c78c4b9403000001"),
  "name" : "europe",
  "path" : "50136e40c78c4b9403000001"
}
{
  "_id" : ObjectId("50136e40c78c4b9403000002"),
  "name" : "sweden",
  "parent" : ObjectId("50136e40c78c4b9403000001"),
  "path" : "50136e40c78c4b9403000001#50136e40c78c4b9403000002"
}
{
  "_id" : ObjectId("50136e40c78c4b9403000003"),
  "name" : "stockholm",
  "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.

API

All examples below are based on the following document hierarchy:

africa
europe
 - norway
 - sweden
   -- stockholm
     --- skansen

getAncestors()

Returns ancestors of a document. Returns a promise.

Signature

document.getAncestors(conditions, [fields], [options])

Arguments

  • See offical docs on model.find() for description of arguments.

Example

const ancestors = await stockholm.getAncestors({});    // (Array) [europe, sweden]

getAllChildren()

Returns all children of a document. Returns a promise.

Signature

document.getAllChildren(conditions, [fields], [options])

Arguments

  • See offical docs on model.find() for description of arguments.

Example

const children = await sweden.getAllChildren({});       // (Array) [stockholm, skansen]
const children = await stockholm.getAllChildren({});    // (Array) [skansen]

getChildrenTree()

Returns all children of a document formatted as a tree hierarchy. Returns a promise.

Signature

document.getChildrenTree([args])    // as method
model.getChildrenTree([args])       // as static

Arguments

  • (Object) args

    {
        (Object) filters: {},            // mongoose query filters
        (Object|String) fields: null,    // mongoose query fields (null equals all fields)
        (Object) options: {},            // mongoose query options
        (String) populate: '',           // string to passed to populate()
        (int) minLevel: 1,               // minimum level to include
        (int) maxLevel: 9999,            // maximum level to include
        (Mongoose.document) rootDoc      // mongoose document
    }
    

    Example

    const args = {
      filters: {author: 'vikpe'},
      fields: '_id name',
      options: {sort: 'name'},
      populate: 'repos',
      minLevel: 2,
      maxLevel: 4
    }

Example

const tree = await sweden.getChildrenTree({});
// tree is an array similar to
/*
[
  {
    'name': 'sthlm',
    'children': [
      {
        'name': 'skansen',
        'children': [],          
      }
    ],
  }
]
*/

getImmediateChildren()

Returns immediate children of a document. Returns a promise.

Signature

document.getImmediateChildren(conditions, [fields], [options])

Arguments

  • See offical docs on model.find() for description of arguments.

Example

const children = await europe.getImmediateChildren({});    // (Array) [norway, sweden]
const children = await sweden.getImmediateChildren({});    // (Array) [stockholm]

getParent()

Returns parent of a document.

Signature

document.getParent([fields], [options])

Arguments

  • See offical docs on model.find() for description of arguments.

Example

const parent = await sweden.getParent();       // (Object) europe
const parent = await stockholm.getParent();    // (Object) sweden

level

A Virtual field that equals to the level of a document in the hierarchy.

Signature

(Number) document.level

Example

africa.level    // 1
sweden.level    // 2
skansen.level   // 4

children

Placeholder variable populated when calling .getChildrenTree().

More examples

Given the following document hierarchy:

africa
europe
 - norway
 - sweden
   -- stockholm
     --- skansen

getAncestors()

europe.getAncestors()       // (Array) []
stockholm.getAncestors()    // (Array) [europe, sweden]
skansen.getAncestors()      // (Array) [europe, sweden, stockholm]

getAllChildren()

europe.getAllChildren()       // (Array) [sweden, stockholm, skansen]
stockholm.getAllChildren()    // (Array) [skansen]
skansen.getAllChildren()      // (Array) []

getImmediateChildren()

europe.getImmediateChildren()       // (Array) [norway, sweden]
stockholm.getImmediateChildren()    // (Array) [skansen]
skansen.getImmediateChildren()      // (Array) []

getChildrenTree()

europe.getChildrenTree()

/*
[
  {
    'name': 'norway',
    'children': []
  },
  {
    'name': 'sweden',
    'children': [
        {
          'name': 'sthlm',
          'children': [
            {
              'name': 'skansen',
              'children': []          
            }
          ],
        }
    ]
   }  
]
*/


sweden.getChildrenTree()

/*
[
  {
    'name': 'sthlm',
    'children': [
      {
        'name': 'skansen',
        'children': [],          
      }
    ],
  }
]
*/

getParent()

europe.getParent()       // (null)
stockholm.getParent()    // (Object) sweden
skansen.getParent()      // (Object) stockholm

level

africa.level       // (Number) 1
europe.level       // (Number) 1
norway.level       // (Number) 2
sweden.level       // (Number) 2
stockholm.level    // (Number) 3
skansen.level      // (Number) 4

Development

Feedback and pull requests are most welcome!

  1. npm install mongoose-mpath
  2. Install MongoDB (Community Server).
  3. Start MongoDB: mongod
  4. Run tests: npm run test

Credits

This plugin is inspired by mongoose-path-tree by swayf.

mongoose-mpath's People

Contributors

dependabot-preview[bot] avatar dependabot[bot] avatar florianbepunkt avatar polo2ro avatar vikpe avatar xeroxoid avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

mongoose-mpath's Issues

.npmrc with engine-strict=true

hi , i got some problem using .npmrc with engine-strict=true

npm ERR! code EBADENGINE
npm ERR! engine Unsupported engine
npm ERR! engine Not compatible with your version of node/npm: [email protected]
npm ERR! notsup Not compatible with your version of node/npm: [email protected]
npm ERR! notsup Required: {"node":"~0.10.0"}
npm ERR! notsup Actual: {"npm":"9.5.0","node":"v18.15.0"}

can you help to make it support for engine-strict=true
thanks,

TypeError: Cannot read property 'length' of undefined

Hello,

When creating a document with an existing parent, i'm getting the following error:

TypeError: Cannot read property 'length' of undefined
    at onStreamData (C:\...\node_modules\mongoose-mpath\lib\mpath.js:46:81)
    at C:\...\node_modules\stream-worker\stream-worker.js:63:39

mpath tries, in preSave, to updateChildPaths of the recently created document, with an undifined pathToReplace, because document.path is not set yet.

There is a simplified way i'm doing the creation:

...
  study.state = 'Published';
  study.save()
    .then(async (savedStudy) => {

            try {
              var s = new Study({
                name: 'blabla',
                state: 'Published',  
                parent: study,
              })
              await s.save()
            } catch (e) {
              console.log(e);
            }
        return res.json(savedStudy)
      })

the study document already has a correct path attribute

I temporaly fixed my problem replacing mpath.js line 67 by:
if (!this.isNew && hasModifiedParent) { // Rewrite child paths when parent is changed

Am I doing something wrong during creation?

Thanks,
Paul

ERROR while serializing: TypeError: Converting circular structure to JSON

This is my schema

const documentSchema = mongoose.Schema({
  name: {type: String, unique: true},
  tags: [String],
  files: [String],
  business: {type: Schema.Types.ObjectId, ref: 'Business'}
}, {timestamps: true})

documentSchema.plugin(require('mongoose-mpath'))
module.exports = mongoose.model('Document', documentSchema)

when I doing the following I am getting error:

Document.findById(req.params.id, (err, doc) => {
    if(err){
      return next(err)
    }
    const ancestors = doc. getAncestors({})
    **res.json({message: 'ancestors', path: ancestors})**
  })

ERROR===:

TypeError: Converting circular structure to JSON
at JSON.stringify ()

Please let me know if you see any issue in my code. Thanks in advance.

Question: How can you store the count/length of child nodes in parent Model?

Hi, given the example of a twitter clone how would you store the number of replies in each tweet?

Example of Tweet model:

const TweetSchema = new Schema({
  content: {
    type: String,
    default: "",
    required: true,
  },
  author: { type: Schema.Types.ObjectId, ref: "user", required: true },
  replies: {
    type: Number,
    default: 0,
  },
  likes: {
    type: Number,
    default: 0,
  },
  createdAt: {
    type: Date,
    default: Date.now,
  },
});

Example of query:

try {
    const tweetParent = await Tweet.findById(req.params.id).populate(
      "author",
      "-password"
    );
    const tweetChildren = await tweetParent
      .getImmediateChildren({})
      .populate("author", "-password");
    
    res.json({ tweets: [tweetParent, ...tweetChildren] });
  } catch (err) {
    res.json(err);
  }

My initial idea was to write a split function that splits the path values by the "#" char and stores the result in a arr and then call arr.length - that way when the Plugin handles reparenting the replies will update. However I cannot get this to work.

Any ideas?

Please help with .getParent() function

So i have an array of categories through which i'm looping in pug. There i need to display also parent category name. So when i do
each category, i in categories #{category.getParent()}
it prints just "Object Object" etc...
What can i do to get the category title and some other information?

Question: Why children attribute is always empty

Greetings,

The plugin is wonderful and you have my apologies. I've implemented the example from the Readme file:

const europe = new LocationModel({name: 'europe'});
const sweden = new LocationModel({name: 'sweden', parent: europe});
const stockholm = new LocationModel({name: 'stockholm', parent: sweden});
const skansen = new LocationModel({name: 'skansen', parent: sweden});
const subSkansen = new LocationModel({name: 'subSkansen', parent: skansen});

await europe.save();
await sweden.save();
await stockholm.save();
await skansen.save();
await subSkansen.save();

I can't understand: What's the benefit children property to be stored in the db collection set with empty value:

Screen Shot 2020-11-21 at 1 12 06 AM

I'm not an expert with this plugging. What I found: The children property is a valuable asset in the following example:

const location = await LocationModel.findOne({name: 'europe'});
const tree = await location.getChildrenTree({});
console.log('location tree', tree[0]);

The response is:
Screen Shot 2020-11-21 at 1 00 06 AM

My questions are: level is a virtual property which is nice.
#1 Why children attribute is not a virtual property ?
#2 Why children attribute is stored as an empty array in the database ?

Question: How to use DELETE to remove all immediate children?

Hello when using this libarary and attempting to remove a parent node with children I would assume REPARENT would move child nodes up a level to the nearest ancestor and DELETE would remove children of the parent node. However, both DELETE and REPARENT do the same thing in that context.

How can I remove all children of a parent node instead of moving them up to nearest ancestor?

I can provide code examples if neccessary. Thanks for this awesome library!!!!!

Unable to sort results by path from database queries

Hi,

That's a really promising implementation of materialized paths. Nice work!
I am facing an issue. Currently the generated path of each document includes also it's ID. This makes it impossible to sort documents by an other field. There is no way to sort two documents of the same parent. Removing the last ID from the path will allow to sort properly in our queries with something like that:
sort: { path: 1, ordering: 1 }
Or:
sort: { path: 1, title: 1 }

I hope this makes sense.

Regards

How to dynamically set the parent?

Hi, exists some way to dynamically set the parent or change the parent?

Imagine this:

  • Father 1
    -- Child
    --- Another Child
  • Father 2

I need to change the child from Father 1 to Father 2.

Thanks.

typescript

How can i use with typescipt in nestjs?

Doesnt save children in node

Hi, seems it doesnt save the children with mongoose

5.13.7

const organisationNode = new this.organisationNode({
  _id: new Types.ObjectId(),
  name,
  parent: rootNode,
  originalId
});
rootNode.save();
return organisationNode.save();

no children can be seen in rootnode

Methods return plain objects instead of mongoose documents?

Not 100% sure but I believe that getChildrenTree method is returning plain objects instead of mongoose documents.

If I use collection.find() then it returns an array of mongoose documents where for example all instance methods are available. If I use collection.getChildrenTree(), the instance methods are not available.

[Help] Help to understand why no childrens are being returned

I have this document:

// root category 

{
  "_id" :  "5be554c8cd98f607feb9b081",
  "title" : "Root",
  "path" : "5be554c8cd98f607feb9b081",
  "value" : "5be554c8cd98f607feb9b081",
  "__v" : 0
}

// parent
 {
            "weight": 0,
            "_id": "5be5561b89f9dc08305b9cbc",
            "title": "Parent",
            "parent": "5be554c8cd98f607feb9b081",
            "path": "5be554c8cd98f607feb9b081/5be5561b89f9dc08305b9cbc"
            "__v": 0
        },
 // children

{
            "_id": "5be55877044ada0873148b6e",
            "title": "Children 1",
            "parent": "5be5561b89f9dc08305b9cbc",
            "path": "5be554c8cd98f607feb9b081/5be5561b89f9dc08305b9cbc/5be55877044ada0873148b6e"
        },
// children of children
     {
            "weight": 0,
            "_id": "5be55c58e848c908e0d061f2",
            "title": "Children of Children 1",
            "parent": "5be55877044ada0873148b6e",
            "path": "5be554c8cd98f607feb9b081/5be5561b89f9dc08305b9cbc/5be55877044ada0873148b6e/5be55c58e848c908e0d061f2",
            "__v": 0
        }

I run this query:

router.post('/categories', (req, res) => {
    const {category, parent} = req.body;

    let args         = {
        filters           : { parent: parent},  //  5be5561b89f9dc08305b9cbc Parent id
        options  : {lean: true},
        minLevel : 3,
    };

    Category.findOne({title: category // 'God' }).exec()
            .then(result => {
                result.getChildrenTree({}).then(categories => {
                        result.getChildrenTree(args).then(children => {
                            res.json({
                                adType      : categories,
                                children     : children,
                            });
                        })
                }).catch(err => console.log(err));
            });
});

The above results to the following response:

{
    "adType": [
        {
            "weight": 0,
            "_id": "5be5561b89f9dc08305b9cbc",
            "title": "Parent",
            "parent": "5be554c8cd98f607feb9b081",
            "path": "5be554c8cd98f607feb9b081/5be5561b89f9dc08305b9cbc",
            "value": "5be554c8cd98f607feb9b081/5be5561b89f9dc08305b9cbc",
            "__v": 0
        },
    ],
    "children": [
        {
            "_id": "5be55877044ada0873148b6e",
            "title": "Children 1",
            "parent": "5be5561b89f9dc08305b9cbc",
            "path": "5be554c8cd98f607feb9b081/5be5561b89f9dc08305b9cbc/5be55877044ada0873148b6e",
            "children": []
        }
]

Why do i get an empty array in "children" : [] and not the Children of Children 1 document?

Cursor() issue on model find

I am getting an error here:

var childCursor = self.model(this.constructor.modelName).find({parent: this._id}).cursor();

it says that the cursor function is not defined. I have changed to stream() and it works fine.

Do you know why is that a problem? Thanks

Can i create children of different type modelSchema document

//codeword schema

const mongoose = require('mongoose');
const MpathPlugin = require('mongoose-mpath');

const codewordSchema = new mongoose.Schema({
    _id: mongoose.Types.ObjectId,
    tag: String,
    active: { type:Boolean, default: true },
    resToAssigned:[{type: Number}]
});
// add plugin
codewordSchema.plugin(MpathPlugin);

const Codeword = mongoose.models.Codeword || mongoose.model('Codeword', codewordSchema);
module.exports = Codeword ;

//folder schema

const mongoose = require('mongoose');
const MpathPlugin = require('mongoose-mpath');
const Codeword = require('./codeword.model');

const folderSchema = new mongoose.Schema({
    _id: mongoose.Types.ObjectId,
    name: String,
    codewords: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Codeword' }]
});
// add plugin
folderSchema.plugin(MpathPlugin);

const Folder = mongoose.models.Folder || mongoose.model('Folder', folderSchema);
module.exports = Folder;

here i want to make tree like

folder=>children[ folder && codeword ]
like folder file tree structure folder can hold folder and file
but when i am going to create as codeword children of folder like this=>

const parent = await Folder.findOne({ _id: user.rootId });
        const newcodeword = new Codeword({
            _id: new mongoose.Types.ObjectId(),
            tag: codeword,
            parent:parent
        }).save(async (err, result) => {
            if (!err) {
                const tree = await parent.getChildrenTree({ populate: 'codewords', options: { lean: false } });
                console.log({ tree });
            } else {
                console.log(err);
            }
});

This code give me error
TypeError: Cannot read property 'path' of null\n at C:\Users\Admin\Desktop\Working\SB\node_modules\mongoose-mpath\lib\mpath.js:140:37
I think , i can't create a children of different type modelSchema document. I am right if not right , how can i create children of folder as codeword

[Question] can i assign parent to a doc only by id?

in the readme examples we see a way to assign a parent when im passing the full doc to the query.
what if im trying to assign a parent only by a given id (mongoose id).
should i fetch the full doc (by the given id) and add the doc to the parent key?

getChildrenTree method returns an empty object (using mongoose-mpath v2.0.4)

i have this model

const mongoose = require('mongoose'); 
//import MpathPlugin from 'mongoose-mpath';
const MpathPlugin = require('mongoose-mpath')

const Schema = mongoose.Schema;
const CategorySchema = new Schema({ 
    name: { 
        type: String,
        unique: 'The name of the category should be unique',
        required: 'name cannot be blank' 
    },
}).plugin(MpathPlugin,[{
        pathSeparator: '#',              // String used to separate ids in path
        onDelete: 'REPARENT',       // 'REPARENT' or 'DELETE'
        idType: Schema.ObjectId   // Type used for model id
    }]
  );

mongoose.model('Category', CategorySchema); 

and this controller

exports.getChildrenTrees = function(req, res) {  
    category=req.category
    const args = {
        options: {lean: false},

      }
   // const category = new Category(req.body);
    const cat = category.getChildrenTree(args);
    res.status(200).json(cat);
    console.log('All children '+ typeof(cat)); 
  
}

when i call this server method, it reurns an empty array i have tried evrything but same result need a solution if one has had this issue please

Path update

Hi, thanks for the nice plugin, I am completely new to mongo/mongoose and therefore happy to have your package. I would like to add new parents to existing nodes and also rearrange/add nodes to the tree. Is the updating of the path supported when I delete a node or add a parent? Currently I tried to add/delete node but the path does not update like stated in the docu :/ Maybe I am doing something wrong?
Thanks!

image

getChildrenTree does not return mongoose documents

Hi, there

I use this plugin for tree structure, I can insert a node to the tree. The path and parent fields work as expected.

But, when I use this model to output the tree as json, the framework tell me

Cannot set property 'rootDoc' of undefined

I use .find to get a model array, so I use department[0] as the model. (Sorry for the issue title, I can't modify it now)

Any advice for this situation?

async findAll() {
    const department = await this.ctx.model.Department.find({ isTop: true }).exec();
    const data = await department[0].getChildrenTree();
    return data;
  }

modal

'use strict';

module.exports = app => {
  const mongoose = app.mongoose;
  const objectId = mongoose.Schema.Types.ObjectId;
  const DepartmentSchema = new mongoose.Schema({
    label: {
      type: String,
      require: true,
      unique: true
    },
    isTop: {
      type: Boolean,
      require: true,
      default: false,
    },
    isDepartment: {
      type: Boolean,
      require: true,
      default: true,
    },
    code: {
      type: String,
    },
    orderNum: {
      type: Number,
    },
    manager: {
      type: objectId, ref: 'User',
    },
    viceManager: {
      type: objectId, ref: 'User',
    },
    state: {
      type: Boolean,
      default: true,
    },
    comment: {
      type: String,
    },
  });
  DepartmentSchema.plugin(require('mongoose-mpath'));

  return mongoose.model('department', DepartmentSchema);
};

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.