Unable to properly access additional fields on junction table - node.js

Apologies if this is obvious as I don't have much experience with sequelize yet. I am trying to get a field that is in the junction table CustomerContact. Currently I am getting a contact and then retrieving its attached customers
const contactList: Contact[] = await db()
.Contact.newActiveScope()
.withCustomers()
.findAll();
const mainCustomer: Customer = contactList[i].getMainCustomer();
const mainCustomerContacts = mainCustomer && mainCustomer?.customerContacts;
const thisCustomerContactRole = mainCustomerContacts?.find((c) => c.contactId === contact.id)?.role;
Outputting the mainCustomer gets me the following information
{
id: 'a94fd13a-1fdd-11ec-a12c-121c563gb2f5',
accountName: 'Test Tests',
CustomerContact: { role: 1, relationship: 2, status: 'active' }
}
I want to use the properties in CustomerContact, but trying to access it gives me a typescript error, saying CustomerContact does not exist on type ContactWithCustomerContactStatic, which has the following shape.
export type ContactWithCustomerContactStatic = Partial<Customer> &
Pick<CustomerContact, 'role' | 'relationship'>;
I associate the Customer in the Contact Model
this.belongsToMany(Customer, {
as: 'customers',
foreignKey: 'contactId',
otherKey: 'customerId',
through: CustomerContact as CustomerContactStatic,
});
this.addScope('withCustomers', {
include: [
{
as: 'customers',
model: Customer as CustomerStatic,
where: { isArchived: false },
required: false,
},
],
});
and vice versa
Customer.belongsToMany(Contact, {
as: 'contacts',
through: CustomerContact,
foreignKey: 'customerId',
otherKey: 'contactId',
});
Customer.addScope('contacts', () => ({
include: [
{
as: 'contacts',
attributes: ['id', 'firstName', 'lastName'],
model: (Contact as ContactStatic).newScope(),
required: false,
through: {
where: { status: CustomerContactStatus.active },
},
},
],
}));
I can technically retrieve the values, but the typescript error gives me pause.
I'm positive I'm missing something but having trouble determining what that something is. Any help would be appreciated!

I believe Typescript is showing this error because of the way you are declaring the ContactWithCustomerContactStatic type.
export type ContactWithCustomerContactStatic = Partial<Customer> &
Pick<CustomerContact, 'role' | 'relationship'>;
When declaring this way you say to Typescript that ContactWithCustomerContactStatic type has the following shape
interface ContactWithCustomerContactStatic {
// all properties from Customer interface comes here
role // it's in the root
relationship // it's also in the root
}
But, they are not in the root of the object, given the output that you presented
{
id: 'a94fd13a-1fdd-11ec-a12c-121c563gb2f5',
accountName: 'Test Tests',
CustomerContact: { role: 1, relationship: 2, status: 'active' }
}
This should work
type ContactWithCustomerContactStatic = Partial<Customer>
& { customerContact: Pick<CustomerContact, 'role' | 'relationship'>
}

Related

Sequelize: Includes not working on defaultScope when querying an object through belongsToMany association

I am trying to move an include object to the default scope of my model CommodityContract. However, when I do this, sequelize throws me an error upon querying results:
'Include unexpected. Element has to be either a Model, an Association or an object.'
Here is my block of code, which works perfectly if I put it inside a custom scope.
defaultScope: {
include: [
{
model: sequelize.models.CommodityContractPayments,
required: false
},
{
model: sequelize.models.CommodityContractTreatmentCharges,
required: false
},
{
model: sequelize.models.CommodityContractRefiningCharges,
required: false
},
{
model: sequelize.models.CommodityContractPriceParticipation,
required: false
},
]
}
The problem is that I am trying to query this specific model as a through object in a belongs-to-many association, and therefore I'm not sure how to apply a scope when querying, unless I do another separate query, which I was trying to avoid.
The main query looks like this:
const contract = await Contract.findOne(
{
where: { id },
include: [
sequelize.models.Commodity,
sequelize.models.ContractSubstance,
sequelize.models.Client,
sequelize.models.Invoice.scope('default_includes')
]
})
Where Commodity is linked to Contract through CommodityContract:
Commodity.belongsToMany(models.Contract, {
through: models.CommodityContract,
foreignKey: 'commodity_id',
otherKey: 'contract_id'
})
And the scope 'default_includes' is defined as such, within the model Invoice:
scopes: {
default_includes: () => {
return {
include: [
{
model: sequelize.models.InvoiceVersion,
required: false
},
{
model: sequelize.models.Contract,
required: false,
},
{
model: sequelize.models.User,
required: false,
},
]
}
}
}
I have had this problem quite a few times before and it annoys me greatly. Sometimes the includes work in a defaultScope, but sometimes it doesn't. Something tells me this has to do with the many-to-many association, but I am not sure.
Any help would be greatly appreciated!

Nested associated data through Sequelize join tables

Using Sequelize, I'm trying to get an output like this:
[{
"Id": 1,
"Name": "Game 1",
"Teams": [{
"Id": 1,
"Name": "Team 1",
"Users": [{
"Id": 1,
"UserName": "User 1"
}]
}]
}, {
"Id": 2,
"Name": "Game 2",
"Teams": [{
"Id": 1,
"Name": "Team 1",
"Users": [{
"Id": 2,
"UserName": "User 2"
}]
}]
}]
Note that Team 1 has 2 different users, but that's only because they're set up that way per game... so a user isn't tied directly to a team, but rather through a team game constraint. Basically, my Game HasMany Teams, and my Game/Team HasMany Users... a many-to-many-to-many relationship. I was trying to follow this thread, but it seems like what they're doing there doesn't actually work, as I tried doing this:
// models/Game.js
module.exports = (sequelize, types) => {
const GameModel = sequelize.define('Game', {
Id: {
type: types.INTEGER,
primaryKey: true,
autoIncrement: true
},
Name: {
type: types.STRING,
allowNull: false
}
});
GameModel.associate = (models) => {
GameModel.belongsToMany(models.Team, {
as: 'Teams',
foreignKey: 'GameId',
through: models.GameTeam
});
};
return GameModel;
};
// models/Team.js
module.exports = (sequelize, types) => {
const TeamModel = sequelize.define('Team', {
Id: {
type: types.INTEGER,
primaryKey: true,
autoIncrement: true
},
Name: {
type: types.STRING,
allowNull: false
}
});
TeamModel.associate = (models) => {
TeamModel.belongsToMany(models.Game, {
as: 'Games',
foreignKey: 'TeamId',
through: models.GameTeam
});
};
return TeamModel;
};
// models/User.js
module.exports = (sequelize, types) => {
const UserModel = sequelize.define('User', {
Id: {
type: types.INTEGER,
primaryKey: true,
autoIncrement: true
},
UserName: {
type: types.STRING,
allowNull: false
}
});
return UserModel;
};
// models/GameTeam.js
module.exports = (sequelize, types) => {
const GameTeamModel = sequelize.define('GameTeam', {
Id: {
type: types.INTEGER,
primaryKey: true,
autoIncrement: true
}
});
GameTeamModel.associate = (models) => {
GameTeamModel.belongsToMany(models.User, {
as: 'Users',
through: 'GameTeamUser'
});
};
return GameTeamModel;
};
The above models create the tables just fine, with what appears to be the appropriate columns. I then do some inserts and try to use a findAll on the Game model like this:
GameModel.findAll({
include: [{
association: GameModel.associations.Teams,
include: [{
association: GameTeamModel.associations.Users,
through: {
attributes: []
}
}],
through: {
attributes: []
}
}]
});
The query starts to go wrong at the 2nd include with the association of the Users. Because I'm trying to nest the users inside of the teams, I figured the join would attempt to use the unique ID on the through table (GameTeams.Id), but instead, the query ends up using this:
LEFT OUTER JOIN `GameTeamUser` AS `Teams->Users->GameTeamUser` ON `Teams`.`Id` = `Teams->Users->GameTeamUser`.`GameTeamId`
I figured the ON would be GameTeams.Id = Teams->Users->GameTeamuser.GameTeamId, but I don't know why it's not, and how to adjust it... I've tried using a custom on in my include (per the docs), but it seems to be ignored completely. Anyone have any advice? Or possibly a better way of structuring this, so it works the way I want it to?
I think you are overcomplicating this thinking you have a many to many to many..and i can see that the fields for your model for GameTeam do not match up with the foreign keys you have declared in your other models...
What do your database tables look like?
Am i correct in saying, that a game has many teams, and a team has many users... however a user can only be on one team at a time, and a team is only in one game at a time? (i am assuming the game/team join and the team/user join are simply temporary records in the join tables disappearing after the game is over etc)

Sequelize how to make a join request?

I'm trying to make joined queries with Sequelize.
That's my db :
What I need is to select all of my relations and get this kind of result:
[
{
id: 1,
State: true,
FK_User: {
id: 2,
Name: "my name"
},
FK_Team: {
id: 3,
Name: "team name"
}
},
...
]
But today I've got this result:
[
{
id: 1,
State: true,
FK_User: 2,
FK_Team: 3
},
...
]
For each of my relations, I've go to do another request to get datas ...
So I putted a look in this Stack and in the doc.
Then I made this code :
let User = this.instance.define("User", {
Name: {
type: this.libraries.orm.STRING,
allowNull: false
}
});
let Team = this.instance.define("Team", {
Name: {
type: this.libraries.orm.STRING,
allowNull: false
}
});
let Relation = this.instance.define("Relation", {
State: {
type: this.libraries.orm.BOOLEAN,
allowNull: false,
defaultValue: 0
}
});
Relation.hasOne(User, {as: "FK_User", foreignKey: "id"});
Relation.hasOne(Team, {as: "FK_Team", foreignKey: "id"});
With this code, I haven't got any relation between tables... So I added theses two lines. I don't understand why I need to make a two direction relation, because I don't need to access Relation From User and Team ...
User.belongsTo(Relation, {foreignKey: 'FK_User_id'});
Team.belongsTo(Relation, {foreignKey: 'FK_Team_id'});
When I do that, I've a FK_User_id in the User table and a FK_Team_id in the Team table ... I don't know how to make this simple relation and get all I need with my futur request and the include: [User, Team]} line.
User.hasOne(Relation);
Team.hasOne(Relation);
Relation.belongsTo(Team);
Relation.belongsTo(User);
This code seems to work.
I don't know why ...
Here your associations are setup correctly you can join it with include :
Relation.findAll({
where : {
state : true
}
include:[
{
model : User
},
{
model : Team
}
]
})

Sequelize: Joining Many to Many tag table with another table in postgresql

I am trying to setup many to many relationship in Sequelize for my Postgres tables using a tag (lookup) table. I was successful in setting up the relationship.
Now my lookup table, has an additional field which is a foreign key to another table. I want the data for that table to be included in my result set. This is where I am having hard time figuring out the syntax.
My models:
Models.user = sequelize.define('user', { //attributes});
Models.school = sequelize.define('school', { //attributes });
Models.role = sequelize.define('role', { //attributes });
Models.userSchools = sequelize.define('user_school', {
user_school_id: { type: Sequelize.INTEGER, primaryKey: true }
});
Models.user.belongsToMany(Models.school, { through: Models.userSchools, foreignKey: 'user_id', otherKey: 'school_id' });
Models.school.belongsToMany(Models.user, { through: Models.userSchools, foreignKey: 'school_id', otherKey: 'user_id' });
Models.userSchools.belongsTo(Models.role, { foreignKey: 'role_id' });
Working query: This query gives me all the users and their schools.
Models.user.findAll({
where: {
//condition
},
include: [Models.school]
})
Now I want to include the role for each schools. None of the below queries are working.
Models.user.findAll({
where: {
//conditions
},
include: [Models.schools, Models.role]
})
Above query is throwing an error saying role is not associated to user, which is expected.
Models.user.findAll({
where: {
//conditions
},
include: [Models.schools, {model: Models.userSchools, include: Models.role}]
})
This query is also throwing an error:
"userSchools is not associated to user".
Can any one help with syntax to how I can get the role information from the userSchools model?
Edit:
I have add a "through" condition which gets me the role_id but not the related role data.
Models.user.findAll({
where: {
//conditions
},
include: [{
model: Models.school,
as: 'schools',
through: {
attributes: ['role_id']
}
}]
})

Sequelize and querying on complex relations

I'm trying to understand how best to perform a query over multiple entities using Sequelize and Node.js.
I have defined a model "User" which has a belongsToMany relation with a model "Location". I then have a model "Asset" which also has a belongsToMany relation with "Location". When I have an instance of a User I would like to fetch all Assets that are associated with Locations that the User is associated with.
I tried the following which doesn't seem to work...
user.getLocations().then(function(userLocations) { return Asset.findAll({ where: { "Locations" : { $any : userLocations } }) })
Could anyone offer any suggestions?
Try this query:
User.findById(user_id, {
include: [{
model: Location,
required: true
}]
}).then(user => Asset.findAll({
where: {
user_id: user.id,
location_id: {
$in: user.locations.map(location => location.id)
}
}
})).then(assets => {
// The rest of your logic here...
});
This was the final result...
User.findById(user_id, {
include: [{
model: Location,
as: 'Locations', // Was needed since the original relation was defined with 'as'
required: true
}]
}).then(user => Asset.findAll({
include: [{
model: Location,
as: 'Locations',
where: {
id: {
$in: user.Locations.map(location => location.id)
}
}
}]
})).then(assets => {
// The rest of your logic here...
});

Resources