简体   繁体   English

如何在Node.js和TypeScript中重载猫鼬模型实例

[英]How to overload mongoose model instance in nodejs & Typescript

I'm trying to change the save() method, but I don't find where I can overload it. 我正在尝试更改save()方法,但找不到在哪里可以重载它。 I use typescript and node.js. 我使用打字稿和node.js。

For the moment, I have a UserModel that contains a mongoose.Schema and a mongoose.Model. 目前,我有一个UserModel,其中包含mongoose.Schema和mongoose.Model。 When I call UserModel.getModel() I retrieve the mongoose.Model from the UserModel. 当我调用UserModel.getModel()时,我从UserModel中检索mongoose.Model。 I basically use a DAO to retrieve the Model class object. 我基本上使用DAO来检索Model类对象。

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

I want to overload automatically the user object to use my own .save() method to check if there is error and always handle them by the same way. 我想自动重载用户对象,以使用我自己的.save()方法检查是否存在错误,并始终以相同的方式处理它们。

When I set the Model, I do it like that: 设置模型时,我会像这样:

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

And in the parent: 并在父母中:

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

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

        // Return overloaded Model class.
        return Model;
    }

I would like to know if there is any way to overload the Model to make sure that each new instance from it will have my own .save method. 我想知道是否有任何方法可以重载模型,以确保模型中的每个新实例都有自己的.save方法。 I thought use the statics/methods (methods actually, I guess) but it's empty or I know that the final object will have save/remove/update methods. 我以为使用了静态方法/方法(我猜实际上是方法),但是它是空的,或者我知道最终对象将具有保存/删除/更新方法。 So I don't know why it's not already into the object, I tried to console.log(Model and new Model()) but no save() method. 所以我不知道为什么它还没有进入对象,我尝试了console.log(Model和new Model())但没有save()方法。

So I'm a little desappointed, maybe I missed something. 所以我有些失望,也许我错过了一些东西。

The fact is, I can't update directly the new Model() because they will be created later, in another context, I need to update the Model directly to make sure that the new instance from this model will have my extra function. 事实是,我无法直接更新新的Model(),因为它们将在以后创建,在另一种情况下,我需要直接更新Model以确保该模型中的新实例具有我的额外功能。

And I don't want to rewrite the basic .save() method, I just want to overload it to add extra validation. 而且我不想重写基本的.save()方法,我只想重载它以添加额外的验证。

Any idea? 任何想法? I'm kinda lost here, it's not that easy. 我有点在这里迷路,这不是那么容易。 Thx. 谢谢。

I found a solution to do so, I'm using typescript so I'll post both .ts and .js to everybody understand. 我找到了解决方案,我正在使用打字稿,所以我会将.ts和.js都发布给大家。

I use CommonJs compilation 我使用CommonJs编译

Model.ts (Super model, parent of all models) 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;
        }
    }
}

The JS version: http://pastebin.com/xBTr1ZVe JS版本: http//pastebin.com/xBTr1ZVe

Error messages (__21, etc.) are: 错误消息(__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",

Basically all my models should manage by themself these exception, of course. 当然,基本上我所有的模型都应该自己管理这些异常。 But if I forgot to do it, I'll get a managed exception, better. 但是,如果我忘记这样做,那么我会得到一个更好的托管异常。

Now I'll post a real Model, UserModel inheriting the parent Model . 现在,我将发布一个真实的模型, 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);
    };

}

The JS version: http://pastebin.com/0hiaMH25 JS版本: http//pastebin.com/0hiaMH25

The schema: 模式:

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

So, what the code does? 那么,代码是什么呢?

Basically, in the Model.getNewModelInstance() I bind to the created model the errorHandler method that I will call if I found a DB error in the controller. 基本上,在Model.getNewModelInstance() ,我将把errorHandler方法绑定到创建的模型,如果我在控制器中发现DB错误,我将调用该方法。

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

The __filename and so on are global functions that I use to get the current data, useful to debug. __filename等是我用来获取当前数据的全局函数,对调试很有用。 I'm still looking for a way to add this automatically but so far I couldn't. 我仍在寻找一种自动添加此内容的方法,但到目前为止我还没有。 The __function doesn't exists when the function is anonymous. 当函数匿名时,__ function不存在。 But it helps me to debug. 但这可以帮助我调试。

Any suggestion? 有什么建议吗? That's a lot of piece of code. 那是很多代码。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM