2014-01-12 1 views
0

Я пытаюсь изменить метод save(), но я не могу найти, где я могу его перегрузить. Я использую машинописные и node.js.Как перегружать экземпляр модели mongoose в nodejs & Typcript

На данный момент у меня есть UserModel, который содержит mongoose.Schema и mongoose.Model. Когда я вызываю UserModel.getModel(), я извлекаю mongoose.Model из UserModel. В основном я использую DAO для извлечения объекта класса Model.

user = message.getDataByKey('user'); 
user.save(function(err, data) { 
// do stuff 
}); 

Я хочу, чтобы автоматически перегружает пользовательский объект, чтобы использовать мой метод собственного .save(), чтобы проверить, есть ли ошибка и всегда обращаться с ними таким же образом.

Когда я поставил модель, я делаю это так:

public static model: any = model.Models.MongooseModel.getNewInstance(UserModel.modelName, UserModel._schema); 

И в родительском:

public static getNewInstance(modelName, schema){ 
     var Model: any = mongoose.model(modelName, schema); 

     // Overload methods. 
     //console.log(new Model()); 

     // Return overloaded Model class. 
     return Model; 
    } 

Я хотел бы знать, если есть способ перегружать модель для убедитесь, что каждый новый экземпляр из него будет иметь свой собственный .save-метод. Я думал использовать статику/методы (на самом деле, методы), но он пуст или я знаю, что у конечного объекта будут методы save/remove/update. Поэтому я не знаю, почему он еще не находится в объекте, я попытался использовать console.log (Model и new Model()), но не метод save().

Так что я немного разочарован, может быть, я что-то пропустил.

Дело в том, что я не могу обновить непосредственно новую модель(), потому что они будут созданы позже, в другом контексте мне необходимо обновить модель напрямую, чтобы убедиться, что новый экземпляр этой модели будет иметь мои дополнительная функция.

И я не хочу переписывать базовый метод .save(), я просто хочу перегрузить его, чтобы добавить дополнительную проверку.

Любая идея? Я немного потерялся здесь, это не так просто. Спасибо.

+0

Чтобы сделать это, вы должны использовать [промежуточное программное обеспечение] (http://mongoosejs.com/docs/middleware.html). – JohnnyHK

+1

Я взглянул на промежуточное ПО, но нечего делать, чтобы подняли ошибки. Я не видел ничего полезного. Я нашел другое решение, но для этого необходимо изменить код для выполнения каждой ошибки, возникающей в каждом запросе модели. Но на самом деле у меня нет выбора. – Vadorequest

ответ

1

Я нашел решение для этого, я использую машинопись, поэтому я буду публиковать оба .ts и .js для всех.

Я использую CommonJS компиляции

Model.ts(Супер модель, родитель всех моделей)

///<reference path='./../../lib/def/defLoader.d.ts'/> 

/** 
* Package that contains all Models used to interact with the database. 
* TODO Use options http://mongoosejs.com/docs/guide.html 
*/ 
export module Models { 
    /** 
    * Interface for all Models, except the parent class. 
    */ 
    export interface IModel{ 

     /** 
     * 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 Model{ 
     /** 
     * 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; 

     /** 
     * 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 = Model.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): mongoose.Schema{ 
      var schema: any = new mongoose.Schema(ChildModel.schema, {collection: ChildModel.modelName.toLowerCase()}); 

      // Overload methods. 
      //schema.methods.toObject = function(callback){} 

      // Return overloaded instance. 
      return 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{ 
      // 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. 
      * @param err   Error. 
      * @param data   Data. Contains the model and the emitter. (+ more) 
      * @param callback  Callback function to execute. 
      */ 
      Model.errorHandler = (err: any, data: any, callback: (message: (any) => any) => any) => { 
       // Extract data. 
       var _Model = data.model; 
       var __function = data.__function; 
       var __line = data.__line; 

       // Will contains the error. 
       var message:any = []; 

       // Mongo error. 
       if(err && err.name && err.name == 'MongoError'){ 
        var _err = MongoError.parseMongoError(err); 

        if(err.code == 11000){ 
         // Duplicate key on create. 
         message[0] = '__19'; 
         message[1] = [_err.value, _err.field]; 
        }else if(err.code == 11001){ 
         // Duplicate key on update. 
         message[0] = '__20'; 
         message[1] = [_err.value, _err.field]; 
        }else{ 
         // Non-managed mongo error. 
         if(dev()){ 
          // Return not only the message but also some information about the error. 
          message[0] = []; 

          // Message. [0][1] could be args. 
          message[0][0] = '__21'; 

          // Data. 
          message[1] = { 
           err: err, 
           model: _Model.modelName 
          }; 
         }else{ 
          message = '__21'; 
         } 
        } 

        fs.appendFile(__config.path.base + __config.mongo.error.log, new Date() + ': ' + JSON.stringify({error: err, model: _Model.modelName, _err: _err}) + '\n'); 
       }else if(err && err.name && err.name == 'ValidationError'){ 
        // Validation error from mongoose. 
        var _err = MongoError.parseValidationError(err); 
        message[0] = []; 

        // Message. [0][1] could be args. 
        message[0][0] = '__24'; 
        message[0][1] = [_err[0].value, _err[0].field, _err[0].type]; 

        if(dev()){ 
         // Will be send as args but not displayed in the message. 
         message[1] = { 
          err: _err, 
          model: _Model.modelName 
         }; 
        } 

        fs.appendFile(__config.path.base + __config.mongo.error.log, new Date() + ': ' + JSON.stringify({error: err, model: _Model.modelName, _err: _err}) + '\n'); 
       }else{ 
        // Another error? I don't know if that could happens, but manage it anyway. 
        message[0] = '__22'; 
        if(dev()){ 
         message[1] = [err, _Model.modelName];// 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: _Model.modelName}) + '\n'); 
       } 

       callback(message);// return an error. 
      }; 

      /** 
      * 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; 
     } 
    } 

    /** 
    * Class that manage MongoDb errors, used statically. 
    */ 
    export class MongoError{ 
     /** 
     * Parse a mongo error to returns data from it because Mongo returns really bad errors. 
     * @param err  The mongo error. 
     * @returns {*} 
     */ 
     public static parseMongoError(err): any{ 
      var _err: any = {}; 
      var _message: string = err.err; 

      if(err.code == 11000 || err.code == 11001){ 
       var message = _message.split(':'); 

       // Get the table where the error was generated. 
       _err.table = 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 
       _err.value = message[3].split('"')[1].replace('\\', ''); 
      } 

      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; 
     } 
    } 
} 

версия JS: http://pastebin.com/xBTr1ZVe

Сообщения об ошибках (__21 и т. Д.):

"__19": "Unable to add the element, the value **_$0** for the field _$1 already exists, it cannot be duplicated.", 
    "__20": "Unable to update the element, the value **_$0** for the field _$1 already exists, it cannot be duplicated.", 
    "__21": "Unable to execute the requested operation. Database error 21. Please report to an administrator.", 
    "__22": "Unable to execute the requested operation. Database error 22. Please report to an administrator.", 
    "__23": "Validation error, the requested operation was aborted. Please check that all your data are correct and retry. Please report to an administrator. (code 23)", 
    "__24": "Unable to perform the operation, the value ***_$0** for the field _$1 didn't pass successfully the validation. Error: _$2", 

В принципе, все мои модели должны управлять ими самими, конечно же. Но если я забуду это сделать, я получу управляемое исключение, лучше.

Сейчас я выложу реальную модель, UserModel наследуя родительский Модель.

///<reference path='./../../lib/def/defLoader.d.ts'/> 

import model = require('./Model'); 

export module Models { 
    /** 
    * Model used to manage users. 
    * The model is primary static, but, to make it easy to use, some things are also stored for each instance. 
    * That allows the code to use both Model or instance of Model such as: 
    *  Model.schema 
    *  model.Schema 
    */ 
    export class UserModel extends model.Models.Model implements model.Models.IModel{ 
     /** 
     ************************************************************************************************* 
     ****************************** Public methods & attributes ************************************** 
     ************************************************************************************************* 
     */ 

     /** 
     * Name of the model. 
     * MUST start by uppercase letter! 
     */ 
     public static modelName: string = 'User'; 

     /** 
     * Readable schema as object. 
     */ 
     public static schema: any = require('../schemas/userSchema.js'); 

     /** 
     * Schema as mongoose Schema type. 
     */ 
     public static Schema: mongoose.Schema = model.Models.Model.getNewSchemaInstance(UserModel); 

     /** 
     * The mongoose Model that uses the mongoose schema. 
     */ 
     public static model: any = model.Models.Model.getNewModelInstance(UserModel); 

     /** 
     * Helpers to always get the property, from instance or static. 
     */ 
     public modelName: string = UserModel.modelName; 
     public schema: mongoose.Schema = UserModel.schema; 
     public model: mongoose.Model<any> = UserModel.model; 

     /** 
     ************************************************************************************************* 
     ***************************** Extended methods & attributes ************************************** 
     ************************************************************************************************* 
     */ 

     /** 
     * These fields are protected, the user password is required to access to them. 
     * These fields are basically shared between applications. 
     * @private 
     */ 
     private static _protectedFields: string[] = [ 
      'login', 
      'email' 
     ]; 

     /** 
     * Method to use to hash the user password. 
     */ 
     private static _passwordHashMethod: string = 'sha256'; 

     /** 
     * Digest to use to hash the user password. 
     */ 
     private static _passwordDigest: string = 'hex'; 

     /** 
     * Returns the protected fields. 
     * @returns {string[]} 
     */ 
     public static getProtectedFields(): string[]{ 
      return this._protectedFields; 
     } 

     /** 
     * Hash a user password depending on the password hash configuration. Currently SHA256 in hexadecimal. 
     * Assuming crypto is global. 
     * @param password  User password. 
     * @returns {string} Hashed password. 
     */ 
     public static hashPassword(password: string): string{ 
      return crypto 
       .createHash(UserModel._passwordHashMethod) 
       .update(password) 
       .digest(UserModel._passwordDigest) 
     } 
    } 

    /** 
    * Don't forget that some methods such as exists() are written in the Model class and available for all Models. 
    * The following methods belong ONLY to the mongoose model instance, not to the Model class itself! 
    * 
    ************************************************************************************************* 
    ******************************** Extended Model methods ***************************************** 
    ************************************************************************************************* 
    */ 

    /** 
    * Connect a user to the game. 
    * @param user  User to check. {} 
    * @param callback Callback to execute. 
    */ 
    UserModel.model.checkAuthentication = (user, callback) => { 
     // Force to provide login and password. 
     UserModel.model.exists({login: user.login, password: UserModel.hashPassword(user.password)}, function(err, userFound){ 
      // Load public profile. 
      UserModel.model._getProtectedInformation(userFound, function(userPublic){ 
       // Provides only public fields. 
       callback(new __message("__17", {err: err, user: userPublic}, !err && userFound ? true: false)); 
      }); 
     }); 
    }; 

    /** 
    * Get the protected fields for the found user. 
    * @param user  User to find. 
    * @param callback Callback to execute. 
    */ 
    UserModel.model.getProtectedInformation = (user, callback) => { 
     // We are looking for an unique user. 
     UserModel.model.exists(user, function(err, userFound){ 
      if(err){ 
       UserModel.model.errorHandler(err, UserModel, callback); 
      }else{ 
       // Load public profile. 
       UserModel.model._getProtectedInformation(userFound, function(userPublic){ 
        // Provides only public fields. 
        callback(new __message('', {err: err, user: userPublic}, err ? false: true)); 
       }); 
      } 
     }); 
    }; 

    /** 
    * Get the protected fields of a user. 
    * @param user Instance of model. 
    * @param callback Callback to execute. 
    * @private 
    */ 
    UserModel.model.hashPassword = (user, callback): any => { 
     var err = false; 
     if(user && user.password){ 
      user.password = UserModel.hashPassword(user.password); 
     }else{ 
      err = true; 
     } 
     callback(new __message(err ? '__18': '', {user: user}, err ? false: true)); 
    }; 

    /** 
    ************************************************************************************************* 
    *************************** Methods to use only locally (private) ******************************* 
    ************************************************************************************************* 
    */ 

    /** 
    * Get the protected fields of a user. 
    * @param user Instance of model. 
    * @param callback Callback to execute. 
    * @private 
    */ 
    UserModel.model._getProtectedInformation = (user, callback): any => { 
     var userPublic = {}; 

     // Get fields to share. 
     var publicFields = UserModel.getProtectedFields(); 

     // Fill the userPublic var with public fields only. 
     for(var field in publicFields){ 
      userPublic[publicFields[field]] = user[publicFields[field]]; 
     } 

     callback(userPublic); 
    }; 

} 

версия JS: http://pastebin.com/0hiaMH25

Схема:

/** 
* Schema ued to create a user. 
* @see http://mongoosejs.com/docs/2.7.x/docs/schematypes.html 
*/ 
module.exports = userSchema = { 
    /** 
    * User Login, used as id to connect between all our platforms. 
    */ 
    login: { 
     type: 'string', 
     //match: /^[a-zA-Z0-9_-]{'+userSchema.login.check.minLength+','+userSchema.login.check.maxLength+'}$/, 
     trim: true, 
     required: true, 
     notEmpty: true, 
     unique: true, 
     check: { 
      minLength: 4, 
      maxLength: 16 
     } 
    }, 

    /** 
    * User email. 
    */ 
    email: { 
     type: 'string', 
     lowercase: true, 
     match: /^[a-zA-Z0-9._-][email protected][a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}$/, 
     required: true, 
     notEmpty: true, 
     unique: true, 
     check: { 
      minLength: 6, 
      maxLength: 30 
     } 
    }, 

    /** 
    * User private password, the one hashed in SHA512 and stored on the database. 
    */ 
    password: { 
     type: 'string', 
     required: true, 
     check: { 
      length: 128 
     } 
    }, 

    /** 
    * Salt to use to decrypt the password. 
    */ 
    passwordSalt: { 
     type: 'string', 
     check: { 
      length: 64 
     } 
    }, 

    /** 
    * Password sent from user interface but hashed before be send on the network. 
    * Used to basically connect an user or generate the final password. 
    * Not stored in the DB. 
    */ 
    passwordProtected: { 
     type: 'string', 
     check: { 
      length: 64 
     } 
    }, 

    /** 
    * Password wrote by the user on the GUI, not hashed or encrypted. 
    * Will be encrypted to respect the "passwordProtected" rules. 
    * Not stored in the DB. 
    */ 
    passwordPublic: { 
     type: 'string', 
     check: { 
      minLength: 8, 
      maxLength: 25 
     } 
    }, 

    /** 
    * User banned status (Temporary of Definitive) 
    */ 
    banned: { 
     temporary : { 
      type : "number", 
      default : Date.now 
     }, 

     definitive: { 
      type: 'boolean', 
      default: false 
     } 
    }, 

    /** 
    * User right 
    */ 
    right : { 
     admin : { 
      type : "boolean", 
      default : false, 
      required: true 
     }, 
     moderator : { 
      type : "boolean", 
      default : false, 
      required: true 
     } 
    } 
}; 

Итак, что делает код?

В основном, в Model.getNewModelInstance() Я связываю с созданной моделью метод errorHandler, который я позвоню, если я обнаружил ошибку БД в контроллере.

**UserController.js** 
User.exists({email: user.email}, function(err, emailFound){ 
    // If we got an err => Don't find couple User/pass 
     if (err) { 
      User.errorHandler(err, {model: User, __filename: __filename,__function: __function || 'subscription#exists', __line: __line}, function(err){ 
       res.json(__format.response(err)); 
      }); 
     ) 
}); 

__filename и так далее глобальные функции, которые я использую, чтобы получить текущие данные, полезные для отладки. Я все еще ищу способ добавить это автоматически, но до сих пор я не мог. Функция __функция не существует, когда функция анонимна. Но это помогает мне отлаживать.

Любое предложение? Это кусок кода.

+0

Эй, этот код выглядит потрясающе. Есть ли у вас некоторые варианты использования? Или сложный учебник? –

+0

@ Mr.Robot Я не уверен, что это часть этого репо, которое я сделал, но вот пример с Sails + TS: https://github.com/Vadorequest/sails-typescript – Vadorequest

Смежные вопросы