Sequelize eager loading association with through table - node.js

I've been through several questions on the site but I still can't see what I'm doing wrong here, so any help would be greatly appreciated.
I'm getting the error:
Organization (organizations) is not associated to User!
Org Model:
module.exports = function (sequelize, DataTypes) {
return sequelize.define('Organization', {
organizationID: {
primaryKey: true,
type: DataTypes.INTEGER(11),
allowNull: false,
},
name: {
type: DataTypes.STRING,
allowNull: false,
},
description: {
type: DataTypes.STRING,
allowNull: true,
}
},
{
tableName: "spa_vOrganization",
freezeTableName: true,
classMethods: {
associate: function (models) {
Organization.hasMany(models.User, {
as: 'users',
through: models.User_Tenant_Organization,
foreignKey: 'organizationID'
});
}
},
});
};
User Model:
module.exports = function (sequelize, DataTypes) {
return sequelize.define('User', {
userID: {
primaryKey: true,
type: DataTypes.INTEGER(11),
allowNull: false,
},
password: {
type: DataTypes.STRING,
allowNull: false,
},
email: {
type: DataTypes.STRING,
allowNull: false,
}
},
{
tableName: "spa_User",
freezeTableName: true,
classMethods: {
associate: function(models) {
User.hasMany(models.Organization, { as: "organizations", through: models.User_Tenant_Organization, foreignKey: 'userID'});
}
}
}
);
};
Matrix table model:
module.exports = function (sequelize, DataTypes) {
return sequelize.define('User_Tenant_Organization', {
userTenantOrganizationID: {
primaryKey: true,
type: DataTypes.INTEGER(11),
allowNull: false,
},
userID: {
type: DataTypes.INTEGER(11),
allowNull: false,
},
organizationID: {
type: DataTypes.INTEGER(11),
allowNull: false,
},
tenantID: {
type: DataTypes.INTEGER(11),
allowNull: false,
},
},
{
tableName: "spa_User_Tenant_Organization",
freezeTableName: true,
});
};
What I'm trying to do is just pull back a user with their organizations eagerly loaded. Here's what I'm using:
models.User.findOne({where: {
email: body.email
}, include: [ {model:models.Organization, as: 'organizations'}]}).complete( function (err, user) {
// do something with the user
}
I've tried with and without foreignKey definitions on both User and Organization, nothing makes any difference. I'm obviously misunderstanding something about the associations. Can anyone tell me where I'm going wrong please?

I found the problem. The associations in the above code are actually correct - what was failing was my models/index.js, which had been automatically generated by the yeoman generator-angular-express-sequelize
index.js was looping through the model files, importing them into the sequelize object and storing a copy in an array db[], then trying to run the classMethod associate(), but it was calling models.options.associate() instead of models.associate():
Object.keys(db).forEach(function (modelName) {
if (db[modelName].options.hasOwnProperty('associate')) {
db[modelName].options.associate(db);
}
});
I've fixed that by removing the ".options" and everything works fine.
Pull request to fix the problem is here for reference: https://github.com/rayokota/generator-angular-express-sequelize/pull/7

Related

Trying to search entity with association Many to Many Sequelize

PROBLEM RESUME:
I'm having trouble when I try to do a findOne or findAll.
At the findOne or findAll answer I catch all the informations from the user but in the answer there aren't any data of "t_roles" associated to this user.
But the stranger issue is that if I use raw: true inside the findOne for example, the informations of roles are shown.
I Have two models
User:
const dbUser = {
a_id: {
primaryKey: true,
autoIncrement: true,
type: Sequelize.BIGINT,
},
a_date_created: {
type: Sequelize.DATE,
allowNull: false,
},
a_first_name: {
type: Sequelize.STRING,
allowNull: false,
},
a_last_name: {
type: Sequelize.STRING,
allowNull: false,
},
a_email: {
type: Sequelize.TEXT,
unique: true,
allowNull: false,
},
a_password: {
type: Sequelize.STRING,
allowNull: false,
},
a_birthday: {
type: Sequelize.DATE,
allowNull: false,
},
a_is_active: {
type: Sequelize.BOOLEAN,
allowNull: false,
},
};
User.init(dbUser, {
sequelize: db,
modelName: 't_user',
timestamps: false,
tableName: 't_users',
});
User.associate = (models) => {
console.log('ASSOCIADO')
User.belongsToMany(models.Role, {
through: { model: UserRole, unique: false },
as: 'roles',
foreignKey: 'a_user',
otherKey: 'a_role',
});
};
and Role:
const dbRole = {
a_id: {
primaryKey: true,
autoIncrement: true,
type: Sequelize.BIGINT,
},
a_role: {
type: Sequelize.STRING,
allowNull: false,
},
};
Role.init(dbRole, {
sequelize: db,
modelName: 't_role',
timestamps: false,
tableName: 't_roles',
});
Role.associate = (models) => {
Role.belongsToMany(models.User, {
through: { model: UserRole, unique: false },
as: 'UserOfRoles',
foreignKey: 'a_role',
otherKey: 'a_user',
});
};
As you can see I'm associating them using another model, UserRole:
const dbUserRole = {
a_id: {
primaryKey: true,
autoIncrement: true,
type: Sequelize.BIGINT,
},
a_role: {
type: Sequelize.INTEGER,
allowNull: false,
primaryKey: false,
references: {
model: Role,
key: 'a_id',
},
onDelete: 'CASCADE',
},
a_user: {
type: Sequelize.INTEGER,
allowNull: false,
references: {
model: User,
key: 'a_id',
},
onDelete: 'CASCADE',
},
};
UserRole.init(dbUserRole, {
sequelize: db,
modelName: 't_user_role',
timestamps: false,
tableName: 't_user_role',
});
UserRole.associate = (models) => {
UserRole.belongsTo(models.User, { targetKey: 'a_id', foreignKey: 'a_user' });
UserRole.belongsTo(models.Role, { targetKey: 'a_id', foreignKey: 'a_role' });
};
To create a user with a role (admin) I do like the code below:
onst createAdmin = async (body) => {
try {
const userResult = await createUser(body);
if (userResult.error) {
return {
ok: false,
error: userResult.error,
};
}
const isAdmin = await UserRole.create({
a_role: 1,
a_user: userResult.a_user_id,
});
return {
ok: true,
};
} catch (error) {
return {
ok: false,
error,
};
}
Seems to be working fine, because the user are being created, and the association using the "t_user_role" too, because the data is also being created at the table.
As I sad at the problem resume, my trouble is when I'm trying to do a findOne or findAll.
For example, when I try the code below, I catch all the informations from the user but in the answer there aren't any data of "t_roles" associated to this user.
const { body } = req;
try {
const result = await User.findOne({
where: {
a_id: 1,
},
include: [
{
association: 'roles',
attributes: ['a_role'],
through: {
attributes: [],
},
},
],
});
console.log('====================================');
console.log(JSON.stringify(result, null, 2));
console.log('====================================');
} catch (error) {
console.error(error);
}
If I use raw: true inside the findOne for example, the informations of roles are shown, so I presume that the association is correct.
I really appreciate any help to find what I'm missing here.
Thanks
Well, after days working on and trying different ways to solve this problem, a friend of mine just helped me starting again the entire project, following the documentation of Sequelize and the exact structure we did before, but a little bit more simple, and surprisingly ... worked. So I suppose that was something with migrations ore models or whatever, but we can't really say.

Sequelize foreign key with association

I have a database that was created with Postgres that was set up for a single foreign key association, Now, this would be mapped as a role table model
consider I have two tables user and roles
roles contain role details and user contain user details of role
const uuid = require('uuid/v4');
('use strict');
module.exports = (sequelize, DataTypes) => {
const role = sequelize.define(
'role',
{
id: {
allowNull: false,
primaryKey: true,
type: DataTypes.UUID,
},
name: {
type: DataTypes.STRING,
allowNull: false,
},
},
{}
);
role.beforeCreate((role) => (role.id = uuid()));
role.associate = function (models) {
role.hasMany(models.user), { foreignKey: 'roleId', as: 'user_roleId' };
};
return role;
};
role migration
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('roles', {
id: {
allowNull: false,
primaryKey: true,
type: Sequelize.UUID,
},
name: {
type: Sequelize.STRING,
},
createdAt: {
allowNull: false,
type: Sequelize.DATE,
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE,
},
});
},
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('roles');
},
};
user model
const uuid = require('uuid/v4');
('use strict');
module.exports = (sequelize, DataTypes) => {
const user = sequelize.define(
'user',
{
id: {
allowNull: false,
primaryKey: true,
type: DataTypes.UUID,
},
firstName: {
type: DataTypes.STRING,
allowNull: false,
},
lastName: DataTypes.STRING,
email: {
type: DataTypes.STRING,
allowNull: false,
},
password: {
type: DataTypes.STRING,
allowNull: false,
},
phoneNumber: {
type: DataTypes.STRING,
},
roleId: {
type: DataTypes.UUID,
},
},
{
timestamps: true,
paranoid: true,
defaultScope: {
attributes: { exclude: ['password'] },
},
}
);
user.beforeCreate((user) => (user.id = uuid()));
user.associate = function (models) {
user.belongsTo(models.role, { foreignKey: 'roleId', onDelete: 'CASCADE' });
};
return user;
};
user migration
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('users', {
id: {
allowNull: false,
primaryKey: true,
type: Sequelize.UUID,
},
firstName: {
type: Sequelize.STRING,
},
lastName: {
type: Sequelize.STRING,
},
email: {
type: Sequelize.STRING,
},
password: {
type: Sequelize.STRING,
},
phoneNumber: {
type: Sequelize.STRING,
},
roleId: {
type: Sequelize.UUID,
},
createdAt: {
allowNull: false,
type: Sequelize.DATE,
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE,
},
deletedAt: {
allowNull: true,
type: Sequelize.DATE,
},
});
},
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('users');
},
};
after running the migration these tables are created in my database.role_id is also present in the user table. but role_id is not generated as a foreign key in my user table. also please verify that the relationship which is mention here(one to many) is correct or not.
please verify my code and give me any suggestions if any changes required. I'm new in development
Your user migration also needs to know about the foreign key; you do this by adding a references: key to the column definition. The Sequelize documentation has a foreign key example; scroll about half way down the page (or just search for references).
In your case the user migration should look something like:
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('users', {
// ... other fields omitted
roleId: {
type: Sequelize.UUID,
references: {
model: { tableName: 'role' }
key: 'id',
},
},
// ... more fields omitted
});
},
// down: omitted
}

Sequelize query singular instead of plural table name

I have an problem and I can't find anything that can solve it. I'm using sequelize and graphql to create an API in nodeJS. The database is using PostgresQL.
So I have two models: Simulation and Data. They are in two tables Simulations and Datas. The relation between them is one Simulation to many Datas.
The problem is this: when I make a query with Simulation (ex: Simulation.findAll()), it works correctly, querying "Simulations", but with Data, it queries on the "Data" table, not "Datas". What I really don't understand is that the code of my two models are almost the same.
Here is the model for Simulation:
module.exports = (sequelize, DataTypes) => {
const Simulation = sequelize.define('Simulation', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: DataTypes.INTEGER,
},
name: {
type: DataTypes.STRING,
allowNull: false,
},
});
Simulation.associate = function(models) {
Simulation.hasMany(models.Data, {
foreignKey: 'SimulationId',
})
};
return Simulation;
};
Here is the model for Data:
module.exports = (sequelize, DataTypes) => {
const Data = sequelize.define('Data', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: DataTypes.INTEGER,
},
name: {
type: DataTypes.STRING,
allowNull: false
},
content: {
type: DataTypes.TEXT,
allowNull: false
},
SimulationId: {
allowNull: false,
type: DataTypes.INTEGER,
},
});
Data.associate = function(models) {
Data.belongsTo(models.Simulation, {
foreignKey: 'SimulationId',
targetKey: 'id',
allowNull: false,
onDelete: 'CASCADE'
});
};
return Data;
};
And here are the migration files:
Simulation
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('Simulations', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
name: {
type: Sequelize.STRING,
allowNull: false,
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
}
});
},
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('Simulations');
}
};
Data
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('Datas', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
name: {
type: Sequelize.STRING,
allowNull: false
},
content: {
type: Sequelize.TEXT,
allowNull: false
},
SimulationId: {
allowNull: false,
type: Sequelize.INTEGER,
onDelete: 'CASCADE',
references: {
model: 'Simulation',
key: 'id',
as: 'SimulationId',
},
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
}
});
},
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('Datas');
}
};
Thanks for helping me :)
You can use freezeTableName option to set whatever the model name you want, sequelize will not make the model names plural.
Sequelize automatically makes the model names plural. Why not call the table "Data" It is actually a plural form of the word "Data", so maybe a better name for the table.

Sequelize belongsToMany additional attributes in join table

I'm having problem with an additional attribute in the join table of the belongsToMany relation.
In the set or add method this attribute is not being passed to mysql.
I'm following the documentation pass as "through" the attribute within the set method, but it is not working.
Would anyone know what could be wrong since following the documentation is not working?
Note: The registration and update of the join is correct, only the additional attribute that is not being passed to the table.
Functionality Model:
export default function(sequelize, DataTypes) {
const Functionality = sequelize.define('functionality', {
id: {
type: DataTypes.INTEGER(11),
allowNull: false,
primaryKey: true,
autoIncrement: true
},
name: {
field: 'name',
type: DataTypes.STRING(300),
allowNull: false
}
}, {
classMethods: {
associate: function(models) {
Functionality.belongsToMany(models.privilege, { as: 'privilegies', through: models.functionality_privilege, foreignKey: 'functionality_id' });
}
},
tableName: 'functionality',
freezeTableName: true,
timestamps: true,
createdAt: 'createdAt',
updatedAt: 'updatedAt'
});
return Functionality;
}
Privilege Model:
export default function(sequelize, DataTypes) {
const Privilege = sequelize.define('privilege', {
id: {
type: DataTypes.INTEGER(11),
allowNull: false,
primaryKey: true,
autoIncrement: true
},
name: {
field: 'name',
type: DataTypes.STRING(300),
allowNull: false
}
}, {
classMethods: {
associate: function(models) {
Privilege.belongsToMany(models.functionality, { as: 'functionalities', through: models.functionality_privilege, foreignKey: 'privilege_id' });
}
},
tableName: 'privilege',
freezeTableName: true,
timestamps: true,
createdAt: 'createdAt',
updatedAt: 'updatedAt'
});
return Privilege;
}
FunctionalityPrivilege Model:
export default function(sequelize, DataTypes) {
const Functionalityprivilege = sequelize.define('functionality_privilege', {
id: {
type: DataTypes.INTEGER(11),
allowNull: false,
primaryKey: true,
autoIncrement: true
},
edit: {
field: 'edit',
type: DataTypes.BOOLEAN
}
}, {
tableName: 'functionality_privilege',
freezeTableName: true,
timestamps: true,
createdAt: 'created_at',
updatedAt: 'updated_at'
});
return Functionalityprivilege;
}
Method Create:
create(options) {
let obj = options.payload;
return this.functionalityDao.create(obj)
.then((result) => {
return result.setPrivilegies(obj.privilegies, { through: { edit: obj.permissions }})
});
}
OR
return result.setPrivilegies(obj.privilegies, { through: { edit: true }})
I didn't manage to do this with 'set' function but it worked for me with 'add' method:
result.addPrivilege(privilege, { through: { edit: true }});
It should work for the already existing privilege. It didn't work with the array of entities (privileges in your case), so I had to call 'add' method several times. Like this:
return Promise.all(
privileges.map(privilege => result.addPrivilege(privilege, { through: { edit: true }}));
)

Sequelize.js still deletes table row even if paranoid is set to true

I'm having trouble getting Sequelize.js to soft delete the rows in my table. I used Sequelize cli to do all my migrations and I'm not using the sync feature to resync the database on start. I have the timestamp fields and even the deletedAt field in my migration and models (model has paranoid: true also) and no matter what it still deletes the row instead of adding a timestamp to the deletedAt field. I noticed when do any querying it doesn't add the deletedAt = NULL in the query like I've seen in some tutorials. I'm using Sequelize.js v3.29.0.
Model File:
'use strict';
module.exports = function(sequelize, DataTypes) {
var Collection = sequelize.define('Collection', {
userId: {
type: DataTypes.INTEGER,
allowNull: false,
validate: {
isInt: true
}
},
name: {
type: DataTypes.STRING,
allowNull: false
},
description: DataTypes.TEXT,
createdAt: {
allowNull: false,
type: DataTypes.DATE
},
updatedAt: {
allowNull: false,
type: DataTypes.DATE
},
deletedAt: {
type: DataTypes.DATE
}
}, {
classMethods: {
associate: function(models) {
Collection.belongsTo(models.User, { foreignKey: 'userId' })
}
}
}, {
timestamps: true,
paranoid: true
});
return Collection;
};
Migration File:
'use strict';
module.exports = {
up: function(queryInterface, Sequelize) {
return queryInterface.createTable('Collections', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
userId: {
allowNull: false,
type: Sequelize.INTEGER
},
name: {
allowNull: false,
type: Sequelize.STRING
},
description: {
type: Sequelize.TEXT
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
},
deletedAt: {
type: Sequelize.DATE
}
});
},
down: function(queryInterface, Sequelize) {
return queryInterface.dropTable('Collections');
}
};
Here is the code in the controller I'm using to destroy the collection object.
Collection.findOne({
where: {
id: collectionId,
userId: user.id
}
}).then(function(collection){
if (collection !== null) {
collection.destroy().then(function(){
res.redirect('/collection');
}).catch(function(error){
res.redirect('/collection/'+collectionId);
});
}
});
Make sure paranoid is attribute defined inside second object param.
..., {
classMethods: {
associate: function(models) {
Collection.belongsTo(models.User,{ foreignKey: 'userId' })
}
},
timestamps: true,
paranoid: true
}
You've defined paranoid as 3. Param and that is the problem.

Resources