filter Sequelize belongsToMany - node.js

I have the following problem:
I defined my tables (product and collection) like this:
module.exports = (sequelize, type) => {
return sequelize.define('product', {
id: {
type: type.UUID,
primaryKey: true,
defaultValue: type.UUIDV4,
},
title: {
type: type.STRING,
allowNull: false,
},
description: {
type: type.TEXT,
allowNull: false,
},
width: {
type: type.FLOAT,
allowNull: false,
},
height: {
type: type.FLOAT,
allowNull: false,
},
weight: {
type: type.FLOAT,
allowNull: false,
},
length: {
type: type.FLOAT,
allowNull: false,
},
vendor: {
type: type.STRING,
allowNull: false,
},
status: {
type: type.ENUM,
values: ['inactive', 'active'],
defaultValue: 'active',
allowNull: false,
},
})
}
module.exports = (sequelize, type) => {
return sequelize.define('collection', {
id: {
type: type.UUID,
primaryKey: true,
defaultValue: type.UUIDV4,
},
name: {
type: type.STRING,
allowNull: false,
},
image: {
type: type.STRING,
allowNull: true,
},
createdAt: {
type: type.DATE,
allowNull: false,
},
updatedAt: {
type: type.DATE,
allowNull: false,
},
status: {
type: type.ENUM,
values: ['inactive', 'active'],
defaultValue: 'active',
allowNull: false,
},
})
}
Then, I need associated the tables (product and collection) with belongsToMany association and i did it like this:
const ProductModel = require('../api/product/model')
const CategoryModel = require('../api/category/model')
const Product = ProductModel(sequelize, Sequelize)
const Collection = CollectionModel(sequelize, Sequelize)
Product.belongsToMany(Collection, {
through: ProductCollection,
foreignKey: 'productId',
otherKey: 'collectionId',
unique: false,
})
Collection.belongsToMany(Product, {
through: ProductCollection,
foreignKey: 'collectionId',
otherKey: 'productId',
unique: false,
})
Now, i want to get all the products of a collection given by the id sent from the body of the request, i have little time working with sequelize and i donĀ“t know how to do this kind of query.
Can you help me with that?

you can use something like this
let products = Collection.findAll({
where: {
id: collection.id,
},
attributes: ['id', 'name'],
include: [{
model: Product,
through: {
model: ProductCollection,
},
as: 'products',
attributes: ['id', 'title', 'description']
}],
});
return products
hope it helps

Related

NodeJS - Sequelize how to declare association for eager loading only once to be used for queries later

The problem:
Whenever I fetch a user, I always have to declare/include the association on the query to get its role:
const user = await DB.PORTALDB.models.AccessUser.findOne({
where: { email },
include: [ // EVERY QUERY, I HAVE TO INCLUDE THIS
{
model: DB.PORTALDB.models.AccessUserRoleLup,
as: 'role'
}
]
});
Now there are instance where I forget to include this association so I get a undefined role.
My question is, is there a way where I only set this association once so that I don't have to include this later on my queries?
This the model for my AccessUser table
const AccessUser = <AccessUserStatic>sequelize.define<AccessUserInstance>(
'AccessUser',
{
user_id: {
autoIncrement: true,
type: DataTypes.INTEGER.UNSIGNED,
allowNull: false,
primaryKey: true
},
email: {
type: DataTypes.STRING(255),
allowNull: false
},
firstname: {
type: DataTypes.STRING(255),
allowNull: false
},
lastname: {
type: DataTypes.STRING(255),
allowNull: false
},
password: {
type: DataTypes.STRING(255),
allowNull: false
},
disable: {
type: DataTypes.TINYINT,
allowNull: false,
defaultValue: 0
},
role_id: {
type: DataTypes.SMALLINT,
allowNull: false,
defaultValue: 0
},
created_modified: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: Sequelize.literal('CURRENT_TIMESTAMP')
}
}, {
tableName: 'access_user',
timestamps: false,
indexes: [
{
name: "PRIMARY",
unique: true,
using: "BTREE",
fields: [
{ name: "user_id" },
]
},
]
});
AccessUserRoleLup table
const AccessUserRoleLup = <AccessUserRoleLupStatic>sequelize.define<AccessUserRoleLupInstance>(
'AccessUserRoleLup',
{
role_id: {
autoIncrement: true,
type: DataTypes.INTEGER.UNSIGNED,
allowNull: false,
primaryKey: true
},
role_name: {
type: DataTypes.STRING(50),
allowNull: false
},
role_code: {
type: DataTypes.CHAR(50),
allowNull: false,
defaultValue: ""
}
}, {
tableName: 'access_user_role_lup',
timestamps: false,
indexes: [
{
name: "PRIMARY",
unique: true,
using: "BTREE",
fields: [
{ name: "role_id" },
]
},
]
});
Association:
db.models.AccessUser.hasOne(db.models.AccessUserRoleLup, {
foreignKey: 'role_id',
as: 'role'
});
Use defaultScope for AccessUser. defaultScope is defined in a model definition and it is always applied (unless you removed inline).
const AccessUser = <AccessUserStatic>sequelize.define<AccessUserInstance>(
'AccessUser',
{
user_id: {
autoIncrement: true,
type: DataTypes.INTEGER.UNSIGNED,
allowNull: false,
primaryKey: true
},
...
}, {
tableName: 'access_user',
timestamps: false,
defaultScope: { // Add this
include: [{
model: AccessUserRoleLup,
as: 'role'
}]
},
...
});
With this model definition, all queries will include AccessUserRoleLup.
If you would like to remove for a certain query, use .unscoped().
// These will automatically add eager loading for role
DB.PORTALDB.models.AccessUser.findAll()
DB.PORTALDB.models.AccessUser.findOne()
// These won't fetch role
DB.PORTALDB.models.AccessUser.unscoped().findAll()
DB.PORTALDB.models.AccessUser.unscoped().findOne()
More detail about scope: https://sequelize.org/master/manual/scopes.html
My initial workaround was to create a utility function for querying the user like so:
export const getAccessUser = (where: WhereOptions, include?: IncludeOptions) => {
return new Promise(async (resolve, reject) => {
try {
const user = await DB.PORTALDB.models.AccessUser.findOne({
where: where,
include: [
{
model: DB.PORTALDB.models.AccessUserRoleLup,
as: 'role'
},
...[include]
]
});
resolve(user);
} catch (err) {
reject(err);
}
});
}
I wonder if my question above can be done in much simpler way.

Sequelize addConstraint syntax

Im trying to add two tables, Playlist and Label.
Label optionally has a playlist, many playlists can relate to the same label.
In order to add the foreign key constraint to playlist im trying to add it using addConstraint.
The docs are terrible on this .
This is what i have:
"use strict";
module.exports = {
up: async (queryInterface, Sequelize) => {
await queryInterface.createTable("Playlist", {
id: { type: Sequelize.STRING, primaryKey: true, allowNull: false },
track_uri: Sequelize.STRING,
track: Sequelize.STRING(150),
artist: Sequelize.STRING(150),
added_at: {
type: Sequelize.DATE,
defaultValue: new Date(),
allowNull: false,
},
created_at: {
type: Sequelize.DATE,
defaultValue: new Date(),
allowNull: false,
},
is_album_track: Sequelize.BOOLEAN,
label: { type: Sequelize.STRING, unique: true },
});
await queryInterface.createTable("Label", {
id: { type: Sequelize.STRING, primaryKey: true },
playlist_link: {
type: Sequelize.STRING(150),
unique: true,
allowNull: false,
},
password: { type: Sequelize.STRING, allowNull: false },
email: { type: Sequelize.STRING, allowNull: false },
playlist: {
type: Sequelize.STRING,
foreignKey: true,
references: {
model: "Playlist",
label: "id",
},
},
});
await queryInterface.addConstraint("Playlist", {
type: "foreign key",
fields: ["label"],
name: "label_fkey",
references: {
table: "Label",
field: "id",
},
onDelete: "cascade",
onUpdate: "cascade",
});
},
down: async (queryInterface, Sequelize) => {
await queryInterface.dropTable("Label");
await queryInterface.dropTable("Playlist");
},
};
Im getting this error:
ERROR: column "label" referenced in foreign key constraint does not exist

Sequelize hasMany assocaition

I am considering these 2 tables "exam_response" and "answer" for hasMany association.
Where both the tables contains "question_id". Using question_id I need the answers.
exam_response table
module.exports = (sequelize, DataTypes) => {
const exam_response = sequelize.define('exam_response', {
id: {
allowNull: false,
primaryKey: true,
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4
},
session_id: {
type: DataTypes.UUID,
allowNull: false
},
exam_id: {
type: DataTypes.UUID,
allowNull: false
},
user_id: {
type: DataTypes.UUID,
allowNull: false
},
question_id: {
type: DataTypes.UUID,
allowNull: false
},
answer_ids: {
type: DataTypes.ARRAY(DataTypes.UUID),
allowNull: false
},
is_correct: {
type: DataTypes.BOOLEAN,
allowNull: false
},
is_bookmarked: {
type: DataTypes.BOOLEAN,
allowNull: false
},
is_attempted: {
type: DataTypes.BOOLEAN,
allowNull: false
},
createdAt: {
type: DataTypes.DATE,
field: 'created_at'
},
updatedAt: {
type: DataTypes.DATE,
field: 'updated_at'
}
}, {});
exam_response.associate = function (models) {
// associations can be defined here
exam_response.hasMany(models.answer, {
foreignKey: 'question_id', sourceKey: 'question_id',as:'exam_answers'
});
};
answer table
'use strict';
module.exports = (sequelize, DataTypes) => {
const answer = sequelize.define('answer', {
//{
// "id":"",
// "question_id":"123",
// "position":0,
// "answer":"This is answer 1."
// }
id: {
allowNull: false,
primaryKey: true,
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4
},
question_id: {
allowNull: false,
type: DataTypes.UUID
},
position: {
allowNull: false,
type: DataTypes.INTEGER
},
answer: {
allowNull: false,
type: DataTypes.TEXT
},
publish_status: {
allowNull: false,
type: DataTypes.ENUM('published', 'unpublished', 'deleted')
},
language: {
type: DataTypes.ENUM('en', 'kn', 'hi')
},
createdAt: {
type: DataTypes.DATE,
field: 'created_at'
},
updatedAt: {
type: DataTypes.DATE,
field: 'updated_at'
}
}, {});
answer.associate = models => {
answer.belongsTo(models.question,{foreignKey:'question_id',as:'answers'});
answer.belongsTo(models.exam_response,{foreignKey:'question_id', targetKey: 'question_id',as:'exam_answers'});
};
return answer;
};
Query::
ExamResponse.findAll({
where: {
exam_id
},
include: [
{
model: Answer,as:'exam_answers'
}
],
}).then(resp => {
response.successGet(res, resp, 'Exam Response');
}).catch(next)
I am getting the output but associated part("exam_answers") is empty.
If I use raw query, i am able to get the output. But the Query is only fetching me the exam_response not the answer even though the value exists.

Sequelize association generate fields that do no exists

I'm converting a PHP API to a GraphQL api. I'm using Sequelize as ORM package. I have two tables that I want to couple via a hasOne connection.
This is my AdvisoryService model:
module.exports = function(sequelize, DataTypes) {
const AdvisoryService = sequelize.define(
'advisoryService',
{
id: {
type: DataTypes.INTEGER(11),
allowNull: false,
primaryKey: true,
autoIncrement: true,
field: 'id',
},
country: {
type: DataTypes.INTEGER(11),
allowNull: true,
references: {
model: 'system_country',
key: 'id',
},
field: 'country',
},
//REDACTED
},
{
tableName: 'advisory_service',
timestamps: false,
},
)
AdvisoryService.associate = models => {
AdvisoryService.hasOne(models.systemCountry, {
as: 'Country',
foreignKey: 'country',
})
}
return AdvisoryService
}
And my systemCountry model:
/* jshint indent: 2 */
module.exports = function(sequelize, DataTypes) {
return sequelize.define(
'systemCountry',
{
id: {
type: DataTypes.INTEGER(11),
allowNull: false,
primaryKey: true,
autoIncrement: true,
field: 'id',
},
oldId: {
type: DataTypes.INTEGER(11),
allowNull: true,
unique: true,
field: 'old_id',
},
subcontinentId: {
type: DataTypes.INTEGER(11),
allowNull: true,
references: {
model: 'system_subcontinent',
key: 'id',
},
field: 'subcontinent_id',
},
code: {
type: DataTypes.STRING(2),
allowNull: false,
field: 'code',
},
longCode: {
type: DataTypes.STRING(3),
allowNull: false,
field: 'long_code',
},
currency: {
type: DataTypes.STRING(255),
allowNull: true,
field: 'currency',
},
currencyCode: {
type: DataTypes.STRING(255),
allowNull: true,
field: 'currency_code',
},
isEu: {
type: DataTypes.INTEGER(1),
allowNull: false,
field: 'is_eu',
},
isAsean: {
type: DataTypes.INTEGER(1),
allowNull: false,
field: 'is_asean',
},
isFragileWb: {
type: DataTypes.INTEGER(1),
allowNull: false,
field: 'is_fragile_wb',
},
isFragilePf: {
type: DataTypes.INTEGER(1),
allowNull: false,
field: 'is_fragile_pf',
},
isFragileOecd: {
type: DataTypes.INTEGER(1),
allowNull: false,
field: 'is_fragile_oecd',
},
title: {
type: DataTypes.STRING(255),
allowNull: false,
field: 'title',
},
latitude: {
type: 'DOUBLE',
allowNull: true,
field: 'latitude',
},
longitude: {
type: 'DOUBLE',
allowNull: true,
field: 'longitude',
},
},
{
tableName: 'system_country',
timestamps: false,
},
)
}
It generates the following query:
Executing (default): SELECT `id`, `old_id` AS `oldId`, `subcontinent_id` AS `subcontinentId`, `code`, `long_code` AS `longCode`, `currency`, `currency_code` AS `currencyCode`, `is_eu` AS `isEu`, `is_asean` AS `isAsean`, `is_fragile_wb` AS `isFragileWb`, `is_fragile_pf` AS `isFragilePf`, `is_fragile_oecd` AS `isFragileOecd`, `title`, `latitude`, `longitude`, `country` FROM `system_country` AS `systemCountry` WHERE `systemCountry`.`country` = 1 LIMIT 1;
And throws the following error: SequelizeDatabaseError: Unknown column 'country' in 'field list'. Which I get, because there is no county field in the system_country table. I don't know why the association generates the country field. Can someone point out what I'm doing wrong?
You relation -
AdvisoryService.associate = models => {
AdvisoryService.hasOne(models.systemCountry, {
as: 'Country',
foreignKey: 'country',
})
}
Is defining the relationship with key - country hence its finding country in systemCountry table
Use the Following object in your relation definition -
{as: "Country", foreignKey: "OtherTableColumn", sourceKey: "SameTableColumn"}

sequelize - inner join gives an error after added a foreign key

i have these 2 models:
module.exports = function(sequelize, DataTypes) {
return sequelize.define('services_prices', {
id: {
type: DataTypes.INTEGER(11),
allowNull: false,
primaryKey: true
},
service_id: {
type: DataTypes.INTEGER(11),
allowNull: true,
references: {
model: 'services',
key: 'id'
}
},
created_at: {
type: DataTypes.DATE,
allowNull: false
},
limit: {
type: DataTypes.INTEGER(11),
allowNull: true
},
price: {
type: DataTypes.INTEGER(11),
allowNull: true
}
});
};
which is parent of this model: (services_user_prices can override services_prices )
module.exports = function(sequelize, DataTypes) {
return sequelize.define('services_user_prices', {
id: {
type: DataTypes.INTEGER(11),
allowNull: false,
primaryKey: true,
autoIncrement: true
},
user_id: {
type: DataTypes.INTEGER(11),
allowNull: true
},
created_at: {
type: DataTypes.DATE,
allowNull: false
},
currency: {
type: DataTypes.STRING(255),
allowNull: true
},
is_active: {
type: DataTypes.INTEGER(1),
allowNull: true,
defaultValue: '0'
},
is_trial: {
type: DataTypes.INTEGER(1),
allowNull: true,
defaultValue: '0'
},
start_date: {
type: DataTypes.DATE,
allowNull: false
},
end_date: {
type: DataTypes.DATE,
allowNull: true
},
price: {
type: DataTypes.INTEGER(11),
allowNull: true
},
bundle_price_id: {
type: DataTypes.INTEGER(11),
allowNull: true,
references: {
model: 'services_prices',
key: 'id'
}
}
});
};
when trying to join them i get an error:
EagerLoadingError: services_prices is not associated to services_user_prices!
const result= await db.services_user_prices.findOne({
where: { is_active: 1, user_id: 123 }, include:[{db.services_prices}]
});
in the db services_user_prices has foreign key to services_prices table
what am i doing wrong?
Well if you are using sequelize then you need to update your model because
by default, sequelize will be looking for foreign key starts with model name like
you have defined bundle_price_id as a foreign key for services_prices.
You need to change your column name to services_price_id then it will get fixed.
or if you want to use bundle_price_id you need to define it in your model relation as.
Model.belongsTo(models.ModelName, { foreignKey: 'your_key'} )
Please feel free if you need to ask anything else.
As complement of the above answer you need to add an identifier with as: on the association like this:
Model.belongsTo(models.ModelName, { foreignKey: 'your_key', as:'your_identifier' } )
Then when you do the include on the method you also call the identifier:
await db.services_user_prices.findOne({
where: { is_active: 1, user_id: 123 },
include:[{
model: db.services_prices
as: 'your_identifier'
}]
});
If you don't define the foreignKey field, the as field will set the column name.

Resources