Sequelize create instance of object Many-to-many relationship - node.js

I'm using Sequelize to create an instance of Many-to-Many relationship, but it not working.
First, I have a model with two class as below:
Room:
Room.init({
id: {
type: DataTypes.UUID,
primaryKey: true,
defaultValue: DataTypes.UUIDV4,
},
name: {
type: DataTypes.STRING,
allowNull: false,
},
}, { sequelize, modelName: "Room" }
);
User:
User.init(
{
id: {
type: DataTypes.UUID,
primaryKey: true,
defaultValue: DataTypes.UUIDV4,
},
name: {
type: DataTypes.STRING,
allowNull: false,
},
},
{ sequelize, modelName: "User"}
);
UserRoom:
UserRoom.init("UserRoom", { sequelize });
Relationship:
User.belongsToMany(Room, { as:'container', through: UserRoom, foreignKey: "userId" });
Room.belongsToMany(User, { as:'joiners', through: UserRoom, foreignKey: "roomId" });
And the UserRoom has another key with Message Model:
UserRoom.belongsTo(Message, { as: "lastReadMessage" });
Ok, now when i create a new Room with code:
const room = await Room.create(
{
name: name,
avatarId: avatarUri,
joiners: [
{
id: '1',
},
{
id: '2',
},
],
},
{
include: [{ association: User, as: 'joiners' }],
}
);
I get an error:
TypeError: Cannot read properties of undefined (reading 'name')
I don't know why error, I try this example but it still wrong.
My question is: How I create object and link to another relationship?
UPDATE
Now, I can run it with below code:
const room = await Room.create({
name: name,
avatarId: avatarUri,
});
await UserRoom.bulkCreate(
[
{
lastReadMessageId: null,
userId: 1,
roomId: room.id,
},
{
lastReadMessageId: null,
userId: 2,
roomId: room.id,
},
]
);

You're trying to create users with id equals to 1 and 2 along with creating a room. If you wish to associate existing users with a new room then you need to call addJoiners of a new room model:
const room = await Room.create(
{
name: name,
avatarId: avatarUri,
);
// pass user instances or ids.
await room.addJoiners([1,2])

Related

Count in Many-to-Many relationship

I want to count the number of likes when I fetch the post details.
User Model
User.init(
{
id: {
allowNull: false,
primaryKey: true,
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
},
userName: {
type: DataTypes.STRING,
allowNull: false,
unique: true,
field: 'user_name',
},
}
);
static associate(models) {
// User has many posts --> One-to-Many Relationship
this.hasMany(models.Post, {
foreignKey: 'userId',
});
// Likes relationship
this.belongsToMany(models.Post, {
through: 'PostLikes',
foreignKey: 'userId',
as: 'likes',
});
}
And here is the Post Model
Post.init(
{
id: {
type: DataTypes.UUID,
primaryKey: true,
allowNull: false,
defaultValue: DataTypes.UUIDV4,
},
body: {
type: DataTypes.STRING,
allowNull: false,
},
}
);
static associate(models) {
// User-Post Relationship
this.belongsTo(models.User, {
foreignKey: 'userId',
});
// Likes Relationship
this.belongsToMany(models.User, {
through: 'PostLikes',
foreignKey: 'postId',
as: 'likes',
});
}
So, it now creates a joined table PostLikes and I am trying query that fetches the post along with the likes and number of likes on that post.
const postsIdCol = '"likes->PostLikes"."postId"';
const usersIdCol = '"likes->PostLikes"."userId"';
const postCol = '"Post"."id"';
const response = await Post.findOne({
where: {
id: postid,
},
// includeIgnoreAttributes : false,
attributes: [
'id', 'body', 'createdAt',
[sequelize.literal(`COUNT(${postsIdCol}) OVER (PARTITION BY ${postCol})`), 'likesCount'],
],
include: [
{
model: Comment, ----> Post is also associated with comments, ignore this
as: 'comments',
attributes: ['id', 'content', 'createdAt', 'updatedAt'],
},
{
model: User,
as: 'likes',
through: {
attributes: [],
},
attributes: ['id', 'userName'],
},
],
});
return response;
The Response I am getting on doing this query is like this :
{
"data": {
"id": "182d6377-5bf6-4b65-9e29-cb79acc85c0a",
"body": "hoping for the best",
"likesCount": "6", -----> this should be 3
"likes": [
{
"id": "1af4b9ea-7c58-486f-a37a-e46461487b06",
"userName": "sdfbsd",
},
{
"id": "484202b0-a6d9-416d-a8e2-6681deffa3d1",
"userName": "ndnadonfsu",
},
{
"id": "b3c70bee-e839-4449-b213-62813af031d1",
"userName": "difniandca",
}
]
}
}
You need another PARTITION BY column from the other association than the one that you want to count.
For example, if you want to count the likes, you need to partition by parent id (Post.id) and other association id (Comment.id).
If you want to count the comments, you need to partition by parent id (Post.id) and other association id ("likes->PostLikes"."UserId").
[Sequelize.literal(`COUNT(${postsIdCol}) OVER (PARTITION BY ${postCol}, "Comments"."id")`), 'likesCount']
Where it says "Comments", you need to add your comment table name.

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

Sequelize.js - choosing the right relationship

I'm using NodeJS and sequelize.
I have user table with a column named duel_id, and each user can be assigned to one duel at a time.
Each duel can have multiple users in it.
I have the following User Model:
const User = Model.define(
'User',
{
user_id: {
type: DataType.INTEGER,
primaryKey: true,
},
username: {
type: DataType.STRING(255),
},
character: {
type: DataType.INTEGER,
},
duel_id: {
type: DataType.INTEGER,
},
},
{
indexes: [{ fields: ['user_id', 'username'] }],
tableName: 'users',
timestamps: false,
},
);
User.hasOne(Duel, { as: 'duel', foreignKey: 'id', sourceKey: 'duel_id' });
with the following Duel model:
const Duel = Model.define(
'DuelRoom',
{
id: {
type: DataType.INTEGER,
primaryKey: true,
autoIncrement: true,
},
round_id: {
type: DataType.INTEGER,
},
status: {
type: DataType.STRING,
},
turn_of_user_id: {
type: DataType.INTEGER,
},
winner: {
type: DataType.STRING,
},
},
{
indexes: [{ fields: ['id'] }],
tableName: 'duel_rooms',
timestamps: true,
},
);
The above code works and return the user and the associated duel if he has one.
I want also to return all the users associate to the same duel.
I tried to connect the relationship with hasMany/ belongsTo with no success. The following errors appears:
Error: DuelRoom.hasMany called with something that's not a subclass of Sequelize.Model
I want to be able to query to get the data like this:
user: {
user_id,
username
duel: {
round_number
players: [{user_id, username}]
}
}
Get the current user with the duel info, with all players associated with the same duel_id as an array named players.
Any idea of how I can define such a relation using sequelize to return all users associated to the user duel?
If a User model has dual_id then you should use belongTo from User to DualRoom instead of hasOne:
User.belongsTo(Duel, { as: 'duel', foreignKey: 'duel_id' });
If you wish to have users collection in a Duel model then this will work with the following hasMany:
Duel.hasMany(User, { as: 'users', foreignKey: 'duel_id' });
Take into account that you should register all associations AFTER all model registrations like I advised in this answer
After all this setup you can get what you wish by executing a query like this:
const user = await User.findOne({
where: {
user_id: id
},
include: [{
model: Duel,
as: 'duel',
include: [{
model: User,
separate: true,
as: 'users'
}]
}]
})
As each user can have a duel and one duel can be associated with many users. It is a one-to-many association, so, you should try:
Duel.hasMany(User);
User.belongsTo(Duel);

Correct way of doing relationships in sequelize model

Recently we moved to Sequelize ORM from Knex due to which we have existing database to which models are map to . Everything working fine except model relationships.
So I have a users table and roles table. Each user can have one role but each role can be assigned to many users. To map these to models, I created model files that is as follows.
User Model:
const Sequelize = require('sequelize');
const sequelize = require('../utility/dbConnection');
const roles = require('./Roles');
module.exports = sequelize.define("User", {
id: {
type: Sequelize.INTEGER(11),
primaryKey: true,
autoIncrement: true,
allowNull: false
},
firstName: Sequelize.STRING(255),
middleName: Sequelize.STRING(255),
lastName: Sequelize.STRING(255),
email: {
type: Sequelize.STRING(255),
unique: true
},
phoneNumber: {
type: Sequelize.STRING(255),
unique: true
},
is_phonenumber_verified: {
type: Sequelize.BOOLEAN,
default: false
},
is_email_verified: {
type: Sequelize.BOOLEAN,
default: false
},
roleID: {
type: Sequelize.INTEGER
},
password: Sequelize.STRING(255)
// }, {
// defaultScope: {
// attributes: { exclude: ['password'] }
// }
}, {
tableName: 'users'
},{
classMethods: {
associate: function() {
this.hasOne(roles,{foreignKey: 'roleID'})
}
}
}
);
roles Model:
const Sequelize = require('sequelize');
const sequelize = require('../utility/dbConnection');
module.exports = sequelize.define("Role", {
id: {
type: Sequelize.INTEGER(11),
primaryKey: true,
autoIncrement: true,
allowNull: false
},
name: {
type: Sequelize.STRING(255),
unique: true
},
},{
tableName: 'roles'
});
Now in the controller file, I want to fetch user details and role name for which code snippet are as follows
const userModel = require('../../models/Users');
const rolesModel = require('../../models/Roles');
let user = await userModel.findOne({ where: { email: req.body.email } },{include:[{model:rolesModel}]});
The problem is it only fetches rolesID from the Users table but not roles name from roles table .
Can anyone help me on this what I am doing wrong here ?
You have To Write Your Include Condition Like This
const userModel = require('../../models/Users');
const rolesModel = require('../../models/Roles');
let user = await userModel.findOne({
where: {
email: req.body.email
},include:
[{ model: rolesModel }]
});
you can use belongsTo relation instead of using hasOne
add this relation in roles table and remove from users table
classMethods: {
associate: function() {
this.belongsTo(users,{foreignKey: 'roleID'})
}
}
You can see this Sequelize Associations docs to know more about Associations and relations

How to add array of ids in a query with many to many relationships Sequelize

I have 2 entities and one for n:m relationship:
const Item = db.define('item', {
id: {
type: Sequelize.BIGINT,
primaryKey: true,
autoIncrement: true,
},
title: Sequelize.STRING,
description: Sequelize.STRING,
price: {
type: Sequelize.FLOAT,
defaultValue: 0.0,
},
});
const Category = db.define('category', {
id: {
type: Sequelize.BIGINT,
primaryKey: true,
autoIncrement: true,
},
title: Sequelize.STRING,
});
const ItemCategory = db.define('item_category', {
id: {
type: Sequelize.BIGINT,
primaryKey: true,
autoIncrement: true,
},
category_id: {
type: Sequelize.BIGINT
},
item_id: {
type: Sequelize.BIGINT
}
});
And relations:
Category.belongsToMany(Item, {
through: {
model: ItemCategory,
unique: false
},
foreignKey: 'category_id',
constraints: false
});
Item.belongsToMany(Category, {
through: {
model: ItemCategory,
unique: false
},
foreignKey: 'item_id',
constraints: false
});
Association is working fine(I guess). But when I try to query Item, the result comes without categories field.
Also I can add include option and it returns category objects:
Item.findAll({ include: [{ model: Category }] })
The QUESTION IS: How to associate ONLY categories_ids when querying Item objects to have something like this in response:
{
id: 1,
categories_ids: [1,3,4],
price: 20
}
You can't actually do that because of how data of nested associations are arranged by default.
Suppose you did
Item.findOne({
where: { item_id: 1},
include: [{
model: ItemCategory,
as: 'categories_ids',
attributes: ['category_id']
}]
})
You will get
{
id: 1,
categories_ids: [
{ category_id: 1 },
{ category_id: 2 },
{...},
{...}
],
price: 20,
}
Of which you can probably re-arrange the information, which involves the process of something like this:
let pojo = JSON.parse(JSON.stringify(data))
pojo.categories_ids.forEach(function(el, index) {
pojo.categories_ids[index] = el.category_id
})
Try something like this
associate your through model directly to item as well so you can include in query
Item.hasMany(ItemCategory, {
foreignKey: 'item_id',
constraints: false
});
Item.findAll({
include: [{
model: ItemCategory,
as: 'categories_ids',
attributes: ['category_id']
}]
});

Resources