Understanding validation graphs

By: Brian Cardarella

If you have heard me speak about ember-validations then you may have heard me mention the term validation graph. What is this? Why is it important?

If you come from a Rails background then you are used to the validations being stored in an array on the instance of the model. When you validate your model all of those validations will be run and an errors object is produced. If you make a change to a property you have to run the validations again to determine the validity of the model.

I would refer to the style of validations described above as lazy validations. Meaning the validity of the model may not be truly representative of its current state. We have to opt-into running the validations again to determine this. Fortunately in most cases, the validations will run for us before we save. On the server this all happens within a request/response cycle so we don’t really care too much about the validations being lazy because we care about the final result, not the state of the model at any given point during that cycle.

ember-validations has eager validations. This means when the property that is associated with any number of validations changes those validations will run again to determine the state of the model. This is great for client side apps that need to show the current state of the entire model any time you make a change, say during a user sign up. I might want to disable the Submit button if there are any failing validations. If I make a correction I want the error message to go away once the correction is made. I should not have to wait upon form submission to see my errors.

How does ember-validations do this? Let’s say you have the following validations:

var UsersController =
Ember.ObjectController.extend(Ember.Validations.Mixin, {
  validations: {
    firstName: {
      presence: true,
      length: 5
    },
    password: {
      confirmation: true
    }
  }
});

There are 3 validations on 2 properties. Each validation is an instantiated class that can observe the one or more properties. In the case of the firstName property the Presence and Length validators are observing it. The Confirmation validator is actually observing password and passwordConfirmation for changes. Each validator has a isValid flag that is set to true or false depending upon the result. Each of these validators are pushed onto a _validators array and the parent object is observing _validators.@each.isValid for any changes. If any of the validators are false the parent’s isValid state is now false.

Please take a moment to re-read the above paragraph because it is very important to have a good handle on this before we move forward. The validating object’s isValid flag is the result of its validator’s isValid flags

Because we are in quack-quack duck-typed JavaScript we don’t have to pass validator instances into the _validators array. What if we pass another validatable object? Now things get interesting.

Let’s say we have a Profile that belongs to a User. The Profile can have its own set of validations as well as its own isValid flag. If the Profile is mixed into the Users’s validation graph then the User will be invalid when the Profile is invalid. We can use this pattern to build an incredibly deep and complex graph where the validation state bubbles up to the root whenever a property change takes place anywhere in the graph.

We can do this simply with:

var UsersController =
Ember.ObjectController.extend(Ember.Validations.Mixin, {
  validations: {
    firstName: {
      presence: true,
      length: 5
    },
    password: {
      confirmation: true
    },
    profile: true
  }
});

Notice profile: true in the graph. As long as profile is the path to the object to validate against ember-validations will work its magic.

However, the above only really works if the validations exist on the Profile model and not the controller.

A visualization of a complex validation graph might look like this. We can see the isValid states bubbling up to the original root node:

I welcome suggestions and thoughts on this API as well as the validation graph in general.