Sequelize self-referential Many-to-Many relationships are not working - node.js

I only found following in Sequelize documentation, but it's not possible to understand how to fetch associations properly.
Person.belongsToMany(Person, { as: 'Children', through: 'PersonChildren' })
// This will create the table PersonChildren which stores the ids of the objects.
This is the implementation.
const sequelizePsqlConfig = new Sequelize(
"postgres://Kavinda Vindika#localhost:5432/hrm_users"
);
export const User = sequelizePsqlConfig.define(
"users",
{
userId: {
field: "user_id",
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true,
},
userName: {
field: "user_name",
type: DataTypes.STRING,
},
email: { type: DataTypes.STRING, key: "email" },
department: { type: DataTypes.STRING, key: "department" },
designation: { type: DataTypes.STRING, key: "designation" },
},
{
timestamps: false,
}
);
export const User_Associations = sequelizePsqlConfig.define(
"user_associations",
{
id: {
field: "id",
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true,
},
managerId: {
field: "manager_id",
type: DataTypes.INTEGER,
},
subordinateId: {
field: "subordinate_id",
type: DataTypes.INTEGER,
},
},
{
modelName: "user_associations",
timestamps: false,
tableName: "user_associations",
}
);
User.belongsToMany(User, {
as: "manager",
through: "user_associations",
foreignKey: "manager_id",
otherKey: "subordinate_id",
});
User.belongsToMany(User, {
as: "subordinate",
through: "user_associations",
foreignKey: "subordinate_id",
otherKey: "manager_id",
});
Then I tried to fetch the data with manager and subordinate details.
public async getAllUsers() {
return User.findAll({
include: ["manager", "subordinate"],
});
}
But it failed with following logs. Please assist me on this issue.
[1] Executing (default): SELECT "users"."user_id" AS "userId", "users"."user_name" AS "userName", "users"."email", "users"."department", "users"."designation", "manager"."user_id" AS "manager.userId", "manager"."user_name" AS "manager.userName", "manager"."email" AS "manager.email", "manager"."department" AS "manager.department", "manager"."designation" AS "manager.designation", "manager->user_associations"."id" AS "manager.user_associations.id", "manager->user_associations"."manager_id" AS "manager.user_associations.managerId", "manager->user_associations"."subordinate_id" AS "manager.user_associations.subordinateId", "manager->user_associations"."manager_id" AS "manager.user_associations.manager_id", "manager->user_associations"."subordinate_id" AS "manager.user_associations.subordinate_id", "subordinate"."user_id" AS "subordinate.userId", "subordinate"."user_name" AS "subordinate.userName", "subordinate"."email" AS "subordinate.email", "subordinate"."department" AS "subordinate.department", "subordinate"."designation" AS "subordinate.designation", "subordinate->user_associations"."id" AS "subordinate.user_associations.id", "subordinate->user_associations"."manager_id" AS "subordinate.user_associations.managerId", "subordinate->user_associations"."subordinate_id" AS "subordinate.user_associations.subordinateId", "subordinate->user_associations"."manager_id" AS "subordinate.user_associations.manager_id", "subordinate->user_associations"."subordinate_id" AS "subordinate.user_associations.subordinate_id" FROM "users" AS "users" LEFT OUTER JOIN ( "user_associations" AS "manager->user_associations" INNER JOIN "users" AS "manager" ON "manager"."user_id" = "manager->user_associations"."subordinate_id") ON "users"."user_id" = "manager->user_associations"."manager_id" LEFT OUTER JOIN ( "user_associations" AS "subordinate->user_associations" INNER JOIN "users" AS "subordinate" ON "subordinate"."user_id" = "subordinate->user_associations"."manager_id") ON "users"."user_id" = "subordinate->user_associations"."subordinate_id";
[1] TypeError: Class constructor model cannot be invoked without 'new'

Related

postgres returns another table column while inserting data in sequelize

When I try to insert new category, I got this error:
error: column "image" does not exist
sql: 'INSERT INTO "Categories" ("id","createdAt","updatedAt") VALUES (DEFAULT,$1,$2) RETURNING "id","image","title","createdAt","updatedAt";'
The problem is that it doesn't insert name and other values and returns columns belong to post table.
My guesses are the problem of sequelize-cli and sequelize version or missing something in models or migrations.
I only insert values into name, createdAt and updatedAt column:
await Category.create({
name: req.body.name,
createdAt: new Date(),
updatedAt: new Date()
});
My category model:
const { Model } = require("sequelize");
module.exports = (sequelize, DataTypes) => {
class Category extends Model {
static associate(models) {
Category.hasMany(models.Post, { as: "posts", foreignKey: "categoryId" });
}
}
Category.init(
{
name: DataTypes.STRING
},
{
sequelize,
modelName: "Category"
}
);
return Category;
};
My Post Model:
const { Model } = require("sequelize");
module.exports = (sequelize, DataTypes) => {
class Post extends Model {
static associate(models) {
Post.belongsTo(models.Category, { foreignKey: "categoryId", onDelete: "CASCADE", as: "category" });
}
}
Post.init(
{
title: DataTypes.STRING,
image: DataTypes.STRING,
content: DataTypes.TEXT,
categoryId: DataTypes.INTEGER
},
{
sequelize,
modelName: "Post"
}
);
return Post;
};
Post migration:
module.exports = {
async up(queryInterface, Sequelize) {
await queryInterface.createTable("Posts", {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
title: {
type: Sequelize.STRING
},
image: {
type: Sequelize.STRING
},
content: {
type: Sequelize.TEXT
},
categoryId: {
type: Sequelize.INTEGER,
allowNull: false,
onDelete: "CASCADE",
references: {
model: "Categories",
key: "id"
}
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
}
});
},
Category migration:
module.exports = {
async up(queryInterface, Sequelize) {
await queryInterface.createTable("Categories", {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
name: {
type: Sequelize.STRING
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
}
});
},
I couldn't find solution for this, therefor I used sequelize.query

Sequelize fetch include data based on alias where condition

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

How can I use current query value in sequelize?

I am new with sequelize and i need to include multiple table, i try belove:
const result = await product.findAll({
where: {
isDeleted: false,
sellerId
},
attributes: ["name", "type", "isActive", "subCategoryIds"],
include: [
{
model: category,
as: 'categories',
attributes: ["name"],
},
{
model: category,
as: 'subCategories',
attributes: ["name"],
where: {
id: _.split(sequelize.col("subCategoryIds"), ",")
},
},
],
offset: (page - 1) * limit,
limit
});
But it's return error :
SequelizeDatabaseError: operator does not exist: character varying = integer
Product model is:
const product = sequelize.define("products",
{
id: {
type: DataTypes.BIGINT.UNSIGNED,
field: "id",
primaryKey: true,
autoIncrement: true,
index: true,
},
name: {
type: DataTypes.STRING,
field: "name",
allowNull: false,
},
type: {
type: DataTypes.STRING,
field: "type",
allowNull: false,
},
categoryId: {
type: DataTypes.NUMBER,
field: "categoryId",
references: {
key: "id",
model: category,
},
allowNull: false,
},
sellerId: {
type: DataTypes.NUMBER,
field: "sellerId",
references: {
key: "id",
model: user,
},
allowNull: false,
},
subCategoryIds: {
type: DataTypes.STRING,
allowNull: false,
get() {
const result = _.split(this.getDataValue("subCategoryIds"), ",").map(item => +item);
return result;
}
},
isDeleted: {
type: DataTypes.BOOLEAN,
field: "isDeleted",
defaultValue: false,
},
createdAt: {
type: DataTypes.DATE,
field: "createdAt",
defaultValue: DataTypes.NOW,
},
updatedAt: {
type: DataTypes.DATE,
field: "updatedAt",
defaultValue: DataTypes.NOW,
}
}, {
tableName: "products",
timestamps: false
}
);
product.belongsTo(category, { as: "categories", foreignKey: 'categoryId' });
category.hasMany(product, { as: "products", foreignKey: 'categoryId' });
product.belongsTo(category, { as: "subCategories", foreignKey: 'subCategoryIds' });
I am not getting what's going wrong
is there any solution for this
details in table like
categoryId => 11 subCategoryIds => 11, 12
if i remove
{
model: category,
as: 'subCategories',
attributes: ["name"],
where: {
id: _.split(sequelize.col("subCategoryIds"), ",")
},
},
then it's working fine, problem is sequelize.col("subCategoryIds") return string col("subCategoryIds") not the actual value of the subCategories ids 11, 12

How to rename entity name in Sequelize result?

How can i change entity name in sequelize result ?
Result in postman
"createDate": "2019-05-30",
"companyHeader.companyName": "Testcomp"
I want to rename 'companyHeader.companyName' -> 'companyName'
repository.js
return await models.userHeader.findAndCountAll({
// join t_userheader and t_companyheader with associate
include: [
{
model: models.companyHeader,
attributes: ["companyName"],
// as : 'company'
},
],
attributes: ["id","userId", "userName", "usedCode", "createDate"],
offset: offset,
limit: pageAmount,
order : [['createdate','DESC']],
raw : true
});
You need to use sequelize.literal on the attributes of the first model as this:
return await models.userHeader.findAndCountAll({
include: [{
model: models.companyHeader,
attributes: ["companyName"],
// as : 'company'
}],
attributes: [
"id",
"userId",
"userName",
"usedCode",
"createDate",
[sequelize.literal('"companyHeader"."companyName"'), 'companyName']
],
offset: offset,
limit: pageAmount,
order : [['createdate','DESC']],
raw : true
});
module.exports = (sequelize, DataTypes) => {
const userHeader = sequelize.define("userHeader",{
id: {
type: DataTypes.UUID,
allowNull: false,
unique: true,
primaryKey: true,
autoIncrement: true,
defaultValue : sequelize.literal('uuid_generate_v4()'),
},
userId: {
type: DataTypes.STRING(),
field : 'userid'
},
password: {
type: DataTypes.STRING(),
allowNull: false,
field : 'password'
},
userName: {
type: DataTypes.STRING(),
allowNull: true,
field : 'username'
},
usedCode: {
type: DataTypes.INTEGER,
allowNull: false,
field : 'usedcode'
},
companyId: {
type: DataTypes.UUID,
allowNull: false,
field : 'companyid'
},
createDate: {
type: DataTypes.DATE(3),
allowNull: false,
defaultValue: sequelize.literal("CURRENT_TIMESTAMP(3)"),
field : 'createdate'
}
userHeader.associate = function(models) {
// associations
userHeader.belongsTo(models.companyHeader,{
foreignKey: 'companyId',
targetKey : 'id',
// as : 'company'
})
};
return userHeader;
};

Sequelize Querying Many to Many Relationship and Accessing Object

I have a case where I am querying information from two tables that have a many-to-many relationship with a "through" table. When I make my query it appears that I am querying correctly by not using the "through" table as the table join reference and receiving the outputted records with both table attributes, but I am unable to access the field properties of the joined table. Here is the outputted values.
{"fullNameSlug":"Tester Test","email":"test#test.com","firstName":"Tester","lastName":"Test","teams":[{"teamName":"Sales","member":{"memberId":1,"memberEmail":"test#test.com","organizationId":1,"teamId":1,"userId":1,"created_at":"2016-08-21T21:15:19.000Z","updated_at":"2016-08-21T22:00:32.000Z","organization_id":1,"team_id":1,"user_id":1}}]}
Here is my query and how I am setting the data:
.get(function(req, res){
models.User.find({
where: {
organizationId: organization.organizationId
}, attributes: ['email', 'firstName', 'lastName'],
include: [{
model: models.Team,
attributes: ['teamName']
}]
});
}).then(function(currentUsers){
res.jsonp(currentUsers);
console.log(currentUsers);
});
Here is how I was trying to access the teamName in my view: {{currentUsers.teams.teamName}}, which is not returning a value, but {{currentUsers.email}} returns the right user email.
User Table:
module.exports = function(sequelize, DataTypes) {
var User = sequelize.define('user', {
userId: {
type: DataTypes.INTEGER,
field:'user_id',
autoIncrement: true,
primaryKey: true
},
firstName: {
type: DataTypes.STRING,
field: 'first_name'
},
lastName: {
type: DataTypes.STRING,
field: 'last_name'
},
email: {
type: DataTypes.STRING,
isEmail: true,
unique: true,
set: function(val) {
this.setDataValue('email', val.toLowerCase());
}
},
password: DataTypes.STRING,
organizationId: {
type: DataTypes.INTEGER,
field: 'organization_id',
allowNull: true
}
}, {
underscored: true,
freezeTableName: true,
},
classMethods: {
associate: function(db) {
User.belongsToMany(db.Organization, { through: 'member', foreignKey: 'user_id'}),
User.belongsToMany(db.Team, { through: 'member', foreignKey: 'user_id'})
}
});
return User;
}
Team table:
module.exports = function(sequelize, DataTypes) {
var Team = sequelize.define('team', {
teamId: {
type: DataTypes.INTEGER,
field: 'team_id',
autoIncrement: true,
primaryKey: true,
notNull: true
},
teamName: {
type: DataTypes.STRING,
field: 'team_name'
},
organizationId: {
type: DataTypes.INTEGER,
field: 'organization_id'
},
},{
underscored: true,
freezeTableName: true,
classMethods: {
associate: function(db) {
Team.belongsToMany(db.User, { through: 'member', foreignKey: 'team_id' });
},
}
});
return Team;
}
Member Table:
module.exports = function(sequelize, DataTypes) {
var Member = sequelize.define('member', {
memberId: {
type: DataTypes.INTEGER,
field: 'member_id',
autoIncrement: true,
primaryKey: true
},
memberEmail: {
type: DataTypes.STRING,
field: 'member_email',
isEmail: true,
unique: true
},
organizationId: {
type: DataTypes.INTEGER,
field: 'organization_id',
allowNull: true
},
teamId: {
type: DataTypes.INTEGER,
field: 'team_id',
allowNull: true
},
userId: {
type: DataTypes.INTEGER,
field: 'user_id',
allowNull: true
}
},{
underscored: true,
freezeTableName: true,
});
return Member;
}
Outputted SQL:
SELECT `user`.*, `teams`.`team_id` AS `teams.teamId`, `teams`.`team_name` AS `teams.teamName`, `teams.member`.`member_id` AS `teams.member.memberId`, `teams.member`.`member_email` AS `teams.member.memberEmail`, `teams.member`.`organization_id` AS `teams.member.organizationId`, `teams.member`.`team_id` AS `teams.member.teamId`, `teams.member`.`user_id` AS `teams.member.userId`, `teams.member`.`created_at` AS `teams.member.created_at`, `teams.member`.`updated_at` AS `teams.member.updated_at`, `teams.member`.`organization_id` AS `teams.member.organization_id`, `teams.member`.`team_id` AS `teams.member.team_id`, `teams.member`.`user_id` AS `teams.member.user_id` FROM (SELECT `user`.`user_id` AS `userId`, `user`.`email`, `user`.`first_name` AS `firstName`, `user`.`last_name` AS `lastName` FROM `user` AS `user` WHERE `user`.`organization_id` = 1 LIMIT 1) AS `user` LEFT OUTER JOIN (`member` AS `teams.member` INNER JOIN `team` AS `teams` ON `teams`.`team_id` = `teams.member`.`team_id`) ON `user`.`userId` = `teams.member`.`user_id`;
Consider your relations, User has many Teams trough table Member and your query returns user with many teams(array of team objects) as expected. You should use user.teams[0].teamName to get specific team by key, or loop objects in this array

Resources