Sequelize many-to-many relation and one-to-many in intermediate table - javascript

Hi have a table Users with many to many relation to Groups through UsersGroups. UsersGroups has a FK to UsersGroupsRoles:
Users:
module.exports = (sequelize, Sequelize) => {
const Users = sequelize.define(
'Users',
{
id: {
type: Sequelize.UUID,
defaultValue: Sequelize.UUIDV4,
primaryKey: true,
},
name: {
type: Sequelize.STRING,
},
},
{}
);
Users.associate = function(models) {
Users.belongsToMany(models.Groups, { through: models.UsersGroups });
};
return Users;
};
Groups:
module.exports = (sequelize, Sequelize) => {
const Groups = sequelize.define(
'Groups',
{
id: {
type: Sequelize.INTEGER,
autoIncrement: true,
primaryKey: true,
allowNull: false,
},
name: {
type: Sequelize.STRING,
},
},
{}
);
Groups.associate = function(models) {
Groups.belongsToMany(models.Users, { through: models.UsersGroups });
};
return Groups;
};
UsersGroups:
module.exports = (sequelize, Sequelize) => {
const UsersGroups = sequelize.define(
'UsersGroups',
{
order: {
allowNull: true,
type: Sequelize.INTEGER,
defaultValue: 10000,
},
},
{}
);
UsersGroups.associate = function(models) {
UsersGroups.belongsTo(models.UsersGroupsRoles, { as: 'UsersGroupsRoles', onDelete: 'CASCADE' });
};
return UsersGroups;
};
UsersGroupsRoles:
module.exports = (sequelize, Sequelize) => {
const UsersGroupsRoles = sequelize.define(
'UsersGroupsRoles',
{
id: {
type: Sequelize.INTEGER,
autoIncrement: true,
primaryKey: true,
allowNull: false,
},
role: {
type: Sequelize.STRING,
},
},
{}
);
UsersGroupsRoles.associate = function(models) {
UsersGroupsRoles.hasMany(models.UsersGroups, { as: 'UsersGroupsRoles', onDelete: 'CASCADE' });
};
return UsersGroupsRoles;
};
Now I want to query Users and UsersGroups, and get the UsersGroupsRoles:
models.Groups.findAll({
attributes: {
exclude: ['createdAt', 'updatedAt'],
},
include: [
{
model: models.Users,
attributes: {
exclude: ['createdAt', 'updatedAt', 'email', 'password'],
},
through: {
include: [
{
model: models.UsersGroupsRoles,
as: 'UsersGroupsRoles',
},
],
},
},
],
})
But the query fails with TypeError: Cannot read property 'indexOf' of undefined. I suppose it is because the include clause inside through, but then, what is the correct way to include a one-to-many association in an intermediate table?
Thanks!

What about try this?
try {
let groups = await models.Groups.findAll({
attributes: {
exclude: ['id','createdAt', 'updatedAt'],
}
})
const userGroupsPromises = groups.map(group => {
return models.UsersGroups.findAll({
where: {
groupId: group.id
},
include: [{
model: models.User,
attributes: {
exclude: ['createdAt', 'updatedAt', 'email', 'password'],
}
}, {
model: models.UsersGroupsRoles,
}]
})
})
const userGroupsPromiseResult = await Promise.all(userGroupsPromises)
groups = groups.map((group, index) => {
const _group = group.get()
_group.UserGroups = userGroupsPromiseResult[index]
return _group
})
console.log(groups)
} catch (err) {
console.log(err)
}

Related

How to find several fields of a foreign key in a join table in Node.js Sequelize

I have a Node.js application with Express, Sequelize as ORM and PostgreSQL for the database. In this app I have candidate model and mission model as below.
'use strict';
const {
Model
} = require('sequelize');
module.exports = (sequelize, DataTypes) => {
class candidat extends Model {
static associate(models) {
this.belongsToMany(models.mission, {
through: "candidat_mission",
foreignKey: "candidatId",
otherKey: "idMission",
});
}
}
candidat.init({
candidatId: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: DataTypes.INTEGER
},
lastName: {
type: DataTypes.STRING,
allowNull: false,
},
firstName: {
type: DataTypes.STRING,
allowNull: false,
},
email: {
isEmail: true,
allowNull: false,
type: DataTypes.STRING,
unique: true,
},
}, {
sequelize,
modelName: 'candidat',
tableName: 'candidat',
freezeTableName: true,
});
return candidat;
};
'use strict';
const {
Model
} = require('sequelize');
module.exports = (sequelize, DataTypes) => {
class mission extends Model {
static associate(models) {
this.belongsToMany(models.candidat, {
through: "candidat_mission",
foreignKey: "idMission",
otherKey: "candidatId",
})
}
}
mission.init({
idMission: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: DataTypes.INTEGER
},
title: {
type: DataTypes.STRING,
allowNull: false
},
aliasTitle: {
type: DataTypes.STRING,
allowNull: true
},
description: {
type: DataTypes.TEXT,
allowNull: true
}
}, {
sequelize,
modelName: 'mission',
tableName: 'mission',
freezeTableName: true,
});
return mission;
};
These two models are linked in many-to-many by a candidate_mission join table. In this model, I added fields like a foreign key which points to another table, that of users.
'use strict';
const {
Model
} = require('sequelize');
module.exports = (sequelize, DataTypes) => {
class candidat_mission extends Model {
static associate(models) {
this.belongsTo(models.user, { foreignKey: "fk_user" });
}
}
candidat_mission.init({
candidatMissionId: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: DataTypes.INTEGER
},
candidatId: {
type: DataTypes.INTEGER,
allowNull: false,
references: {
model: { tableName: 'candidat' },
key: "candidatId",
},
},
idMission: {
type: DataTypes.INTEGER,
allowNull: false,
references: {
model: { tableName: 'mission' },
key: "idMission",
},
},
fk_user: {
type: DataTypes.INTEGER,
allowNull: true,
references: {
model: { tableName: 'user' },
key: "userId",
},
},
}, {
sequelize,
modelName: 'candidat_mission',
tableName: 'candidat_mission',
timestamps: true,
freezeTableName: true,
});
return candidat_mission;
};
When I make a "GET" request, I do have the information from the candidate_mission table (if a candidate is linked to this mission), but for the user it only returns the ID and I would like it to return all the fields present in the Users model, what can I do?
Here, my function in the mission controller which allows to add a candidate to this mission :
const addCandidats = async (req, res) => {
try {
const mission = await Mission.findByPk(req.body.idMission);
if (mission) {
const candidat = await Candidat.findByPk(req.body.candidatId);
if (candidat) {
mission.addCandidat(candidat,
{through: {
fk_user: req.body.fk_user && req.body.fk_user
}});
return res.status(200).send(mission);
} else {
console.log("Candidat non trouvé");
return null;
}
} else {
console.log("Mission non trouvée!")
return null;
}
} catch (error) {
console.log(error);
}
};
Currently, my query returns me this :
"candidat_mission":
{
"candidatMissionId": 2,
"candidatId": 1,
"idMission": 7,
"fk_user": 1,
"createdAt": "2023-02-14T10:34:08.302Z",
"updatedAt": "2023-02-14T15:06:10.232Z"
},
And i want it to come back to me :
"candidat_mission":
{
"candidatMissionId": 2,
"candidatId": 1,
"idMission": 7,
"fk_user": {
"userId": 1,
"email": "blabla#gmail.com",
"name": "blabla"
},
"createdAt": "2023-02-14T10:34:08.302Z",
"updatedAt": "2023-02-14T15:06:10.232Z"
},
After associating 2 models, we have to query again to get the object along with the relationship.
await mission.addCandidat(candidat,
{through: {
fk_user: req.body.fk_user && req.body.fk_user
}});
const result = await CandidatMission.findOne({
where: { candidatId: req.body.candidatId, idMission: req.body.idMission },
include: models.user,
})
return res.status(200).send(result);

Sequelize belongTo association giving error "table name "contact" specified more than once"

I am trying to connect the phone model to the contact model through the contact_phone model. When I .findAll like below, the error returns: Unhandled rejection SequelizeDatabaseError: table name "contact" specified more than once. Are the associations/'as' correct in this case? The error happens when I try to include the contact_phone model. I have also inserted the table with an initial phone row.
exports.findAll = (req, res) => {
models.contact.findAll({
include: [
{
model: models.contact_phone,
as: 'contact'
}
]
})
.then(contacts => {
res.status(200).send(contacts);
});
};
Models
module.exports = (sequelize) => {
const Contact = sequelize.define(
'contact',
{
contact_id: {
type: Sequelize.BIGINT,
primaryKey: true,
autoIncrement: true,
field: 'contact_id'
},
first_name: {
type: Sequelize.STRING,
field: 'first_name'
},
last_name: {
type: Sequelize.STRING,
field: 'last_name',
},
created_at: {
type: Sequelize.DATE,
field: 'created_at'
},
updated_at: {
type: Sequelize.DATE,
field: 'updated_at'
}
},
{
tableName: 'contacts',
freezeTableName: true,
}
);
Contact.associate = (models) => {
Contact.belongsTo(models.contact_phone, {foreignKey: 'contact_id', as: 'contact'});
Contact.hasOne(models.phone, {foreignKey: 'phone_id', through: models.contact_phone});
};
return Contact;
};
module.exports = (sequelize) => {
const ContactPhone = sequelize.define(
'contact_phone',
{
contact_id: {
type: Sequelize.BIGINT,
references: {
model: 'contact',
key: 'contact_id'
}
},
phone_id: {
type: Sequelize.STRING,
references: {
model: 'phone',
key: 'phone_id'
}
}
}
);
ContactPhone.associate = (models) => {
ContactPhone.belongsTo(models.phone, {foreignKey: 'phone_id', as: 'phone'});
ContactPhone.belongsTo(models.contact, {foreignKey: 'contact_id', as: 'contact'});
};
return ContactPhone;
};
module.exports = (sequelize) => {
const Phone = sequelize.define(
'phone',
{
phone_id: {
type: Sequelize.STRING,
primaryKey: true,
field: 'phone_id'
},
updated_at: {
type: Sequelize.DATE,
field: 'updated_at'
},
created_at: {
type: Sequelize.DATE,
field: 'created_at'
}
},
{
tableName: 'phones',
freezeTableName: true,
}
);
Phone.associate = (models) => {
Phone.belongsTo(models.contact, {foreignKey: 'phone_id', through: models.contact_phone});
};
return Phone;
};
you can do like this . you don't have include extra contact_phone you can directly do with contact & phone with association like this .
contact module :
module.exports = (sequelize) => {
const Contact = sequelize.define(
'contact',
{
contact_id: {
type: Sequelize.INT,
primaryKey: true,
autoIncrement: true,
field: 'contact_id'
},
first_name: {
type: Sequelize.STRING,
field: 'first_name'
},
last_name: {
type: Sequelize.STRING,
field: 'last_name',
},
created_at: {
type: Sequelize.DATE,
field: 'created_at'
},
updated_at: {
type: Sequelize.DATE,
field: 'updated_at'
}
},
{
tableName: 'contacts',
freezeTableName: true,
}
);
Contact.associate = (models) => {
Contact.hasOne(models.phone, { // has many or hasOne relationship .here read like contact has one phone
foreignKey: 'phone_id',
onDelete: "CASCADE"
});
};
return Contact;
};
phone module :
module.exports = (sequelize) => {
const Phone = sequelize.define(
'phone',
{
phone_id: {
type: Sequelize.INT,
primaryKey: true,
autoIncrement: true,
field: 'phone_id'
},
contact_id: {
type: Sequelize.INT,
},
updated_at: {
type: Sequelize.DATE,
field: 'updated_at'
},
created_at: {
type: Sequelize.DATE,
field: 'created_at'
}
},
{
tableName: 'phones',
freezeTableName: true,
}
);
Phone.associate = (models) => {
Phone.belongsTo(models.contact, {
foreignKey: 'contact_id',
as: 'contact',
onDelete: "CASCADE"
});
};
}
your query be like :
models.contact.findAll({
include: [
{
model: models.phone,
as: 'contact'
}
]
}).then(contacts => {
res.status(200).send(contacts);
});
EDIT :
how can you define more association with same key and different alias
Phone.associate = (models) => {
Phone.belongsTo(models.contact, {
foreignKey: 'contact_id',
as: 'contact',
onDelete: "CASCADE"
});
Phone.belongsTo(models.contact, {
foreignKey: 'contact_id',
as: 'phone',
onDelete: "CASCADE"
});
};

Aggregate error when trying to use many to many table with sequelize

I'm getting this error:
AggregateError: aggregate error
at Promise.try.then
...
when trying to setup a many to many table in sequelize with this code:
let _document
db.document.create(payload, { include: ['documentAttributes', 'documentChildren'] })
.then(document => {
_document = document
const promises = []
// Add tags if there are any
if (req.body.tags) {
req.body.tags.forEach(tag => {
promises.push(db.tag.findOrCreate({ where: { key: tag.key } }))
})
}
return Promise.all(promises)
})
.then(tags => {
if (tags.length > 0) {
const allTags = tags.map(tag => tag[0])
return _document.setTags(allTags) // THIS LINE CAUSES THE ISSUE
}
document.js model:
'use strict'
module.exports = (sequelize, DataTypes) => {
const document = sequelize.define('document', {
id: {
allowNull: false,
primaryKey: true,
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4
},
...
lastUpdatedBy: {
allowNull: false,
type: DataTypes.UUID
}
},
{
updatedAt: 'lastUpdatedAt'
})
document.associate = function (models) {
document.hasMany(models.documentAttribute)
document.hasMany(models.documentChildren)
document.belongsToMany(models.tag, { through: 'documentTags' })
}
return document
}
tag.js model:
'use strict'
module.exports = (sequelize, DataTypes) => {
const tag = sequelize.define('tag', {
id: {
allowNull: false,
primaryKey: true,
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4
},
key: {
allowNull: false,
type: DataTypes.STRING
},
value: {
allowNull: false,
type: DataTypes.STRING
},
lastUpdatedBy: {
allowNull: false,
type: DataTypes.UUID
}
},
{
updatedAt: 'lastUpdatedAt'
})
tag.associate = function (models) {
tag.belongsToMany(models.document, { through: 'documentTags' })
}
return tag
}
documenttags.js model:
'use strict'
module.exports = (sequelize, DataTypes) => {
const documentTags = sequelize.define('documentTags', {
id: {
allowNull: false,
primaryKey: true,
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4
},
documentId: {
allowNull: false,
type: DataTypes.UUID
},
tagId: {
allowNull: false,
type: DataTypes.UUID
},
lastUpdatedBy: {
allowNull: false,
type: DataTypes.UUID
}
},
{
updatedAt: 'lastUpdatedAt'
})
documentTags.associate = function (models) {
// associations can be defined here
}
return documentTags
}
After reviewing the docs and discussing in the chat. The through property is necessary to set extra properties on the many-to-many table.
http://docs.sequelizejs.com/class/lib/associations/belongs-to-many.js~BelongsToMany.html
return _document.setTags(allTags, { through: { lastUpdatedBy: tag.lastUpdatedBy }})

fliped foreign key relations?

i have a strange effekt at a m:n relation..
this are the model definitions:
Role Model:
'use strict';
module.exports = (sequelize, DataTypes) => {
const Role = sequelize.define('Role', {
uuid: {
allowNull: false,
primaryKey: true,
unique: true,
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
},
....
}, {});
/** #param models.User */
Role.associate = function(models) {
Role.belongsToMany(
models.User, {
through: 'user_role',
foreignKey: 'userId',
}
);
};
return Role;
};
User Model:
'use strict';
module.exports = (sequelize, DataTypes) => {
const User = sequelize.define('User', {
uuid: {
allowNull: false,
primaryKey: true,
unique: true,
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
},
....
}, {});
/** #param models.Role */
User.associate = function(models) {
User.belongsToMany(
models.Role, {
through: 'user_role',
foreignKey: 'roleId',
}
);
};
return User;
};
the migration is the following:
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('user', {
uuid: {
allowNull: false,
unique: true,
primaryKey: true,
type: Sequelize.UUIDV4,
defaultValue: Sequelize.UUIDV4,
},
....
}).then(() => {
queryInterface.createTable('role', {
uuid: {
allowNull: false,
unique: true,
primaryKey: true,
type: Sequelize.UUIDV4,
defaultValue: Sequelize.UUIDV4,
},
....
});
}).then(() => {
queryInterface.createTable('user_role', {
userId: {
type: Sequelize.UUIDV4,
references: {
model: 'User',
key: 'uuid',
},
allowNull: false,
},
roleId: {
type: Sequelize.UUIDV4,
references: {
model: 'Role',
key: 'uuid',
},
allowNull: false,
},
....
});
}).then(() => {
return queryInterface.addConstraint('user_role', ['UserId', 'RoleId'], {
unique: true,
type: 'primary key',
name: 'userrole_pkey',
});
});
},
down: (queryInterface, Sequelize) => {
....
},
};
if i try to insert now a user with a new role:
let models = require('../models');
models.Role.create({
role: 'Administrator',
description: 'Administrator Gruppe',
}).then(role => {
models.User.create({
login: 'admin',
password: '123',
nick: 'Admini',
mail: 'admin#localhost.com',
}).then(user => {
user.addRole(role);
user.save().then(() => {
console.log('admin created');
}).catch(err => {
console.log(err);
});
}).catch(err => {
console.log(err);
});
}).catch(err => {
console.log(err);
});
it tries to add the role uuid in the userid and the user uuid in the roleid.. and for that the constraint fails...
any hints or tips where i made a mistake?
found the mistake myself (with help of a college)
at
models.User, {
through: 'user_role',
foreignKey: 'userId',
}
i set the wrong foreign key, it's not the field in the helper table, it's needed to be the source table (in this case uuid of user model) or leave it blank for sequelize's default behaviour to use the primary key.

Sequelize findAll with belongsToMany association

I have two tables related in this way :
League
sequelize.define('league', {
name: {
type: DataTypes.STRING,
},
ownerId: {
type: DataTypes.INTEGER,
}
}, {
classMethods: {
associate: function(models) {
League.belongsToMany(models.user, {
constraints: false,
through: models.UserLeague,
});
}
}
}, {
freezeTableName: true
});
User
sequelize.define('user', {
name: {
type: DataTypes.STRING
},
email: {
type: DataTypes.STRING,
unique: true,
validate: {
isEmail: true
}
},
profile_picture: {
type: DataTypes.STRING
}
}, {
classMethods: {
associate: function(models) {
User.belongsToMany(models.league, {
constraints: false,
through: models.UserLeague,
});
}
}
}, {
freezeTableName: true
});
I would like to get all the leagues that have a certain User in them : I'm doing this at the moment but I get the error column league.users.id does not exist
sequelize.transaction(function (t) {
return models.user.findOne({where: {email: req.query.email}}, {transaction: t}).then(function(user) {
return models.league.findAll({where: {'users.id': user.id}, include: [{model: models.user, as: 'users'}]}).then(function(leagues) {
res.json(leagues);
});
});
});
How could I retrieve the leagues where there is a certain user ?
You should add your where to the included model:
sequelize.transaction(function (t) {
return models.user.findOne({where: {email: req.query.email}}, {transaction: t}).then(function (user) {
return models.league.findAll({
include: [{
model: models.user,
as: 'users',
where: {
id: user.id
},
required: true
}]
}).then(function (leagues) {
res.json(leagues);
});
});
});
Update:
Its' not really nice, but I thinik, there is no better solution:
sequelize.transaction(function (t) {
return models.user.findOne({where: {email: req.query.email}}, {transaction: t}).then(function (user) {
var _leagueIds = [];
return models.league.findAll({
include: [{
model: models.user,
as: 'users',
where: {
id: user.id
},
required: true
}]
})
.each(function (league) {
_leagueIds.push(league.id);
})
.then(function () {
return models.league.findAll({
where: {
id: _leagueIds,
},
include: [{
model: models.user,
as: 'users'
}]
});
})
.then(function (leagues) {
res.json(leagues);
});
});
});

Categories

Resources