Sequelize.js - choosing the right relationship - node.js

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);

Related

Sequelize create instance of object Many-to-many relationship

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])

Sequelize Associate: User is not associated to Appointments

Basically I have a user model and appointment model. The two models are linked with a one-to-many relationship. The Appointment table has two columns that are associated with the user model. When ever I try to include the properties of the user table in appointment, I get the above error.
These are my model designs
Appointment Model
export default ({
sequelize
}:{
sequelize: Sequelize
}) => {
const Appointments: ModelDefined<AppointmentsAttribute, AppointmentsCreationAttributes> = sequelize.define('Appointments', {
appointmentId: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
},
client: {
type: DataTypes.STRING,
allowNull: false,
references: {
model: 'Users',
key: 'uid'
}
},
serviceProvider: {
type: DataTypes.STRING,
allowNull: false,
references: {
model: 'Users',
key: 'uid'
}
},
})
return Appointments
}
User Model
export default ({
sequelize
}: {
sequelize: Sequelize
}) => {
const User: ModelDefined<UserAttribute, UserCreationAttributes> = sequelize.define('User', {
uid: {
type: DataTypes.STRING,
allowNull: false,
unique:true,
primaryKey: true
},
firstname: {
type: DataTypes.STRING,
allowNull: false,
},
lastname: {
type: DataTypes.STRING,
allowNull: false,
},
}
})
return User
}
I have associated the user and appointment models with
Users.hasMany(Appointments);
The code that I am trying to use to fetch the appointment data and include the corresponding user value is
db.appointment.findAll({
where: {
client: this.uid
},
include: 'Users'
})
Sequelize always use associations for models whose association method was called. i.e. if you call Model1.hasMany(Model2) then you can execute queries like:
Model1.findAll({
include: [{
model: Model2
}]
}
and NOT vice versa like this:
Model2.findAll({
include: [{
model: Model1
}]
}
If you wish to request appointment with users as an associated model then you need to add a reversed association from Appointments to Users like this:
Users.hasMany(Appointments);
Appointments.belongsTo(Users);

How to create a query in sequelize

I have a Lessons table:
const Lesson = sequelize.define(
'lesson',
{
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
date: { type: DataTypes.DATEONLY },
title: { type: DataTypes.STRING },
status: { type: DataTypes.INTEGER }
},
{ underscored: true, timestamps: false }
);
Which has a belongsToMany relationship to the Teachers table
const Teacher = sequelize.define(
'teacher',
{
id: {
type: DataTypes.INTEGER,
primaryKey: true
},
name: { type: DataTypes.STRING }
},
{ underscored: true, timestamps: false }
);
I am trying to make a request to get a list of lessons:
const data = await Lesson.findAll({
include: [
{ model: Teacher, through: { attributes: [] } }
],
order: [['id', 'DESC']],
limit,
offset
});
How to make a limit on the number of teachers, that is, to accept only those lessons where the number of teachers belonging to this lesson, for example, is equal to 3.
Your query will form something like this
const data = await Lesson.findAll({
include: [
{
model: Teacher,
required: true
attributes: [[Sequelize.literal('(count(teacher.id))')], 'teacherCount']
}
],
order: [['id', 'DESC']],
having: {teacherCount: {[Op.gt]: 3}}
limit: 10,
offset: 0
});
This is still a raw code you might want to do some trial and error on your own, basically you want to do a join and get count of teachers from that join once you have it you just want to filter your query using that virtual column you just created.
P.S: Above code will maybe throw group by error for that you will need to add a group: ['column_name'] so that non-aggregated columns come along in aggreation query
Reference for group by:
https://www.postgresqltutorial.com/postgresql-group-by/
Lastly, I would suggest writing a raw query first and then finding ways to convert that into sequelize that way you know what is the exact issue that you are facing.

Sequelize: Query by custom fields on a many to many relationship table between the same model

I have a user model with a many to many relationship with itself defined as follows. It uses a friends table as the joining table with a couple of custom fields defined on it - approved and denied.
const User = db.define('user', {
username: {
type: DataTypes.STRING,
allowNull: false,
unique: true
}
})
const Friend = db.define('friend', {
approved: {
type: DataTypes.BOOLEAN,
defaultValue: false
},
denied: {
type: DataTypes.BOOLEAN,
defaultValue: false
}
})
User.belongsToMany(User, {through: Friend, as: 'friends'})
I am able to query the User table for a username and am able to get the friends list for a matched user just fine using the following:
const user = await User.findOne({
where: {
username
},
include: {
model: User,
as: 'friends'
}
})
However, I'm wondering how to query using the two additional fields defined on the friends' table - i.e. approved or denied. Alternatively, I have tried the following:
const user = await User.findOne({
where: {
username
},
include: {
model: User,
where: {
approved: true
},
as: 'friends'
}
})
But this results in the error - column friends.approved does not exist.

Sequelize: Querying where I use foreignKey instead of as

So I'm having some trouble getting the query results I'm looking for. In terms of models I have
models.UserRole = sequelize.define('userrole', {
name: {
type: Sequelize.STRING,
primaryKey: true
},
permissions: {
type: Sequelize.ARRAY(Sequelize.STRING),
defaultValue: []
}
});
models.User = sequelize.define('user', {
id: {
type: Sequelize.UUID,
defaultValue: Sequelize.UUIDV4,
primaryKey: true
},
...
});
models.User.belongsTo(models.UserRole, {foreignKey: 'role'});
And I used foreignKey instead of as because I wanted the field to be called role exactly and not what Sequelize was changing it to (roleName).
Anyway, I'm now trying to query and include the permissions along with the selected user, so I use
models.User.findById(id, {
attributes: ['id','role'],
include: [{
model: models.UserRole,
attributes: ['name', 'permissions']
}]
})
And it works, but it retrieves them on the field userrole and looks like this
{"id":"id-here","role":"admin","userrole":{"name":"admin", "permissions":["p1","p2",..]}}
So finally, my question is how do I make it so that the stuff it retrieves from the UserRole model is retrieved under the key "role" instead? So it looks like
{"id":"id-here","role":{"name":"admin", "permissions":["p1","p2",..]}}

Resources