Trying to connect two database tables in Sequelize with a joiner table - javascript

I am trying to get a query of all merchants associated with a user.
There are three tables:
user_tbl, user_favorite_tbl, and merchant_tbl
For each, I have created a Sequelize instance.
User, UserFavorite, and Merchant.
I am trying to do a query that will get me, per user, every merchant favorited via the user_favorite_tbl.
Here is my code:
public async fetch_user_with_favorite() {
let favMerchants = await User.findAll({
attributes: ["given_name", "family_name", "email"],
include: [
{
model: UserFavorite,
attributes: ["merchant_id"],
include: [
{
model: Merchant,
attributes: ["name"]
}
]
}
]
});
return favMerchants;
}
And
User.belongsToMany(Merchant, { through: "user_favorite_tbl" });
Merchant.belongsToMany(User, { through: "user_favorite_tbl" });
However when I try to call the end point associated with this code, I get the following error:
SequelizeEagerLoadingError: user_favorite_tbl is not associated to user_tbl!
When I try to add this:
User.hasMany(UserFavorite, { foreignKey: "user_id" });
Merchant.hasMany(UserFavorite, { foreignKey: "merchant_id" });
I get this error:
SequelizeEagerLoadingError: merchant_tbl is not associated to user_favorite_tbl!
Where exactly am I going wrong?

You can define associations as below -
Many to many relationships can be achieved using two one to many relationships as below-
User.hasMany(UserFavorite, { foreignKey: 'user_id', targetKey: 'id'});
UserFavorite.hasMany(User,{ foreignKey: 'user_id',targetKey: 'id'});
Merchant.hasMany(UserFavorite, { foreignKey: 'merchant_id', targetKey: 'id'});
UserFavorite.hasMany(Merchant,{ foreignKey: 'merchant_id',targetKey: 'id'});

Your belongsToMany association looks correct, however, when you have belongsToMany, your findAll should not have the middle table in include. through is taking care of the middle table association internally.
let favMerchants = await User.findAll({
attributes: ["given_name", "family_name", "email"],
include: [
{
model: Merchant,
attributes: ["name"],
through: { attributes: [] } // if you do not need any metadata in user_favorite_tbl.
}
]
});
On a side note
User.belongsToMany(Merchant, { through: "user_favorite_tbl" });
This maybe better to use
User.belongsToMany(Merchant, { through: UserFavorite });

Related

Sequelize: Using include in a model that represents an association table between two other models

My models are defined as follows:
/** operation_sav Has Many composant
*/
model.operation_sav.belongsToMany(model.composant, {
through: "operation_sav_composant_reserves",
});
/** composant Has Many operation_sav*/
model.composant.belongsToMany(model.operation_sav, {
through: "operation_sav_composant_reserves",
});
This is the definition of the association model operation_sav_composant_reserves:
module.exports = function (sequelize, DataTypes) {
var operation_sav_composant_reserves = sequelize.define(
"operation_sav_composant_reserves",
{
id: {
type: DataTypes.INTEGER,
primaryKey: true,
allowNull: false,
autoIncrement: true,
unique: true,
},
operationSavId: DataTypes.INTEGER, //refers to model 1
composantId: DataTypes.INTEGER, // refers to model 2
qte: DataTypes.INTEGER,
// delivres: DataTypes.BOOLEAN,
},
{ timestamps: true }
);
return operation_sav_composant_reserves;
};
What I am trying to do is get the data inside operation_sav_composant_reserves table, and include with it the data of table composant.
So this is what I tried:
models.operation_sav_composant_reserves
.findAll({
where: {
operationSavId: opSavId,
},
// EagerLoadingError [SequelizeEagerLoadingError]: composant is not associated to operation_sav_composant_reserves!
include: [models.composant],
})
I got that error as you see in the comment:
// EagerLoadingError [SequelizeEagerLoadingError]: composant is not
associated to operation_sav_composant_reserves!
Any idea how to solve this?
I have found the solution in the Sequelize documentation:
In order to use include like I have described above, the join association operation_sav_composant_reserves needs to be defined like this:
// Setup a One-to-Many relationship between User and Grant
User.hasMany(Grant);
Grant.belongsTo(User);
// Also setup a One-to-Many relationship between Profile and Grant
Profile.hasMany(Grant);
Grant.belongsTo(Profile);
Instead of:
User.belongsToMany(Profile, { through: Grant });
Profile.belongsToMany(User, { through: Grant });
Grant here is the join table.
From a database perspective, these produce the exact same result: A join table Grant with foreign keys that refer to the User and Profile table.
The only difference is:
// With the Many-to-Many approach, you can do:
User.findAll({ include: Profile });
Profile.findAll({ include: User });
// However, you can't do:
User.findAll({ include: Grant });
Profile.findAll({ include: Grant });
Grant.findAll({ include: User });
Grant.findAll({ include: Profile });
// On the other hand, with the double One-to-Many approach, you can do:
User.findAll({ include: Grant });
Profile.findAll({ include: Grant });
Grant.findAll({ include: User });
Grant.findAll({ include: Profile });
// However, you can't do:
User.findAll({ include: Profile });
Profile.findAll({ include: User });
// Although you can emulate those with nested includes, as follows:
User.findAll({
include: {
model: Grant,
include: Profile
}
}); // This emulates the `User.findAll({ include: Profile })`, however
// the resulting object structure is a bit different. The original
// structure has the form `user.profiles[].grant`, while the emulated
// structure has the form `user.grants[].profiles[]`.
The best of both worlds: the Super Many-to-Many relationship
We can simply combine both approaches shown above!
// The Super Many-to-Many relationship
User.belongsToMany(Profile, { through: Grant });
Profile.belongsToMany(User, { through: Grant });
User.hasMany(Grant);
Grant.belongsTo(User);
Profile.hasMany(Grant);
Grant.belongsTo(Profile);
This way, we can do all kinds of eager loading:
// All these work:
User.findAll({ include: Profile });
Profile.findAll({ include: User });
User.findAll({ include: Grant });
Profile.findAll({ include: Grant });
Grant.findAll({ include: User });
Grant.findAll({ include: Profile });

Cant make Associations sequelize

Im trying to create association between two tables using models that are generated by sequelize-cli. But it is not making the relations in db.
Model User
User.hasMany(models.Albums, {
foreignKey: 'user_id',
as: 'userAlbums'
});
};
Model Album
Albums.associate = function(models) {
// associations can be defined here
Albums.belongsTo(models.User, {
foreignKey: 'user_id',
onDelete: 'cascade'
});
};

Sequelize.js: How to set foreignkey in two 1:N relationships between two models?

What I want to do is to make the relationships that User sell or buy Item. To do it with Sequelize and MySQL, I wrote code like this.
db.User.hasMany(db.Item);
db.Item.belongsTo(db.User, { as: "buyer" });
db.User.hasMany(db.Item);
db.Item.belongsTo(db.User, { as: "seller" });
However, this code makes an error. I think db.User.hasMany(db.Item) creates userId in Item, and the code make that happen twice. But the problem is, I don't want to add userId in Item, but buyerId and sellerId. How do I need to set options in db.User.hasMany(db.Item)?
Given that you have buyerId and sellerId columns in Item, this should do:
db.User.hasMany(db.Item);
db.Item.belongsTo(db.User, { as: "buyer", targetKey: "buyerId"});
db.Item.belongsTo(db.User, { as: "seller", targetKey: "sellerId" });
If not User.id is the primary key, you should also add foreignKey: [key name] to both options.
See reference manual
You can create two 1-M relationships between User and Item like this:
db.User.hasMany(db.Item, {
foreignKey: 'sellerId',
});
db.User.hasMany(db.Item, {
foreignKey: 'buyerId',
});
db.Item.belongsTo(db.User, {
foreignKey: 'sellerId',
constraints: false,
as: 'seller'
});
db.Item.belongsTo(db.User, {
foreignKey: 'buyerId',
constraints: false,
as: 'buyer'
});

Propagating cascading onDelete foreign key constraint using sequelize and MSSQL

I have a problem creating foreign key onDelete constraint using Sequelize on MSSQL db.
This is my use case: each company can have multiple users and those users can create multiple requests. Some requests can be created without a reference to the user, which is why I have both references to company and user in the request model. I want to achieve that once a company is deleted, all users from the company are deleted and all requests from the company are deleted.
These are my models defined in Sequelize:
Company model:
const CompanyModel = sequelize.define('company', {
... some props ...
});
CompanyModel.associate = function (models) {
CompanyModel.hasMany(models.user, {
as: 'users',
foreignKey: 'companyId',
onDelete: 'CASCADE',
onUpdate: 'CASCADE',
});
CompanyModel.hasMany(models.request, {
as: 'requests',
foreignKey: 'companyId',
onDelete: 'CASCADE',
onUpdate: 'CASCADE',
});
};
User model:
const UserModel = sequelize.define('user', {
... some props ...
});
UserModel.associate = function (models) {
UserModel.belongsTo(models.company, {
as: 'company',
foreignKey: 'companyId',
});
UserModel.hasMany(models.request, {
as: 'requests',
foreignKey: 'userId',
});
};
Request model:
const RequestModel = sequelize.define('request', {
... some props ...
});
RequestModel.associate = function (models) {
RequestModel.belongsTo(models.company, {
as: 'company',
foreignKey: 'companyId',
});
RequestModel.belongsTo(models.user, {
as: 'user',
foreignKey: 'userId',
});
};
However, when I try to sync my database, this is the error I'm getting:
RequestError: Could not create constraint or index. See previous errors
'IF OBJECT_ID(\'[request]\', \'U\') IS NULL CREATE TABLE [request] ([id] CHAR(36) , ... some props ..., [companyId] CHAR(36) NULL, [userId] CHAR(36) NULL, PRIMARY KEY ([id]), FOREIGN KEY ([companyId]) REFERENCES [company] ([id]) ON DELETE CASCADE, FOREIGN KEY ([userId]) REFERENCES [user] ([id]) ON DELETE SET NULL);'
If I comment the relationship to the user inside of the request model then syncing of the databases passes. The same thing happens if I comment onDelete foreign key constraint inside of the company model for the user. The foreign key constraint for request doesn't matter. I guess, the problem is that deleting company will delete user which might be the creator of the request. The logic is that those requests will be deleted as well, but the database cannot know if only users from the same company (which will be deleted) will be the owners of requests deleted as well for the company.
I've also tried setting allowNull to foreign key for the user in request model, but it didn't work.
Any suggestions on what should I do? I need to achieve that deleting a company deletes its users and requests.

Querying association tables in Sequelize

I have two tables (users and games) joined by an association table (game_players), creating a many-to-many relationship:
models.Game.belongsToMany(models.User, { through: models.GamePlayer, as: 'players' });
models.User.belongsToMany(models.Game, { through: models.GamePlayer, foreignKey: 'user_id' });
In addition to the foreign keys user_id and game_id, game_players has a few extra columns for link-specific data:
sequelize.define('game_player', {
isReady: {
defaultValue: false,
type: Sequelize.BOOLEAN,
field: 'is_ready'
},
isDisabled: {
defaultValue: false,
type: Sequelize.BOOLEAN,
field: 'is_disabled'
},
powerPreferences: {
type: Sequelize.TEXT,
field: 'power_preferences'
},
power: {
type: Sequelize.STRING(2),
defaultValue: '?'
}
}, {
underscored: true
});
Suppose I want to fetch a game and eagerly load active players. This was my first effort:
db.models.Game.findAll({
include: [{
model: db.models.User,
as: 'players',
where: { 'game_player.isDisabled': false }
}]
}).nodeify(cb);
This generates the following SQL, which throws the error Column players.game_player.isDisabled does not exist:
SELECT "game"."id",
"game"."name",
"game"."description",
"game"."variant",
"game"."status",
"game"."move_clock" AS "moveClock",
"game"."retreat_clock" AS "retreatClock",
"game"."adjust_clock" AS "adjustClock",
"game"."max_players" AS "maxPlayers",
"game"."created_at",
"game"."updated_at",
"game"."gm_id",
"game"."current_phase_id",
"players"."id" AS "players.id",
"players"."email" AS "players.email",
"players"."temp_email" AS "players.tempEmail",
"players"."password" AS "players.password",
"players"."password_salt" AS "players.passwordSalt",
"players"."action_count" AS "players.actionCount",
"players"."failed_action_count" AS "players.failedActionCount",
"players"."created_at" AS "players.created_at",
"players"."updated_at" AS "players.updated_at",
"players.game_player"."is_ready" AS
"players.game_player.isReady",
"players.game_player"."is_disabled" AS
"players.game_player.isDisabled",
"players.game_player"."power_preferences" AS
"players.game_player.powerPreferences",
"players.game_player"."power" AS "players.game_player.power",
"players.game_player"."created_at" AS
"players.game_player.created_at",
"players.game_player"."updated_at" AS
"players.game_player.updated_at",
"players.game_player"."game_id" AS
"players.game_player.game_id",
"players.game_player"."user_id" AS
"players.game_player.user_id"
FROM "games" AS "game"
INNER JOIN ("game_players" AS "players.game_player"
INNER JOIN "users" AS "players"
ON "players"."id" = "players.game_player"."user_id")
ON "game"."id" = "players.game_player"."game_id"
AND "players"."game_player.isdisabled" = false;
Clearly Sequelize is wrapping my constraint alias with incorrect quotes: 'players'.'game_player.isdisabled' should be 'players.game_player'.isdisabled. How can I revise my Sequelize code above to correctly query this column?
I got it, but only through manually browsing the repository's closed tickets and coming upon #4880.
Clauses using joined table columns that don't work out of the box can be wrapped in $. I honestly don't understand its magic, because I swear I don't see any documentation for it. Modifying my query above achieved what I wanted:
db.models.Game.findAll({
include: [{
model: db.models.User,
as: 'players',
where: { '$players.game_player.is_disabled$': false }
}]
}).nodeify(cb);
After searching around, I found that through.where can also be used:
db.models.Game.findAll({
include: [{
model: db.models.User,
as: 'players',
through: { where: { isDisabled: false } }
}]
})
Reference:
Is it possible to filter a query by the attributes in the association table with sequelize?
Eager loading with Many-to-Many relationships
Your query should be on the join table with the 'where' condition, and then you should use the 'include' clause to include the two other models, like this:
db.models.GamePlayer.findAll({
where: {isDisabled: false},
attributes: [],
include: [models.User, models.Game]
}).then(function(result){
....
});

Categories

Resources