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.
Related
I am trying to configure a Foreign Key association between two tables on 'non-PrimaryKey' fields for one-to-many relation:
Asset.belongsTo(AssetClass)
AssetClass.hasMany(Asset)
I create tables first and add the constraint in the third migration:
migrations\20220621223626-create-asset.js
'use strict';
module.exports = {
async up(queryInterface, Sequelize) {
await queryInterface.createTable('Assets', {
ticker: {
allowNull: false,
autoIncrement: false,
primaryKey: true,
type: Sequelize.STRING
},
shortName: {
allowNull: false,
type: Sequelize.STRING
},
fullName: {
allowNull: false,
type: Sequelize.STRING
},
assetClass: {
allowNull: false,
type: Sequelize.STRING
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
}
});
},
async down(queryInterface, Sequelize) {
await queryInterface.dropTable('Assets');
}
};
migrations\20220622035610-create-asset-class.js
'use strict';
module.exports = {
async up(queryInterface, Sequelize) {
await queryInterface.createTable('AssetClasses', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
name: {
type: Sequelize.STRING,
allowNull: false
},
prio: {
type: Sequelize.INTEGER,
allowNull: false
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
}
});
},
async down(queryInterface, Sequelize) {
await queryInterface.dropTable('AssetClasses');
}
};
migrations\20220627211055-add-constraint-fk_asset-assetClass.js
'use strict';
module.exports = {
async up(queryInterface, Sequelize) {
await queryInterface.addConstraint('Assets', {
fields: ['assetClass'], //existing field in Assets table
type: 'foreign key',
name: 'fk_asset-assetClass',
references: {
table: 'AssetClasses', //reference to AssetClasses table
field: 'name' //name of the target field
}
});
},
async down(queryInterface, Sequelize) {
await queryInterface.removeConstraint('Assets', 'fk_asset-assetClass');
}
};
After running db::migrate I am getting a following error message:
SQLITE_ERROR: foreign key mismatch - "Assets_backup" referencing "AssetClasses"
which leaves me with a Assets_backup table in the database which I need to remove manually.
What seems to works though is:
Creating a new column assetClassId in Assets table and referencing it to Primary Key field (id) of AssetClasses table:
//addConstraint migration
module.exports = {
async up(queryInterface, Sequelize) {
await queryInterface.addConstraint('Assets', {
fields: ['assetClassId'], //existing field in Assets table
type: 'foreign key',
name: 'fk_asset-assetClass',
references: {
table: 'AssetClasses', //reference to AssetClasses table
field: 'id' //name of the target field
}
});
},
async down(queryInterface, Sequelize) {
await queryInterface.removeConstraint('Assets', 'fk_asset-assetClass');
}
};
//createTable Assets migration
assetClassId: {
allowNull: false,
type: Sequelize.INTEGER
},
How can I make it work for existing non-PK fields?
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 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');
},
};
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'
});
};