The User model we created in “User model” Section now has working name and email attributes, but they are completely generic: any string (including an empty one) is currently valid in either case. And yet, names and email addresses are more specific than this. For example, name should be non-blank, and email should match the specific format characteristic of email addresses.
In short, we shouldn’t allow name and email to be just any strings; we should enforce certain constraints on their values. In this section, we’ll cover several of the most common cases, validating presence, length, format and uniqueness. In “Adding a secure password” Section we’ll add a final common validation, confirmation. And we’ll see in “Unsuccessful signups” Section how validations give us convenient error messages when users make submissions that violate them.
A validity test
To get us started, the generate model command produced an initial test for testing users, though in this case it’s practically blank
To test the model we use mocha framework
To write a test for a valid object, we’ll create an initially valid User model object user using the special beforeEach method, which automatically gets run before each test. Because user is an instance variable, it’s automatically available in all the tests, and we can test its validity using the validate() method
test/models/user_test.js
Because our User model doesn’t currently have any validations, the initial test should pass
Validating presence
We’ll start with a test for the presence of a name attribute test/models/user_test.js
At this point, the model tests should be failing
The way to validate the presence of the name attribute is to use the validate param with argument notEmpty: true
app/models/user.js
Let’s drop into the console to see the effects of adding a validation to our User model
Here we check the validity of the user variable using the validate method
Because the user isn’t valid, an attempt to save the user to the database automatically fails
As a result, the test should now be successful
Following this test, writing a test for email attribute presence is easy, as is the application code to get it to pass
test/models/user_test.js
app/models/user.js
At this point, the presence validations are complete, and the test suite should be successful
Length validation
We’ve constrained our User model to require a name for each user, but we should go further: the user’s names will be displayed on the sample site, so we should enforce some limit on their length.
test/models/user_test.js
We can see how this works using the console
The email length validation arranges to make a valid email address that’s one character too long
At this point, the tests should be failing
To get them to pass, we need to use the validation argument to constrain length
app/models/user.js
Now the tests should be successful
Format validation
Our validations for the name attribute enforce only minimal constraints—any non-blank name under 51 characters will do—but of course the email attribute must satisfy the more stringent requirement of being a valid email address.
test/models/user_test.js
Next we’ll add tests for the invalidity of a variety of invalid email addresses.
At this point, the tests should be failing
The application code for email format validation uses the isEmail validation
app/models/user.js
At this point, the tests should be successful
Uniqueness validation
To enforce uniqueness of email addresses (so that we can use them as usernames), we’ll be using the unique option.
We’ll start with some short tests.
test/models/user_test.js
We can get the new test to pass by adding unique: true to the indexes option
app/models/user.js
At this point, our application—with an important caveat—enforces email uniqueness, and our test suite should pass
There’s just one small problem, which is that the uniqueness validation does not guarantee uniqueness at the database level. Here’s a scenario that explains why:
1. Alice signs up for the sample app, with address [email protected].
2. Alice accidentally clicks on “Submit” twice, sending two requests in quick succession.
3. The following sequence occurs: request 1 creates a user in memory that passes validation, request 2 does the same, request 1's user gets saved, request 2's user gets saved.
4. Result: two user records with the exact same email address, despite the uniqueness validation
Luckily, the solution is straightforward to implement: we just need to enforce uniqueness at the database level as well as at the model level. Our method is to create a database index on the email column, and then require that the index be unique.
We saw in “User model” Section that generating the User model automatically created a new migration; in the present case, we are adding structure to an existing model, so we need to create a migration directly using the migration generator
Unlike the migration for users, the email uniqueness migration is not pre-defined, so we need to fill in its contents
Having addressed the uniqueness caveat, there’s one more change we need to make to be assured of email uniqueness. Some database adapters use case-sensitive indices, considering the strings “[email protected]” and “[email protected]” to be distinct, but our application treats those addresses as the same. To avoid this incompatibility, we’ll standardize on all lower-case addresses, converting “[email protected]” to “[email protected]” before saving it to the database.
We’ll use beforeCreate and beforeUpdate to downcase the email attribute before saving the user.