Creating a foreignKey in sequelize, that is not a integer association - node.js

I want to translate this psql table creation query to sequelize:
PSQL:
CREATE TABLE categories
(
id INTEGER NOT NULL PRIMARY KEY,
name CHARACTER VARYING(50) NOT NULL UNIQUE,
description CHARACTER VARYING(100) NOT NULL
);
CREATE TABLE posts
(
id INTEGER NOT NULL PRIMARY KEY,
title CHARACTER VARYING(50) NOT NULL,
content CHARACTER VARYING(100) NOT NULL,
from_category CHARACTER VARYING(50) NOT NULL,
CONSTRAINT fk_from_category
FOREIGN KEY(from_category)
REFERENCES categories(name)
)
Its a simple fk association, with varchar type.
I have read sequelize docs, but i still don't know how to change the relation from primary keys to varchar.
From what i read, this is what you can do with associations:
Post.belongsTo(Category, {
foreignKey: {
onDelete: ...,
onUpdate: ...,
validate: {...},
},
});
and thats all i could find about on youtube too..
I would be really happy if you can help me. I have spent too much time on this already, but i want it to work!

Follow this example using belongsTo:
class Category extends Model { }
Category.init({
id: {
type: DataTypes.INTEGER,
primaryKey: true
},
name: {
type: DataTypes.STRING,
unique: true,
allowNull: false
},
description: {
type: DataTypes.STRING,
allowNull: false
}
}, { sequelize, modelName: 'categories' });
class Post extends Model { }
Post.init({
id: {
type: DataTypes.INTEGER,
primaryKey: true
},
title: {
type: DataTypes.STRING,
allowNull: false
},
content: {
type: DataTypes.STRING,
allowNull: false
},
from_category: {
type: DataTypes.STRING,
allowNull: false
}
}, { sequelize, modelName: 'posts' });
Post.belongsTo(Category, {
foreignKey: 'from_category',
targetKey: 'name'
});
Read more about to understand with more details in the official docs.

Related

Model with Foreign Key already done, sequelize

I am new to the backend field, I have been looking for solutions on how to make the model of two related tables, One-To-Many relationship of an existing database, but each example confuses me more.
CREATE TABLE my_db.person (
id INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(255) NOT NULL
created_at DATETIME,
craeted_by INTEGER, -- account_id
);
CREATE TABLE my_db.account (
id INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT,
email VARCHAR(255) UNIQUE,
password VARCHAR(255),
person_id INTEGER NOT NULL,
role INT NOT NULL
);
This is the Foreign Key that I want to make, the same
ALTER TABLE my_db.account
ADD CONSTRAINT fk_account_person_id
FOREIGN KEY (person_id)
REFERENCES my_db.person (id);
ON DELETE CASCADE
ON UPDATE CASCADE;
I did this but it doesn't work
person.ts
import { DataTypes } from 'sequelize';
import { db } from '../../db/connection';
import { Account } from './account';
export const Person = db.define( 'person', {
id: {
type: DataTypes.INTEGER,
allowNull: false,
primaryKey: true,
autoIncrement: true
},
name: {
type: DataTypes.STRING,
allowNull: false
},
created_at: {
type: DataTypes.DATE,
allowNull: true
},
created_by : {
type: DataTypes.INTEGER,
allowNull: true
}
}, {
timestamps: false,
});
Person.hasMany( Account, {
foreignKey: 'person_id',
onDelete: 'cascade',
onUpdate: 'cascade'
}
account.ts
import { DataTypes } from 'sequelize';
import { db } from '../../db/connection';
import { Person } from './Person';
export const Account = db.define( 'account', {
id: {
type: DataTypes.INTEGER,
allowNull: false,
primaryKey: true,
autoIncrement: true
},
email: {
type: DataTypes.STRING,
allowNull: false,
unique: true
},
password: {
type: DataTypes.STRING,
allowNull: false
},
person_id: {
type: DataTypes.INTEGER,
allowNull: false
},
role: {
type: DataTypes.INTEGER,
allowNull: false
}
}, {
timestamps: false,
});
Account.belongsTo( Person, {
foreignKey: 'person_id',
onDelete: 'CASCADE',
onUpdate: 'CASCADE'
}
Official Documentation
I thought of using the example from the documentation, however, it changes the name to the FK, but it does not show the exact table
enter image description here
enter image description here
clubId or TeamId?
enter image description here
In case the name of the attribute is not needed, does it infer it?
What if I have more than one FK to the same table? How would you infer those two without a constraint?
I did this, but I don't know where to put the constraint, since it already exists.
The database is in production, I cannot make changes to it, and I do not want a new constraint to be created

How do I add a composite foreign key in sequelize?

I have defined the following tables, archive and infos. Each file can have many information tags. The combination of field and tag is unique. For this I require a composite primary key consisting of field and tag. field refers to field in the archive table. The model definitions are given below.
Archive table:
module.exports = (sequelize, DataTypes) => {
let archive = sequelize.define('archive', {
fileid: {
type: DataTypes.STRING,
primaryKey: true,
allowNull: false
},
filename: {
type: DataTypes.STRING,
unique: false,
allowNull: false
},
originalname: {
type: DataTypes.STRING,
unique: false,
allowNull: false
},
downloadlink: {
type: DataTypes.STRING,
unique: false,
allowNull: false
},
domain: {
type: DataTypes.STRING,
unique: false,
allowNull: false
},
sem: {
type: DataTypes.INTEGER,
unique: false,
allowNull: false
},
branch: {
type: DataTypes.STRING,
unique: false,
allowNull: false
}
});
archive.associate = models => {
models.archive.hasMany(models.info, {
foreignKey: 'fileid'
});
models.archive.hasMany(models.upvotes, {
foreignKey: 'fileid'
});
};
return archive;
};
Info Table
module.exports = (sequelize, DataTypes) => {
let info = sequelize.define('info', {
tag: {
type: DataTypes.STRING,
allowNull: false,
primaryKey: true
}
});
info.associate = models => {
models.info.belongsTo(models.archive, {
foreignKey: 'fileid',
primaryKey: true
});
};
return info;
};
Making the primaryKey: true did not work. I have tried through: as well. I cannot seem to make it work.
Sequelize can be a giant pain. I follow the docs to the letter and still things don't work with some of the more complex queries, such as can be the case with composite primary key. My suggestion to you is when you bump in to Model fails, go with Raw queries. That being said still take the extra steps to protect against SQL injection attacks.
here is an example:
ensure that you include type
santize variables (in this case code)
db.sequelize
.query('SELECT count(*) FROM logs WHERE code = :code ', {
replacements: {
code: code
},
type: Sequelize.QueryTypes.SELECT
})
.then((data) => {
...
})
.catch((err) => {
...
});
I would have posted this a comment but there was not enough space.

How can I make a composite unique constraint with foreign keys and regular keys in Sequelize?

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'] }]
});
};

Sequelize has and belongs to many

Is there a way to do a polymorphic self-association with a through table (e.g. Collection has and belongs to many Collections)?
Trying to adapt http://docs.sequelizejs.com/manual/tutorial/associations.html#n-m to this scenario:
// Inside collection.js associate
Collection.belongsToMany(Collection, {
through: {
model: models.CollectionItem,
unique: false,
scope: {
collectible: 'collection'
}
},
foreignKey: 'collectibleUid',
constraints: false
});
Where collectionItem.js would look like
const CollectionItem = sequelize.define("CollectionItem", {
uid: {
type: DataTypes.BIGINT,
primaryKey: true
},
collectionUid: {
type: DataTypes.BIGINT,
allowNull: false,
unique: 'collection_item_collectible'
},
order: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: 0
},
collectibleUid: {
type: DataTypes.BIGINT,
allowNull: false,
references: null, // Because the column is polymorphic, we cannot say that it REFERENCES a specific table
unique: 'collection_item_collectible'
},
collectible: {
type: DataTypes.STRING,
allowNull: false,
unique: 'collection_item_collectible'
}
}, {
classMethods: {
}
});
It seems Sequelize wants me to name this differently through yet another join / through table, but that would essentially be creating a new table versus just getting a true hasAndBelongsToMany type relationship.
Error: 'as' must be defined for many-to-many self-associations
Try being explicit and add:
as: "CollectionItem"
as does not create a new join table, it just gives the relation a name

Sequelize create through association

I'm working on a create method for an association between two classes. The sequelize documentation indicates that this can be done in one step using includes
IntramuralAthlete.create(intramuralAthlete,{
include: [Person]
}).then((data,err)=>{
if(data)res.json(data);
else res.status(422).json(err);
}).catch(function(error) {
res.status(422).json({message: "failed to create athlete", error: error.message});
});
My model association looks like this
var Person = require('../models').person;
var IntramuralAthlete = require('../models').intramuralAthlete;
IntramuralAthlete.belongsTo(Person);
And the value of intramural athlete when I log it is
{
person:
{ firstName: 'Test',
lastName: 'User',
email: 'test#user.com'
},
grade: '12th',
organizationId: 1
}
But I get the error notNull Violation: personId cannot be null. This error makes it sound like something is wrong with the way I'm indicating to Sequelize that I'm intending to create the personId in that same call.
Is there something wrong in the way I indicate to the create statement what associated tables to create with the IntramuralAthlete?
Thanks!
EDIT:
I have also tried with the following structure with the same result
{
Person: {
firstName: 'Test',
lastName: 'User',
email: 'test#user.com'
},
grade: '12th',
organizationId: 1
}
My model is as follows:
module.exports = function(sequelize, DataTypes) {
return sequelize.define('intramuralAthlete', {
id: {
type: DataTypes.INTEGER(11),
allowNull: false,
primaryKey: true,
autoIncrement: true
},
createdAt: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: sequelize.literal('CURRENT_TIMESTAMP')
},
updatedAt: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: sequelize.literal('CURRENT_TIMESTAMP')
},
grade: {
type: DataTypes.STRING,
allowNull: true
},
age: {
type: DataTypes.INTEGER(11),
allowNull: true
},
school: {
type: DataTypes.STRING,
allowNull: true
},
notes: {
type: DataTypes.STRING,
allowNull: true
},
guardianId: {
type: DataTypes.INTEGER(11),
allowNull: true,
references: {
model: 'contact',
key: 'id'
}
},
personId: {
type: DataTypes.INTEGER(11),
allowNull: false,
references: {
model: 'person',
key: 'id'
}
},
mobileAthleteId: {
type: DataTypes.INTEGER(11),
allowNull: true,
references: {
model: 'mobileAthlete',
key: 'id'
}
},
organizationId: {
type: DataTypes.INTEGER(11),
allowNull: false,
references: {
model: 'organization',
key: 'id'
}
}
}, {
tableName: 'intramuralAthlete'
});
};
I suppose that your models are named Person and IntramuralAthlete (first arguments of sequelize.define method). In this case, when you create an association like yours, and do not define the as attribute, your create data object should look as follows
{
Person: {
firstName: 'Test',
lastName: 'User',
email: 'test#user.com'
},
grade: '12th',
organizationId: 1
}
If you want to use person instead (just as in your code), you should define the association a little bit differently
IntramuralAthlete.belongsTo(Person, { as: 'person' });
Then, you would have to perform some changes in the create query in the include attribute of the options like this
IntramuralAthlete.create(data, {
include: [
{ model: Person, as: 'person' }
]
}).then((result) => {
// both instances should be created now
});
EDIT: Trick the save() method with empty value of personId
You can maintain the allowNull: false if you do something like that
{
person: {
// person data
},
personId: '', // whatever value - empty string, empty object etc.
grade: '12th',
organizationId: 1
}
EDIT 2: Disable validation when creating.
This case assumes that the validation is turned off. It seems like a bad idea to omit model validation, however there still maintains the database table level validation - defined in migrations, where it can still check if personId value was set
IntramuralAthlete.create(data, {
include: [
{ model: Person, as: 'person' }
],
validate: false
}).then((result) => {
// both instances should be created now
});
In this case the data object can be as in your example - without the personId attribute. We omit the model level validation which allows to pass null value, however if during the save() method it would still be null value - database level validation would throw an error.
first of all, when you associatea a model with belongsTo, sequelize will add automatically the target model primary key as a foreign key in the source model. in most of cases you don't need to define it by yourself, so in your case when you define IntramuralAthlete.belongsTo(Person) sequelize adds PersonId as a foreign key in IntramuralAthlete. your IntramuralAthlete model should looks like:
module.exports = function(sequelize, DataTypes) {
return sequelize.define('intramuralAthlete', {
grade: {
type: DataTypes.STRING,
allowNull: true
},
age: {
type: DataTypes.INTEGER(11),
allowNull: true
},
school: {
type: DataTypes.STRING,
allowNull: true
},
notes: {
type: DataTypes.STRING,
allowNull: true
}
});
};
now you can create an intramuralAthlete like your code above. for example:
let data = {
Person: {
firstName: 'Test',
lastName: 'User',
email: 'test#user.com'
},
grade: '12th',
notes: 'test notes'
}
IntramuralAthlete.create(data, {include: [Person]}).then((result) => {
// both instances should be created now
});
be carefull with the model name.
second I suppose that your IntramuralAthlete model has more than one belongsTo association. just you need to define them as the previous one association and sequelize will add their primary keys as foreign keys in the IntramuralAthlete model.
third, when you define a model, sequelize adds automatically an id datafield as a primary key and autoincrement and also adds createdAt and updatedAt datafields with a default CURRENT_TIMESTAMP value, so you don't need to define them in your model

Resources