I have two models with relations one to many.
I don't understand how to create migration files. Does each model have its own migration file or one migration file can create several tables from models and relations between them (for example as in rails migrations)?
I had a look at many examples including Sequelize docs, and there are primitive examples of models creating and its migration.
//User model
module.exports = function (sequelize, Sequelize) {
var User = sequelize.define('users', {
id: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true,
},
email: {
type: Sequelize.STRING,
allowNull: false,
unique: true,
},
password: {
type: Sequelize.STRING,
allowNull: false,
},
});
return User;
}
//Order model
module.exports = function (sequelize, Sequelize) {
var Order = sequelize.define('orders', {
id: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true,
},
price: {
type: Sequelize.INTEGER,
allowNull: false,
},
totalPrice: {
type: Sequelize.INTEGER,
allowNull: false,
},
});
return Order;
}
//db.js
//Relations
db.orders.belongsTo(db.users);
db.users.hasMany(db.orders);
Addition
I create migration for two models:
module.exports = {
up: function (queryInterface, Sequelize, done) {
return [
queryInterface.createTable('users', {
id: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true,
},
email: {
type: Sequelize.STRING,
allowNull: false,
unique: true,
},
password: {
type: Sequelize.STRING,
allowNull: false,
},
}),
queryInterface.createTable('orders', {
id: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true,
},
price: {
type: Sequelize.INTEGER,
allowNull: false,
},
totalPrice: {
type: Sequelize.INTEGER,
allowNull: false,
},
userId: {
type: Sequelize.INTEGER,
references: {
model: 'users',
key: 'id'
},
onUpdate: 'CASCADE',
onDelete: 'CASCADE'
}
}),
done()
]
},
down: function (queryInterface, Sequelize, done) {
return [
queryInterface.dropTable('users'),
queryInterface.dropTable('orders'),
done()
]
}
};
Do I need to add into my migration file class methods for my models?
//for Order
classMethods: {
associate: function(models) {
Model.belongsTo(models.users, (as: 'users'));
}
}
//for User
classMethods: {
associate: function(models) {
Model.hasMany(models.orders, (as: 'orders'));
}
}
//Addition 2
In order to create new migration file you need to call sequelize migration:create, which creates new file in /migrations directory (that is default migrations directory, can be different). In the migration file you can use bunch of functions in order to create tables, update them or specified table columns etc. If you want you can create all your database tables within single migration file. There is no straight connection between your models and migration files - they are independent on each other. The same concerns relations between models/table. You need to specify that given column in given table references other table.
// example column definition inside migration file
// creates a foreign key referencing table 'users'
userId: {
type: Sequelize.INTEGER,
references: {
model: 'users',
key: 'id'
},
onDelete: 'CASCADE'
}
You just need to remember about consistency between fields definition in Model and field/column definitions in the migration file corresponding to specified model/table.
You can also use command sequelize model:create, which, at the same time, creates a file used for defining a Sequelize model, as well as migration file responsible for creating a table corresponding to this model.
In order to show all possible sequelize-cli commands simply run sequelize help.
EDIT
The class methods like associate must be present only in the Model definition files, not in the migration files.
EDIT 2
The functions used in migration files like createTable are asynchronous, so you cannot simply run them in order just like you did it in your migration file. You can chain them via .then() method or return them as an array like
return [queryInterface.createTable(...), queryInterface.createTable(...)];
Related
Nodejs. Sequelize 4.41. Try to make 2 models with relation many-to-many through another table. Running with sequelize-cli, for example...
sequelize model:generate --name Camera --attributes name:string,sn:string
Here is models
// Camera model
'use strict';
module.exports = (sequelize, DataTypes) => {
const Сamera = sequelize.define('Сamera', {
name: DataTypes.STRING,
sn: DataTypes.STRING
}, {});
Сamera.associate = function(models) {
// associations can be defined here
Camera.belongsToMany(models.Relay, {through: 'CameraRelay'});
};
return Сamera;
};
And
// Relay model
'use strict';
module.exports = (sequelize, DataTypes) => {
const Relay = sequelize.define('Relay', {
name: DataTypes.STRING,
sn: DataTypes.STRING,
channel: DataTypes.INTEGER
}, {});
Relay.associate = function(models) {
// associations can be defined here
Relay.belongsToMany(models.Camera, {through: 'CameraRelay'});
};
return Relay;
};
In documentation there are phrase
Belongs-To-Many associations are used to connect sources with multiple targets. Furthermore the targets can also have connections to multiple sources.
Project.belongsToMany(User, {through: 'UserProject'});
User.belongsToMany(Project, {through: 'UserProject'});
This will
create a new model called UserProject with the equivalent foreign keys
projectId and userId.
Migrations is
// create-relay
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('Relays', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
name: {
type: Sequelize.STRING
},
sn: {
type: Sequelize.STRING
},
channel: {
type: Sequelize.INTEGER
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
}
});
},
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('Relays');
}
};
And
//create camera
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('Сameras', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
name: {
type: Sequelize.STRING
},
sn: {
type: Sequelize.STRING
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
}
});
},
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('Сameras');
}
};
Why it doesn't create model CameraRelay and doesn't create migration for same model when I running migrate?
I guess the misunderstanding is about sync vs migration: great part of documentation you are referring to, is using the sync method to create all tables and associations starting from models.
When you are using migrations, you are creating db all of your table/columns/associations using migration files (and in my hopinion, this is a better way for something that is going to production).
To understand the difference, just look at your camera model vs your camera migration file:
the model has only name and sn properties defined
the migration file has of course name and sn, but it has id, createdAt and updatedAt too.
Migrations are file with the aim of change your db in a safe way, allowing you to rollback to any point in the past.
So, back to your problem, you have to:
create a new migration file to create your new CameraRelay table, with foreign keys to both Camera and Relay tables
update your current Camera migration file with one-to-many relation to CameraRelay table
update your current Relay migration file with one-to-many relation to CameraRelay table
CameraRelay migration example:
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('CameraRelays', {
cameraId: {
primaryKey: true,
type: Sequelize.INTEGER,
allowNull: false,
references: {
model: 'Relay',
key: 'id'
}
},
relayId: {
primaryKey: true,
type: Sequelize.INTEGER,
allowNull: false,
references: {
model: 'Camera',
key: 'id'
}
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
}
});
},
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('CameraRelays');
}
};
I'm learning Sequelize and there is something that I found quite strange, so I think that I'm doing something wrong.
This is my migration for a simple Posts table :
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('Posts', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
title: {
type: Sequelize.STRING,
allowNull: false,
},
content: {
type: Sequelize.TEXT,
allowNull: false,
},
authorId: {
type: Sequelize.INTEGER,
onDelete: 'CASCADE',
allowNull: false,
references: {model: 'Users', key: 'id'}
},
publishedAt: {
type: Sequelize.DATE,
allowNull: true
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
}
});
},
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('Posts');
}
};
Another little question here, do I have to specify allowNull: false for the title and the content if I don't want them to be null. I think yes, but many projects I saw don't specify it.
This is the Post model :
'use strict';
module.exports = (sequelize, DataTypes) => {
const Post = sequelize.define('Post', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: DataTypes.INTEGER
},
title: {
type: DataTypes.STRING,
allowNull: false,
},
content: {
type: DataTypes.TEXT,
allowNull: false,
},
publishedAt: {
type: DataTypes.DATE,
allowNull: true
},
}, {
classMethods: {
associate: function (models) {
Post.belongsTo(models.User, {
onDelete: 'CASCADE',
foreignKey: {
fieldName: 'authorId',
allowNull: false
}
});
}
}
});
return Post;
};
I repeted the same data between to file... I come from Laravel so maybe it's usual in the NodeJS world to do things like these.
To avoid code duplication between models and migrations, use models inside migrations as follows:
'use strict';
const { User } = require('../models');
module.exports = {
up: (queryInterface, Sequelize) => {
return User.sync();
// return queryInterface.createTable('user', { id: Sequelize.INTEGER });
},
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('user');
}
};
EDIT:
This is a good option for adding new tables only!
Caution for "{ alter: true }"! When changing existing columns, be aware that sequelize may not recognise a column rename and will perform two "DROP" and "ADD" operations instead of just one "CHANGE".
So, for updating schema, better use queryInterface.renameColumn and queryInterface.changeColumn or raw queries.
.sync()
queryInterface
raw queries
Yes, currently you have to duplicate the field definitions from Model to its respective Migration, which is really not a great way to build an application.
However there is a package which automatically generates migrations from your Model, you can check it here: https://github.com/flexxnn/sequelize-auto-migrations but is it in early stages.
You can also read discussion on same topic here: https://github.com/sequelize/cli/issues/257
If you want your data not to be null it is a good practice to add DB constraints even though it is not mandatory and you can enforce that in your code.
Regarding your second question, short answer is yes. In your case you are duplicating your code. But it is most likely that you will add or modify your table schema in the future which will cause your model to look different from your first migration file. Remember you don't change a migration file after it ran on production since it runs only once. Any time you want to change your table schema you will have to create a new migration file.
I want also to help future visitors
#Wizix, allowNull is important when you want to avoid empty input in the database. it is validation on the server-side.
Example:
const User = db.define('users', {
fullname: {
type: Sequelize.STRING(50),
allowNull: false,
validate: {
notEmpty: {
args: true,
msg: 'Please provide fullname',
},
},
}
})
I expect user input to not be empty.
allowNull in models validate input before processed to a database
allowNull in migration is the last checking validation where we decide to save to a database or to reject the user input.
duplication of data
I found it necessary to have both conditions validating inputs.
I'm currently running through using Node-Express and Sequelize as the ORM for PostgreSQL. I am trying to test my API route when I noticed that my todoId is not being returned. When I check my table I see a null value even though I have allowNull:false set.
module.exports = {
up: (queryInterface, Sequelize) =>
queryInterface.createTable('TodoItems', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER,
},
content: {
type: Sequelize.STRING,
allowNull: false,
},
complete: {
type: Sequelize.BOOLEAN,
defaultValue: false,
},
createdAt: {
allowNull: false,
type: Sequelize.DATE,
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE,
},
todoId: {
type: Sequelize.INTEGER,
onDelete: 'CASCADE',
allowNull: false,
references: {
model: 'Todos',
key: 'id',
as: 'todoId',
},
},
}),
down:(queryInterface/*, Sequelize*/) => queryInterface.dropTable('TodoItems'),
};
I have tried everything from dropping my database and creating a new one and migrations for the schema all over again. What I'm trying to achieve is to ensure that the value of the todoId column references and maps my the id of a separate table called todo.
Solved it. The error was using a deprecated class methods
We have two models, users and items. Under User.js
User = Model.define('User', {
id: {
type: DataType.UUID,
defaultValue: DataType.UUIDV1,
primaryKey: true,
},
});
And under Item.js
Item = Model.define('Item', {
id: {
type: DataType.UUID,
defaultValue: DataType.UUIDV1,
primaryKey: true,
},
});
Here is their association, a user can have many items.
User.hasMany(Items, {
foreignKey: {
allowNull: false,
name: 'itemId',
},
onUpdate: 'cascade',
onDelete: 'cascade',
});
Assume that each user may only have one of each type of item. How do I add a unique constraint for this? The following code does not work.
User.hasMany(Items, {
foreignKey: {
allowNull: false,
name: 'itemId',
unique: 'userItemUnique',
},
onUpdate: 'cascade',
onDelete: 'cascade',
});
Item = Model.define('Item', {
id: {
type: DataType.UUID,
defaultValue: DataType.UUIDV1,
primaryKey: true,
unique: 'userItemUnique',
},
});
You can use migrations for this.
Sequelize-cli provides a methods addConstraint and andIndex which can be used to achieve
From the docs
queryInterface.addConstraint('Users', ['email'],
{ type: 'unique', name: 'custom_unique_constraint_name'
});
If anyone is still following this, I solved this by manually defining the foreign keys in the model where the unique constraint is required (you can still use sequelize association such as .hasMany).
Regarding your own code, I think there might be a confusion when you ask for Assume that each user may only have one of each type of item since you are not defining what is an item type.
I've drafted something with my own understanding and taking into account my previous comment.
User = Model.define('User', {
id: {
type: DataType.UUID,
defaultValue: DataType.UUIDV1,
primaryKey: true,
allowNull: false,
validate: {
isUUID: 1,
},
},
});
Item = Model.define('Item', {
id: {
type: DataType.UUID,
defaultValue: DataType.UUIDV1,
primaryKey: true,
allowNull: false,
validate: {
isUUID: 1,
},
},
type: {
type: DataType.STRING,
unique: 'uniqueUserItemType' // see note 1
}
userId: {
type: DataType.UUID,
references: { // see note 2
model: User,
key: 'id',
},
unique: 'uniqueUserItemType',
}
});
User.hasMany(Item, {
foreignKey: {
allowNull: false,
name: 'itemId',
},
onUpdate: 'cascade',
onDelete: 'cascade',
});
Item.belongsTo(User);
I've also added a belongsTo association as recommended by Sequelize.
[1] More info on composite unique constraint here.
[2] More info on foreign key definition inside of model here.
In my case I did something like this based on Joel Barenco's answer.
const { Model, DataTypes } = require('sequelize');
const User = require('../models/user');
module.exports = function(sequelize){
class Algorithm extends Model {}
UserModel = User(sequelize);//#JA - Gets a defined version of user class
var AlgorithmFrame = Algorithm.init({
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
name: {
type: DataTypes.STRING,
allowNull: false,
},
user_Id: {
type: DataTypes.INTEGER,
references: {
model: UserModel,
key: 'id',
},
}
}, {
sequelize,
modelName: 'Algorithm',
indexes: [{ unique: true, fields: ['name','user_id'] }]
});
return AlgorithmFrame
};
The idea here is to manually create the foreign key, but you can define the unique indexes instead with indexes: [{ unique: true, fields: ['name','user_id'] }]
My tactic also shows how to define the model in a class as well. To call it you simply pass sequelize to it like this, where sequelize is the variable holding all your connection info etc...
const Algorithm = require('../models/algorithm');
const AlogorithmModel = Algorithm(sequelize);
then you can make sure it's created with
await AlogorithmModel.sync({ alter: true });
My user model file is this:
const { Model, DataTypes } = require('sequelize');
module.exports = function(sequelize){
class User extends Model {}
return User.init({
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
name: {
type: DataTypes.STRING,
allowNull: false
},
trading_system_key: {
type: DataTypes.STRING,
allowNull: false
},
}, {
sequelize,
modelName: 'User',
indexes: [{ unique: true, fields: ['trading_system_key'] }]
});
};
Background:
This is for a product database prototype, each product can have multiple related products, and each relationship has a type, e.g. 'Accessory', 'Spare Part', 'Related to', 'Similar to', etc.
Technology
We are using sequelize js on node to define the model.
Model Snippet:
sequelize.define('Product', {
id: { type: DataTypes.INTEGER, autoIncrement: true, primaryKey: true },
name: { type: DataTypes.STRING, allowNull: false, comment: 'product name'}
...
});
sequelize.define('ProductRelationType', {
id: { type: DataTypes.INTEGER, autoIncrement: true, primaryKey: true },
name: { type: DataTypes.STRING, allowNull: false, comment: 'relationship type description' }
});
sequelize.sync({force: false}).then( function() {
...
});
Question
Is it possible to use models themselves as datatypes in Sequelize, to establish a collection in another table, for example:
sequelize.define('ProductRelation', {
id: { type: DataTypes.INTEGER, autoIncrement: true, primaryKey: true },
relatedProduct: { type: Product } //reference to product model
});
and followed by:
Product.hasMany(ProductRelation, { as: 'relatedProducts' });
ProductRelation.hasOne(ProductRelationType, { as: 'RelationType' } );
alternatively, exclude the ProductRelation table definition, and use:
db.Product.hasMany(db.Product, { through: 'RelatedProduct' } );
db.RelatedProduct.hasMany(db.Product, { through: 'RelatedProduct' } );
Note: These are concept examples, they do not work.
Any suggestions, or alternative modeling approaches are appreciated.
Thank you
It appears, what you want is simply establishing an n:m relationship from Product to Product.
The only way to get there is by establishing a link- (or "through-") table. You can either do it manually or let Sequelize do it automatically using belongsToMany:
var RelatedProducts = sequelize.define('RelatedProducts', {
// other columns here
});
Product.belongsToMany(Product, { through: RelatedProducts, foreignKey: 'relatedProductId' });
Product.belongsToMany(Product, { through: RelatedProducts });