SequelizeEagerLoadingError: product is not associated to collection - mysql

I use sequelize ORM in Mysql. I have 3 Models: Product, Collection, CollectionProduct
relationship between Product and Collection are many to many and for handle this in sequelize i used belongsToMany association. every thing is Ok but when i run this code to get a collection with its products with include Eager this error occure:
SequelizeEagerLoadingError: product is not associated to collection!
Product Model:
module.exports = (sequelize, Sequelize) => {
const Product = sequelize.define('product', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER,
},
name_en: {
type: Sequelize.STRING(255),
},
description_en: {
type: Sequelize.STRING(1024),
},
price: {
type: Sequelize.FLOAT,
allowNull: false,
},
type: {
type: Sequelize.STRING(255),
},
height: {
type: Sequelize.INTEGER,
},
width: {
type: Sequelize.INTEGER,
},
createdAt: {
type: Sequelize.DATE,
defaultValue: Sequelize.NOW,
allowNull: false,
},
updatedAt: {
type: Sequelize.DATE,
defaultValue: Sequelize.NOW,
allowNull: false,
},
}, {})
Product.associate = (models) => {
Product.belongsToMany(models.collection, { through: models.collectionProduct, as: 'collections', foreignKey: 'productId' })
}
return Product
}
Collection Model :
module.exports = (sequelize, Sequelize) => {
const Collection = sequelize.define(
'collection', {
id: {
type: Sequelize.INTEGER,
primaryKey: true,
allowNull: false,
autoIncrement: true,
},
name_en: {
type: Sequelize.STRING(255),
},
itemsCount: {
type: Sequelize.INTEGER,
},
createdAt: {
type: Sequelize.DATE,
defaultValue: Sequelize.NOW,
allowNull: false,
},
updatedAt: {
type: Sequelize.DATE,
defaultValue: Sequelize.NOW,
allowNull: false,
},
}, {},
)
Collection.associate = (models) => {
Collection.belongsToMany(models.product, { through: models.collectionProduct, as: 'products', foreignKey: 'collectionId' })
}
return Collection
}
CollectionProduct Model:
module.exports = (sequelize, Sequelize) => {
const CollectionProduct = sequelize.define('collectionProduct', {
collectionId: {
type: Sequelize.INTEGER,
},
productId: {
type: Sequelize.INTEGER,
},
createdAt: {
type: Sequelize.DATE,
defaultValue: Sequelize.NOW,
allowNull: false,
},
updatedAt: {
type: Sequelize.DATE,
defaultValue: Sequelize.NOW,
allowNull: false,
},
}, {})
CollectionProduct.associate = (models) => {}
return CollectionProduct
}
routes/collection.js
const express = require('express')
const router = express.Router()
const Collection = require('../../controllers/collectionController')
router.get('/:id', async (req, res) => {
const { id: collectionId } = req.params
const collection = await Collection.getOne(collectionId)
return collection
}
collectionController
const db = require('../models/index')
const Collection = db.collection
const Product = db.product
const getOne = async (collectionId) => {
const collection = await Collection.findByPk(collectionId, {
include: {
model: Product,
as: 'products',
attributes: ['id', 'name_en'],
},
})
return collection
}

I found my problem. in collectionController i used model: Product
and Product should be a sequelize model. but in my code this is a function that have been called in models/index. so i changed my calling and pass a sequelize model to getOne

Related

Creating Associations with sequelize and mysql

I am trying to create seqeulize models in nodejs app on mysql db. However, When I run the codes, foreign keys are not being created as intended.
This are my models:
Category Model
"use strict";
module.exports = (sequelize, DataTypes) => {
const Category = sequelize.define("Category", {
category_id: {
type: DataTypes.STRING(255),
unique: true,
primaryKey: true,
},
name: { type: DataTypes.STRING, unique: true, allowNull: false },
slug: { type: DataTypes.STRING, unique: true, allowNull: false },
created_at: {
type: DataTypes.DATE,
defaultValue: sequelize.literal("CURRENT_TIMESTAMP"),
},
updated_at: {
type: DataTypes.DATE,
defaultValue: sequelize.literal(
"CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP"
),
},
});
Category.associate = (models) => {
Category.hasMany(models.Product);
};
return Category;
};
Product Model
"use strict";
module.exports = (sequelize, DataTypes) => {
const Product = sequelize.define("Product", {
product_id: {
type: DataTypes.STRING(255),
allowNull: false,
unique: true,
primaryKey: true,
},
name: {
type: DataTypes.STRING,
allowNull: false,
},
slug: {
type: DataTypes.STRING,
allowNull: false,
unique: true,
},
sku: {
type: DataTypes.STRING,
allowNull: false,
unique: true,
},
short_description: {
type: DataTypes.TEXT,
allowNull: false,
},
description: {
type: DataTypes.TEXT,
allowNull: false,
},
price: {
type: DataTypes.DECIMAL,
allowNull: false,
min: 1,
},
discount: {
type: DataTypes.DECIMAL,
allowNull: true,
},
quantity: {
type: DataTypes.INTEGER,
},
vat: {
type: DataTypes.DECIMAL,
allowNull: true,
},
tags: {
type: DataTypes.STRING(255),
allowNull: true,
},
rate: {
type: DataTypes.INTEGER,
allowNull: true,
},
points: {
type: DataTypes.INTEGER,
allowNull: true,
},
new: {
type: DataTypes.VIRTUAL,
get() {
var msPerDay = 8.64e7;
// Copy dates so don't mess them up
var x0 = new Date(this.getDataValue("created_at"));
var x1 = new Date();
// Set to noon - avoid DST errors
x0.setHours(12, 0, 0);
x1.setHours(12, 0, 0);
// Round to remove daylight saving errors
return Math.round((x1 - x0) / msPerDay) <= 30;
},
set(value) {
throw new Error("Can't set Product.new property.");
},
},
thumb_image: {
type: DataTypes.STRING,
allowNull: true,
get() {
if (this.getDataValue("thumb_image")) {
return JSON.parse(this.getDataValue("thumb_image"));
}
return [];
},
set(val) {
this.setDataValue("thumbImage", JSON.stringify(val));
},
},
images: {
type: DataTypes.STRING,
allowNull: true,
get() {
if (this.getDataValue("images")) {
return JSON.parse(this.getDataValue("images"));
}
return [];
},
set(val) {
this.setDataValue("images", JSON.stringify(val));
},
},
featured: {
type: DataTypes.BOOLEAN,
defaultValue: false,
},
created_at: {
type: DataTypes.DATE,
defaultValue: sequelize.literal("CURRENT_TIMESTAMP"),
},
updated_at: {
type: DataTypes.DATE,
defaultValue: sequelize.literal(
"CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP"
),
},
});
Product.associate = (models) => {
Product.belongsTo(models.Category, {
foreignKey: "category_id",
});
Product.belongsTo(models.Brand, {
foreignKey: "brand_id",
});
Product.belongsTo(models.CartItem, {
foreignKey: "cart_id",
});
};
return Product;
};
Brand Model
"use strict";
module.exports = (sequelize, DataTypes) => {
const Brand = sequelize.define("Brand", {
brand_id: {
type: DataTypes.STRING(255),
unique: true,
primaryKey: true,
},
name: { type: DataTypes.STRING, unique: true, allowNull: false },
slug: { type: DataTypes.STRING, unique: true, allowNull: false },
created_at: {
type: DataTypes.DATE,
defaultValue: sequelize.literal("CURRENT_TIMESTAMP"),
},
updated_at: {
type: DataTypes.DATE,
defaultValue: sequelize.literal(
"CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP"
),
},
});
Brand.associate = (models) => {
Brand.hasMany(models.Product);
};
return Brand;
};
And these is my Models/index.js file
/* eslint-disable no-undef */
"use strict";
const fs = require("fs");
const path = require("path");
const Sequelize = require("sequelize");
const basename = path.basename(__filename);
const env = process.env.NODE_ENV || "development";
const config = require(__dirname + "/../../config/config.js")[env];
const db = {};
let sequelize;
if (config.use_env_variable) {
sequelize = new Sequelize(process.env[config.use_env_variable], config);
} else {
sequelize = new Sequelize(
config.database,
config.username,
config.password,
config
);
}
db.sequelize = sequelize;
db.Sequelize = Sequelize;
fs.readdirSync(__dirname)
.filter((file) => {
return (
file.indexOf(".") !== 0 && file !== basename && file.slice(-3) === ".js"
);
})
.forEach((file) => {
const model = require(path.join(__dirname, file))(
sequelize,
Sequelize.DataTypes
);
db[model.name] = model;
});
Object.keys(db).forEach((modelName) => {
if (db[modelName].associate) {
db[modelName].associate(db);
}
});
db.sequelize
.sync({ force: process.env.NODE_ENV === "development" })
.then(() => {
console.log("Drop and re-sync db.");
});
module.exports = db;
Now when I run the server, I expected the products table to have the fields category_id and brand_id. However, am getting additional fields brand_brand_id and category_category_id on the products table.
Besides, I cannot retrieve the categories, brands and products with the include properties when fecthing data.
What I want is to have the products table properly created in the database with the required foreign keys
Since you have customized foreign keys you have to indicate them in both paired associations:
Category.associate = (models) => {
Category.hasMany(models.Product, { foreignKey: "category_id" });
};
Product.associate = (models) => {
Product.belongsTo(models.Category, {
foreignKey: "category_id",
});
Product.belongsTo(models.Brand, {
foreignKey: "brand_id",
});
Product.belongsTo(models.CartItem, {
foreignKey: "cart_id",
});
};
Brand.associate = (models) => {
Brand.hasMany(models.Product, { foreignKey: "brand_id" });
};

Sequelize belongsTo query results in belongsTo called with something that's not a subclass of Sequelize.Model error

I am trying to write a join query using belongsTo but getting error:
Error: LearningCertificateEvent.belongsTo called with something that's
not a subclass of Sequelize.Model
at Function. (/Users/msmexmac/node/msmex-backend/msmex-api/node_modules/sequelize/lib/associations/mixin.js:93:13)
I am trying to fetch events related to learning cerfificate events. I tried every thing but it does not work somehow.
My models are as follows:
LearningCertificate Model
"use strict";
const Sequelize = require("sequelize");
const sequelize = require("../helpers/db");
const LearningCertificateEvent = require("./LearningCertificateEvent");
const LearningCertificate = sequelize.define(
"LearningCertificate",
{
id: {
autoIncrement: true,
type: Sequelize.BIGINT,
primaryKey: true,
},
userId: {
type: Sequelize.INTEGER,
field: "user_id",
},
certificateURL: {
type: Sequelize.STRING,
field: "certificate_url",
},
certificatePNGURL: {
type: Sequelize.STRING,
field: "certificate_png_url",
},
certificateType: {
type: Sequelize.STRING,
field: "certificate_type",
},
createdAt: {
type: Sequelize.BIGINT,
field: "created_at",
},
updatedAt: {
type: Sequelize.BIGINT,
field: "updated_at",
},
},
{
timestamps: false,
tableName: "learning_certificate",
}
);
LearningCertificate.hasMany(LearningCertificateEvent, {
foreignKey: "certificateId",
});
module.exports = LearningCertificate;
The second model is : LearningCertificateEvent Model
"use strict";
const Sequelize = require("sequelize");
const sequelize = require("../helpers/db");
const MsmexEvent = require("./MsmexEvent");
const LearningCertificateEvent = sequelize.define(
"LearningCertificateEvent",
{
id: {
autoIncrement: true,
type: Sequelize.BIGINT,
primaryKey: true,
},
userId: {
type: Sequelize.INTEGER,
field: "user_id",
},
certificateId: {
type: Sequelize.INTEGER,
field: "certificate_id",
},
eventId: {
type: Sequelize.INTEGER,
field: "event_id",
},
createdAt: {
type: Sequelize.BIGINT,
field: "created_at",
},
updatedAt: {
type: Sequelize.BIGINT,
field: "updated_at",
},
},
{
timestamps: false,
tableName: "learning_certificate_event",
}
);
LearningCertificateEvent.belongsTo(MsmexEvent, {
foreignKey: "eventId",
});
// MsmexEvent.hasMany(LearningCertificateEvent, {
// foreignKey: "eventId",
// });
module.exports = LearningCertificateEvent;
And last model is: MsmexEvent Model
"use strict";
const Sequelize = require("sequelize");
const sequelize = require("../helpers/db");
const LearningCertificateEvent = require("./LearningCertificateEvent");
const Event = sequelize.define(
"Event",
{
id: {
autoIncrement: true,
type: Sequelize.BIGINT,
primaryKey: true,
},
identifier: {
type: Sequelize.STRING,
field: "identifier",
},
expertId: {
type: Sequelize.INTEGER,
field: "expert_id",
},
waitList: {
type: Sequelize.BOOLEAN,
field: "waitList",
},
},
{
timestamps: false,
tableName: "msmex_event",
}
);
module.exports = Event;
The query which I have written is as follows:
return LearningCertificate.findAll({
where: {
userId: userId,
},
include: [
{
model: LearningCertificateEvent,
required: true,
include: [
{
model: MsmexEvent,
required: true,
},
],
},
],
order: [["id", "DESC"]],
});
In query however if I replace MsmexEvent with some other table for example User it works fine.
Can some one please help ?

How to use add Methods with belongsToMany in Sequelize?

I am attempting to add a product to my cartitems Table
using sequilize:
Product.belongsTo(User, { constraints: true, onDelete: 'CASCADE' });
User.hasMany(Product);
User.hasOne(Cart);
Cart.belongsTo(User);
Cart.belongsToMany(Product, { through: CartItem });
Product.belongsToMany(Cart, { through: CartItem });
Here is my Product Model:
const Product = sequelize.define('product', {
id: {
type: Sequelize.STRING,
allowNull: false,
primaryKey: true,
},
title: {
type: Sequelize.STRING,
allowNull: false,
},
price: {
type: Sequelize.INTEGER,
allowNull: false,
},
imageURL: {
type: Sequelize.STRING,
allowNull: false,
},
description: {
type: Sequelize.STRING,
allowNull: false,
},
});
module.exports = Product;
Here is my Cart Model:
const Cart = sequelize.define('cart', {
id: {
type: Sequelize.STRING,
allowNull: false,
primaryKey: true,
},
});
module.exports = Cart;
Here is my User Model:
const User = sequelize.define('user', {
id: {
type: Sequelize.STRING,
allowNull: false,
primaryKey: true,
},
fullName: {
type: Sequelize.STRING,
allowNull: false,
},
email: {
type: Sequelize.STRING,
allowNull: false,
},
});
module.exports = User;
Here is my Cart-Item Model:
const CarteItem = sequelize.define('carteItem', {
id: {
type: Sequelize.STRING,
allowNull: false,
primaryKey: true,
},
quantity: {
type: Sequelize.INTEGER,
allowNull: false,
},
});
module.exports = CarteItem;
the problem occurs when I attend to Post a product to a cart using
exports.postCart = (req, res, next) => {
const prodId = req.body.productId;
let fetchedCart;
let newQuantity = 1;
req.user
.getCart()
.then((cart) => {
fetchedCart = cart;
return cart.getProducts({ where: { id: prodId } });
})
.then((products) => {
let product;
if (products > 0) product = products[0];
return Product.findByPk(prodId);
})
.then((product) => {
console.log(product);
return fetchedCart.addProduct(product, {
through: { quantity: newQuantity },
});
})
.then(() => {
res.redirect('/');
})
.catch((err) => console.log(err));
};
and I get this error
Executing (default): SELECT `id`, `quantity`, `createdAt`, `updatedAt`, `cartId`, `productId` FROM `carteItems` AS `carteItem` WHERE `carteItem`.`cartId` = '1' AND `carteItem`.`productId` IN ('7c64a81f-59d3-427d-af2a-f9c9ee6e4fde');
AggregateError
at recursiveBulkCreate (/home/horus/Documents/WorkStation/learningPath/learningPathNode/node-complete-course/node_modules/sequelize/lib/model.js:2600:17)
at processTicksAndRejections (node:internal/process/task_queues:96:5)
at async Function.bulkCreate (/home/horus/Documents/WorkStation/learningPath/learningPathNode/node-complete-course/node_modules/sequelize/lib/model.js:2837:12)
at async Promise.all (index 0)
at async BelongsToMany.add (/home/horus/Documents/WorkStation/learningPath/learningPathNode/node-complete-course/node_modules/sequelize/lib/associations/belongs-to-many.js:740:30) {
errors: [
BulkRecordError [SequelizeBulkRecordError]: notNull Violation: carteItem.id cannot be null
at /home/horus/Documents/WorkStation/learningPath/learningPathNode/node-complete-course/node_modules/sequelize/lib/model.js:2594:25
at processTicksAndRejections (node:internal/process/task_queues:96:5)
at async Promise.all (index 0)
at async recursiveBulkCreate (/home/horus/Documents/WorkStation/learningPath/learningPathNode/node-complete-course/node_modules/sequelize/lib/model.js:2590:9)
at async Function.bulkCreate (/home/horus/Documents/WorkStation/learningPath/learningPathNode/node-complete-course/node_modules/sequelize/lib/model.js:2837:12)
at async Promise.all (index 0)
at async BelongsToMany.add (/home/horus/Documents/WorkStation/learningPath/learningPathNode/node-complete-course/node_modules/sequelize/lib/associations/belongs-to-many.js:740:30) {
errors: [ValidationError],
record: [carteItem]
}
]
}
My problem was using the wrong definition of CartItem Schema, should be autoIncremented

sequelize many to many not working correcttly with a legacy databse

I've got an existing mysql database where i've got the following tables : category,product and product_category.I've used sequelizer-auto package to generate models from the 3 tables like the following:
Product.js ,model generated from product table:
module.exports = function(sequelize, DataTypes) {
const Product= sequelize.define('product', {
productId: {
type: DataTypes.INTEGER(11),
allowNull: false,
primaryKey: true,
autoIncrement: true,
field: 'product_id'
},
name: {
type: DataTypes.STRING(100),
allowNull: false,
field: 'name'
},
description: {
type: DataTypes.STRING(1000),
allowNull: false,
field: 'description'
},
price: {
type: DataTypes.DECIMAL,
allowNull: false,
field: 'price'
},
discountedPrice: {
type: DataTypes.DECIMAL,
allowNull: false,
defaultValue: '0.00',
field: 'discounted_price'
},
image: {
type: DataTypes.STRING(150),
allowNull: true,
field: 'image'
},
image2: {
type: DataTypes.STRING(150),
allowNull: true,
field: 'image_2'
},
thumbnail: {
type: DataTypes.STRING(150),
allowNull: true,
field: 'thumbnail'
},
display: {
type: DataTypes.INTEGER(6),
allowNull: false,
defaultValue: '0',
field: 'display'
}
}, {
tableName: 'product'
});
Product.associate=(models)=>{
Product.belongsToMany(models.category,{
through:'product_category',
foreignkey:'product_id',
as:'categories'
})
}
return Product;
};
Category.js generated from 'category table'
module.exports = function(sequelize, DataTypes) {
const Category= sequelize.define('category', {
categoryId: {
type: DataTypes.INTEGER(11),
allowNull: false,
primaryKey: true,
autoIncrement: true,
field: 'category_id'
},
departmentId: {
type: DataTypes.INTEGER(11),
allowNull: false,
field: 'department_id'
},
name: {
type: DataTypes.STRING(100),
allowNull: false,
field: 'name'
},
description: {
type: DataTypes.STRING(1000),
allowNull: true,
field: 'description'
}
},
{
tableName: 'category',
});
Category.associate=(models)=>{
Category.belongsToMany(models.Product, {
through: 'product_category',
foreignkey: 'category_id',
as: 'products'
});
}
return Category;
};
ProductCategory from product_category table
module.exports = function(sequelize, DataTypes) {
return sequelize.define('product_category', {
productId: {
type: DataTypes.INTEGER(11),
references:{
key:'product_id',
model:'product'
}
},
categoryId: {
type: DataTypes.INTEGER(11),
references:{
key: 'category_id',
model:'category'
}
}
}, {
tableName: 'product_category'
});
};
And here is the category controller categories.js:
const db=require('../services/db_init');
const DataTypes=require('sequelize').DataTypes;
const Category=require('../database/models/Category')(db,DataTypes);
const Product=require('../database/models/Product')(db,DataTypes);
const {category_errors:{cat_01,cat_02}} = require('../services/errors.js');
//test code
const Product = require('../database/models/Product')(db,DataTypes);
module.exports=(app)=>{
app.get('/categories',async(req,res)=>{
try {
const categories = await Category.findAll({
include:{
model:Product,
as:'products'
}
});
return res.send(categories).status(200);
} catch (err) {
return res.json({error:err}).status(400);
}
});
app.get('/categories/:id',async(req,res)=>{
const id=req.params.id;
//checking if the id is a number
if(isNaN(id)){
return res.json({error:cat_01})//error returned
}
try {
const category=await Category.findByPk(id);
if(category){
return res.send(category).status(200);
}
return res.json({error:cat_02}).status(404);
} catch (err) {
return res.json(err).status(400);
}
});
}
All methode are working as expected,but after adding relashionship between models i've got some problems.First in GET /categories ,the implementation of the query was const categories = await Category.findAll() and everything was working fine,but after changing the implementation to const categories = await Category.findAll({include:{model:Product,as:'products'}}); i get the follwing error {
"error": {
"name": "SequelizeEagerLoadingError"
}
}
I've tried to read many topics,and solutions but i always have the same issue

Can i use `SET` variable in sequelize in nodejs

Can i use SET variable in sequelize in nodejs?
Sequelize
Model
Session Model
"use strict";
module.exports = (sequelize, DataTypes) => {
const Sessions = sequelize.define(
"sessions",
{
id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true },
challenge_id: {
type: DataTypes.INTEGER,
references: {
model: "challenges",
key: "id"
},
allowNull: false
},
createdAt: { type: DataTypes.DATE },
createdBy: {
type: DataTypes.INTEGER,
references: {
model: "users",
key: "id"
},
allowNull: false
},
showAtLeaderboard: { type: DataTypes.ENUM("yes", "no") },
sessionFile: { type: DataTypes.STRING },
score: { type: DataTypes.INTEGER },
},
{
timestamps: false,
underscored: true
}
);
return Sessions;
};
Challenge Model
"use strict";
module.exports = (sequelize, DataTypes) => {
const Challenges = sequelize.define(
"challenges",
{
id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true },
song_id: { type: DataTypes.INTEGER },
challenge_name: { type: DataTypes.STRING },
challengeDescription: { type: DataTypes.STRING },
challengeImg: { type: DataTypes.STRING },
challengeType: { type: DataTypes.STRING },
coins: { type: DataTypes.INTEGER },
createdAt: { type: DataTypes.DATE },
expireAt: { type: DataTypes.DATE },
isActive: { type: DataTypes.ENUM("yes", "no") },
tags: { type: DataTypes.STRING }
},
{
timestamps: false,
underscored: true
}
);
return Challenges;
};
Relationship
db.challenges.hasMany(db.sessions, {
foreignKey: "challenge_id",
sourceKey: "id"
});
Can I perform below SQL query with sequelize Orm model?
SET #rank=0
SELECT `challenges`.`*`, `sessions`.*, #rank:=#rank+1 AS rank FROM `challenges` JOIN `sessions` ON `sessions`.`challenge_id` = `challenges`.`id` ORDER BY `sessions`.`score` DESC
Sequelize support this type of query with it's ORM Model.