Friday, September 28, 2012

Sequelize Asynchronous Validation

The most frustrating flaw of the Sequelize is it's validation model - it simply doesn't allow asynchronous validations. If you want to check uniqueness of the email in database, you will end up writing this validation in the controller.

However, here is a solution. It is a fork of sequelize-1.5.0. Instead of calling model.validate() and waiting for error, you register success and error callbacks:
model
    .validate()
    .success(function () {
         model.save();
    })
    .error(function (errors) {
        // errors = { field1: error, field2: error} as 
        // if returned from old .validate() method
    });
 
It breaks the old way of user-defined validations, but brings this missing flexibility to validation. Here is sample model
    var User = sequelize.define("User", {
        name: {
            type: DataTypes.STRING,
            validate: {
                notEmpty: true
            }
        },
        email: {
            type: DataTypes.STRING,
            validate: {
                isEmail: true,
                isUnique: function (value, next) {
                    if (value) {
                        User
                            .find({ where: { email: value }})
                            .success(function (user) {
                                if (user) {
                                    next('Already taken')
                                } else {
                                    next()
                                }
                            })
                            .error(function (err) {
                                next(err.message);
                            });
                    } else {
                        next("String is empty");
                    }
                }
            }
        },
        password: {
            type: DataTypes.STRING,
            validate: {
                len: [4, 10]
            }
        },
        verifyPassword: {
            type: DataTypes.STRING,
            validate: {
                isSame: function (value, next) {
                    if (value == this.values.password)
                        next();
                    else
                        next('Passwords differ');
                }
            }
        },
        confirmed: {
            type: DataTypes.BOOLEAN,
            defaultValue: false
        },
    });
Note email.validate.isUnique function - it gets an extra parameter 'next', which is a callback used to report error back to sequelize. Calling next("error message") will report field validation error, while calling next() without parameters will report success.

Another less significant, but still valuable benefit of this fork is that custom validation functions are called now within model context, so that you can compare current field with another field, see verifyPassword.validate.isSame above.


I'm not sure though whether these changes will ever be accepted back to sequelize, since they break compatibility.

No comments:

Post a Comment