backbone-validator icon indicating copy to clipboard operation
backbone-validator copied to clipboard

call function that performs custom validation on entire model

Open taivo opened this issue 11 years ago • 8 comments

Is there a way to call a function that validates the model as a whole instead of individual attributes. This would allow more flexible validation of relationship among attributes instead of just their values.

This also allows working with inherited model classes that already have a validate method defined. For example, if you're working with a mobile backend like Parse.com, whose technology is based on Backbone but instead of Backbone.Model, you are required to use Parse.Object. This class has a method Parse.Object.validate that takes care of a few behind the scene data fields. I'm trying to figure out a way to call Parse.Object.validate either before or after the normal flow of backbone-validator

taivo avatar Jul 17 '14 07:07 taivo

Currently it is not supported. Any issues with simple overriding of _validate/validate methods for those models?

Also you can have validation as a function and it should return object of attributes to be validated:

validation: function() {
  ... logic ...
  return {
    name: { required: true }
  };
}

fantactuka avatar Jul 17 '14 08:07 fantactuka

By overriding _validate/validate do you mean copying bacbone-validator's implementation into those models and add my function calls there?

As for option 2, if the custom validation code returns error, currently I don't see a way to propagate that error through the normal flow of backbone-validator

Are you open to including some kind of reserved key word, for example the empty string, as a way to specify model-wide validation? With that, it's just a matter of using fn validator to carry out whatever custom validation needed.

validation: {
  "":{
     fn: function(args){}
  },
  name: { required: true }
}

Either that or use some sort of pre and post event hooks. Let me know if you are open to including these. I'd like to help with a PR.

taivo avatar Jul 18 '14 01:07 taivo

Reserved word could be an asterisk and in this case fn validator must return error object instead of string / array so it can be merged with other validators results:

validation: {
   '*': {
    fn: function() {
      ... 
      return { name: 'Invalid name', email: ['Does not match format', 'Could not be empty'] } 
    }
  },

  password: {
    required: true
  }
}

model.validate() -> { name: '...', email: ['...', '...'], password: '...' };

The only thing I don't like in this solution, is that '*' will accept only fn validator.


By overriding validate methods I meant that you do something like:

Model.prototype.validate = function() {
  ... validation, same as in `fn` validator ...
  var errors = { ... };
  this.triggerValidated(errors);

  return errors;
};

or something similar, depending on your needs. Also it sounds like a good improvement to allow easier way manually call separate validators, so that within overridden validate methods it's still possible to use standalone validation. Currently it's possible to use Backbone.Validator._validators.required(myValue, true) which looks ugly.


Also describe a little pre/post hooks idea, how do you see it – sounds interesting. And of course I'm opened for PR help ;)

fantactuka avatar Jul 18 '14 04:07 fantactuka

Something simple, along the lines of sandwiching the current validation loop between a preValidate step and a postValidate step. Roughly, this means changing Model.validate like this:

Model :{
  validate: function(attributes, options) {
        var validation = _.result(this, 'validation') || {},
              attrs = getAttrsToValidate(this, attributes);

        //code to instantiate preValidate and postValidate from validation specs.
        //defaults to function(){return {}; }

        var errors = _.extend({}, preValidate.call(this, attributes, options), 
                                               Validator.validate(attrs, validation, this), 
                                               postValidate.call(this, attributes, options));

        // the rest of validate's error processing code
        //
        }

        return options && options.suppress ? null : errors;
   },
}

The specs for preValidate and postValidate can just be something like this, instead of using fn:

validation: {
   '*': {
    preValidate: function(attrs, options) { 
        // same signature as Backbone.Model.validate
        // freedom to do what's necessary while still enjoying backbone-validator's usefulness
    }
    postValidate: Parse.Object.prototype.validate
  },

  password: {
    required: true
  }
}

I'm suggesting both pre and post for the sake of completeness, and in case you ever decide to have the option of stopping validation upon encountering the first error. For the usecases I can think of, the postValidate case is more important, i.e., after we've checked on all the pieces, postValidate lets us check on the whole model.

taivo avatar Jul 18 '14 09:07 taivo

I think that pre/post validation could be achieved via processErrors options, that allows you to modify errors object before it's being triggered / returned from validation method: https://github.com/fantactuka/backbone-validator/blob/master/backbone-validator.js#L171-L173

It probably might make sense to allow processErrors also be an instance method (currently only option / global option allowed).

With that being said I'd go with having '*' case where only fn validator allowed that returns entire errors object, that merged into model errors. Thoughts?

fantactuka avatar Jul 19 '14 09:07 fantactuka

Yea, I think the '*' is cleaner where as processErrors should be constrained to error processing by virtue of its name and what users/coders would expect from that.

Since '*' is already special in the sense that only fn is allowed, why don't we just take fn out of the picture altogether and specify '*' as a function. This would make the validation specs look a bit cleaner...just a bit :)

taivo avatar Jul 19 '14 10:07 taivo

Yep, completely agree - '*' should have a function value to exclude additional nesting. Let me know if you're going to create a PR, or I can take a look into it next week

fantactuka avatar Jul 19 '14 13:07 fantactuka

I will send a PR within a couple of days. I have the code running on my local machine now and I'm testing it by using it in my current project.

taivo avatar Jul 19 '14 21:07 taivo