Related
I have two tables in a MySQL schema, 'members' and 'events', which share a many-to-many relationship. I am attempting to model that relationship using Sequelize via a junction table containing a member_id and an event_id (which correspond to columns in the Members and Events tables respectively) and an event_date - the date when an event is attended by many members.
I am following the guidance in the Sequelize 'Advanced Associations' section (link), but am getting an error when my node.js server attempts to start, as follows:
Members.belongsToMany(models.Events, { through: 'member_events' })
^
TypeError: Members.belongsToMany is not a function
I'm really stuggling to understand what specifically this means and how I can address the issue. The following is my code for the three models in question:
memberEvents.js
module.exports = (sequelize, DataTypes) => {
const Members = require('../models/members')
const Events = require('../models/events')
const MemberEvents = sequelize.define(
"MemberEvents",
{
member_id: {
type: DataTypes.INTEGER,
allowNull: false,
primaryKey: true,
},
event_id: {
type: DataTypes.INTEGER,
allowNull: false,
primaryKey: true,
},
event_date: {
type: DataTypes.DATEONLY,
allowNull: false,
primaryKey: true,
},
},
{ tableName: "member_events" }
);
Members.belongsToMany(Events, { through: 'member_events' })
Events.belongsToMany(Members, { through: 'member_events' })
return MemberEvents;
};
Members.js
module.exports = (sequelize, DataTypes) => {
//Below creates the member table in the schema
const Members = sequelize.define(
"Members",
{
member_id: {
type: DataTypes.INTEGER,
autoIncrement: true,
allowNull: false,
primaryKey: true,
},
forename: {
type: DataTypes.STRING(35),
allowNull: false,
},
surname: {
type: DataTypes.STRING(35),
allowNull: false,
},
date_of_birth: {
type: DataTypes.DATEONLY,
allowNull: false,
},
address_1: {
type: DataTypes.STRING(35),
allowNull: false,
},
address_2: {
type: DataTypes.STRING(35),
},
address_3: {
type: DataTypes.STRING(35),
},
address_4: {
type: DataTypes.STRING(35),
},
address_5: {
type: DataTypes.STRING(35),
},
postcode: {
type: DataTypes.STRING(12),
allowNull: false,
},
directions: {
type: DataTypes.TEXT("long"),
},
mobile_phone: {
type: DataTypes.STRING(12),
},
email_address: {
type: DataTypes.STRING(255),
},
key_safe_code: {
type: DataTypes.STRING(8),
},
next_of_kin_name: {
type: DataTypes.STRING(70),
},
next_of_kin_phone: {
type: DataTypes.STRING(12),
},
next_of_kin_local: {
type: DataTypes.STRING(33),
},
next_of_kin_relationship: {
type: DataTypes.STRING(40),
},
doctor_name: {
type: DataTypes.STRING(35),
},
initial_medical_conditions: {
type: DataTypes.TEXT("long"),
},
deceased: {
type: DataTypes.DATEONLY,
},
normally_escorted: {
type: DataTypes.STRING(3),
},
blue_badge_holder: {
type: DataTypes.STRING(3),
},
medical_equipment: {
type: DataTypes.STRING,
},
},
{ tableName: "Member" }
);
return Members;
};
Events.js
module.exports = (sequelize, DataTypes) => {
//Below creates the event table in the schema
const Events = sequelize.define(
"Events",
{
event_id: {
type: DataTypes.INTEGER,
autoIncrement: true,
allowNull: false,
primaryKey: true,
},
event_name: {
type: DataTypes.STRING(70),
allowNull: false,
},
staff_id: {
type: DataTypes.INTEGER,
allowNull: false,
},
},
{ tableName: "Events" }
);
return Events;
};
When separating your Sequelize models into separate imports, use the associate function to access the models to make these associations. You also don't need to import the other models into each other.
Something along these lines should work:
TableA.js
module.exports = (sequelize, DataTypes) => {
const TableA = sequelize.define('table_a', {
foobar: DataTypes.STRING,
}, {});
TableA.associate = function(models) {
TableA.belongsTo(models.TableB, { through: 'table_c' });
};
return TableA;
};
TableB.js
module.exports = (sequelize, DataTypes) => {
const TableB = sequelize.define('table_b', {
fazbaz: DataTypes.STRING,
}, {});
TableB.associate = function(models) {
TableB.belongsTo(models.TableA, { through: 'table_c' });
};
return TableB;
};
I wrote here yesterday, but I did not find the right solution,
I have such a connection (pictures 1)
How can I change my code so that I would create a sizeChart, bind sizeChartParameters to it, and sizeChartAttributes to it? I use sequelizeORM
Now there is a record only in sizeChart
const findChart = await models.sizeChart.create({
categoryId:categoryId,
nameChart:sizeChartName,
include:[{
required:true,
model:models.sizeChartParameters,
attributeValue:attributeValue,
include:[{
required:true,
model:models.sizeChartAttributes,
attributeName:attributeName,
}]
}]
});
sizeChart table
module.exports = function (sequelize, Sequelize) {
const SizeChart = sequelize.define('sizeChart', {
id: {
type: Sequelize.INTEGER,
autoIncrement: true,
primaryKey: true,
allowNull: false,
},
categoryId:{
type: Sequelize.INTEGER,
allowNull: false,
},
nameChart:{
type: Sequelize.TEXT,
allowNull: false,
},
});
SizeChart.associate = function (models) {
SizeChart.belongsTo(models['sizeChartParameters']);
};
return SizeChart;
};
sizeChartParameters table
module.exports = function(sequelize, Sequelize) {
const SizeChart = sequelize.define('sizeChartParameters', {
id: {
type: Sequelize.INTEGER,
autoIncrement: true,
primaryKey: true,
allowNull: false,
},
attributeValue: {
type: Sequelize.TEXT,
allowNull: false,
},
});
return SizeChart;
};
sizeChartAttributes table
module.exports = function(sequelize, Sequelize) {
const SizeChart = sequelize.define('sizeChartAttributes', {
id: {
type: Sequelize.INTEGER,
autoIncrement: true,
primaryKey: true,
allowNull: false,
},
attributeName: {
type: Sequelize.TEXT,
allowNull: false,
},
});
SizeChart.associate = function(models) {
SizeChart.hasOne(models['sizeChartParameters'],{foreignKey:'attributeId'});
};
return SizeChart;
};
const findChart = await models.sizeChart.create({
categoryId:categoryId,
nameChart:sizeChartName,
include:[{
required:true,
model:models.sizeChartParameters,
attributeValue:attributeValue,
include:[{
required:true,
model:models.sizeChartAttributes,
attributeName:attributeName,
}]
}]
});
I have issue: "is not associated to". I want to have one to many association. Here is my code
Index.js
fs.readdirSync(__dirname)
.filter(file => {
return file.indexOf('.') !== 0 && file !== basename && file.slice(-3) === '.js';
})
.forEach(file => {
const model = require(path.join(__dirname, file))(sequelize, Sequelize);
db[model.name] = model;
});
Object.keys(db).forEach(modelName => {
if (db[modelName].associate) {
db[modelName].associate(db);
}
});
db.Sequelize = Sequelize;
db.sequelize = sequelize;
module.exports = db;
Here is course.js
module.exports = (sequelize, Sequelize) => {
const Course = sequelize.define(
'course',
{
idCourse: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true,
field: 'idcourse',
},
nameOfCourse: {
type: Sequelize.STRING,
field: 'nameofcourse',
},
idLevel: {
type: Sequelize.INTEGER,
field: 'idlevel',
},
idTypeOfCourse: {
type: Sequelize.INTEGER,
field: 'idtypeofcourse',
},
startDate: {
type: Sequelize.DATE,
field: 'startdate',
},
endDate: {
type: Sequelize.DATE,
field: 'enddate',
},
fee: {
type: Sequelize.BIGINT,
field: 'fee',
},
isDeleted: {
type: Sequelize.BOOLEAN,
field: 'isdeleted',
},
},
{
freezeTableName: true,
timestamps: false,
createdAt: false,
updatedAt: false,
}
);
Course.associate = function (models) {
models.course.belongsToMany(models.bill, {
through: models.billinfo,
as: 'bill',
foreignKey: 'idCourse',
});
models.course.belongsTo(models.typeofcourse, {
foreignKey: 'idTypeOfCourse',
sourceKey: 'idCourse',
as: 'typeofcourse',
});
};
return Course;
};
Here is TypeOfCourse.js
module.exports = (sequelize, Sequelize) => {
const TypeOfCourse = sequelize.define(
'typeofcourse',
{
idTypeOfCourse: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true,
field: 'idtypeofcourse',
},
nameOfType: {
type: Sequelize.STRING,
field: 'nameoftype',
},
language: {
type: Sequelize.STRING,
field: 'language',
},
tags: {
type: Sequelize.ARRAY(Sequelize.TEXT),
field: 'tags',
},
isDeleted: {
type: Sequelize.BOOLEAN,
field: 'isdeleted',
},
},
{
freezeTableName: true,
timestamps: false,
createdAt: false,
updatedAt: false,
}
);
TypeOfCourse.associate = function (models) {
models.typeofcourse.hasMany(models.course, {
foreignKey: 'idTypeOfCourse',
as: 'course',
});
};
return TypeOfCourse;
};
In course.controller.js, I define function findAll Course and want to display info about typeOfCourse
exports.findAll = (req, res) => {
Course.findAll({
include: [
{
model: TypeOfCourse,
as: 'typeofcourse',
through: { attributes: [] },
},
],
})
.then(data => {
res.send(data);
})
.catch(err => {
res.status(500).send({
message: err.message || 'Some error occurred while retrieving course.',
});
});
};
But when i send request to get course. This message responed:
{
"message": "typeofcourse is not associated to course!"
}
Where I wrong and how to fix this error. Thank you so much.
You can use the hasMany association, it will Create an N:M association with a join table
Course.hasMany(TypeOfCourse, {
foreignKey: { name: 'idCourse' },
targetKey: 'idCourse',
constraints: false,
as: 'typeOfCourses',
}),
```
This is my Diagram DATABASE : https://i.stack.imgur.com/CGAwh.png
I made models of my databases with SEQUELIZE like that :
MODEL : Level
module.exports = (sequelize, DataTypes) => {
const Level = sequelize.define(
'Level',
{
level_id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true,
},
label: {
type: DataTypes.STRING,
allowNull: false,
unique: {
args: true,
msg: 'Level:Label already exist!',
},
validate: {
notEmpty: { msg: `Level:Label cannot be empty!` },
notNull: { msg: `Level:Label cannot be NULL!` },
},
},
ref: {
type: DataTypes.STRING,
allowNull: true,
},
description: {
type: DataTypes.TEXT,
allowNull: true,
},
},
{
tableName: 'levels',
timestamps: false,
}
);
Level.associate = (models) => {
Level.belongsToMany(models.Test, {
through: models.testHasLevel,
foreignKey: 'level_id',
otherKey: 'test_id',
timestamps: false,
});
};
return Level;
};
Model : TEST :
module.exports = (sequelize, DataTypes) => {
const Test = sequelize.define(
'Test',
{
test_id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true,
},
label: {
type: DataTypes.STRING,
allowNull: false,
validate: {
notEmpty: { msg: `Test:label cannot be empty!` },
notNull: { msg: `Test:label cannot be NULL!` },
},
},
isInternal: {
type: DataTypes.BOOLEAN,
defaultValue: false,
allowNull: false,
validate: {
notEmpty: { msg: `Test:isInternal cannot be empty!` },
notNull: { msg: `Test:isInternal cannot be NULL!` },
},
},
parent_id: {
type: DataTypes.INTEGER,
defaultValue: null,
allowNull: true,
},
},
{
tableName: 'tests',
timestamps: false,
}
);
Test.associate = (models) => {
Test.belongsToMany(models.Level, {
through: models.testHasLevel,
foreignKey: 'test_id',
otherKey: 'level_id',
timestamps: false,
});
Test.hasMany(models.Test, { foreignKey: 'parent_id', as: 'children' });
};
return Test;
};
MODEL : TEST HAS MODEL
module.exports = (sequelize, DataTypes) => {
const testHasLevel = sequelize.define(
'testHasLevel',
{},
{
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true,
},
tableName: 'test_has_level',
timestamps: false,
}
);
testHasLevel.associate = (models) => {
testHasLevel.belongsTo(models.Test, {
foreignKey: 'test_id',
targetKey: 'test_id',
});
testHasLevel.belongsTo(models.Level, {
foreignKey: 'level_id',
targetKey: 'level_id',
});
};
return testHasLevel;
};
I made also SESSION MODEL :
module.exports = (sequelize, DataTypes) => {
const Session = sequelize.define(
'Session',
{
session_id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true,
},
institut_id: {
type: DataTypes.INTEGER,
},
start: {
type: DataTypes.DATE,
},
end: {
type: DataTypes.DATE,
},
test_id: {
type: DataTypes.INTEGER,
},
level_id: {
type: DataTypes.INTEGER,
},
limitDateSubscribe: {
type: DataTypes.DATE,
},
placeAvailable: {
type: DataTypes.INTEGER,
},
},
{
tableName: 'sessions',
timestamps: false,
}
);
Session.associate = (models) => {
Session.hasMany(models.sessionHasUser, { foreignKey: 'session_id' });
};
return Session;
};
But i have no idea how to "BIND" SESSION with TEST_HAS_LEVEL with Sequelize ....
What should i change ? cause i know "composite key" are not allowed with the last version of sequelize.
In other term :
How associate properly a cross table with a one to many relationship to an other table ?
Model: Level
module.exports = (sequelize, DataTypes) => {
const Level = sequelize.define(
"Level",
{
level_id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true,
},
label: {
type: DataTypes.STRING,
allowNull: false,
unique: {
args: true,
msg: "Level:Label already exist!",
},
validate: {
notEmpty: { msg: `Level:Label cannot be empty!` },
notNull: { msg: `Level:Label cannot be NULL!` },
},
},
ref: {
type: DataTypes.STRING,
allowNull: true,
},
description: {
type: DataTypes.TEXT,
allowNull: true,
},
},
{
tableName: "levels",
timestamps: false,
}
);
Level.associate = (models) => {
Level.hasMany(models.testHasLevel, {
foreignKey: "level_level_id",
as: "levels",
});
};
return Level;
};
Model: Test
module.exports = (sequelize, DataTypes) => {
const Test = sequelize.define(
"Test",
{
test_id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true,
},
label: {
type: DataTypes.STRING,
allowNull: false,
validate: {
notEmpty: { msg: `Test:label cannot be empty!` },
notNull: { msg: `Test:label cannot be NULL!` },
},
},
isInternal: {
type: DataTypes.BOOLEAN,
defaultValue: false,
allowNull: false,
validate: {
notEmpty: { msg: `Test:isInternal cannot be empty!` },
notNull: { msg: `Test:isInternal cannot be NULL!` },
},
},
parent_id: {
type: DataTypes.INTEGER,
defaultValue: null,
allowNull: true,
},
},
{
tableName: "tests",
timestamps: false,
}
);
Test.associate = (models) => {
Test.hasMany(models.testHasLevel, {
foreignKey: "test_test_id",
as: "tests",
});
Test.hasMany(models.Test, { foreignKey: "parent_id", as: "children" });
};
return Test;
};
Model: Test has level
module.exports = (sequelize, DataTypes) => {
const testHasLevel = sequelize.define(
"testHasLevel",
{
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true,
},
test_test_id: {
type: DataTypes.INTEGER,
},
level_level_id: {
type: DataTypes.INTEGER,
},
},
{
tableName: "test_has_level",
timestamps: false,
}
);
testHasLevel.associate = (models) => {
testHasLevel.belongsTo(models.Test, {
foreignKey: "test_test_id",
as: "tests",
});
testHasLevel.belongsTo(models.Level, {
foreignKey: "level_level_id",
as: "levels",
});
testHasLevel.hasMany(models.Session, {
foreignKey: "test_has_level_id",
as: "test_has_level",
});
};
return testHasLevel;
};
Model: Session
module.exports = (sequelize, DataTypes) => {
const Session = sequelize.define(
"Session",
{
session_id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true,
},
institut_id: {
type: DataTypes.INTEGER,
},
start: {
type: DataTypes.DATE,
},
end: {
type: DataTypes.DATE,
},
test_has_level_id: {
type: DataTypes.INTEGER,
},
limitDateSubscribe: {
type: DataTypes.DATE,
},
placeAvailable: {
type: DataTypes.INTEGER,
},
},
{
tableName: "sessions",
timestamps: false,
}
);
Session.associate = (models) => {
Session.belongsTo(models.testHasLevel, {
foreignKey: "test_has_level_id",
as: "test_has_level",
});
};
return Session;
};
I have checked this answer and made sure with both scenario are correct. But still having issue creating constraints:
Model:
user.js
'use strict';
module.exports = (sequelize, DataTypes) => {
const User = sequelize.define('User', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: DataTypes.INTEGER
},
username: {
type: DataTypes.STRING,
unique: true,
allowNull: false
},
password: {
type: DataTypes.STRING,
allowNull: false
}
}, {});
User.associate = function(models) {
models.User.hasMany(models.Answer);
};
return User;
};
answer.js
'use strict';
module.exports = (sequelize, DataTypes) => {
const Answer = sequelize.define('Answer', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: DataTypes.INTEGER
},
userId: {
type: DataTypes.INTEGER,
references: {
model: 'Users',
key: 'id'
},
allowNull: true
},
content: DataTypes.TEXT
}, {});
Answer.associate = function(models) {
models.belongsTo(models.User)
};
return Answer;
};
Migration
...-create-user.js
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('Users', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
username: {
type: Sequelize.STRING,
unique: true,
allowNull: false
},
password: {
type: Sequelize.STRING,
allowNull: false
}
});
},
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('Users');
}
};
...create-answer.js
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('Answers', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
userId: {
allowNull: true,
type: Sequelize.INTEGER
},
content: {
allowNull: false,
type: Sequelize.TEXT
}
});
},
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('Answers');
}
};
What else might be the issue? Is it due to allowNull: true? But I am sure this should not be the case because I also tested using allowNull: false if it works, but it did not.
I have also tried model: User and .INTEGER(11) exactly but nothing work. I cannot see foreign key constraints on PHPMyAdmin.
You should add
userId: {
type: Sequelize.INTEGER,
references: {
model: 'Users',
key: 'id'
},
allowNull: true
}
in create-answer.js
instead of
userId: {
allowNull: true,
type: Sequelize.INTEGER
},