Man: The next bit of functionality that I want to introduce is actually integrating the validatable library, so we're going to create a validatable mix-in, and we're going to mix it into our base class so that when we call person.validates, and we pass it some number of options, it's going to use some built-in validators that we create, such as required and length.
Then it's going to add those validations to that person class, so we'll have person.validations.name0.field is name, so that's expecting this validation class to have been added and instantiated, and we're going to store each of those validations on the class itself.
Let's get started. First, we're going to create some private state where we're going to store all of our validations. We'll store them on a hash here. We want to expose a couple of methods. We want to say, "this.validates is going to be a function," and it'll probably be a private function, so we'll probably say, "validations.add."
Validations.add can be a private variable, like we've used in the past, so we'll say, "validations add," and this will be a function. It will take a validation spec, which is something like, if we remember, person.validates. It will take a name, and that will have a hash of options here, and this will have something like length is a min 5, max 10, and we added required true as well, so we'll just keep this here so that we can consult with it.
For each in our validation spec we'll have a field, and we'll say, "add field field." We can really clean this up by just saying "add field" here, so add field. We'll take a validation set, so that will be this object here containing required and length, and it will have the field name. Here we'll say, "if validations field name," meaning we've already added validations for a particular field name already.
This will allow us to say, "validates multiple times," and continue to add validators anywhere we want to. We'll say, "_validations field name.addValidators," and we'll pass in the validation set, so we'll expose this add validators property to the field, and we'll be creating a field class to do that, else we'll say, "validations field name equals new field," and we'll pass in the field name and the validation set.
We'll need to go ahead and create a home for a validatable field, and we'll head in here after copying out this stuff, so we'll say, "validatable field," and we'll have a function, a constructor for the field, and we'll return the field. Again, we're going to take a field name and we're going to take a validation set.
I'm just going to back up and rename this name because in the context of a field, we know what the name refers to.
A field will be an array because it's an array of validations, so it's going to store each of the validations in this array and manage the state. The end of this, we'll just return field as well. We know that we're going to expose field.addValidators, and that's going to be a function that takes a validation set. For each of the validators in the validation set, we'll call field.addValidator. Field.addValidator will be a function.
We know that a validator consists of options and the name of the validation, so now we need to get the actual validator that the validation name is referring to. We'll have some registered validators somewhere and we'll say, "find validation name," or we'll want to create a new validator if it doesn't find that particular validation. We'll pass it the options and the validation name in order to create our new validator.
Then we're going to want to configure each of those. We'll call configured functions, and that's going to be a flattened array of the validator.configure, passing in the options for this validator. If we remember, sometimes validator will configure and return a single validators. Other times, if it has child validators, it will return an array of validators.
In either case, we want to turn that into an array and then flatten it so that we always end up with an array. It might be an array consisting of just one validator that's configured, or it may be a number of configured validator functions.
For each of our configured functions, we will have a configured function, and we'll call field.push new validation. We're going to pass the field name, which we have all the way up at the top from above, and we're going to pass it the configured function.
Remember how we create a validation is passing in a field name and a configured function, and that's exactly what we'll do here. Now we know we're going to be relying on a number of things. We're going to be relying on bcValidatable.validator in order to get this new validator.
We know we're going to be relying on bcValidatable.validation in order to get the validation class, and we're going to be relying on bcValidatable.validators, which we haven't defined yet, but that's going to keep track of each of our registered validators. We'll pass these in as validator, validation, and validators, since that's how we've referred to them -- validators, validator, and validation.
Let's go ahead and define our validators, where we're going to store each of these functions. There we go. This is going to be a singleton. It's going to save our registered validators, and return our registered validators, and on validators we'll expose a registered method so we can register new validators. We'll register the find method, which is going to allow us to find validators, which is what we wanted to call upon before. We know that register will take a validator.
We'll say, "if _. is undefined, validators validator.name," since we know that validators have a name. We'll want to add it, so we'll say, "validators validator.name equals validator." Otherwise we're going to want to throw a new duplicate validator error, and we'll pass in validator.name, and we'll define this custom validator error.
We'll pass in bcValidatable.duplicateValidatableError, and that will be duplicate validator error. We'll save that off and we'll create a validationErrors. We'll call this duplicateValidatorError.js, and I'll just go ahead and paste in an error here, so this is the duplicate validator error, and it will just say a validator by the name of the name passed in has already been registered. Simple enough.
We'll head back over here and we know that under find, we're going to be passed a validator name, and we'll just return validators validator name. Simple enough.
Next we're going to need to make sure that when we create new validators, that they register themselves. Here we'll pass in the validators, and after we create our validator, we'll just call validators.registerValidator.
Next we will define the built-in validators that will come out of the box, or at least enough of them to get our tests passing. We already know that we need a length validator, and we've defined this in the past, so I'll just paste it in. Again, we're using validator, and we're going to define validators.length.max and validators.length.min, and we'll expose them here as options as we've seen in the past.
We'll make max, save that off, we'll make min, save that off, and we'll make required, which is the final validator that we had to find for our tests, and we'll save that off.
Heading back to validatable, we'll need to make sure we import validatable.field, so we have access to field, and actually it looks like I had misnamed that in the past, so let's name that .field, and we'll head back to validatable, and we'll make sure that we expose the validations for this publicly.
Finally, we need to make sure that we load in all of the registered validators that we created, so now we'll see the tests are passing.
If you've been following along off-screen, you might have another failure, which is the already registered validation error that we created ourselves, and that's because some of our previous tests had defined the min validator, and if you go in, you can rename that minimum off-screen, and again, same thing with maximum, but this is how we will get these tests to pass.
That was a whole lot of information for one episode, and we're going to continue working on the validatable library in the next episode.