Skip to content

Instantly share code, notes, and snippets.

@Vadorequest
Created May 4, 2014 14:05
Show Gist options
  • Save Vadorequest/11517177 to your computer and use it in GitHub Desktop.
Save Vadorequest/11517177 to your computer and use it in GitHub Desktop.
///<reference path='./../../../lib/def/defLoader.d.ts'/>
/**
* Package that contains all Models used to interact with the database.
*/
export module Models {
/**
* Interface for all Models, except the parent class.
*/
export interface CoreIModel{
/**
* Name of the model.
* It's a helper to always get the name, from instance or static.
* MUST start by uppercase letter!
*/
modelName: string;
/**
* Contains the static value of the public schema as object.
* It's a helper to always get the schema, from instance or static.
*/
schema: mongoose.Schema;
/**
* Contains the static value of the object used to manipulate an instance of the model.
* It's a helper to always get the model, from instance or static.
*/
model: any;
}
/**
* Parent class for all models.
* A model contains a mongoose schema and a mongoose model and other things.
*/
export class CoreModel{
/**
* Suffix used to load automatically models.
*/
public static suffix: string = 'Model';
/**
* Suffix used to load automatically models.
* It's a helper to always get the schema, from instance or static.
*/
public suffix: string;
/**
* Name of the model.
* MUST start by uppercase letter!
*/
public static modelName: string = '';
/**
* Readable schema as object.
*/
public static schema: any;
/**
* Object that contains all the indexes configuration to apply when a schema is created.
* Each configuration is inside an array, composed in two objects.
* The first object contains the fields.
* The second object contains the field options.
*
* @see http://mongoosejs.com/docs/api.html#schema_Schema-index
*/
public static indexes: any = [];
/**
* Schema mongoose options.
* /!\ Don't share this object between child, that would become a MESS /!\
*
* @see http://mongoosejs.com/docs/guide.html#options
* - autoIndex
* - capped
* - collection
* - id
* - _id
* - read
* - safe
* - shardKey
* - strict
* - toJSON http://mongoosejs.com/docs/api.html#document_Document-toObject
* - toObject http://mongoosejs.com/docs/api.html#document_Document-toObject
* - versionKey
*/
public static schemaOptions: any = {
autoIndex: true,
id: true,
_id: true,
strict: true
};
/**
* Source code executed to modify the mongoose Schema middleware.
*
* @see http://mongoosejs.com/docs/middleware.html
*/
public static applyMiddleware = function(schema: mongoose.Schema){
return schema;
};
/**
* Schema as mongoose Schema type.
*/
public static Schema: mongoose.Schema;
/**
* The mongoose model that uses the mongoose schema.
*/
public static model: any;
/**
* Use static values as instance values.
*/
constructor(){
// Use static values as instance values.
this.suffix = CoreModel.suffix;
}
/**
* Returns a new mongoose.Schema customized instance.
*
* @param ChildModel Child model that made the call.
* @returns {*}
* @see http://mongoosejs.com/docs/2.7.x/docs/methods-statics.html
*/
public static getNewSchemaInstance(ChildModel: any): mongoose.Schema{
// Load the schema options, but don't save any modification in case of the schema wouldn't be overloaded by some childs and the values would be shared.
var config = JSON.parse(JSON.stringify(ChildModel.schemaOptions));
if(!config.collection){
// Use model name as default.
config.collection = _.lcFirst(ChildModel.modelName);
}
var schema: any = new mongoose.Schema(ChildModel.schema, config);
// Generate all indexes.
CoreModel._generateIndexes(schema, ChildModel);
// Overload methods.
/*schema.methods.toObject = function(callback){
// Ts doesn't recognize this as a model, properties aren't accessible, we need to declare it as a any type.
var self: any = this;
console.log(self.id)
};*/
// Return overloaded instance.
return ChildModel.applyMiddleware(schema);
}
/**
* Retrieves a new Model instance and overload it to add statics methods available for all Models.
*
* @param ChildModel
* @returns {*}
* @see http://mongoosejs.com/docs/2.7.x/docs/methods-statics.html
*/
public static getNewModelInstance(ChildModel: any): any{
// Get the Model class.
var Model: any = mongoose.model(ChildModel.modelName, ChildModel.Schema);
/**
**************************************************************************************************
************************ Extended Model static methods for all Models ****************************
**************************************************************************************************
*/
/**
* Handler for all database/mongoose errors.
*
* /!\ Cannot return Message instance because of some limitations due to the location of the script in some way. /!\
*
* @param err Error.
* @param trace Must contain an instance of traceback that contains everything useful to debug.
* @param callback Callback function to execute.
*/
Model.errorHandler = (err: any, trace: any, callback: (message: (any) => any) => any) => {
// If this array contains useful information then use it, otherwise use the other,
// that will depend on the way the script is executed but only one of the two array is useful to debug.
var origin = trace[1].name !== null || trace[1].method !== null ? 1 : 0;
// Extract data from the trace.
var __filename = trace[origin].file;
var __path_filename = trace[origin].path;
var __function = trace[origin].name || trace[origin].method;
var __line = trace[origin].line;
// Will contains the error.
var message: any = [];
// Mongo error.
if(err && err.name && err.name == 'MongoError'){
var _err = CoreMongoError.parseMongoError(err);
if(err.code == 11000){
// Duplicate key on create.
message[0] = '__19';
message[1] = {duplicateValue: _err.value, field: _err.field};
}else if(err.code == 11001){
// Duplicate key on update.
message[0] = '__20';
message[1] = {duplicateValue: _err.value, field: _err.field};
}else{
// Non-managed mongo error.
if(dev()){
// Return not only the message but also some information about the error.
message[0] = [['__21'], {err: _err}];
}else{
message = '__21';
}
}
fs.appendFile(__config.path.base + __config.mongo.error.log, new Date() + ': ' + JSON.stringify({error: err, model: _err.model, _err: _err, filename: __filename, path_filename: __path_filename, function: __function, line: __line}) + '\n');
}else if(err && err.name && err.name == 'ValidationError'){
// Validation error from mongoose.
var _err = CoreMongoError.parseValidationError(err);
message[0] = [];
if(_err[0].type === 'required'){
// Missing required value. Message. [0][1] could be args.
message[0][0] = '__33';
message[0][1] = {field: _.ucFirst(_err[0].field), error: _err[0].type};
}else{
// Duplicate value. Message.
message[0][0] = '__24';
message[0][1] = {duplicateValue: _err[0].value, field: _err[0].field, error: _err[0].type};
}
if(dev()){
// Will be send as args but not displayed in the message.
message[1] = {err: _err};
}
fs.appendFile(__config.path.base + __config.mongo.error.log, new Date() + ': ' + JSON.stringify({error: err, model: _err.model, _err: _err, filename: __filename, path_filename: __path_filename, function: __function, line: __line}) + '\n');
}else{
// Another error? I don't know if that could happens, but manage it anyway.
message[0] = '__22';
if(dev()){
message[1] = {err: err};// Will be send as args but not displayed in the message.
}
fs.appendFile(__config.path.base + __config.mongo.error.log, new Date() + ': ' + JSON.stringify({error: err, model: _err.model}) + '\n');
}
if(message.length){
// Log errors in dev mod.
consoleDev('A DB error happened, consult ' + __config.mongo.error.log + ' file to get more information.');
consoleDev(JSON.stringify(message) + ' in the file ' + __filename + ' at the function ' + __function + ':' + __line);
}
// Return an error, this IS NOT a Message instance!
callback(message);
};
/**
* Check if the object exists and returns it in this case.
*
* @param object Object to find.
* @param callback Callback to execute.
* @return
* err Error if it happens. [null]
* found Found object or false.
*/
Model.exists = (object, callback): any => {
// If object is null or false or empty or whatever, don't do the research, the result could be wrong!
if(!object){
callback (null, false);
}else{
Model.findOne(object, function (err, found) {
if (err){
Model.errorHandler(err, ChildModel, callback);
}else if (found){
callback(null, found);
}else{
callback (null, false);
}
});
}
};
// Return overloaded instance.
return Model;
}
/**
* Generate all schma indexes. Basically complex indexes such s compound indexes.
* Simples indexes are defined inside the schema itself.
*
* @param schema Mongoose Schema.
* @param ChildModel Model static class where find the indexes configuration to apply.
* @private
*/
public static _generateIndexes(schema: mongoose.Schema, ChildModel: any): void{
ChildModel.indexes.forEach(function(index){
var fields = index[0] || {};
var options = index[1] || {};
schema.index(fields, options);
});
}
}
/**
* Class that manage MongoDb errors, used statically.
*/
export class CoreMongoError{
/**
* Parse a mongo error to returns data from it because Mongo returns really bad errors.
*
* @param err The mongo error.
* @returns {
* database,
* model,
* field,
* value
* }
*/
public static parseMongoError(err): any{
var _err: any = {};
var _message: string = err.err;
/**
* E11000 - Duplicate key on create.
* E11001 - Duplicate key on update.
*/
if(err.code == 11000 || err.code == 11001){
var message = _message.split(':');
// Get the database where the error was generated.
_err.database = message[0].split('.')[0];
// Get the model where the error was generated.
_err.model = message[1].split('.')[1];
// Get the field name where the error was generated.
_err.field = message[1].split('.')[2].split(' ')[0].replace('$', '');
_err.field = _err.field.substr(0, _err.field.lastIndexOf('_'));
// Get the value, if exists.
_err.value = null;
if(message[3]){
var _value: any = message[3].split('"');
if(_value[1]){
_err.value = _value[1].replace('\\', '');
}else{
_err.value = _value[0].split(' ')[1].trim();
}
}
}
return _err;
}
/**
* Parse a mongoose validation error, probably generated during a save/update function.
*
* @param err The mongoose error.
* @returns {*}
*/
public static parseValidationError(err): any{
var _errors: any = new Array();
var i = 0;
for(var error in err.errors){
_errors[i] = [];
_errors[i]['field'] = err.errors[error]['path'];
_errors[i]['value'] = err.errors[error]['value'];
_errors[i]['type'] = err.errors[error]['type'];
i++;
}
return _errors;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment