Related
I'm trying to add order clause to method function but got this unknown column error.
Error: Unknown column 'Product.createdAt' in 'order clause'
So I looked up the SQL and found that the problem is column name in order clause that sequelize
generated.
I'll be very thankful for any idea to solve this problem.
System
windows 10
nodejs v13.11.0
sequelize [ CLI: 6.2.0, ORM: 6.3.4 ]
Router
...
const models = require('./models')
const { Product, ViewItem, Category, ... } = models
...
//Middleware apply the req.filter, generated filter object will be like this
const req = {
...req,
filter : {
order : [
[{ model : Product, as : 'Product'}, 'rating', 'desc'],
['createdAt', 'desc']
],
...
}
}
const products = await ViewItem.listAndApply({
subquery : false,
include: [
{ model : Brand, as : 'Brand', required: true },
{
model : Category,
as : 'Category',
required: true,
attributes : [ 'id', 'level', 'name' ],
where : req.filters.toJson({ namespace : 'Category' })
},
{ model : ProductImage, as : 'ProductImage', required: false },
{ model : ProductStatus, as : 'ProductStatus', required: false },
{ model : ProductType, as : 'ProductType', required: false },
{ model : ProductRating, as : 'ProductRating', required : true }
],
where : req.filters.toJson(),
limit : limit,
offset : offset,
order : req.filters.order
...
models/ViewItem.js
ViewItem.listAndApply = async function (options) {
const {
ProductView,
Product,
} = require('.')
const payload = lodash.cloneDeep(options)
let {
include,
} = payload
if (!include) include = []
.
.
.
const items = await ViewItem.findAll({
subquery: false,
include: [
.
.
.
{
model: Product,
as: 'Product',
required: true,
where: product.where,
include: include
}
],
where: where,
limit: limit,
order: order
})
//Please don't mind the applyView(), this function do nothing with sequelize level.
return items.map(item => applyView(item))
}
This SQL is broken (What I got from sequelize)
SELECT
`ViewItem`.*,
.
.
.
FROM
(
SELECT
.
.
.
`Product`.`rating` AS `Product.rating`,
.
.
.
FROM
`ViewItems` AS `ViewItem`
.
.
.
ORDER BY
`Product`.`rating` ASC,
`ViewItem`.`createdAt` DESC;
This SQL works fine (What I want to generate)
SELECT
`ViewItem`.*,
.
.
.
FROM
(
SELECT
.
.
.
`Product`.`rating` AS `Product.rating`,
.
.
.
FROM
`ViewItems` AS `ViewItem`
.
.
.
ORDER BY
`Product.rating` ASC,
`ViewItem`.`createdAt` DESC;
What I tried to solve this problem.
// SQL broken with syntax error, col name return is " ``. "
req.filter.order.push([sequelize.literal('Product.rating'), 'desc'])
// also broken with same error above
req.filter.order.push([sequelize.col('Product.rating'), 'desc'])
// same
I'm
Any idea for resolving this error?
Model Code added
models/Product.js
'use strict'
module.exports = (sequelize, DataTypes) => {
const Product = sequelize.define('Product', {
name: DataTypes.STRING,
sku: DataTypes.STRING,
supplier: DataTypes.STRING,
manufacturer: DataTypes.STRING,
thumbnail: DataTypes.STRING,
wholeSalePrice: DataTypes.INTEGER,
retailPrice: DataTypes.INTEGER,
originalPrice: DataTypes.INTEGER,
discount: DataTypes.FLOAT,
description: DataTypes.STRING,
domestic: {
type: DataTypes.BOOLEAN,
defaultValue: true
},
underAge: {
type: DataTypes.BOOLEAN,
defaultValue: true
},
display: {
type: DataTypes.BOOLEAN,
defaultValue: true
},
views: {
type: DataTypes.INTEGER,
defaultValue: 0
},
reviews : {
type : DataTypes.INTEGER,
defaultValue : 0
},
jjim : {
type: DataTypes.INTEGER,
defaultValue: 0
},
sold: {
type: DataTypes.INTEGER,
defaultValue: 0
},
rating: {
type: DataTypes.FLOAT,
defaultValue: 0
}
}, { timestamps: true, paranoid: true })
Product.associate = function(models) {
// associations can be defined here
Product.belongsToMany(models.Category, { as: 'Category', through: 'ProductCategory', onDelete: 'CASCADE' })
Product.belongsToMany(models.ProductView, { as: 'ProductView', through: 'ProductViewMap', onDelete: 'CASCADE' })
Product.belongsTo(models.Brand, { as: 'Brand' })
Product.hasMany(models.ProductImage, { as: 'ProductImage', foreignKey: 'ProductId' , onDelete: 'CASCADE' })
Product.hasMany(models.ProductItem, { as: 'ProductItem', foreignKey: 'ProductId', onDelete: 'CASCADE' })
Product.belongsTo(models.ProductStatus, { as: 'ProductStatus' })
Product.belongsTo(models.ProductType, { as: 'ProductType' })
Product.hasOne(models.ProductRating, { foreignKey : 'ProductId', as : 'ProductRating' })
Product.hasMany(models.UserJjim, { as : 'Jjims'})
}
Product.afterCreate(async (product, option) =>{
const {
sequelize,
Product,
ProductViewMap,
ViewItem,
ProductRating
} = require('.')
const { id, name, thumbnail } = product
const DEFAULT_VIEW = 1
// await ProductViewMap.create({ ProductId : id, ProductViewId : DEFAULT_VIEW })
const item = await ViewItem.create({ ProductId : id, ProductViewId : DEFAULT_VIEW })
console.log(item)
await ProductRating.create({ ProductId : id })
})
Product.prototype.findActiveItem = async function(){
const { ViewItem, View } = require('.')
return ViewItem.findOne({
subquery : false,
include : [
{ model : View, as : 'View', required : true }
],
where : {
display : true,
ProductId : this.id
}
})
}
Product.prototype.findActiveView = async function(){
const { ViewItem, View } = require('.')
return View.findOne({
subquery: false,
include : [
{
model : View,
as : 'View',
required : true,
include : [
{
model : ViewItem,
as : 'ViewItem',
required : true,
where : {
display : true
}
}
]
}
]
})
}
return Product
}
models/ViewItem.js
'use strict'
const lodash = require('lodash')
module.exports = (sequelize, DataTypes) => {
const ViewItem = sequelize.define('ViewItem', {
name: DataTypes.STRING,
thumbnail: DataTypes.STRING,
retailPrice: DataTypes.INTEGER,
discount: DataTypes.FLOAT,
staticDiscount: DataTypes.INTEGER,
description: DataTypes.STRING,
promotion: {
type: DataTypes.JSON,
defaultValue: null
},
position: DataTypes.INTEGER,
display: DataTypes.BOOLEAN
}, {
timestamps: true,
paranoid: true
})
ViewItem.associate = function (models) {
// associations can be defined here
ViewItem.belongsTo(models.ProductView, {
as: 'ProductView',
foreignKey: 'ProductViewId',
onDelete: 'CASCADE'
})
ViewItem.belongsTo(models.Product, {
as: 'Product',
onDelete: 'CASCADE'
})
}
/* Hook */
ViewItem.afterCreate(async function (item) {
const {
Product,
ProductViewMap
} = require('.')
const {
id,
ProductId,
ProductViewId
} = item
/* Join table에 row를 추가합니다 */
await ProductViewMap.create({
ProductId,
ProductViewId
})
/* View 아이템중에 같은 Product를 가르키는 상품의 노출 설정을 false로 합니다. */
const product = await Product.findOne({
where: {
id: ProductId
}
})
await ViewItem.update({
display: false,
}, {
where: {
ProductId: ProductId
}
})
/* 생성되는 ViewItem의 초기 display 속성은 Product의 display를 따릅니다. */
await this.update({
display: product.display
}, {
where: {
id: id
}
})
})
ViewItem.listAndApply = async function (options) {
const {
ProductView,
Product,
} = require('.')
const payload = lodash.cloneDeep(options)
let {
include,
where,
order,
limit,
offset,
product
} = payload
if (!include) include = []
if (!product) product = {}
where = where ? where : null
order = order ? order : null
limit = limit ? limit : null
offset = offset ? offset : null
product.where = product.where ? product.where : null
const items = await ViewItem.findAll({
subquery: false,
include: [{
model: ProductView,
as: 'ProductView',
required: true
},
{
model: Product,
as: 'Product',
required: true,
where: product.where,
include: include
}
],
where: where,
limit: limit,
order: order
})
return items.map(item => applyView(item))
}
return ViewItem
}
/* private function */
const applyView = (item) => {
if (!item) return null
if (!item.Product) return null
if (!item.ProductView) return null
const product = lodash.cloneDeep(item.Product.toJSON())
const view = lodash.cloneDeep(item.ProductView.toJSON())
/*
View를 적용합니다.
#1. 가격정보를 수정합니다.
#2. 스티커와 태그를 추가합니다.
#3. 상세페이지 url을 추가합니다.
*/
if (view.additionalDiscount) {
const discount = product.discount + view.additionalDiscount
const retailPrice = Math.floor(product.originalPrice * (1 - discount / 100))
product.discount = discount
product.retailPrice = retailPrice
} else if (view.discount) {
const retailPrice = Math.floor(product.retailPrice * (1 - view.discount / 100))
const discount = Math.floor((1 - retailPrice / product.originalPrice) * 100)
product.retailPrice = retailPrice
product.discount = discount
}
if (view.staticDiscount) {
const retailPrice = product.retailPrice - view.staticDiscount
const discount = Math.floor((1 - retailPrice / product.originalPrice) * 100)
product.retailPrice = retailPrice
product.discount = discount
}
if (view.tag) product.tag = view.tag
if (view.sticker) product.sticker = sticker
product.detailUrl = view.detailUrl ? `${view.detailUrl}/${item.id}` : `/products/${item.id}`
/*
ViewItem
#1. 상품정보를 수정합니다.
#2. 가격정보를 수정합니다.
#3. retailPrice가 있다면 기존 계산값을 무시하고 이 값을 사용합니다.
*/
if (item.name) product.name = item.name
if (item.thumbnail) product.thumbnail = item.thumbnail
if (item.description) product.description = item.description
if (item.discount) {}
if (item.staticDiscount) {}
if (item.retailPrice) product.retailPrice = item.retailPrice
return ({
...product,
ProductView: view,
id: item.id,
ProductId: item.ProductId,
ProductViewId: item.ProductViewId
})
}
Full code is about 500 lines, I reduced some unrelacted part for readability.
Other models are not related with order clause.
Can you please try below for order sequelize literal:
req.filter.order.push([sequelize.literal(`"Product.rating"`), 'desc'])
// OR
req.filter.order.push([sequelize.literal("`Product.rating`"), 'desc'])
My problem wasn't in sequelize itself but my instance method.
I copied option object before querying to prevent mutation of option object, using lodash.cloneDeep(). But it turns out to that even deep clone can't copy the prototype chain.
const payload = lodash.cloneDeep(options)//I massed up in here
I can demonstrate it by executing code below.
const sequelize = require('sequelize')
const lodash = require('lodash')
const orginal = sequelize.literal('Product.rating')
const copied = lodash.cloneDeep(original)
console.log(original instanceof sequelize.Utils.SequelizeMethod )//true
console.log(copied instanceof sequelize.Utils.SequelizeMethod)//false
That is the reason why I got
Error: Order must be type of array or instance of a valid sequelize method.
Because copied object has no prototype chain pointing SequelizeMethod, inside of sequlieze query generator, the process failed to match my literal query and it leaded for me to getting broken query.
In the meantime, I still can't resolve unknown column name error in order clause when passing array for option.order argument. I took a look in abstract/query-generator.js briefly, and got some insight to resolve this problem. Generating order clause in getQueryOrders(), it would be even better than now, if order query generator refer the selected column name.
SELECT
`ViewItem`.*,
.
.
.
FROM
(
SELECT
.
.
.
`Product`.`rating` AS `Product.rating`,
#this column name should be equal to order clause's column name
.
.
.
FROM
`ViewItems` AS `ViewItem`
.
.
.
ORDER BY
`Product`.`rating` ASC,#otherwise, it invokes unknow column name error.
`ViewItem`.`createdAt` DESC;
# Then how about referring the selected column name in the where and order clause
# instead of managing each clause loosely?
I am using the beforeCreate to encrypt password before saving to database.
When I do:
const user = await User.create({name, email, password});
res.json(user);
I see the encrypted password in response. But in the database the password is not encrypted. If I do user.reload() and then send, I see what's stored in the database(unencrypted password).
This is the model:
User.init({
name: {
type: DataTypes.STRING,
allowNull: false
},
...
},{
sequelize,
hooks: {
beforeCreate: (user, options) => {
return bcrypt.genSalt(10)
.then(salt => {
bcrypt.hash(user.password, salt)
.then(hashedPassword => {
user.password = hashedPassword;
console.log(user.password, "FS");
return user.password;
})
.catch(err => console.log(err));
})
.catch(err => console.log(err));
}
}
})
This is the controller:
try{
const {name, email, password} = req.body;
if(isEmpty(name) || isEmpty(email) || isEmpty(password)){
res.status(400).json({errMessage: 'Enter name, email and password'});
}
const user = await User.create({name, email, password});
res.json(user); //data with encrypted password is sent, but not saved in db
}
The beforeCreate hook does not need to return a value, the return value type of the function signature as follows:
export type HookReturn = Promise<void> | void;
Besides, you forgot to add return before bcrypt.hash(user.password, salt) statement causes the beforeCreate function not to wait for the encryption asynchronous operation to complete.
Here is a working example:
import { sequelize } from '../../db';
import { Model, DataTypes } from 'sequelize';
import bcrypt from 'bcrypt';
class User extends Model {
password!: string;
name!: string;
}
User.init(
{
name: {
type: DataTypes.STRING,
allowNull: false,
},
email: {
type: DataTypes.STRING,
allowNull: false,
},
password: {
type: DataTypes.STRING,
allowNull: false,
},
},
{
sequelize,
modelName: 'users',
hooks: {
beforeCreate: (user: User) => {
return bcrypt
.genSalt(10)
.then((salt) => {
return bcrypt
.hash(user.password, salt)
.then((hashedPassword) => {
user.password = hashedPassword;
})
.catch((err) => console.log(err));
})
.catch((err) => console.log(err));
},
},
},
);
(async function() {
try {
await sequelize.sync({ force: true });
await User.create({ name: 'ab', email: 'test#gmail.com', password: '123456' });
} catch (error) {
console.log(error);
} finally {
await sequelize.close();
}
})();
The execution log:
Executing (default): DROP TABLE IF EXISTS "users" CASCADE;
Executing (default): DROP TABLE IF EXISTS "users" CASCADE;
Executing (default): CREATE TABLE IF NOT EXISTS "users" ("id" SERIAL , "name" VARCHAR(255) NOT NULL, "email" VARCHAR(255) NOT NULL, "password" VARCHAR(255) NOT NULL, PRIMARY KEY ("id"));
Executing (default): SELECT i.relname AS name, ix.indisprimary AS primary, ix.indisunique AS unique, ix.indkey AS indkey, array_agg(a.attnum) as column_indexes, array_agg(a.attname) AS column_names, pg_get_indexdef(ix.indexrelid) AS definition FROM pg_class t, pg_class i, pg_index ix, pg_attribute a WHERE t.oid = ix.indrelid AND i.oid = ix.indexrelid AND a.attrelid = t.oid AND t.relkind = 'r' and t.relname = 'users' GROUP BY i.relname, ix.indexrelid, ix.indisprimary, ix.indisunique, ix.indkey ORDER BY i.relname;
Executing (default): INSERT INTO "users" ("id","name","email","password") VALUES (DEFAULT,$1,$2,$3) RETURNING *;
Check data record in the database:
node-sequelize-examples=# select * from users;
id | name | email | password
----+------+----------------+--------------------------------------------------------------
1 | ab | test#gmail.com | $2b$10$XQb89m.b6ie8ImokS6JPdurWfIH4Cq19y.XGhb7LpWYUklp5jaYh2
(1 row)
I have Users table, that related to many Stations and each user has relation with Info table, that contains fields firstName and lastName. I grab it like this:
const joinUserInfo: any = {
model: userInfoModel,
where: { deletedAt: null },
required: true,
};
const joinStations = {
model: stationModel,
required: true,
};
const notFormatedItems = await this.userRepository.findAll({
where: { role: 'user' },
include: [joinUserInfo, joinStations],
paranoid: false,
order,
});
Than I have a method to build order. It looks like this:
private createAdminsListOrdering(query) {
const validOrderingItems = [];
if (query.fullName) {
validOrderingItems.push(['info', 'lastName', query.fullName]);
validOrderingItems.push(['info', 'firstName', query.fullName]);
}
if (query.email) {
validOrderingItems.push(['email', query.email]);
}
if (query.station) {
validOrderingItems.push(['stations', 'name', query.station]);
}
return validOrderingItems.push(['createdAt', 'DESC']);
}
In cases with email or stations all works correct. But with info it say that didn't see and info column. When I remove stations from join like this:
const notFormatedItems = await this.userRepository.findAll({
where: { role: 'user' },
include: [joinUserInfo],
paranoid: false,
order,
});
My ordering by firstName and lastName working correct. What i do wrong? Thank you.
Users related with Stations as many-to-many:
#BelongsToMany(
() => stationModel,
() => userStationModel,
)
stations?: stationModel[];
The problem was in pagination... I use custom function to create pagination:
export const paginatedQuery = ({ page = 0, pageSize = 0 }) => {
if (!pageSize) {
return {};
}
const limit = pageSize;
const offset = page * pageSize;
return { offset, limit };
};
And full my request was:
const notFormatedItems = await this.userRepository.findAll({
where: { role: 'user' },
include: [joinUserInfo, joinStations],
paranoid: false,
order,
...paginatedQuery(query),
});
And, when I debug request with logging i notice, that my Info table is in subquery. Then I began to googling about subqueries in sequelize and found flag subQuery, that force to not use subqueries. After this my request works fine:
const notFormatedItems = await this.userRepository.findAll({
where: { role: 'user' },
include: [joinUserInfo, joinStations],
paranoid: false,
order,
...paginatedQuery(query),
subQuery: false,
});
Hope it will helps someone.
I am testing all the associations with sequelize and I am getting a problem to get joins for many to many with belongsToMany.
https://github.com/lhferrh/Sequelize-Playground.git
When I do the findAll through I get the all null for the join result
I have been checking several combinations but the thing is that the SQL produced by sequelize is working on MySQL directly and it makes me really confused.
This is the sql provided:
SELECT `user`.`userId`, `user`.`name`, `user`.`createdAt`, `user`.`updatedAt`, `cars`.`carId` AS `cars.carId`, `cars`.`make` AS `cars.make`, `cars`.`createdAt` AS `cars.createdAt`, `cars`.`updatedAt` AS `cars.updatedAt`, `cars->favorites`.`favoritesId` AS `cars.favorites.favoritesId`, `cars->favorites`.`date` AS `cars.favorites.date`, `cars->favorites`.`createdAt` AS `cars.favorites.createdAt`, `cars->favorites`.`updatedAt` AS `cars.favorites.updatedAt`, `cars->favorites`.`userId` AS `cars.favorites.userId`, `cars->favorites`.`carId` AS `cars.favorites.carId` FROM `users` AS `user` LEFT OUTER JOIN ( `favorites` AS `cars->favorites` INNER JOIN `cars` AS `cars` ON `cars`.`carId` = `cars->favorites`.`carId`) ON `user`.`userId` = `cars->favorites`.`userId`;
This is the code I am running:
const Users = sequelize.define('user', {
userId: {
type: Sequelize.INTEGER,
allowNull: false,
primaryKey: true,
autoIncrement: true
},
name: {
type: Sequelize.STRING,
},
}
);
const Favorites = sequelize.define('favorites', {
favoritesId: {
type: Sequelize.INTEGER,
allowNull: false,
primaryKey: true,
autoIncrement: true
},
date: {
type: Sequelize.DATE,
}
}
);
const Cars = sequelize.define('cars', {
carId: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true
},
make: Sequelize.STRING
})
Users.belongsToMany(Cars, {
through: 'favorites',
sourceKey: 'userId',
foreignKey: 'userId'
});
Cars.belongsToMany(Users, {
through: 'favorites',
sourceKey: 'carId',
foreignKey: 'carId'
});
const intiDataBase = async () => {
await sequelize.sync({force: true});
}
const run = async () => {
const max = 3;
await intiDataBase();
await Promise.all( DATA.users.map( async elem=>
Users.create({...elem})
))
await Promise.all( DATA.cars.map( async elem =>
Cars.create({...elem})
))
for( let i = 0 ; i < 5 ; i++ ){
Favorites.create({
userId: getRandomInt(1 , max),
carId: getRandomInt(0, max)
})
}
const car = await Users.findAll({
include: [{
model: Cars,
through: {
attributes: ['userId', 'carId'],
}
//attributes: ['make'],
}],
raw: true
});
const favorites = await Favorites.findAll({
where:{
userId:2
},
raw: true
});
console.log(car);
console.log(favorites);
}
run();
This is result I get:
name: 'Johan',
createdAt: 2019-10-05T08:57:57.000Z,
updatedAt: 2019-10-05T08:57:57.000Z,
'cars.carId': null,
'cars.make': null,
'cars.createdAt': null,
'cars.updatedAt': null,
'cars.favorites.favoritesId': null,
'cars.favorites.date': null,
'cars.favorites.createdAt': null,
'cars.favorites.updatedAt': null,
'cars.favorites.userId': null,
'cars.favorites.carId': null } ], ...
Probably this is a naming problem but the fact the SQL is working directly makes is really confusing.
I hope any of you can see the error.
By the way, I was also wondering what would be the disadvantage of creating the many to many relationship manually by creating two 1:m associations to the intermediate table?
I found my error. There is an await missing when I am inserting favorites to the database. For that reason the join was giving the the wrong result in the query and the correct one in the database directly.
for( let i = 0 ; i < 5 ; i++ ){
await Favorites.create({
userId: getRandomInt(1 , max),
carId: getRandomInt(0, max)
})
}
As I was expecting, a silly error.
Anyways I am still hesitant about using belongToMany or creating the many to many relationship by myself and use always two steps.
For an existing MySQL database, I used Sequelize-auto package to generate the models. But the associations don't come with model classes.
I have an MySQL database and I'm using it for NodeJS web project. Also I'm using Sequelize as the ORM. Since database is already there I wanted to generate the model classes as the entities. So I used sequelize-auto
https://github.com/sequelize/sequelize-auto to generate the model classes. But when they were generated attributes have been correctly set but the associations doesn't come up with the model classes. So I have faced problem when fetching the data from the database.
Here are the two model classes that were generated with sequlize-auto. There are two table in the database named as department and category. department.js and category.js are the two model classes that were generated
department.js
module.exports = function(sequelize, DataTypes) {
return sequelize.define('department', {
department_id: {
type: DataTypes.INTEGER(11),
allowNull: false,
primaryKey: true,
autoIncrement: true
},
name: {
type: DataTypes.STRING(100),
allowNull: false
},
description: {
type: DataTypes.STRING(1000),
allowNull: true
}
}, {
tableName: 'department',
timestamps: false,
});
};
category.js
module.exports = function(sequelize, DataTypes) {
return sequelize.define('category', {
category_id: {
type: DataTypes.INTEGER(11),
allowNull: false,
primaryKey: true,
autoIncrement: true
},
department_id: {
type: DataTypes.INTEGER(11),
allowNull: false
},
name: {
type: DataTypes.STRING(100),
allowNull: false
},
description: {
type: DataTypes.STRING(1000),
allowNull: true
}
}, {
tableName: 'category',
timestamps: false,
});
};
So what else needs to be done in order to get the associations and to fetch data successfully. Can some one help me here. Table structure is as following.
1) inside of your models folder create an index.js file and add the following code
import Sequelize from 'sequelize';
const fs = require('fs');
const path = require('path');
const basename = path.basename(__filename);
const db = {};
// #ts-ignore
const sequelize = new Sequelize('dbname', 'dbUser', 'password', {
host: '127.0.0.1',
port: 'PORT',
dialect: 'mysql',
define: {
freezeTableName: true,
timestamps: false,
},
pool: {
max: 5,
min: 0,
acquire: 30000,
idle: 10000,
},
// <http://docs.sequelizejs.com/manual/tutorial/querying.html#operators>
operatorsAliases: false,
});
const tableModel = {};
fs.readdirSync(__dirname)
.filter(file => file.indexOf('.') !== 0 && file !== basename && file.slice(-3) === '.js')
.forEach(file => {
const model = sequelize.import(path.join(__dirname, file));
db[model.name] = model;
tableModel[model.name] = model;
});
Object.getOwnPropertyNames(db).forEach(modelName => {
const currentModel = db[modelName];
Object.getOwnPropertyNames(currentModel.rawAttributes).forEach(attributeName => {
if (
Object.prototype.hasOwnProperty.call(
currentModel.rawAttributes[attributeName],
'references'
) &&
Object.prototype.hasOwnProperty.call(
currentModel.rawAttributes[attributeName].references,
'model'
) &&
Object.prototype.hasOwnProperty.call(
currentModel.rawAttributes[attributeName].references,
'key'
)
) {
if (
!(
currentModel.rawAttributes[attributeName].references.model &&
currentModel.rawAttributes[attributeName].references.key
)
) {
console.log(
`*SKIPPED* ${modelName} ${attributeName} references a model ${currentModel.rawAttributes[attributeName].references.model} with key ${currentModel.rawAttributes[attributeName].references.key}`
);
return;
}
console.log(
`${modelName} ${attributeName} references a model ${currentModel.rawAttributes[attributeName].references.model} with key ${currentModel.rawAttributes[attributeName].references.key}`
);
const referencedTable =
tableModel[currentModel.rawAttributes[attributeName].references.model];
currentModel.belongsTo(referencedTable, { foreignKey: attributeName });
referencedTable.hasMany(currentModel, { foreignKey: attributeName });
}
});
});
// #ts-ignore
db.sequelize = sequelize;
// #ts-ignore
db.Sequelize = Sequelize;
// eslint-disable-next-line eol-last
module.exports = db;
2) inside of your resolver just reference the above:
const db = require('../assets/models/index');
To add to #CodingLittles answer which he based from here.
I added the following for making many to many assosiations:
enum Junctions {
user = 'user',
roles = 'roles',
}
enum JuntiontThrough {
userroles = 'userroles',
}
interface JunctionObject {
junctionBelongsTo?: any;
}
const checkIfAttrExists= (obj, value) => {
return Object.prototype.hasOwnProperty.call(obj, value);
};
const checkRefrence = (obj, attr, value) => {
return obj.rawAttributes[attr].references[value];
};
export const getJunction = (junc: Junctions): JunctionObject => {
const junctions = {
user: {
junctionBelongsTo: [
{ key: Junctions.roles, value: juntiontThrough.userroles }
],
},
roles: {
junctionBelongsTo: [{ key: Junctions.user, value: juntiontThrough.userroles }],
},
}[junc];
if (!junctions) return {};
return junctions;
};
const models = Object.getOwnPropertyNames(db);
models.forEach(modelName => {
const currentModel = db[modelName];
const junction = getJunction(modelName as Junctions);
if (!_.isEmpty(junction)) {
// eslint-disable-next-line array-callback-return
junction.junctionBelongsTo.reduce((key, value) => {
currentModel.belongsToMany(db[value.key], {
through: db[value.value],
});
}, {});
}
const attributes = Object.getOwnPropertyNames(currentModel.rawAttributes);
attributes.forEach(attributeName => {
if (
checkIfAttrExists(currentModel.rawAttributes[attributeName], 'references') &&
checkIfAttrExists(currentModel.rawAttributes[attributeName].references, 'model') &&
checkIfAttrExists(currentModel.rawAttributes[attributeName].references, 'key')
) {
if (
!(
checkRefrence(currentModel, attributeName, 'model') &&
checkRefrence(currentModel, attributeName, 'key')
)
) {
return;
}
const referencedTable =
tableModel[currentModel.rawAttributes[attributeName].references.model];
if (!(modelName.toString() in juntiontThrough)) {
console.log(
`${modelName} ${attributeName} references a model ${currentModel.rawAttributes[attributeName].references.model} with key ${currentModel.rawAttributes[attributeName].references.key}`
);
currentModel.belongsTo(referencedTable, { foreignKey: attributeName });
referencedTable.hasMany(currentModel, { foreignKey: attributeName });
}
}
});
})
Note that for the many to many relations to work you have to add the relations manually like I did in the getJunction function
I followed the pattern demonstrated in the typescript example on sequelize-auto (init-models.ts) - within the existing initModels function, works fine in js.
export function initModels(sequelize: Sequelize) {
Product.initModel(sequelize);
Supplier.initModel(sequelize);
Supplier.hasMany(Product, { as: "products", foreignKey: "supplierId"});
return {...