如何在nodejs&amp ;;中重载mongoose模型实例打字稿

时间:2014-01-12 22:12:07

标签: node.js mongoose

我正在尝试更改save()方法,但我找不到可以重载它的位置。我使用typescript和node.js。

目前,我有一个包含mongoose.Schema和mongoose.Model的UserModel。 当我调用UserModel.getModel()时,我从UserModel检索mongoose.Model。 我基本上使用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方法。 我认为使用静态/方法(实际上是方法,我猜)但是它是空的,或者我知道最终对象将具有保存/删除/更新方法。所以我不知道为什么它还没有进入对象,我尝试了console.log(Model和new Model())但没有save()方法。

所以我有点失望,也许我错过了什么。

事实是,我无法直接更新新的Model(),因为它们将在以后创建,在另一个上下文中,我需要直接更新Model以确保此模型中的新实例将具有我的额外功能

我不想重写基本的.save()方法,我只想重载它以添加额外的验证。

有什么想法吗?我有点迷失在这里,并不容易。 THX。

1 个答案:

答案 0 :(得分: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._-]+@[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()我绑定到创建的模型时,如果我在控制器中发现DB错误,我将调用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等是我用来获取当前数据的全局函数,对调试很有用。我仍在寻找一种自动添加的方法,但到目前为止我还没有。当函数是匿名函数时,__函数不存在。但它帮助我调试。

有什么建议吗?这是很多代码。