sequelize: beforeUpdate hook is not being called

Hello, I am trying to call the beforeUpdate hook which uses the same function as beforeCreate hook but it doesnā€™t even get called:

//user model
'use strict';

var bcrypt = require('bcrypt-nodejs');

module.exports = function(sequelize, DataTypes) {
  var User = sequelize.define('User', {
      username: DataTypes.STRING,
      password: DataTypes.STRING,
      email: DataTypes.STRING
    }, {
      instanceMethods: {
        comparePassword: function(candidatePassword, cb) {
          var user = this;
          bcrypt.compare(candidatePassword, user.get('password'), function(err, isMatch) {
            if (err) return cb(err);
            cb(null, isMatch);
          });
        }
      },
      hooks: {
        beforeCreate: hashPassword,
        beforeUpdate: hashPassword
      },
      classMethods: {
        associate: function(models) {
          User.hasMany(models.PasswordReset, {foreignKey: 'userId'});
        }
      }
    } 
  );

  return User;
};


var hashPassword = function(instance, optons, next) {
  console.log(arguments);
  var SALT_FACTOR = 5;

  console.log('in hook');
  console.log('PWD CHANGED? ' + instance.changed('password'));

  if (!instance.changed('password')) return next();

  bcrypt.genSalt(SALT_FACTOR, function(err, salt) {
    if (err) return next(err);

    bcrypt.hash(instance.get('password'), salt, null, function(err, hash) {
      if (err) return next(err);
      instance.set('password', hash);
      console.log(instance.get('password'))
      next();
    });
  });
};

And i try to hash the password before update but the hook doesnt even get called:

    PasswordReset.findOne({ where: { token: req.params.token, expirationDate: {gt: new Date()}}, include: [{model: User}]}).then(function (pwdReset) {
      if (!pwdReset) {
        req.flash('error', 'Password reset token is invalid or has expired.');
        return res.redirect('/forgot');
      }

      User.update({ password: req.body.password }, { where: { id: pwdReset.userId }}).then(function (user){
        req.flash('success', 'Password reset was successful');
        res.redirect('/login');
      });
    }).catch(function (err) {
      return next(err);
    });

Any idea why the password is hashing beforeCreate but not beforeUpdate? What am i doing wrong?

About this issue

  • Original URL
  • State: closed
  • Created 9 years ago
  • Comments: 25 (6 by maintainers)

Most upvoted comments

@jedwards1211 Oh my god yes. That would be great!

Why is so much of sequeilize so un-intuitive? And why does documentation not tell us about any such quirks?

I have to go treasure hunting for clues on Google/SO/Github to do anything with this library.

Hi guys from 2018 ! Thank you for this topic, it helped me a lot a year later ! See you šŸ˜ƒ

@DoctypeRosenthal You can add a beforeBulkUpdate hook which modifies the options, setting individualHooks: true

@janmeier when the where clause has a single value for the primary key, couldnā€™t sequelize automatically treat that as an individual update instead of a bulk update?

You are calling bulk update (which updates several), so you should attach a beforeBulkCreate instead - or passing individualHooks: true in the options object http://docs.sequelizejs.com/en/latest/api/model/#updatevalues-options-promisearrayaffectedcount-affectedrows

user.model.js:

    'use strict';
    
    const bcrypt = require('bcryptjs');
    
    const hashCostFactor = 1;
    
    const hashPassword = async (user) => {
      if (user.changed('password')) {
        user.password = await bcrypt.hash(user.password, hashCostFactor);
      }
      return user
    }
    
    module.exports = (sequelize, DataTypes) => {
      const user = sequelize.define(
        'user',
        {
          username: {
            type: DataTypes.STRING(255),
            allowNull: false,
            unique: true,
          },
          password: {
            type: DataTypes.STRING(255),
            allowNull: false,
          },
        },
        {
          tableName: 'user',
        }
      );
    
      user.beforeCreate(
        async (user) => await hashPassword(user)
      );
    
      user.beforeUpdate(
        async (user) => await hashPassword(user)
      );
    
      /**
       * Checks if the parameter matches stored password
       * @param password
       */
      user.prototype.validatePassword = function validatePassword(password) {
        if(!password || !this.password)
          return false;
        return bcrypt.compareSync(password, this.password);
      };
    
      return user;
    }

On Sequelize update call:

updateUserById.js:

    module.exports = async ({ id, ...user }, options) =>
     await models.user.update(user,
        {
          where: {
            id
          },
          individualHooks: true,
          ...options
        }
      );
    };

models is from Sequelize models defined

Yes, the option applies to both

Hello.

I have a question: is it possible to activate the individualHooks-option for all update, create, destroy etc. methods? The only way I found was something like:

db.models.customers.update(args, { where: {id: args.id}, individualHooks: true })

Is there any other way than passing that option at runtime?

This ā€œindividualHooks: trueā€ defenitely should be in a documentation with simple example, i wasted about an hour to find the problem

Thx man! Maybe someone could point this out more clearly in the documentationā€¦

Great work otherwise! I really appreciate Sequelize!