User validations

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 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

Validating presence

We’ll start with a test for the presence of a name attribute test/models/user_test.js

require('trainjs').initServer();varassert=require('assert');describe('UserTest',function(){varuser;beforeEach(function(){user=User.build({name:"Example User",email:"[email protected]"});});it('should be valid',function(done){user.validate().then(function(errors){assert.equal(errors,undefined);done();});});it('name should be present',function(done){user.name=" ";user.validate().then(function(errors){assert.notEqual(errors,undefined);done();});});});

The way to validate the presence of the name attribute is to use the validate param with argument notEmpty: true

app/models/user.js

varSequelize=require('sequelize');varsequelize=CONFIG.database;varUser=sequelize.define('user',{name:{type:Sequelize.STRING,allowNull:false,validate:{notEmpty:true}},email:{type:Sequelize.STRING,},},{freezeTableName:true// Model tableName will be the same as the model name});module.exports=User;

Let’s drop into the console to see the effects of adding a validation to our User model

~/sample_app $ node

Here we check the validity of the user variable using the validate method

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

require('trainjs').initServer();varassert=require('assert');describe('UserTest',function(){varuser;beforeEach(function(){user=User.build({name:"Example User",email:"[email protected]"});});it('should be valid',function(done){user.validate().then(function(errors){assert.equal(errors,undefined);done();});});it('name should be present',function(done){user.name=" ";user.validate().then(function(errors){assert.notEqual(errors,undefined);done();});});it('email should be present',function(done){user.email=" ";user.validate().then(function(errors){assert.notEqual(errors,undefined);done();});});});

app/models/user.js

varSequelize=require('sequelize');varsequelize=CONFIG.database;varUser=sequelize.define('user',{name:{type:Sequelize.STRING,allowNull:false,validate:{notEmpty:true}},email:{type:Sequelize.STRING,allowNull:false,validate:{notEmpty:true}},},{freezeTableName:true// Model tableName will be the same as the model name});module.exports=User;

At this point, the presence validations are complete, and the test suite should be successful

~/sample_app $ mocha test/models/user_test.js
UserTest
✓ should be valid
✓ name should be present
✓ email should be present
3 passing (31ms)

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

require('trainjs').initServer();varassert=require('assert');describe('UserTest',function(){varuser;beforeEach(function(){user=User.build({name:"Example User",email:"[email protected]"});});...it('name should not be too long',function(done){user.name="a".repeat(51);user.validate().then(function(errors){assert.notEqual(errors,undefined);done();});});it('email should not be too long',function(done){user.email="a".repeat(244)+"@example.com";user.validate().then(function(errors){assert.notEqual(errors,undefined);done();});});});

~/sample_app $ mocha test/models/user_test.js
UserTest
✓ should be valid
✓ name should be present
✓ email should be present
✓ name should not be too long
✓ email should not be too long
5 passing (36ms)

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.

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

...it('email addresses should be unique',function(done){varduplicate_user=user;user.save().then(function(){duplicate_user.save().then(function(){done();}).catch(function(errors){assert.notEqual(errors,undefined);});}).catch(function(error){done();});});...

We can get the new test to pass by adding unique: true to the indexes option

At this point, our application—with an important caveat—enforces email uniqueness, and our test suite should pass

~/sample_app $ mocha test/models/user_test.js
UserTest
✓ should be valid (39ms)
✓ name should be present
✓ email should be present
✓ name should not be too long
✓ email should not be too long
....
✓ email addresses should be unique (239ms)
16 passing (365ms)

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:

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

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.