I run node 10.16.3 and sequelize 5.19.5. I created two models with a many to many relationship between them, using sequelize model:generate. It created both model and migration files. Supposedly, when I specify a many to many association, sequelize should generate the join table by itself, and also generate add methods on those models, so I can associate them properly. None of those things happened in my case after I ran sequelize db:migrate and sequelize db:seed:all. I saw many people just creating a join table manually, but I'd like to avoid that if there's a simpler way. Code files follow (with omitted imports for some constants that are irrelevant):
model Activity
module.exports = (sequelize, DataTypes) => {
const Activity = sequelize.define('Activity', {
name: DataTypes.ENUM(cleaningActivity),
category: DataTypes.STRING,
baseRate: DataTypes.FLOAT,
specialEquipment: DataTypes.STRING,
description: DataTypes.STRING,
deleted: DataTypes.BOOLEAN
}, {});
Activity.associate = function(models) {
Activity.belongsToMany(models.ActivityBundle, {
through: 'Activity_ActivityBundle'
});
};
return Activity;
};
Activity migration:
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('Activity', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
name: {
type: Sequelize.ENUM(cleaningActivity)
},
category: {
type: Sequelize.STRING
},
baseRate: {
type: Sequelize.FLOAT
},
specialEquipment: {
type: Sequelize.STRING
},
description: {
type: Sequelize.STRING
},
deleted: {
type: Sequelize.BOOLEAN
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
}
});
},
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('Activity');
}
};
Now, ActivityBundle model:
module.exports = (sequelize, DataTypes) => {
const ActivityBundle = sequelize.define('ActivityBundle', {
name: DataTypes.ENUM(cleaningBundle),
deleted: DataTypes.BOOLEAN
}, {});
ActivityBundle.associate = function(models) {
ActivityBundle.belongsToMany(models.Activity, {
through: 'Activity_ActivityBundle'
});
};
return ActivityBundle;
};
and finally, ActivityBundle migration:
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('ActivityBundle', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
name: {
type: Sequelize.ENUM(cleaningBundle)
},
deleted: {
type: Sequelize.BOOLEAN
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
}
});
},
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('ActivityBundle');
}
};
Then, I have a seed file, where I ma trying to first create bundles, then associate them to activities, like so:
module.exports = {
up: async (queryInterface, Sequelize) => {
const bundles = await ActivityBundle.bulkCreate(bundlesArray);
return Activity.bulkCreate(activitiesArray).then(activities => {
return Promise.all(activities.map(activity => {
const bundlesPerActivity = activityBundleMapping[activity.get('name')]
.map(name => bundles.find(b => b.get('name') === name));
return activity.addBundles(bundlesPerActivity); // this method does not exist, even though it should
}));
})
},
down: (queryInterface, Sequelize) => {
}
};
Clearly I am doing something wrong. What more am I supposed to define and where? I guess the migration files need to have some mention of many to many association? Not a clue, and official documentation is incomplete imho.
You need to generate the join table yourself. Sequelize isn't that magical. :)
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable(
'Activity_ActivityBundle',
{
createdAt: {
allowNull: false,
type: Sequelize.DATE,
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE,
},
ActivityId: {
type: Sequelize.INTEGER,
primaryKey: true,
},
ActivityBundleId: {
type: Sequelize.INTEGER,
primaryKey: true,
},
}
);
},
down: (queryInterface, Sequelize) => {
// remove table
return queryInterface.dropTable('Activity_ActivityBundle');
},
};
Related
I have a database that was created with Postgres that was set up for a single foreign key association, Now, this would be mapped as a role table model
consider I have two tables user and roles
roles contain role details and user contain user details of role
const uuid = require('uuid/v4');
('use strict');
module.exports = (sequelize, DataTypes) => {
const role = sequelize.define(
'role',
{
id: {
allowNull: false,
primaryKey: true,
type: DataTypes.UUID,
},
name: {
type: DataTypes.STRING,
allowNull: false,
},
},
{}
);
role.beforeCreate((role) => (role.id = uuid()));
role.associate = function (models) {
role.hasMany(models.user), { foreignKey: 'roleId', as: 'user_roleId' };
};
return role;
};
role migration
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('roles', {
id: {
allowNull: false,
primaryKey: true,
type: Sequelize.UUID,
},
name: {
type: Sequelize.STRING,
},
createdAt: {
allowNull: false,
type: Sequelize.DATE,
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE,
},
});
},
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('roles');
},
};
user model
const uuid = require('uuid/v4');
('use strict');
module.exports = (sequelize, DataTypes) => {
const user = sequelize.define(
'user',
{
id: {
allowNull: false,
primaryKey: true,
type: DataTypes.UUID,
},
firstName: {
type: DataTypes.STRING,
allowNull: false,
},
lastName: DataTypes.STRING,
email: {
type: DataTypes.STRING,
allowNull: false,
},
password: {
type: DataTypes.STRING,
allowNull: false,
},
phoneNumber: {
type: DataTypes.STRING,
},
roleId: {
type: DataTypes.UUID,
},
},
{
timestamps: true,
paranoid: true,
defaultScope: {
attributes: { exclude: ['password'] },
},
}
);
user.beforeCreate((user) => (user.id = uuid()));
user.associate = function (models) {
user.belongsTo(models.role, { foreignKey: 'roleId', onDelete: 'CASCADE' });
};
return user;
};
user migration
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('users', {
id: {
allowNull: false,
primaryKey: true,
type: Sequelize.UUID,
},
firstName: {
type: Sequelize.STRING,
},
lastName: {
type: Sequelize.STRING,
},
email: {
type: Sequelize.STRING,
},
password: {
type: Sequelize.STRING,
},
phoneNumber: {
type: Sequelize.STRING,
},
roleId: {
type: Sequelize.UUID,
},
createdAt: {
allowNull: false,
type: Sequelize.DATE,
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE,
},
deletedAt: {
allowNull: true,
type: Sequelize.DATE,
},
});
},
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('users');
},
};
after running the migration these tables are created in my database.role_id is also present in the user table. but role_id is not generated as a foreign key in my user table. also please verify that the relationship which is mention here(one to many) is correct or not.
please verify my code and give me any suggestions if any changes required. I'm new in development
Your user migration also needs to know about the foreign key; you do this by adding a references: key to the column definition. The Sequelize documentation has a foreign key example; scroll about half way down the page (or just search for references).
In your case the user migration should look something like:
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('users', {
// ... other fields omitted
roleId: {
type: Sequelize.UUID,
references: {
model: { tableName: 'role' }
key: 'id',
},
},
// ... more fields omitted
});
},
// down: omitted
}
I have a User model (with associated migration file created by sequelize-cli):
'use strict';
module.exports = (sequelize, DataTypes) => {
const User = sequelize.define('User', {
dispName: DataTypes.STRING,
email: DataTypes.STRING,
phoneNum1: DataTypes.STRING
}, {});
User.associate = function (models) { // associations added manually
User.belongsToMany(models.Role, { through: 'UserRoles', foreignKey: 'userId' });
};
return User;
};
here's the generated migration file:
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('Users', {
cognitoId: { // modified: was an auto-incrementing integer
allowNull: false,
primaryKey: true,
type: Sequelize.STRING(100)
},
dispName: {
type: Sequelize.STRING(100) // modified: was just plain STRING
},
email: {
type: Sequelize.STRING(100) // modified: was just plain STRING
},
phoneNum1: {
type: Sequelize.STRING(15) // modified: was just plain STRING
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
}
});
},
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('Users');
}
};
and a Role model (with associated migration file created by sequelize-cli):
'use strict';
module.exports = (sequelize, DataTypes) => {
const Role = sequelize.define('Role', {
name: DataTypes.STRING
}, {});
Role.associate = function (models) { // associations added manually
Role.belongsToMany(models.User, { through: 'UserRoles', foreignKey: 'roleId' });
};
return Role;
};
here's the generated migration file for Role:
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('Roles', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
name: {
type: Sequelize.STRING(100), // modified: was just plain STRING
unique: true // Added manually
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
}
});
},
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('Roles');
}
};
When I run sequelize-cli db:migrate, the table UserRoles is not created. Why is that?
The Associations page of the Sequelize manual seems to suggest that defining the associations in the model file is all that is required...
Research:
I seem to have done what is suggested in this answer to the question Sequelize not creating model association columns but doesn't seem to be working for me (the answer isn't accepted either).
Not quite what I need: Querying association tables in Sequelize
Nor this: Sequelize how to use association table?
I have 2 tables: Countries and Spots. A country can have many spots and a spot belongs to one country.
I have generated the migrations necessary with sequelize for the 2 tables:
Countries.js
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('Countries', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
name: {
allowNull: false,
unique: true,
type: Sequelize.STRING
},
createdAt: {
allowNull: false,
defaultValue: Sequelize.literal('NOW()'),
type: Sequelize.DATE,
},
updatedAt: {
allowNull: false,
defaultValue: Sequelize.literal('NOW()'),
type: Sequelize.DATE
}
});
},
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('Countries');
}
};
Spots.js
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('Spots', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
name: {
allowNull: false,
type: Sequelize.STRING
},
wind: {
type: Sequelize.FLOAT
},
country_id: {
type: Sequelize.INTEGER,
references: {
model: 'Countries', // name of Target table
key: 'id', // key in Target table that we're referencing
},
onDelete: 'CASCADE',
},
createdAt: {
allowNull: false,
defaultValue: Sequelize.literal('NOW()'),
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
defaultValue: Sequelize.literal('NOW()'),
type: Sequelize.DATE
}
});
},
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('Spots');
}
};
Everything works ok. I define some routes and some controllers and I try to do Spots.findAll() in my controller
const models = require('../models/index')
const Spot = models.Spot
exports.index = async (req, res, next) => {
const spots = await Spot.findAll()
res.status(200).json(spots)
}
However the query Spot.findAll() tries to ask for CountryId which is a key that obviously doesn't exist and I do not wish for it to exist.
Executing (default): SELECT `id`, `name`, `wind`, `country_id`, `createdAt`, `updatedAt`, `CountryId` FROM `Spots` AS `Spot`;
(node:13027) UnhandledPromiseRejectionWarning: SequelizeDatabaseError: Unknown column 'CountryId' in 'field list'
These are the spot and countries models:
Country.js
'use strict';
module.exports = (sequelize, DataTypes) => {
const Country = sequelize.define('Country', {
name: DataTypes.STRING
}, {});
Country.associate = function(models) {
// associations can be defined here
Country.hasMany(models.Spot)
};
return Country;
};
Spot.js
'use strict';
module.exports = (sequelize, DataTypes) => {
const Spot = sequelize.define('Spot', {
name: DataTypes.STRING,
wind: DataTypes.FLOAT,
country_id: DataTypes.INTEGER
}, {});
Spot.associate = function(models) {
// associations can be defined here
Spot.belongsTo(models.Country, {
foreignKey: 'country_id'
});
Spot.hasMany(models.Favorite)
};
return Spot;
};
I added the foreign_key attribute to belongs_to as I thought that the error surely comes from the associations(I still think it does).
Why does it happen and how to fix it?
The problem is because you are mixing everything here please follow one convention either camelCase or snack_case.
Write country_id as countryId and change your table names to lowercase and you will good to go.
I am using "sequelize": "^5.8.6" and have created my project structure using "sequelize-cli": "^5.4.0". I would like to create associations so that:
One company has many ratings
I have created a company model, which looks like that:
'use strict';
module.exports = (sequelize, DataTypes) => {
const Company = sequelize.define('Company', {
name: DataTypes.STRING,
symbol: DataTypes.STRING,
}, {});
Company.associate = function(models) {
Company.hasMany(models.Rating);
};
return Company;
};
My Rating model looks like that:
'use strict';
module.exports = (sequelize, DataTypes) => {
const Rating = sequelize.define('Rating', {
action: DataTypes.STRING,
}, {});
Rating.associate = function(models) {
Rating.belongsTo(models.Company);
// associations can be defined here
};
return Rating;
};
My Company Migration look like the following:
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('Companies', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
name: {
type: Sequelize.STRING
},
symbol: {
type: Sequelize.STRING
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
}
});
},
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('Companies');
}
};
My Rating migration looks like the following:
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('Ratings', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
companyid: {
type: Sequelize.INTEGER,
references: {
model: 'Company',
key: 'id',
},
onUpdate: 'CASCADE',
onDelete: 'SET NULL',
},
action: {
type: Sequelize.STRING
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
}
});
},
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('Ratings');
}
};
When running, I get the following error:
> npx sequelize-cli db:migrate
ERROR: Can't create table `test_db`.`ratings` (errno: 150 "Foreign key constraint is incorrectly formed")
Any suggestions what I am doing wrong?
I appreciate your replies!
If you haven't just left it out of your code, your company model association should read:
Company.associate = function(models) {
Company.hasMany(models.Rating, {
foreignKey: 'companyid',
targetKey: 'id'
});
};
And your rating model should read:
Rating.associate = function(models) {
Rating.belongsTo(models.Company, {
// associations can be defined here
foreignKey: 'companyid',
targetKey: 'id'
});
};
Given this ERD, I'm trying to figure out why foreign keys are not being created on documentChildren and documentAttribute as they each have a column which should be a FK to document.
My sequelize models are all working fine, but i'm curious what i'm doing wrong in that real FK's are not being generated.
document migration:
'use strict'
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('documents', {
id: {
allowNull: false,
primaryKey: true,
type: Sequelize.UUID,
defaultValue: Sequelize.UUIDV4
},
...
})
}
...
}
documentChildren migration:
'use strict'
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('documentChildren', {
id: {
allowNull: false,
primaryKey: true,
type: Sequelize.UUID,
defaultValue: Sequelize.UUIDV4
},
documentId: {
allowNull: false,
type: Sequelize.UUID,
references: {
model: 'documents',
key: 'id'
}
},
...
})
}
...
}
documentAttribute migration:
'use strict'
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('documentAttributes', {
id: {
allowNull: false,
primaryKey: true,
type: Sequelize.UUID,
defaultValue: Sequelize.UUIDV4
},
documentId: {
allowNull: false,
type: Sequelize.UUID,
references: {
model: 'documents',
key: 'id'
}
},
...
})
}
...
}
document model associations:
document.associate = function (models) {
document.hasMany(models.documentAttribute)
document.hasMany(models.documentChildren)
}
Your code shows
"documentId" uuid NOT NULL REFERENCES documents(id)
The REFERENCES documents(id) is the FK. Check postgresql-docs
You are confused with the indexes creation, that does not mean having a FK.