Sequelize cli creating association belongsto user or staff - mysql

i found many things about associations but nothing to my particular case,
i created some models and i'm trying to associate them,
so i think it is a matter of understanding database modeling too.
I've got the models user and staff, both share an attribute user_id.
user.js
'use strict';
module.exports = function(sequelize, DataTypes) {
var User = sequelize.define('User', {
user_id: DataTypes.STRING,
fullname: DataTypes.STRING,
username: DataTypes.STRING,
comment: DataTypes.TEXT
}, {
classMethods: {
associate: function(models) {
// associations can be defined here
User.hasMany(models.Sshkey, {foreignKey: 'sshkey_id'})
}
}
});
return User;
};
staff.js
'use strict';
module.exports = function(sequelize, DataTypes) {
var Staff = sequelize.define('Staff', {
user_id: DataTypes.STRING,
fullname: DataTypes.STRING,
username: DataTypes.STRING,
password: DataTypes.STRING,
isAdmin: DataTypes.BOOLEAN
}, {
classMethods: {
associate: function(models) {
// associations can be defined here
Staff.hasMany(models.Sshkey, {foreignKey: 'sshkey_id'})
}
}
});
return Staff;
};
And i've got a model sshkey which can belong to either an user or a staff member.
I'm using Sequelize cli and haven't done any migrations yet.
And i'm pretty new to Js and creating databases, thinking about the database models and the associations, and i'm curios if i could write or do such thing as:
'use strict';
module.exports = function(sequelize, DataTypes) {
var Sshkey = sequelize.define('Sshkey', {
sshkey_id: DataTypes.STRING,
sshkey: DataTypes.TEXT
}, {
classMethods: {
associate: function(models) {
// associations can be defined here
// My Problem starts here |
// Should i write |
// |
// V
Sshkey.hasOne(models.User || models.Staff, {foreignKey: 'user_id'})
// Or maybe:
// Sshkey.hasOne(models.User, {foreignKey: 'user_id'}) ||
// Sshkey.hasOne(models.Staff, {foreignKey: 'user_id'})
// Should i rather rename models.Staffs foreignKey user_id to staff_id?
// Or maybe:
// Sshkey.hasOne(models.User, {as: 'userkey', foreignKey: 'user_id'})
// Sshkey.hasOne(models.Staff, {as: 'staffkey', foreignKey: 'user_id'})
}
}
});
return Sshkey;
};
What would be a proper solution for the problem that if i later on want to reference a sshkey to either a user or a staff member?
Making two models with staffkeys and userkeys?
Thanks in advance,
BigZ

If you want a 1:m relationship where the foreign key user_id is added to the Sshkey model, should be:
User.hasMany(models.Sshkey, {foreignKey: 'user_id'});
Staff.hasMany(models.Sshkey, {foreignKey: 'user_id'});
Sshkey.belongsTo(models.User, {foreignKey: 'user_id'});
Sshkey.belongsTo(models.Staff, {foreignKey: 'user_id'});
One issue I have with your example is that user_id and sshkey_id are both strings with no constraints around them, which makes them very bad foreignKey's and terrible for designing databases. To get a User and their Sshkey:
User.findAll({
where: {},
include: [ { model: Sshkey } ]
});

Related

Sequelize associate not creating foreign key

According to sequelize documentation, Model.hasMany should automatically create foreign key references with constraints. But when i try it in my code it does not. I have to manually add the foreign key to the migration. Please how can i fix this? This is my User model
'use strict';
module.exports = (sequelize, DataTypes) => {
const User = sequelize.define('User', {
name: DataTypes.STRING,
username: DataTypes.STRING,
email: DataTypes.STRING,
password: DataTypes.STRING
});
User.associate = function(models) {
// associations can be defined here
User.hasMany(models.Post);
};
return User;
};
and this is my post model
'use strict';
module.exports = (sequelize, DataTypes) => {
const Post = sequelize.define('Post', {
title: DataTypes.STRING,
body: DataTypes.TEXT
});
Post.associate = function(models) {
Post.belongsTo(models.User);
};
return Post;
};
To create foreign key, you'll need to put the information of which column is the primary key and the foreign key referred:
'use strict';
module.exports = (sequelize, DataTypes) => {
const User = sequelize.define('User', {
name: {
DataTypes.STRING,
primaryKey: true
},
username: DataTypes.STRING,
email: DataTypes.STRING,
password: DataTypes.STRING
});
User.associate = function(models) {
// associations can be defined here
User.hasMany(models.Post, {
foreignKey: modelId
);
};
return User;
};
Note that, in your User, I defined which column is the primary key and, in hasMany, I defined which column on Post is the foreignKey.
After much struggle with this I finally figured it out.
The db:migrate command uses the migration files and does not include the model files.
To use the model files instead, I just needed to do this in the app.js file
...
...
const db = require('./models');
db.sequelize.init();
Although a lot of experience programmers do not recommend this method. They suggest that the best method is manually creating the foreign key in the migration files.

Sequelize associations not being created in MySQL, PostgreSQL and SQLite

I am defining associations in models using sequalize with MYSQL. But after migration, the foreign key is not being added to the target model as explained in sequelize docs.
I have also tried to manually define foreign keys in models and migration files but still no association is being created between tables. When I view the tables in relation view in PhpMyAdmin, not foreign key constraints or relationship is being created.
I have tried this with SQLite, and PostgreSQL with the same results. I don't know what I am doing wrong. Here are models.
AURHOR MODEL
//One author hasMany books
'use strict';
module.exports = (sequelize, DataTypes) => {
const Author = sequelize.define('Author', {
Name: DataTypes.STRING
}, {});
Author.associate = function(models) {
// associations can be defined here
Author.hasMany(models.Book)
};
return Author;
};
I expect sequelize to add authorId on books table as specified in the docs, but this not happening
BOOK MODEL
//Book belongs to Author
'use strict';
module.exports = (sequelize, DataTypes) => {
const Book = sequelize.define('Book', {
Title: DataTypes.STRING
}, {});
Book.associate = function(models) {
// associations can be defined here
Book.belongsTo(models.Author)
};
return Book;
};
No associations is being created between these two tables after migration.
I have as well tried to define custom foreign keys in model associations like this:
//Author model
Author.hasMany(models.Book,{foreignKey:'AuthorId'})
//Book model
Book.belongsTo(models.Author,{foreignKey:'AuthorId'})
still this not solving the problem
I have gone ahead to define foreign keys in models then referencing them in the association like this:
'use strict';
module.exports = (sequelize, DataTypes) => {
const Book = sequelize.define('Book', {
Title: DataTypes.STRING,
AuthorId:DataTypes.INTEGER
}, {});
Book.associate = function(models) {
// associations can be defined here
Book.belongsTo(models.Author,{foreignKey:'AuthorId'})
};
return Book;
};
But still no associations is being created
I finally decided to add references in migration files like so:
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('Books', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
Title: {
type: Sequelize.STRING
},
AuthorId:{
type: Sequelize.INTEGER,
references:{
model:'Author',
key:'id'
}
}
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
}
});
},
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('Books');
}
};
But when I run this kind of migration setup, I get this error: ERROR: Can't create table dbname.books (errno: 150 "Foreign key constraint is i
ncorrectly formed")
I get similar error when I switch to PostgreSQL.
I have been held back by this issue for quite long. What may I doing wrong. I am using sequelize version 4.31.2 with sequelize CLI.
I was referencing to models wrongly in migrations.
Wrong way
AuthorId:{
type: Sequelize.INTEGER,
references:{
model:'Author',
key:'id'
}
}
Correct Way
// Notes the model value is in lower case and plural just like the table name in the database
AuthorId:{
type: Sequelize.INTEGER,
references:{
**model:'authors',**
key:'id'
}
}
This solved my problem. The associations is now getting defined.

How to query many-to-many relationship data in Sequelize

I have got a many-to-many relationship between two models, users and groups.
I have two models which are specifying the belongsToMany with a foreignKey and through attribute.
Users model
'use strict';
module.exports = function(sequelize, DataTypes) {
var User = sequelize.define('User', {
firstName: DataTypes.STRING,
lastName: DataTypes.STRING,
email: DataTypes.STRING,
username: DataTypes.STRING,
facebookId: DataTypes.STRING,
password: DataTypes.STRING,
}, {
classMethods: {
associate: function(models) {
User.hasMany(models.Payment);
User.hasMany(models.Friend, {foreignKey: 'userIdLink1'});
User.belongsToMany(models.Group, { through: 'UsersGroups', foreignKey: 'facebookId' });
}
},
instanceMethods: {
toJSON: function () {
var values = Object.assign({}, this.get());
delete values.password;
return values;
}
}
});
return User;
};
groups model
'use strict';
module.exports = function(sequelize, DataTypes) {
var Group = sequelize.define('Group', {
name: DataTypes.STRING
}, {
classMethods: {
associate: function(models) {
// associations can be defined here
Group.belongsToMany(models.User, {through: 'UsersGroups', foreignKey: 'groupId'});
Group.hasMany(models.Payment)
}
},
instanceMethods: {
toJSON: function () {
var values = Object.assign({}, this.get());
delete values.password;
return values;
}
}
});
return Group;
};
which are being joined via a junction table UsersGroups
This works fine and I can create a new group and it links the user with it successfully but when I try fetch the data the SQL query is trying to find Groups based on User.id as opposed to User.facebookId like I specified in my model User.belongsToMany(models.Group, { through: 'UsersGroups', foreignKey: 'facebookId' });
I call the following code to fetch the data:
const options = {
where: {
facebookId: facebookId,
},
defaults: {
firstName: data.firstName,
lastName: data.lastName,
email: data.email,
facebookId: facebookId
},
include: [
{ model: db.Group }
]
}
db.User.findOrCreate(options)
.then((user) => {
res.send(user)
}, (err) => {
res.status(500).send(err)
})
and it returns a user but with an empty Groups array which is incorrect as there is definitely data there as I can create it fine and see it in the DB.
You can see the SQL query that is generated by Sequelize here:
SELECT `User`.*, `Groups`.`id` AS `Groups.id`, `Groups`.`name` AS `Groups.name`, `Groups`.`createdAt` AS `Groups.createdAt`, `Groups`.`updatedAt` AS `Groups.updatedAt`, `Groups.UsersGroups`.`createdAt` AS `Groups.UsersGroups.createdAt`, `Groups.UsersGroups`.`updatedAt` AS `Groups.UsersGroups.updatedAt`, `Groups.UsersGroups`.`facebookId` AS `Groups.UsersGroups.facebookId`, `Groups.UsersGroups`.`groupId` AS `Groups.UsersGroups.groupId`
FROM (
SELECT `User`.`id`, `User`.`firstName`, `User`.`lastName`, `User`.`email`, `User`.`username`, `User`.`facebookId`, `User`.`password`, `User`.`createdAt`, `User`.`updatedAt` FROM `Users` AS `User` WHERE `User`.`facebookId` = '1341052992643877' LIMIT 1) AS `User`
LEFT OUTER JOIN (`UsersGroups` AS `Groups.UsersGroups`
INNER JOIN `Groups` AS `Groups` ON `Groups`.`id` = `Groups.UsersGroups`.`groupId`
)
ON `User`.`id` = `Groups.UsersGroups`.`facebookId`;
Note the last line
ON `User`.`id` = `Groups.UsersGroups`.`facebookId`
needs to be
ON `User`.`facebookId` = `Groups.UsersGroups`.`facebookId`
Turns out I had to specify facebookId as a primary key in my user model
facebookId: {
type: DataTypes.STRING,
primaryKey: true,
},

Creating row in both source and association with Sequelize many to many model

I have a many to many relationship between a user and a group. A user has many groups and a group has many users.
I have a users table, a groups table and junction table called usersGroups.
My User model:
'use strict';
module.exports = function(sequelize, DataTypes) {
var User = sequelize.define('User', {
firstName: DataTypes.STRING,
lastName: DataTypes.STRING,
email: DataTypes.STRING,
username: DataTypes.STRING,
facebookId: DataTypes.STRING,
password: DataTypes.STRING,
}, {
classMethods: {
associate: function(models) {
User.hasMany(models.Payment);
User.hasMany(models.Friend, {foreignKey: 'userIdLink1', allowNull: false});
User.belongsToMany(models.Group, { as: 'Groups', through: 'usersGroups', foreignKey: 'userId' });
}
},
instanceMethods: {
toJSON: function () {
var values = Object.assign({}, this.get());
delete values.password;
return values;
}
}
});
return User;
};
My group model
'use strict';
module.exports = function(sequelize, DataTypes) {
var Group = sequelize.define('Group', {
}, {
classMethods: {
associate: function(models) {
// associations can be defined here
Group.belongsToMany(models.User, { as: 'Users', through: 'usersGroups', foreignKey: 'groupId' });
}
},
instanceMethods: {
toJSON: function () {
var values = Object.assign({}, this.get());
delete values.password;
return values;
}
}
});
return Group;
};
When I try create a new group with associated user with the following 2 methods, it creates a new group but no association
const values = {
userId: 1
}
const options = {
include: db.Users
}
db.Group
.create(values, options)
.then( (group) => {
res.send(group)
}).catch(err => {
res.status(500).send({err: err})
})
or
db.Group
.create()
.then( (group) => {
group.addUser({ userId: 1 }).then(result => {
res.send(result)
}).catch(err => {
res.status(500).send({err: err})
})
}).catch(err => {
res.status(500).send({err: err})
})
If you simply want to assign user to newly created group, you need to use addUser, just like you did, but this method accepts first parameter as instance of Model or ID of instance, just like the documentation says
An instance or primary key of instance to associate with this.
So you would have to perform group.addUser(userId).then(...) which in your case would be group.addUser(1).then(...).

How to remove all associated data of the removed object in SequelizeJS?

module.exports = function(sequelize, DataTypes) {
var MenuTranslation = sequelize.define('MenuTranslation', {
name: DataTypes.STRING,
description: DataTypes.STRING
},{
tableName: 'menu_translations',
timestamps: false,
associate: function(models){
MenuTranslation.belongsTo(models.Menu, { onDelete: 'cascade' });
MenuTranslation.belongsTo(models.Language);
}
});
return MenuTranslation;
};
UPDATE: I add "onDelete: 'cascade'" into my MenuTranslation model. But this time it worked like onDelete: 'null'
onDelete option goes on the child object, not on the parent. It's as in SQL. Relevant documentation.