Building on our experience with the Trees model, we're now going to create a more complex model for storing information about insects. Think of this as creating a digital museum catalog for insect specimens. Just as a museum needs precise ways to catalog and describe its specimens, our database needs specific rules and structures to store insect information accurately.
This new model introduces some interesting validation challenges, such as ensuring names are properly capitalized and facts don't exceed a certain length - similar to how a museum catalog might have specific formatting requirements for its entries.
Let's break this down into manageable steps:
First, let's create our Insect model using the Sequelize CLI:
npx dotenv sequelize-cli model:generate --name Insect --attributes \
name:string,\
description:string,\
territory:string,\
fact:string,\
millimeters:float
In server/db/migrations/XXXXXX-create-insect.js:
'use strict';
module.exports = {
up: async (queryInterface, Sequelize) => {
await queryInterface.createTable('Insects', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
name: {
type: Sequelize.STRING,
allowNull: false,
unique: true
},
description: {
type: Sequelize.STRING
},
territory: {
type: Sequelize.STRING
},
fact: {
type: Sequelize.STRING(240) // Constraining fact length at database level
},
millimeters: {
type: Sequelize.FLOAT,
allowNull: false
},
createdAt: {
allowNull: false,
type: Sequelize.DATE,
defaultValue: Sequelize.literal('CURRENT_TIMESTAMP')
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE,
defaultValue: Sequelize.literal('CURRENT_TIMESTAMP')
}
});
},
down: async (queryInterface, Sequelize) => {
await queryInterface.dropTable('Insects');
}
};
In server/db/models/insect.js:
'use strict';
const { Model } = require('sequelize');
module.exports = (sequelize, DataTypes) => {
class Insect extends Model {
static associate(models) {
// define associations here
}
}
Insect.init({
name: {
type: DataTypes.STRING,
allowNull: false,
unique: true,
validate: {
notEmpty: true,
titleCase(value) {
// Custom validation for title case
const words = value.split(' ');
const properFormat = words.every(word =>
word[0] === word[0].toUpperCase() &&
word.slice(1) === word.slice(1).toLowerCase()
);
if (!properFormat) {
throw new Error('Name must be in title case (each word capitalized)');
}
}
}
},
description: {
type: DataTypes.STRING
},
territory: {
type: DataTypes.STRING
},
fact: {
type: DataTypes.STRING,
validate: {
len: [0, 240] // Ensures fact is no longer than 240 characters
}
},
millimeters: {
type: DataTypes.FLOAT,
allowNull: false,
validate: {
min: 0
}
}
}, {
sequelize,
modelName: 'Insect',
});
return Insect;
};
Let's examine each new type of validation we've introduced:
Our custom titleCase validation ensures proper capitalization of insect names. Here's how it works:
The len validation for facts works like a character limit on social media:
len: [0, 240] // Minimum length is 0, maximum is 240 characters
Let's execute our migration:
npx dotenv sequelize-cli db:migrate
Verify the table structure:
sqlite3 db/dev.db ".schema Insects"
Run the provided tests:
npm test test/phase-3-spec.js
Our implementation uses two levels of data validation:
These are like the physical rules of our museum catalog:
These are like having a curator check entries before they're added:
Watch out for these common issues:
Remember to handle edge cases:
Consider these scenarios:
When implementing complex validations: