Nested associated data through Sequelize join tables - node.js

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)

Related

Create one to many relationship object in database using Sequelize

I've searched a lot and didn't find any answer.
My english isn't that good so i might not used the best keywords...
Here is my problem, is there a way to insert in one query an object like this ?
I have a one to many relationship between two objects define like this :
foo can have many bar
bar can have one foo
export const foo = sequelize.define('foos', {
idfoo: {
type: Sequelize.INTEGER,
autoIncrement: true,
primaryKey: true
},
value: Sequelize.STRING,
valueInt: Sequelize.INTEGER
})
export const bar = sequelize.define('bars', {
idbar: {
type: Sequelize.INTEGER,
autoIncrement: true,
primaryKey: true
},
value: Sequelize.STRING,
valueInt: Sequelize.INTEGER
})
foo.hasMany(bar, {
foreignKey: 'idfoo'
});
bar.belongsTo(foo);
Imagine you got this object you want to insert in your db. How would you do it ?
{
"value": "test",
"valueInt": 5,
"bars": [
{
"value": "test",
"valueInt": 6
},
{
"value": "test",
"valueInt": 7
}
]
}
I have read Sequelize documentation i didn't find any clue of this issue (or maybe i've missed it ).
I wasn't be able to make this solution works Sequelize : One-to-Many relationship not working when inserting values
Actually here is my Sequelize create function, i thought Sequelize handled all the imports alone (pretty naive i guess)...
static async post(body) {
await foo.create(body);
}
First, you need to indicate the same foreignKey option for both paired associations:
foo.hasMany(bar, {
foreignKey: 'idfoo'
});
bar.belongsTo(foo, {
foreignKey: 'idfoo'
});
That way Sequelize can make sure these two models linked by the same fields from both ends.
Second, in order to insert both main and child records together you need to indicate the include option in the create call with bar model:
static async post(body) {
await foo.create(body, {
include: [bar]
});
}

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!

How to add a field from associated model using Sequelize?

I'm making a web API using Node, Express, and Sequelize. I have models Users and Teams (shown below). Users has a teamId that references Teams.id, and there is an association between the two to reflect that.
User definition
const User = sequelize.define('User', {
id: {
autoIncrement: true,
type: DataTypes.INTEGER,
allowNull: false,
primaryKey: true
},
displayName: {
type: DataTypes.STRING,
allowNull: false
},
teamId: {
type: DataTypes.INTEGER,
allowNull: false,
references: {
model: 'Team',
key: 'id'
}
}
}
Team definition
const Team = sequelize.define('Team', {
id: {
autoIncrement: true,
type: DataTypes.INTEGER,
allowNull: false,
primaryKey: true
},
name: {
type: DataTypes.STRING,
allowNull: false
}
}
Association
User.belongsTo(Team, { as: 'team', foreignKey: 'teamId' });
Query
Users.findAll({
include: [
{
model: Team,
as: 'team'
}
]
});
As expected, this returns a list of User objects, with the added "team" property the respective Team objects embedded like so:
[
{
"id": 1,
"displayName": "John Smith",
"teamId": 1,
"team": {
"id": 1,
"name": "My Awesome Team"
}
}
]
My goal is to return User objects, but instead of embedding the entire Team object under the team property, I'd like to add just the name of the team as the value of the property, like this:
[
{
"id": 1,
"displayName": "John Smith",
"teamId": 1,
"team": "My Awesome Team"
}
]
Is there a way to accomplish this with Sequelize?
Well, through typing up my question I thought of better ways to search for the answer, and I think I found one right off the bat...
I found this StackOverflow question where the asker was doing what I am trying to do.
The solution is to use attributes.include and Sequelize.col() to include the attribute I want, and in my include options, use attributes: [] to hide the Team objects.
Going off the examples I included in the original post, this is what works for me, giving me nearly the exact output I wanted:
Users.findAll({
attributes:{
include: [[Sequelize.col(team.name), 'teamName']]
}
include: [
{
model: Team,
as: 'team',
attributes: []
}
]
});
Output
[
{
"id": 1,
"displayName": "John Smith",
"teamId": 1,
"teamName": "My Awesome Team"
}
]
Since the association uses the alias "team", I had to name my property something different, and went with "teamName" which is perfectly fine and probably preferable since it is more descriptive.

Nodejs sequelize hasMany issue

I've breaking my head over this sequelize to get the products questions and also to include its answers as well
const ProductQuestions = sequelize.define('product_questions', {
user: {
type: Sequelize.BIGINT
},
product: {
type: Sequelize.BIGINT
},
question: {
type: Sequelize.TEXT
}
});
ProductQuestions.associate = function(models) {
ProductQuestion.hasMany(models.product_answers,{
foreignKey: 'question',
as: 'questionId'
});
}
const ProductAnswer = sequelize.define('product_answers', {
question: {
type: Sequelize.BIGINT,
field: 'questionId'
},
answer: {
type: Sequelize.TEXT
},
user: {
type: Sequelize.BIGINT,
field: 'userId'
}
});
ProductQuestiosn.findAll({include: ['product_answers']});
for some reason when I that, the columns is wrong when the query runs
SELECT
"product_questions".*,
"product_answers"."id" AS "product_answers.id",
"product_answers"."questionId" AS "product_answers.questionId",
"product_answers"."answer" AS "product_answers.answer",
"product_answers"."userId" AS "product_answers.user",
"product_answers"."productQuestionId" AS "product_answers.productQuestionId"
FROM (
SELECT
"product_questions"."id",
"product_questions"."productId" AS "product",
"product_questions"."question",
"product_questions"."userId" AS "user",
FROM
"product_questions" AS "product_questions")
AS "product_questions"
LEFT OUTER JOIN "product_answers" AS "product_answers"
ON "product_questions"."id" = "product_answers"."productQuestionId"
not sure why is
ON "product_questions"."id" = "product_answers"."productQuestionId"
when it should be
ON "product_questions"."id" = "product_answers"."questionId"
thank you for your help
so i figured it out!
so it appears that I have to name my columns properly
for the product_answers table in my postgres database, I had the column questionId but it should be named productQuestionId. I guess it's for naming convention, i can't just name the foreign key the way i want.

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

Resources