I have two models, policyTable and policy_rule defined as below. There is a column called policy_id in policy_rule which is a foreign key and references to id column in policyTable. The policy_rule can have multiple policyTable, i.e there is a 1:N relationship.
var policyTable = dbController.db.define('policyTable', {
id: {
type: Sequelize.BIGINT,
autoIncrement: true,
primaryKey: true
},
't': {
type: Sequelize.BIGINT,
unique: true
},
'name': {
type: Sequelize.STRING,
},
src: {
type: Sequelize.STRING
}
}, {
timestamps: false,
freezeTableName: true,
tableName: 'zo_policy',
});
var policy_rule = dbController.db.define('policy_rule', {
policy_id: {
type: Sequelize.BIGINT,
references: {
model: policyTable,
key: 'id'
},
onUpdate: 'cascade',
onDelete: 'cascade'
},
agent_id: {
type: Sequelize.BIGINT,
},
enabled: {
type: Sequelize.BOOLEAN,
}
}, {
timestamps: false,
freezeTableName: true,
tableName: 'zo_policy_rule',
});
Now I want to join these two models and get all columns of both of them. How can I do that? I tried the below code but it says
Error: policy_rule is not associated to policyTable!
function getAllPolicies() {
return policyTable.findAndCountAll({
include: [{
model: policy_rule
}]
}).then(function (users) {
console.log(users);
data.count = users.count;
data.users = users.rows;
console.log(data);
return data;
});
};
Even though your associations are defined at the database level, and you indicate this in your column definitions, to use sequelize's include feature, you still need to explicitly define your associations between models, too.
Here's the relevant section in the doco: http://docs.sequelizejs.com/en/latest/docs/associations/#belongsto
and
http://docs.sequelizejs.com/en/latest/docs/associations/#1m
In your case, it'll mean adjusting your code to something along the lines of below - note the new classMethods section in each model definition:
var policyTable = dbController.db.define('policyTable', {
id: {
type: Sequelize.BIGINT,
autoIncrement: true,
primaryKey: true
},
't': {
type: Sequelize.BIGINT,
unique: true
},
'name': {
type: Sequelize.STRING,
},
src: {
type: Sequelize.STRING
}
}, {
timestamps: false,
freezeTableName: true,
tableName: 'zo_policy',
classMethods: {
associate(models) {
this.hasMany(models.policy_rule, {foreignKey: 'policy_id'})
}
}
});
var policy_rule = dbController.db.define('policy_rule', {
policy_id: {
type: Sequelize.BIGINT,
references: {
model: policyTable,
key: 'id'
},
onUpdate: 'cascade',
onDelete: 'cascade'
},
agent_id: {
type: Sequelize.BIGINT,
},
enabled: {
type: Sequelize.BOOLEAN,
}
}, {
timestamps: false,
freezeTableName: true,
tableName: 'zo_policy_rule',
classMethods: {
associate(models) {
this.belongsTo(models.policyTable, {foreignKey: 'policy_id'})
}
}
});
Related
The problem:
Whenever I fetch a user, I always have to declare/include the association on the query to get its role:
const user = await DB.PORTALDB.models.AccessUser.findOne({
where: { email },
include: [ // EVERY QUERY, I HAVE TO INCLUDE THIS
{
model: DB.PORTALDB.models.AccessUserRoleLup,
as: 'role'
}
]
});
Now there are instance where I forget to include this association so I get a undefined role.
My question is, is there a way where I only set this association once so that I don't have to include this later on my queries?
This the model for my AccessUser table
const AccessUser = <AccessUserStatic>sequelize.define<AccessUserInstance>(
'AccessUser',
{
user_id: {
autoIncrement: true,
type: DataTypes.INTEGER.UNSIGNED,
allowNull: false,
primaryKey: true
},
email: {
type: DataTypes.STRING(255),
allowNull: false
},
firstname: {
type: DataTypes.STRING(255),
allowNull: false
},
lastname: {
type: DataTypes.STRING(255),
allowNull: false
},
password: {
type: DataTypes.STRING(255),
allowNull: false
},
disable: {
type: DataTypes.TINYINT,
allowNull: false,
defaultValue: 0
},
role_id: {
type: DataTypes.SMALLINT,
allowNull: false,
defaultValue: 0
},
created_modified: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: Sequelize.literal('CURRENT_TIMESTAMP')
}
}, {
tableName: 'access_user',
timestamps: false,
indexes: [
{
name: "PRIMARY",
unique: true,
using: "BTREE",
fields: [
{ name: "user_id" },
]
},
]
});
AccessUserRoleLup table
const AccessUserRoleLup = <AccessUserRoleLupStatic>sequelize.define<AccessUserRoleLupInstance>(
'AccessUserRoleLup',
{
role_id: {
autoIncrement: true,
type: DataTypes.INTEGER.UNSIGNED,
allowNull: false,
primaryKey: true
},
role_name: {
type: DataTypes.STRING(50),
allowNull: false
},
role_code: {
type: DataTypes.CHAR(50),
allowNull: false,
defaultValue: ""
}
}, {
tableName: 'access_user_role_lup',
timestamps: false,
indexes: [
{
name: "PRIMARY",
unique: true,
using: "BTREE",
fields: [
{ name: "role_id" },
]
},
]
});
Association:
db.models.AccessUser.hasOne(db.models.AccessUserRoleLup, {
foreignKey: 'role_id',
as: 'role'
});
Use defaultScope for AccessUser. defaultScope is defined in a model definition and it is always applied (unless you removed inline).
const AccessUser = <AccessUserStatic>sequelize.define<AccessUserInstance>(
'AccessUser',
{
user_id: {
autoIncrement: true,
type: DataTypes.INTEGER.UNSIGNED,
allowNull: false,
primaryKey: true
},
...
}, {
tableName: 'access_user',
timestamps: false,
defaultScope: { // Add this
include: [{
model: AccessUserRoleLup,
as: 'role'
}]
},
...
});
With this model definition, all queries will include AccessUserRoleLup.
If you would like to remove for a certain query, use .unscoped().
// These will automatically add eager loading for role
DB.PORTALDB.models.AccessUser.findAll()
DB.PORTALDB.models.AccessUser.findOne()
// These won't fetch role
DB.PORTALDB.models.AccessUser.unscoped().findAll()
DB.PORTALDB.models.AccessUser.unscoped().findOne()
More detail about scope: https://sequelize.org/master/manual/scopes.html
My initial workaround was to create a utility function for querying the user like so:
export const getAccessUser = (where: WhereOptions, include?: IncludeOptions) => {
return new Promise(async (resolve, reject) => {
try {
const user = await DB.PORTALDB.models.AccessUser.findOne({
where: where,
include: [
{
model: DB.PORTALDB.models.AccessUserRoleLup,
as: 'role'
},
...[include]
]
});
resolve(user);
} catch (err) {
reject(err);
}
});
}
I wonder if my question above can be done in much simpler way.
I have two tables Employee and Department
Department
const Department = Sequelize.define(
"Department",
{
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true,
},
name: {
type: DataTypes.STRING,
allowNull: false,
},
},
{
underscored: true,
timestamps: true,
paranoid: true,
modelName: "Department",
tableName: "departments",
},
);
Department.associate = function (models) {
// associations can be defined here
models.Department.hasMany(models.Employee, {
foreignKey: "department_id",
as: "employees",
});
};
return Department;
};
Employee
const Employee = Sequelize.define(
"Employee",
{
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true,
},
name: {
type: DataTypes.STRING,
unique: true,
allowNull: false,
},
status: {
type: DataTypes.STRING,
defaultValue: "active",
},
departmentId: {
type: DataTypes.INTEGER,
},
},
{
underscored: true,
timestamps: true,
modelName: "Employee",
tableName: "employees",
},
);
Employee.associate = function (models) {
models.Employee.belongsTo(models.Department, {
foreignKey: "department_id",
as: "department",
});
};
return Employee;
};
Now I have to fetch the list of employees and putting a filter of department_id = 1
const { departmentId } = req.body;
const employees = await Employee.findAll({
include: [
{
model: Department,
where: {
id: departmentId,
},
},
],
});
I am getting the issue. Department is mapped by association "departments"
Cannot fetch the data.
I found the answer on sequelize docs
const employees = await Employee.findAll({
include: [
{
association: "department", // this is the place to change
where: {
id: departmentId,
},
},
],
});
Learnings:
We will not be able to put association and model together.
We will be able to use the Model if no association is there.
We will be able to use association if there is one.
References: https://sequelize.org/master/manual/eager-loading.html#:~:text=You%20can%20also%20include%20by%20alias%20name%20by%20specifying%20a%20string%20that%20matches%20the%20association%20alias
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.
I have the following error:
Unhandled rejection Error: Cyclic dependency found. coupons is
dependent of itself. Dependency chain: coupons -> orders => coupons
My order.js model looks like as follows:
'use strict';
module.exports = (sequelize, DataTypes) =>
{
var Order = sequelize.define('orders',
{
id:
{
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: DataTypes.INTEGER
},
userid:
{
type: DataTypes.INTEGER,
references:
{
model: 'users',
key: 'id'
}
},
coupon_id:
{
type: DataTypes.INTEGER,
allowNull: true,
references:
{
model: 'coupons',
key: 'id'
},
},
product:
{
type: DataTypes.INTEGER,
references:
{
model: 'products',
key: 'id'
}
},
address:
{
type: DataTypes.INTEGER,
references:
{
model: 'address',
key: 'id'
}
},
canceled:
{
type: DataTypes.INTEGER,
defaultValue: 0
},
quantity:
{
type: DataTypes.INTEGER,
},
note:
{
allowNull: true,
type: DataTypes.STRING
},
},
{
freezeTableName: true,
tableName: 'orders',
createdAt: 'createdat',
updatedAt: 'updatedat',
});
Order.associate = models => {
Order.hasMany(models.coupons, {
foreignKey: 'id',
onDelete: 'cascade',
onUpdate: 'cascade',
constrains: false
});
};
return Order;
};
and coupon.js looks like this:
'use strict';
module.exports = (sequelize, DataTypes) =>
{
var Coupon = sequelize.define('coupons',
{
id:
{
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: DataTypes.INTEGER
},
name:
{
type: DataTypes.STRING
},
code:
{
type: DataTypes.STRING
},
discount:
{
type: DataTypes.FLOAT
},
expires:
{
type: 'TIMESTAMP',
},
created_by:
{
type: DataTypes.INTEGER,
references:
{
model: 'users',
key: 'id'
},
},
maxuse:
{
type: DataTypes.INTEGER,
},
},
{
freezeTableName: true,
tableName: 'coupons',
createdAt: 'createdat',
updatedAt: 'updatedat'
});
Coupon.associate = models => {
Coupon.belongsTo(models.orders,
{
foreignKey: 'id',
onDelete: 'cascade',
onUpdate: 'cascade',
});
};
return Coupon;
};
It seems that I am doing something wrong with the associations. Any help would be appreciated.
NOTE: Everytime I comment out coupon_id: on orders.js, the error goes out. Still, I need this functionality.
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 }}));
)