noot
Version:
High quality, well tested, lightweight object oriented toolset for Node.js
271 lines (220 loc) • 7.12 kB
JavaScript
/**
* Dependencies
*/
var NOOT = require('../../../')('object', 'internal-utils');
var mongoose = require('mongoose');
var _ = require('lodash');
var path = require('path');
var fs = require('fs');
var Case = require('case');
/**
* Variables
*/
var MongooseSchema = mongoose.Schema;
var Model = mongoose.Model;
var MIDDLEWARES_PATH = './middlewares';
var oldFindOne = Model.findOne;
var oldFind = Model.find;
var oldInit = Model.prototype.init;
var oldModel = mongoose.model;
/***********************************************************************************************************************
* @class Schema
* @constructor
* @namespace NOOT.Mongoose
**********************************************************************************************************************/
/**
* Override findOne method to handle inheritance and find the right document based on the modelName
*/
Model.findOne = function() {
return oldFindOne.apply(this, parseArguments.apply(this, arguments));
};
/**
* Override find method to handle inheritance and find the right documents based on the modelName
*/
Model.find = function() {
return oldFind.apply(this, parseArguments.apply(this, arguments));
};
/**
* Set the prototype based on the discriminator __t
*
* @param {Object} doc
* @param {Object} query
* @param {function} fn
* @returns {*}
*/
Model.prototype.init = function(doc, query, fn) {
if (doc.__t) {
var model = this.db.model(doc.__t);
var newFn = function() {
process.nextTick(function() {
fn.apply(this, arguments);
});
};
this.schema = model.schema;
var obj = oldInit.call(this, doc, query, newFn);
obj.__proto__ = model.prototype;
return obj;
}
return oldInit.apply(this, arguments);
};
/**
* Allow inheritance for native statics
*/
var OVERRIDABLE_MODEL_STATICS = _.pick(Model, function(obj, prop) {
return NOOT.isFunction(Model[prop]);
});
/**
* Allow inheritance for native methods
*/
var OVERRIDABLE_MODEL_METHODS = _.pick(Model.prototype, function(obj, prop) {
return NOOT.isFunction(Model.prototype[prop]);
});
/**
* Extend schema, define discriminator, create model
*
* @param {Object} definition Schema properties
* @param {Object} [ownStatics] Schema static properties
* @returns {MongooseSchema}
*/
MongooseSchema.extend = function(definition, ownStatics) {
definition = definition || {};
if (NOOT.isUndefined(definition.schema)) definition.schema = {};
var properties = definition.schema;
var parentProperties = (this.__nootDef && this.__nootDef.schema) || {};
for (var key in parentProperties) {
if (NOOT.isUndefined(properties[key])) properties[key] = _.cloneDeep(parentProperties[key]);
}
var schema = new MongooseSchema(properties, _.merge({}, this.options || {}, definition.options));
_.defaults(this, { methods: {}, statics: {} });
_.defaults(this.methods, OVERRIDABLE_MODEL_METHODS);
_.defaults(this.statics, OVERRIDABLE_MODEL_STATICS);
NOOT.InternalUtils.buildSuper(schema.methods, this.methods, definition.methods || {});
NOOT.InternalUtils.buildSuper(schema.statics, this.statics, definition.statics || {});
NOOT.InternalUtils.buildSuper(schema, this, ownStatics);
for (var virtual in this.virtuals) {
if (NOOT.isUndefined(schema.virtuals[virtual])) schema.virtuals[virtual] = this.virtuals[virtual];
}
schema.__nootDef = definition;
schema.__nootParent = this;
return schema;
};
var SchemaBase = MongooseSchema.extend({
statics : {
migrate : function (match, callback) {
var self = this;
var __t = self.modelName;
var __ts = self.schema.__nootDef.parents;
__ts.push(__t);
return self.update(match || {}, { __ts: __ts, __t: __t }, { multi: true }, function (err) {
if (err) return callback(err);
return callback();
});
}
}
});
/**
* Parse incoming arguments for find and findOne
*
* @param {Object} conditions
* @param {String} fields
* @param {Object} options
* @param {Function} callback
* @returns {Array}
*/
var parseArguments = function(conditions, fields, options, callback) {
if (NOOT.isFunction(conditions)) {
callback = conditions;
conditions = {};
fields = null;
options = null;
} else if (NOOT.isFunction(fields)) {
callback = fields;
fields = null;
options = null;
} else if (NOOT.isFunction(options)) {
callback = options;
options = null;
fields = fields;
} else if (NOOT.isFunction(callback)) {
fields = fields;
}
if (!conditions) conditions = {};
if (!options) options = {};
if (options.bypass === true) return [conditions, fields, options, callback];
if (!_.isEmpty(this.schema.__nootDef.parents)) {
if (options.strict) conditions.__t = this.modelName;
else conditions.__ts = this.modelName;
} else {
if (options.strict) conditions.__t = { $exists : false };
}
return [conditions, fields, options, callback];
};
/**
* Attach middlewares functions
*/
fs.readdirSync(path.resolve(__dirname, MIDDLEWARES_PATH)).forEach(function(middleware) {
var middlewareName = Case.camel(path.basename(middleware, '.js'));
middleware = require(path.join(__dirname, MIDDLEWARES_PATH, middleware));
MongooseSchema.prototype[middlewareName] = function() {
var args = NOOT.makeArray(arguments);
args.unshift(this);
return middleware.apply(middleware, args);
};
});
/**
* Check if parent schema is already registered in a model
*
* @param {Object} parent
* @param {Object} models
* @returns {Array}
*/
function checkRegisteredModel(parent, models) {
for (var registeredModelName in models) {
if (models[registeredModelName].schema === parent) {
return [registeredModelName];
}
}
return [];
}
/**
* Redefine model creation to attach discriminators
*
* @param {String} modelName
* @param {Object} schema
* @param {String} collection
* @param {Object} options
* @returns {Object}
*/
mongoose.model = function(modelName, schema, collection, options) {
if (schema) {
var definition = {};
definition.parents = [];
if (schema.__nootDef) {
var parent = schema.__nootParent;
var parentDef = parent.__nootDef;
if (options && NOOT.isObject(options)) {
definition.parents = checkRegisteredModel(parent, options.connection.models);
} else {
definition.parents = checkRegisteredModel(parent, this.models);
}
definition.parents = ((parentDef && parentDef.parents) || []).concat(definition.parents);
schema.__nootDef.parents = _.cloneDeep(definition.parents);
definition.parents.push(modelName);
var identificator = { __t: { type: String, default: modelName, index: true } };
var discriminator = {
__ts: { type: Array, default: function () {
return definition.parents;
} }
};
if (!NOOT.isEmpty(schema.__nootDef.parents)) {
schema.add(discriminator);
schema.add(identificator);
}
}
}
return oldModel.apply(this, arguments);
};
/**
* @exports
*/
module.exports = SchemaBase;