Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mongoose pre save is using incorrect "this" context?

Can anyone figure out what's wrong with my code below?

From documentation it looks like the this in Mongoose .pre('save') method should be the model itself, but in my code below this ends up being an empty object.

const Mongoose = require('./lib/database').Mongoose;
const Bcrypt = require('bcrypt');

const userSchema = new Mongoose.Schema({
    email: { type: String, required: true, index: { unique: true } },
    password: { type: String, required: true }
});

userSchema.pre('save', (next) => {

    const user = this;

    Bcrypt.genSalt((err, salt) => {

        if (err) {
            return next(err);
        }

        Bcrypt.hash(user.password, salt, (err, encrypted) => {

            if (err) {
                return next(err);
            }

            user.password = encrypted;
            next();
        });
    });
});

const User = Mongoose.model('User', userSchema);

When saving a user, I get the following error [Error: data and salt arguments required].

function createUser(email, password, next) {

    const user = new User({
        email: email,
        password: password
    });

    user.save((err) => {

        if (err) {
            return next(err);
        }

        return next(null, user);
    });
}

createUser('[email protected]', 'testpassword', (err, user) => {

    if (err) {
        console.log(err);
    }
    else {
        console.log(user);
    }

    process.exit();
});

If I remove the .pre('save') then it saves fine of course. Version of Mongoose I'm using is 4.2.6.

like image 349
jawang35 Avatar asked Dec 15 '22 10:12

jawang35


2 Answers

Problem here is a fat arrow functions. You must rewrite your callback with simple functions. Here small example to show the diff

var obj = {};

obj.func1 = function () {
    console.log(this === obj);
};

obj.func2 = () => {
    console.log(this === obj);
};

obj.func1(); // true
obj.func1.bind(obj)(); // true

obj.func2(); // false
obj.func2.bind(obj)(); // false
like image 194
Alexey B. Avatar answered Dec 17 '22 02:12

Alexey B.


I was able to figure out the issue. It turns out Arrow Functions in ES6 preserve the context of the declaring scope rather than using the context of the calling scope so changing the code to the below fixed the issue.

userSchema.pre('save', function (next) {

    Bcrypt.genSalt((err, salt) => {

        if (err) {
            return next(err);
        }

        Bcrypt.hash(this.password, salt, (err, encrypted) => {

            if (err) {
                return next(err);
            }

            this.password = encrypted;
            next();
        });
    });
});

Thank you Michelem for giving me the idea that ES6 may have been the culprit.

like image 34
jawang35 Avatar answered Dec 17 '22 02:12

jawang35