Foreign field reference in Sequelize model files - node.js

I am writing a lead capture app using nodejs, express and sequelize. I have a Lead object, but I want to introduce a new table LeadSource to the schema, with Lead having a FK to LeadSource
/path/to/model/Lead.js
'use strict';
module.exports = (sequelize, DataTypes) => {
var Lead = sequelize.define('Lead', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
allowNull: false,
primaryKey: true
},
first_name: {
type: DataTypes.STRING,
},
last_name: {
type: DataTypes.STRING,
},
email: {
type: DataTypes.STRING,
allowNull: false,
},
});
return Lead;
};
This is the schema for LeadSource
LeadSource = sequelize.define('LeadSource', {
id: { type: Sequelize.INTEGER },
name: { type: Sequelize.STRING }
});
My questions are:
How do I reference LeadSource as FK in the Lead.js file?
I like to keep each model in a seperate file - can I have this separation and still have LeadSource reference by a FK in Lead?

Yes, you can define the relationships between the models while keeping each one in a separate file.
Here is how you can define associations between Lead and LeadSource in Lead.js file
module.exports = (sequelize, DataTypes) => {
var Lead = sequelize.define('Lead', {
// ...
})
Lead.associate = function (models) {
// associations can be defined here
//One-To-One relationship with foreign key added to Lead
Lead.belongsTo(models.LeadSource, {
foreignKey: { name: 'leadSourceId' }
})
//One-To-Many relationship with foreign key added to Lead
models.LeadSource.hasMany(Lead, {
foreignKey: { name: 'leadSourceId' }
})
}
return Lead;
}

Related

How to fix? Unhandled rejection SequelizeForeignKeyConstraintError: insert or update on table "x" violates foreign key constraint "y_fkey"

This error is showing when I attempt to POST / create a new contact in the database. This is the error Unhandled rejection SequelizeForeignKeyConstraintError: insert or update on table "contacts" violates foreign key constraint "org_name_fkey". Below are the models, does anyone know how to fix this? All of the other fields POST fine, it is only when attempted to add org_name. If I don't include org_name in the POST everything stores in the postgres database.
Contact Model
module.exports = (sequelize, DataTypes) => {
const Contacts = sequelize.define('contact', {
contact_id: {
type: Sequelize.INTEGER,
field: 'contact_id',
primaryKey: 'true'
},
first_name: {
type: Sequelize.STRING,
field: 'first_name'
},
last_name: {
type: Sequelize.STRING,
field: 'last_name'
},
contact_type_id: {
type: Sequelize.STRING,
references: {
model: 'contact_type',
key: 'contact_type_id'
}
},
org_name: {
type: Sequelize.STRING,
references: {
model: 'org',
key: 'org_name'
}
}
},
{
tableName: 'contacts',
}
);
Contacts.associate = (models) => {
Contacts.belongsTo(models.contact_type, {foreignKey: 'contact_type_id'});
Contacts.belongsTo(models.org, {foreignKey: 'org_name'});
};
return Contacts;
};
Contact Type Model
module.exports = (sequelize, DataTypes) => {
const ContactType = sequelize.define('contact_type', {
// attributes
contact_type_id: {
type: Sequelize.STRING,
field: 'contact_type_id'
},
updated_at: {
type: Sequelize.DATE,
field: 'updated_at'
},
created_at: {
type: Sequelize.DATE,
field: 'created_at'
}
},
);
ContactType.associate = (models) => {
ContactType.hasOne(models.contact, {foreignKey: 'contact_id'});
};
return ContactType;
};
Org Model
module.exports = (sequelize, DataTypes) => {
const Org = sequelize.define('org', {
org_name: {
type: Sequelize.STRING,
field: 'org_name',
primaryKey: 'true'
},
org_type_id: {
type: Sequelize.STRING,
field: 'org_type_id'
},
website: {
type: Sequelize.STRING,
field: 'website'
}
},
);
Org.associate = (models) => {
Org.hasMany(models.contact, {foreignKey: 'contact_id'});
};
return Org;
};
What that error tells you is that whatever org_name you are passing into your POST request does not have a corresponding Org record in the database. i.e. You are creating a person who works at ABC Corp, but you haven't added ABC Corp to your Orgs. You would have to fix this by creating the Org first, then the Contact.
You do not give the exact request that causes the error. This would be helpful in tracking down your issue. Also, you should query your database to find the state of the Org table before the POST. Perhaps you forgot to call save on the Org instance.

Saving Sequelize Record/Referencing it in Another Model

I have a postrgresql/Sequelize model called Segment, which belongs to many models:
module.exports = (sequelize, DataTypes) => {
const Segment = sequelize.define(
'segment',
{
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true
},
provider_id: {
type: DataTypes.INTEGER,
references: {
model: 'provider',
key: 'id'
}
},
summary_id: {
type: DataTypes.INTEGER,
references: {
model: 'summary',
key: 'id'
}
},
audience_id: {
type: DataTypes.INTEGER,
references: {
model: 'audience',
key: 'id'
}
},
onboarding_id: {
type: DataTypes.INTEGER,
references: {
model: 'onboarding',
key: 'id'
}
}
},
{
// disable the modification of table names; By default, sequelize will automatically
// transform all passed model names (first parameter of define) into plural.
// if you don't want that, set the following
freezeTableName: true,
tableName: 'segment'
}
);
Segment.associate = models => {
Segment.belongsTo(models.Provider, { foreignKey: 'id' });
Segment.belongsTo(models.Summary, { foreignKey: 'id' });
Segment.belongsTo(models.Audience, { foreignKey: 'id' });
Segment.belongsTo(models.Onboarding, { foreignKey: 'id' });
};
return Segment;
};
The models that segment has associations to (ie provider_id, summary_id, audience_id, onboarding_id) look like this:
Provider:
module.exports = (sequelize, DataTypes) => {
const Provider = sequelize.define(
'provider',
{
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true
},
providerName: {
type: DataTypes.STRING
},
email: {
type: DataTypes.STRING
},
privacyPolicy: {
type: DataTypes.STRING
}
},
{
freezeTableName: true,
tableName: 'provider'
}
);
Provider.associate = models => {
Provider.hasMany(models.Segment, { foreignKey: 'provider_id' });
};
return Provider;
};
Summary:
module.exports = (sequelize, DataTypes) => {
const Summary = sequelize.define(
'summary',
{
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true
},
summaryName: DataTypes.STRING,
standardIdName: DataTypes.STRING,
description: DataTypes.STRING,
},
{
freezeTableName: true,
tableName: 'summary'
}
);
Summary.associate = models => {
Summary.hasMany(models.Segment, { foreignKey: 'summary_id' });
};
return Summary;
};
Audience:
module.exports = (sequelize, DataTypes) => {
const Audience = sequelize.define(
'audience',
{
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true
},
refreshCadence: DataTypes.STRING,
sourceLookbackWindow: DataTypes.STRING
},
{
freezeTableName: true,
tableName: 'audience'
}
);
Audience.associate = models => {
Audience.hasMany(models.Segment, { foreignKey: 'audience_id' });
};
return Audience;
};
Onboarding:
module.exports = (sequelize, DataTypes) => {
const Onboarding = sequelize.define(
'onboarding',
{
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true
},
onboardingExpansions: DataTypes.STRING,
onboardingAudiencePrecision: DataTypes.STRING
},
{
freezeTableName: true,
tableName: 'onboarding'
}
);
Onboarding.associate = models => {
Onboarding.hasMany(models.Segment, { foreignKey: 'onboarding_id' });
};
return Onboarding;
};
My question is: what should come first when creating and saving a Segment record? Do I create and save each one of the other models first (provider, summary, audience, onboarding), and then create/save a Segment with references to those ids? I don't really know what the order of events should be in this situation. Any help is much appreciated! Thanks!
TLDR:
In order to create an instance of Segment, you must have all 4 foreign keys reference exist records on the referenced tables(provider, summary, audience and onboarding).
Explanation:
provider, summary, audience and onboarding tables are independent.
However, Segment model is not independent.
Segment model has 4 columns which are foreign keys.
From PostgresSql Tutorial:
A foreign key is a field or group of fields in a table that uniquely
identifies a row in another table. In other words, a foreign key is
defined in a table that references to the primary key of the other
table.
The table that contains the foreign key is called referencing table or
child table. And the table to which the foreign key references is
called referenced table or parent table.
It means that a foreign key is a constraint that the column should reference the primary key of the referenced table.
So, you must create all the resources of a created row of Segment.

Problem setting up Sequelize association - query with 'include' is failing

I'm new to Sequelize and trying to test if an n:m association I set up between two models, User and Podcast, is working. When I try to run this query, I get some kind of DB error that isn't specific about what's wrong:
User.findOne({
where: { id: id },
include: [{ model: Podcast }]
});
Does anyone know what I'm messing up? I suspect there's something wrong in how I've set up the association, like I'm referencing the names of tables slightly incorrectly, but the migration to create the association worked.
Here's my User.js model file:
module.exports = (sequelize, DataTypes) => {
const User = sequelize.define('User', {
name: {
type: DataTypes.STRING,
allowNull: false
},
email: {
type: DataTypes.STRING,
allowNull: false,
unique: true
},
photo: {
type: DataTypes.STRING
}
});
User.associate = function(models) {
// associations can be defined here
User.belongsToMany(models.Podcast, {
through: 'user_podcast'
});
};
return User;
};
And here's my Podcast.js file:
'use strict';
module.exports = (sequelize, DataTypes) => {
const Podcast = sequelize.define('Podcast', {
id: {
type: DataTypes.STRING,
primaryKey: true,
allowNull: false
},
title: {
type: DataTypes.STRING,
allowNull: false
},
thumbnail: {
type: DataTypes.STRING
},
website: {
type: DataTypes.STRING
}
});
Podcast.associate = function(models) {
// associations can be defined here
Podcast.belongsToMany(models.User, {
through: 'user_podcast'
});
};
return Podcast;
};
And here's the migration I ran to join the two tables:
'use strict';
module.exports = {
up: function(queryInterface, Sequelize) {
return queryInterface.createTable('user_podcast', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
userId: {
type: Sequelize.INTEGER,
references: {
model: 'Users',
key: 'id'
}
},
podcastId: {
type: Sequelize.STRING,
references: {
model: 'Podcasts',
key: 'id'
}
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
}
});
},
down: function(queryInterface, Sequelize) {
return queryInterface.dropTable('user_podcast');
}
};
And here's the project on Github for further reference:
https://github.com/olliebeannn/chatterpod
You don't need to create a migration for the M:N table. Now you have something wrong on your user_podcast model. If you are setting a M:N relation between to tables your primary key will be the combination between the foreign key from these two models. If you still want a single id primary key for your table, then you won't use belongsToMany instead use hasMany on user and podcast models pointing to a new model user_podcast.
As far as I see on your first query, it seems that you really need a M:N relation so you can define the model as you do with user and podcast like this:
module.exports = (sequelize, DataTypes) => {
const UserPodcast = sequelize.define('user_podcast', {
userId: {
// field: 'user_id', #Use 'field' attribute is you have to match a different format name on the db
type: DataTypes.INTEGER
},
podcastId: {
// field: 'podcast_id',
type: DataTypes.INTEGER
},
});
UserPodcast.associate = function(models) {
models.User.belongsToMany(models.Podcast, {
as: 'podcasts', //this is very important
through: { model: UserPodcast },
// foreignKey: 'user_id'
});
models.Podcast.belongsToMany(models.User, {
as: 'users',
through: { model: UserPodcast },
// foreignKey: 'podcast_id'
});
};
return UserPodcast;
};
I do prefer to have the belongsToMany associations on the save function where I define the join model, and you have to notice that I used as: attribute on the association. This is very important because this will help sequelize to know which association are you referring on the query.
User.findOne({
where: { id: id },
include: [{
model: Podcast,
as: 'podcasts' //here I use the previous alias
}]
});

hasMany called with something that's not an instance of Sequelize.Model

as you guys can see my issue is related to the title description, i created a User Model, and a Foto Model in sequelize, basicly a user can shoot many fotos, but each foto can be related to just 1 user.
My User model
"use strict";
var sequelize = require('./index');
var bcrypt = require('bcrypt-nodejs');
var Foto = require('./Foto');
module.exports = function (sequelize, DataTypes) {
var User = sequelize.define("User", {
username: {
type: DataTypes.STRING,
allowNull: false,
unique: true,
validate: {
isUnique: function (value, next) {
var self = this;
User.find({ where: { username: value } })
.then(function (user) {
// reject if a different user wants to use the same username
if (user && self.id !== user.id) {
return next('username already in use!');
}
return next();
})
.catch(function (err) {
return next(err);
});
}
}
},
email: {
type: DataTypes.STRING,
allowNull: false,
unique: true,
validate: {
isUnique: function (value, next) {
var self = this;
User.find({ where: { email: value } })
.then(function (user) {
// reject if a different user wants to use the same email
if (user && self.id !== user.id) {
return next('Email already in use!');
}
return next();
})
.catch(function (err) {
return next(err);
});
}
}
},
typeOfUser: {
type: DataTypes.INTEGER,
allowNull:true,
defaultValue:null
},
country: {
type: DataTypes.STRING,
allowNull:true,
defaultValue:null
},
birthDate:{
type: DataTypes.DATEONLY,
allowNull:true,
defaultValue:null
},
reports: {
type: DataTypes.INTEGER,
defaultValue: 0
},
points: {
type: DataTypes.INTEGER,
defaultValue: 0
},
password: {
type: DataTypes.STRING,
allowNull:false
},
numberFotos: {
type: DataTypes.INTEGER,
defaultValue: 0
}
}, {
classMethods: {
generateHash: function (password) {
return bcrypt.hashSync(password, bcrypt.genSaltSync(8), null);
},
},
instanceMethods: {
validPassword: function (password) {
return bcrypt.compareSync(password, this.password);
}
}
});
User.hasMany(Foto,{as: 'fotos', foreignKey: 'userId'})
return Foto;
}
My foto model
"use strict";
var sequelize = require('./index');
var bcrypt = require('bcrypt-nodejs');
var User = require('./User');
module.exports = function (sequelize, DataTypes) {
var Foto = sequelize.define("Foto", {
reports: {
type: DataTypes.INTEGER,
defaultValue: 0
},
image: {
type: DataTypes.STRING,
allowNull: false
},
date: {
type: DataTypes.DATE,
allowNull:true
},
position: {
type: DataTypes.RANGE,
allowNull: true
}
});
Foto.belongsTo(User, {foreignKey: 'userId'});
return Foto;
}
You don't need to declare the association on the Photo Model:
Foto.belongsTo(User, {foreignKey: 'userId'});
When you have a 1:N relation between models you only need to refer the id from the "1" model, on our case the User model, on the "N" model, Photos. So doing:
User.hasMany(Foto,{as: 'fotos', foreignKey: 'userId'})
Will create a column on your Foto table with name "userId" that refer to user table. On this way both models are associate as you want.
You can define relations for both models in one file. It doesn't throw any errors that way.
In your Foto.js, you can try:
...
Foto.belongsTo(User);
User.hasMany(Foto);
return Foto;
I had a similar problem. Sometimes it can be caused because in your index.js or app.js the files are loaded in a specific order, so for example if you have a relationship between A and B, A loads first and references B, and B in turn references A, the error will be thrown inside the B file because A has not been fully defined/executed yet.
The solution to this would be to remove all associations from the model files, and inside your app or index.js require them all, and then define their relationships.
Example
const entities = {
A: require('./src/Entity/A'),
B: require('./src/Entity/B'),
};
entities.A.belongsToMany(entities.B, {through: 'AB'});
entities.B.belongsToMany(entities.A, {through: 'AB'});
So I was getting this error and it took me some time to deal with the bug. I realised I was getting the Error because I was referencing the model wrongly. Sequelize is case sensitive so if you created the model with UpperCase ensure to keep it uniform throughout your referencing.
I would also point out you could try this out instead
User.hasMany(models.Foto ,{as: 'fotos', foreignKey: 'userId'})
It seems you need to define both ends of the relationship in the file containing the 1 part of the 1:many association. That is, the "User" file in your case.
So:
User.hasMany(Foto);
Foto.belongsTo(User);
None of the above solutions worked for my scenario (could work for other setups). I stumbled upon this article which states you have to have the models defined and exported prior to applying the associations. Using a separate extra-setup.js file to define the associations, worked for me.
https://github.com/sequelize/express-example/tree/master/express-main-example
I had lots of issues, but I switched to using the sequelize CLI which generated models in this format, I then found creating associations a lot easier as the index file took care of everything and the static associate({ PersonalDetail }) that is in the model itself already requires your models in one place all you need to do is deconstruct them, so no need to require anything at the top of the file.
This youtube video really helped me out... https://www.youtube.com/watch?v=3qlnR9hK-lQ
'use strict'
const { Model } = require('sequelize')
module.exports = (sequelize, DataTypes) => {
class User extends Model {
/**
* Helper method for defining associations.
* This method is not a part of Sequelize lifecycle.
* The `models/index` file will call this method automatically.
*/
static associate({ PersonalDetail }) {
// define association here
this.hasMany(PersonalDetail, {
foreignKey: 'userId',
//as: 'personalDetails',
})
}
}
User.init(
{
uuid: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
},
moredata below: {
type: DataTypes.STRING,
allowNull: false,
},
//createdAt/updatedAt is defined in migration and updated automatically
},
{
sequelize,
tableName: 'users',
modelName: 'User',
}
)
return User
}
I got the same type issue. All mappings were done perfectly as explained in the document.
Yet, I received the issue regarding the association.
Reason is given by Dorian in this forum.
https://stackoverflow.com/a/60760296/16790144
My approach:
models/company.js
const company = sequelize.define("company",{
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true,
},
companyName: {
type: DataTypes.STRING,
allowNull: false,
}
});
export default company;
models/client.js
const Client = sequelize.define("client", {
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true,
},
firstName: {
type: DataTypes.STRING,
allowNull: false,
}
});
export default Client;
models/clientCompany.js
const clientCompany = sequelize.define("client_company",{
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true,
},
companyId: {
type: DataTypes.INTEGER
},
clientId: {
type: DataTypes.INTEGER
}
});
export default clientCompany;
models/index.js
import Company from './company';
import Client from './client';
import ClientCompany from './clientCompany';
Company.belongsToMany(Client, { through : ClientCompany });
Client.belongsToMany(Company, { through : ClientCompany });
export {
Company,
Client,
ClientCompany,
};
handler.js
This file contains the business logic.
import { Client, Company } from '../../models';
const company = await Company.findOne({
where: { id: companyId },
include: Client,
});

Sequelize cyclic dependency

This is my User model
'use strict';
var bcrypt = require('bcrypt');
module.exports = function(sequelize, DataTypes) {
var User = sequelize.define('User', {
email: {
type: DataTypes.STRING,
validate: {
isEmail: true,
notEmpty: true,
notNull: false
},
unique: true
},
password: DataTypes.STRING,
name: DataTypes.STRING,
username: {
type: DataTypes.STRING,
unique: true
},
admin: DataTypes.BOOLEAN,
googleId: DataTypes.BOOLEAN
}, {
classMethods: {
associate: function(models) {
User.hasMany(models.Award);
User.hasMany(models.Media);
User.hasMany(models.Comment);
User.hasMany(models.Like);
User.hasMany(models.CheckIn);
}
}
});
return User;
};
and this is my Media model:
'use strict';
module.exports = function(sequelize, DataTypes) {
var Media = sequelize.define('Media', {
type: DataTypes.ENUM('photo', 'video'),
description: DataTypes.STRING,
url: DataTypes.STRING,
gps: DataTypes.GEOMETRY('POINT')
}, {
classMethods: {
associate: function(models) {
//Media.belongsTo(models.Event);
//Media.belongsTo(models.User);
Media.hasMany(models.Comment);
Media.hasMany(models.Like);
}
}
});
return Media;
};
And I'm getting this error:
Unhandled rejection Error: Cyclic dependency found. Users is dependent of itself.
Dependency chain: Awards -> Users -> Media => Users
Previously I had a cyclic dependency and it's now removed but sequelize still throws this error. Why is this happening?
If I remove the User.hasMany(models.Media) association the error will disappear. But why is it still happening when the Media model has no reference to the User model?
Consider using hasMany as follows:
User.hasMany(models.Award, {as: 'ifYouWantAlias', constraints: false, allowNull:true, defaultValue:null});
note that you don't need the following part but it makes it clearer in my opinion.
allowNull:true, defaultValue:null
It is explained well in here: http://docs.sequelizejs.com/en/latest/api/associations/
Settings constraints: false will work, but will not create the foreign key in your DB.
If all your db access is done from sequelize, that can be an acceptable solution. But if you access your DB in several ways, that becomes complicated to handle. E.g: Hasura does not create relationships between models.
The real issue is that sequelize is not smart enough to create the table in 2 steps.
Other ORMs handle that by first creating the tableA without the foreign key, create the tableB with foreignKey to tableA, and alter TableA to create foreign key to tableB.
So the solution is to add constraints: false to your User.hasMany(models.Media) then create then run foreign key constrain.
const addMediaUserForeignKey = queryInterface.addConstraint(
'Media', {
type: 'foreign key',
onUpdate: 'CASCADE',
onDelete: 'CASCADE',
references: {
table: 'User',
field: 'id',
},
fields: [ 'userId' ]
}
).catch((e) => {})

Resources