Now that we have validations, we will want to be able to provide the user feedback on the errors in models. This is useful for forms, and is a critical part of the validation process.
This lesson is part of a series!
All right, we're ready to look at the public errors interface that will exist on instances of our base class. Here we have a person instance, and eventually we'll have a person.validate method, and so we might pass in the name of a field to validate, or if we don't pass in the name of any fields, then we'll be validating every single field on that instance. When a person fails any of the validations, we're going to add keys to the errors hash, so any of our validations that they'll for email will get added to person.errors.email.
We might say our email is too short, or we might say is not a valid email, those types of things, and they'll all be added to an array here. If we revalidate, for instance, and it's no longer too short, but it's still not a valid email, we will have this remaining. If we validate and there are no errors for email, we simply won't contain the key email. The errors hash just won't have that key on it, and ideally it'll be empty.
If the errors hash is empty, then the person is a valid instance. That's what we're going to be working towards. Here we're going to actually create another sub library called errorable, and errorable will just be capable of adding errors. It'll be capable of cleaning errors for us, and all the functionality that we're going to want to mix into validateable, so that we'll be able to add and remove errors as they pass and fail different validations.
Here's our first couple of tests. The first one is when we call add, name is too short, person.errors.name will be an array, and that will contain "is too short." We also want these errors to be added idempotently, and what that simply means is it won't duplicate errors, so if I add the same error twice, the count will only be one. Actually, really we want to test that on name.
We'll say .length is one. There will only be one error in the length array. The reason this is important is because if you can consider working through a form field, let's say you validated the name field twice, and the first time it was too short, and the second time it was also too short, we don't want multiple errors to pop up and be displayed on the interface. We only want that error to be displayed once.
Let's get cracking on this. First we need to create a home for errorable. We'll based in our boilerplate. All right, so now were going to create another mix in errorable, and return errorable. The first thing we're going to do is have a private errors cash, so that will be an empty object. We need to define a private variable on errors.
It'll be called add, and it's a function. It'll take a field name, and it'll take an error message. First and foremost, if it is undefined, the field name...or I'm sorry, it'll be errors field name. If the errors hash does not have this field name on it already, errors field name will be an empty array. Then if we do not currently have this particular error message within this field, this array of errors, so if error messages not included within that, we'll say, errors field name.push, error message, and will increment errors errors.count.
That's why the count of errors was what I previously had declared, if we return back here, as being the count should be one as opposed two, but this makes it more declarative and obvious that we expect only one error on this errors hash here. We'll save this out, and so now we have to make sure we declare a private variable for errors, and it'll be called count. We'll initialize it at zero.
Since this is going to be a mix in, and we want to mix it into instances of our base class, we'll say this._errors equals our private errors. We'll save that off. If we head back to our base class, we can mix in BC errorable. Bump this down, and we'll makes it in as errorable, and remember include is going to mix in for instance properties.
We'll mix in errorable, and if we save that off, our tests are now passing. With this example, I hope we can see how building each of these small components separately is helping us to compose a larger, and more complex base class really quickly and easily. Next, we're going to consider clearing errors, and a variety of assertions we'll make about that.
In the first case, we'll add name is too short. Then if we simply call clear, it's going to clear all of the errors, so person.errors.count will equal zero. Moving along, it can clear errors on individual fields, so if we add name is too short and age is too young, and we clear the name field, then person.errors.count will equal one, because we still have the one on the age field.
Next we can clear an array of field names of their errors. If we add to name, age, and email, and we clear name and age, then errors.count will equal one, and then it can clear particular error messages. If we add name is too short, and name is to weird, which is a strange validation, but let's say we then clear name is too weird, now person.errors.count will again be one, because we'll still have this too short error.
Let's go ahead and implement this. We'll make another private variable, errors clear function, and again we take a field name, and we take an error message potentially. If is not undefined, error message, so if we get an error message to clear, we'll say clear error message, and we'll give it the field name, and the error message.
Let's add this function, clear error message, field name, error message. First we want to make sure that our errors field name includes this error message, and if it does, we want to pull from errors field name, the message. We'll remove that error message from the field, and then we'll say errors.count, we'll decrement it. Now let's move back up here.
If we didn't get an error message, meaning we just got a field name, and we might not have even gotten a field name. We're going to figure out which fields we need to clear, so we'll set to clear equal to an empty array. If the field name is actually an array of fields, we'll set to clear equal to the field name, so it will be equal to that array. If it's a string, we'll just say to clear.push field name, and if it's undefined, meaning we didn't get past a field name, then what we actually want to clear is all of the fields, so we'll say _.keys.
This will be an array consisting of all of the error keys. Then for each of the fields that we have to clear, we'll save remove field of errors. Now let's go ahead and create remove field of errors. We'll get passed a field name here, and so will say if our count equals errors field name.length, so we'll find out how many errors were in their first, we'll delete errors field name, so we'll remove that key completely.
Finally, we'll say errors.count minus equals count, so we'll remove the field of that many errors exactly. Now when we save this out, we see that our tests are passing, so we're successfully removing each of these errors. The last bit of functionality I want to check here is that when we remove our error messages, and there are no more error messages remaining for a particular key, then that key will be removed completely from the errors hash, so errors.name will be undefined when we have no remaining errors on it.
The last thing that we have to do to make sure that passes is to come back here and we'll say if errors field name.length is exactly zero, then we'll errors field name, and so that will make that test pass. To double check our logic, and make sure that this will work in all instances, we see that we'll either be passed an error message to remove, in which case we know that we'll have deleted the key properly. Otherwise we'll be passed a field name, an array of field names, or no names at all.
In those cases, we know that we delete that field name from the errors hash, so we're sure that when there are no more errors for that field name it will definitely be removed. That's all of the logic for the errorable module. We've already got it mixed in, and we're ready to incorporate the errors hash in our validate function, which is the next and final piece that we'll write for the validatable library.