UNPKG

lbg-mongoose-utils

Version:

a collection of plugins used in my projects for mongoose

177 lines (155 loc) 4.55 kB
/** * Mongoose extension implementing ancestor tree as defined in the link below. * @url http://docs.mongodb.org/manual/tutorial/model-tree-structures-with-ancestors-array/ * @author Marius Kubilius * @todo add methods and statics dealing with the tree. */ var Mongoose = require('mongoose'); var ObjectId = Mongoose.Schema.ObjectId; ancestorTree = function(schema){ var fields = {}; if (!schema.paths.parent) { fields.parent = { type: ObjectId, set : function(val) { if(typeof(val) === "object" && val._id) { return val._id; } if(val === '') { return undefined} return val; }, index: true } } if (!schema.paths.ancestors) { fields.ancestors = [{type: ObjectId, index: true}]; } schema.add(fields); var createAncestors = function(ancestors, parent) { return ancestors.push(parent); } schema.pre('save', function (next) { var parentModified = this.isDirectModified('parent'); var self = this; if (self.isNew || parentModified) { if(!self.parent && self.isNew){ return next(); } self.collection.findOne({ _id : self.parent }, function(err, doc) { if(err) return next(err); var oldAncestorsLength = self.ancestors.length; if(doc) { self.ancestors = doc.ancestors; // @todo find atomic operation for that. self.ancestors.nonAtomicPush(self.parent); //self.markModified('ancestors'); } else { self.ancestors = []; self.markModified('ancestors'); } if(!self.isNew && parentModified){ self.collection.find({ ancestors : self._id }, function(err, cursor) { if(err) return next(err); var stream = cursor.stream(); stream.on('data', function (doc) { //console.log(self.ancestors.concat(doc.ancestors.slice(oldAncestorsLength))); var newPath = self.ancestors.concat(doc.ancestors.slice(oldAncestorsLength)); self.collection.update({ _id : doc._id }, { $set : { ancestors : newPath } }, function(err){ if(err) return next(); }); }); stream.on('close', function() { next(); }); stream.on('error', function(err) { next(err); }); }); } else{ next(); } }); } else{ next(); } }); schema.pre('remove', function(next){ this.collection.count({ancestors: this._id},function(err, count){ if(err) return next(err); if(count > 0 ){ return next(new Error('Please delete/move all children before proceeding')); } else { return next(); } }); }); schema.method('findDirectChildren', function(opts, cb) { if(arguments.length === 1 && typeof arguments[0] === 'function'){ cb = opts; opts = {}; } opts = opts || {}; fields = opts.fields || {title: 1, slug: 1}; return this.model(this.constructor.modelName).find({parent: this._id}, fields, cb); }); schema.method('findChildren', function(opts, cb) { if(arguments.length === 1 && typeof arguments[0] === 'function'){ cb = opts; opts = {}; } opts = opts || {}; fields = opts.fields || {title: 1, slug: 1}; return this.model(this.constructor.modelName).find({ancestors: this._id}, fields, cb); }); schema.method('getAncestors', function(opts, cb) { if(arguments.length === 1 && typeof arguments[0] === 'function'){ cb = opts; opts = {}; } opts = opts || {}; fields = opts.fields || {title: 1, slug: 1}; return this.model(this.constructor.modelName).find({_id: {$in: this.ancestors}}, fields, cb); }); /** * Static Methods for schema */ schema.statics = { /** * Find direct children * @param {ObjectId} [id] _id of parent item, or null if no parent exists. * @param {Object} [options] pass options here * @param {Function} [cb] Callback function. */ directChildren: function (parent, options, cb) { if(arguments.length === 2 && typeof arguments[1] === 'function'){ cb = options; options = {}; } options.fields = options.fields || {'title':1, 'slug':1}; options.sort = options.sort || {'title': 1}; this.find({parent: parent}) .sort(options.sort) .select(options.fields) .lean() .exec(cb) }, /** * Find all children _ids and return them as an array * @param {ObjectId} [current] items _id which children you are looking. */ childrenIdArray: function (current, cb) { this.find({ancestors: current}).distinct('_id', function(err, result) { result.push(current); return cb(err, result) }) } } }; /** * @todo write tests for statics. */ module.exports = ancestorTree;