Search code examples
javascriptbackbone.jsbookshelf.js

Bookshelf js model causes save/changed loop


The following Bookshelf model hashes the users password when the model is saved, the only problem is that if I change that model.set() call to a model.save() it goes into an infinite save/changed loop.

var User = bookshelf.Model.extend({
    tableName: 'users',
    hasTimestamps: true,

    constructor: function() {
        var self = this;
        bookshelf.Model.apply(this, arguments);

        this.on('saving', function(model) {
            if(!model.get('password')) {
                return self.hashPassword(model);
            }
        });
    },

    hashPassword: function(model) {
        bcrypt.genSalt(10, function(error, salt) {
            bcrypt.hash(model.attributes.password, salt, function(error, hash) {
                model.set({password: hash});
                console.log(model.attributes);
            });
        });
    }
});

I know that Backbone has a silent: true option you can pass so that save() doesn't trigger a changed event but I don't think Bookshelf supports it.

How can I save the changes model.set() has made without causing a save/changed loop?


Solution

  • So it turns out that the model is saving before the hash_password method has returned a value, so I promiseified the bcrypt code like so:

    hashPassword: function(password) {
        return new Promise(function(resolve, reject) {
            bcrypt.genSalt(10, function(error, salt) {
                if(error) return reject(error);
    
                bcrypt.hash(password, salt, function(error, hash) {
                    if(error) return reject(error);
                    return resolve(hash);
                });
            });
        });
    }

    and completely refactored the model's constructor to use it:

    constructor: function() {
        var self = this;
        bookshelf.Model.apply(this, arguments);
    
        this.on('saving', function(model) {
            if(!model.attributes.password) {
                delete model.attributes.password;
            } else {
                return self.hashPassword(model.attributes.password)
                .then(function(hash) {
                    model.set({ password: hash });
                });
            }
        });
    }

    Hope this helps somebody :-)