Sequelize N-M association doesn't recognize the include model - mysql

I am trying to do a many-to-many assocition with sequelize but I constantly having the same error without reason...
Here is the error I get : SequelizeEagerLoadingError: User is not associated to UserTool!
I searched on the web and my code is corresponding to many right answers that I found.
The M-M association goes like this : User-UserTool-Tool, UserTool is the bridge table between User and Tool
Here is the following code :
Model User - User.js
const Sequelize = require('sequelize');
var db = require('../../config/database');
const User = db.define('User', {
id: {
type: Sequelize.INTEGER
},
name: {
type: Sequelize.STRING
},
}, {
timestamps: false,
freezeTableName: true,
tableName: 'User'
});
module.exports = User;
Model Tool - Tool.js
const Sequelize = require('sequelize');
var db = require('../../config/database');
const Tool = db.define('Tool', {
id: {
type: Sequelize.INTEGER
},
name: {
type: Sequelize.STRING
},
}, {
timestamps: false,
freezeTableName: true,
tableName: 'Tool'
});
module.exports = Tool;
Model UserTool - UserTool.js
const Sequelize = require('sequelize');
var db = require('../../config/database');
const UserTool = db.define('UserTool', {
userId: {
type: Sequelize.INTEGER
},
toolId: {
type: Sequelize.INTEGER
},
status: {
type: Sequelize.STRING
},
active: {
type: Sequelize.STRING
},
}, {
timestamps: false,
freezeTableName: true,
tableName: 'UserTool'
});
module.exports = UserTool;
Main.js
const Sequelize = require('sequelize');
var db = require('../../config/database');
const User = require('./User');
const Tool = require('./Tool');
const UserTool = require('./UserTool');
User.belongsToMany(Tool, { through: UserTool });
Tool.belongsToMany(User, { through: UserTool });
module.exports = {
User,
Tool,
UserTool
}
And then when I try to do a findAll() with an include, I get the error mentionned previously.
Here is the code of the findAll with the include
Find.js
const {User, UserTool} = require('./Main');
exports.FindAllUsers = async function (params) {
let message;
await UserTool.findAll({
where: {
userId: params.userId
},
include: [
{
model: User
},
]
});

As I understand it, if you want to query all users. Simply use the following method:
const result = await User.findAll();
If you want to query the user with id = 1 and all tools for that user, using:
const result = await User.findAll({
where: { id: '1' },
include: [{ model: Tool }],
});
sequelize will query the appropriate Tools for User based on the join table UserTool. Eager loading needs both sides(User and Tool) of the model are established associations(belongsToMany). UserTool doesn't establish associations with User, so you can't use include options, that's why sequelize throw that error.

Related

How to write object id reference of mongoose using Sequelize for MySql in Node.js

I am trying to write the category model for MySQL using Sequelize taking reference from this code of mongoose model in node(shared below) . I have gone through some articles but going through some confusion. Please help.
const mongoose = require("mongoose");
const categorySchema = new mongoose.Schema(
{
name: {
type: String,
required: true,
trim: true,
},
createdBy: {
type: mongoose.Schema.Types.ObjectId,
ref: "User",
required: true,
},
},
);
module.exports = mongoose.model("Category", categorySchema);
Here I've tried my code in using Sequelize for MySql but got stuck in createdBy block. Need Help!!!
module.exports = (sequelize, Sequelize) => {
const Category = sequelize.define("categories", {
name:{
type: Sequelize.STRING,
required: true,
trim: true,
},
createdBy:{
}
});
return Category;
};
The createdBy value from Mongo will be an ObjectID, which you can save as a string in MySQL, so use a DataTypes.STRING type in the definition, then set it on insert/update/etc from your Mongo lookup. The Sequelize variable in your example should be DataTypes as well.
module.exports = (sequelize, DataTypes) => {
const Category = sequelize.define('categories', {
name: {
type: DataTypes.STRING,
required: true,
trim: true,
},
createdBy: {
type: DataTypes.STRING,
// any other settings
},
});
return Category;
};
// get the value from mongoose
const createdBy = ...;
// set it on the create for your model to insert.
await Category.create({
name: 'Some Name',
createdBy,
});

Sequelize model.create is not a function

I'm new to sequelize and trying to set it up for my new project. I checked some answers on this, but couldnt get past my error. Can someone point out how to fix this.
models/index.js
// Database service
// Connects to the database
const { Sequelize } = require('sequelize');
const path = require('path');
const sequelize = new Sequelize(process.env.DB_NAME, process.env.DB_USER, process.env.DB_PASS, {
host: process.env.DB_HOST,
dialect: 'mysql',
logging: process.env.QUERY_LOGGING == "true" ? console.log : false,
pool: {
max: 10,
min: 0,
acquire: 30000,
idle: 10000
}
});
module.exports = sequelize
models/users.js
const sequelize = require("./index")
module.exports = (sequelize, DataTypes) => {
const User = sequelize.define('Users', {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
firstName: {
type: DataTypes.STRING,
allowNull: false
},
lastName: {
type: DataTypes.STRING
},
profileURL: {
type: DataTypes.STRING
},
emailId: {
type: DataTypes.STRING,
allowNull: false
},
passwordHash: {
type: DataTypes.STRING,
allowNull: false
},
street: {
type: DataTypes.STRING
},
city: {
type: DataTypes.STRING,
allowNull: false
},
phone: {
type: DataTypes.STRING
},
newsletter: {
type: DataTypes.STRING
},
visibility: {
type: DataTypes.BOOLEAN,
defaultValue: true
},
},{
});
return User;
};
And finally, I'm importing the User model in my service file like below:
const User = require("../models/users")
const createUser = async(req) => {
const {firstName, lastName, profileURL, emailId, passwordHash, street, city, phone, newsletter, visibility} = req.body
const user = await User.create({
firstName,
lastName,
profileURL,
emailId,
passwordHash,
street,
city,
phone,
newsletter,
visibility
})
console.log("new user==>>", user)
return
}
module.exports = { createUser }
However, I get the following error.
TypeError: User.create is not a function
Can someone point out what I could be doing wrong? I realize it could be something minor.
Thank you
You export a function that registers the User model and not the model itself. So you just need to call it passing sequelize instance and DataTypes somewhere like database.js where you will register all models and their associations or directly in models/index.js:
const UserModelConstructor = require("../models/users")
const { DataTypes } = require("sequelize");
...
const UserModel = UserModelConstructor(sequelize, DataTypes);
module.exports = {
sequelize,
User: UserModel
}
You can look at how to register multiple models and association in my other answer here
Please don't forget to remove this line
const sequelize = require("./index")
from models/users.js

Sequelize failing to create table with sync()

I'm learning node.js and mySQL. I have tried Sequelize and according to my learning source sync should create new table if doesn't exist. But for some reason it doesn't create new table.
Here is my database.js file
const Sequelize = require('sequelize');
const sequelize = new Sequelize('test-schema', 'root', 'mypassword',{dialect:'mysql', host:'localhost'});
module.exports = sequelize;
Here is my model Product file
const Sequelize = require('sequelize');
const sequelize = require('../util/database')
const Product = sequelize.define('product', {
id: {type: Sequelize.INTEGER, autoIncrement: true, allowNull: false, primaryKey: true},
title: Sequelize.STRING,
price: {type: Sequelize.DOUBLE, allowNull: false}
});
module.exports = Product;
and here is my server.js file
const express = require('express');
const sequelize = require('./util/database')
const app = express();
app.get('/',(req,res,next)=>{
res.send({"id": "1"});
});
sequelize.sync().then(result=>{
//console.log(result);
app.listen(3000);
}).catch(err => {
console.log(err);
});
Once I start server I get
Executing (default): SELECT 1+1 AS result
I tried to change the name of my schema in my database file to wrong name and I get an error schema doesn't exist so I believe that connection to db is correct
Here are my installed packages
"dependencies": {
"express": "^4.17.1",
"mysql2": "^2.1.0",
"sequelize": "^5.21.10"
}
Define all models as functions so that you can call them to register models in sequelize and then register models in database.js just like I described in this answer. You can see in the question of this answer how to define a model like a function.
'use strict';
module.exports = (sequelize, DataTypes) => {
const User = sequelize.define('User', {
id: { type: DataTypes.BIGINT, allowNull: false, autoIncrement: true, unique: true, primaryKey: true },
first_name: DataTypes.STRING,
last_name: DataTypes.STRING
}, {});
User.associate = function(models) {
User.belongsTo(models.Role, { foreignKey: 'role_id' });
};
return User;
};
In your Product file, change that p after sequelize.define from small letter
to capital letter. It should be "Product" not "product". It should look like:
const Product = sequelize.define('Product', {
id: {type: Sequelize.INTEGER, autoIncrement: true, allowNull: false, primaryKey: true},
title: Sequelize.STRING,
price: {type: Sequelize.DOUBLE, allowNull: false}
});
module.exports = Product;

Sequelize.js: Include unexpected. Element has to be either a Model, an Association or an object

I'm using Sequelize.js in my Node.js application and keep running into a very strange problem.
Background: I have two models, Account and AccountCategory as follows. My API endpoint calls the route /accounts which calls the accounts controller to do an Account.findAll() query.
Accounts model has a defaultScope to include the related category by default, without having to specify it each time inside the findAll({}) block.
Problem: When the Accounts model is attempting to access and return the data from the database, the defaultScope is trying to include the AccountCategory, Sequelize throws the error:
Include unexpected. Element has to be either a Model, an Association or an object.
I suspect it has to do with the fact that AccountCategory is placed after Account in my models folder when the models are being set up and thus not processed (associated). I base this on the fact that other associations like User and Role (ie. a user has a role) are fine using the same method (ie. no problem with path depth as this answer suggests).
I've spent the last 2 days trying to get the defaultScope working and stop producing this error without any luck. Similar questions do not provide an answer and I would greatly appreciate any help resolving this problem. Thanks.
Account:
module.exports = (sequelize, DataTypes) => {
const Account = sequelize.define(
"Account",
{
id: {
type: DataTypes.INTEGER(11),
allowNull: false,
primaryKey: true,
autoIncrement: true
},
name: {
type: DataTypes.STRING(100)
},
category_id: {
type: DataTypes.INTEGER(11),
allowNull: false
}
},
{
timestamps: false,
tableName: "Account",
defaultScope: {
include: [{
model: sequelize.models.AccountCategory,
as: "category"
}]
}
}
);
Account.associate = models => {
// Association: Account -> AccountCategory
Account.belongsTo(models.AccountCategory, {
onDelete: "CASCADE",
foreignKey: {
fieldName: "category_id",
allowNull: false,
require: true
},
targetKey: "id",
as: "category"
});
};
return Account;
};
Account Category:
module.exports = (sequelize, DataTypes) => {
var AccountCategory = sequelize.define(
"AccountCategory",
{
id: {
type: DataTypes.INTEGER(11),
allowNull: false,
primaryKey: true,
autoIncrement: true
},
name: {
type: DataTypes.STRING(30),
allowNull: false,
unique: true
}
},
{
timestamps: false,
tableName: "Account_Category"
}
);
return AccountCategory;
};
Models Index:
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 db = {};
const sequelize = new Sequelize(
process.env.DB_NAME,
process.env.DB_USER,
process.env.DB_PASS,
{
host: process.env.DB_HOST,
dialect: "mysql",
operatorAliases: false,
pool: {
max: 5,
min: 0,
acquire: 30000,
idle: 10000
}
}
);
fs.readdirSync(__dirname)
.filter(function(file) {
return (
file.indexOf(".") !== 0 && file !== basename && file.slice(-3) === ".js"
);
})
.forEach(function(file) {
var model = sequelize["import"](path.join(__dirname, file));
db[model.name] = model;
});
Object.keys(db).forEach(function(modelName) {
if (db[modelName].associate) {
db[modelName].associate(db);
}
db[modelName].associate(db);
});
db.sequelize = sequelize;
db.Sequelize = Sequelize;
module.exports = db;
You are correct when you say:
I suspect it has to do with the fact that AccountCategory is placed after Account in my models folder when the models are being set up and thus not processed (associated).
TLDR: Add a new function to your model class definition similar to the associate function, and use the addScope function to define any scopes that reference other models that may have not been initialized due to file tree order. Finally, call that new function the same way you call db[modelName].associate in your models.index.js file.
I had a similar problem and solved it by defining any scopes that reference any models, e.g. in an include, after all the models are initialized after running the following in your models/index.js file.
Here is an example:
models/agent.js
'use strict';
const { Model } = require('sequelize');
const camelCase = require('lodash/camelCase');
const { permissionNames } = require('../../api/constants/permissions');
module.exports = (sequelize, DataTypes) => {
/**
* #summary Agent model
*/
class Agent extends Model {}
Agent.init(
{
id: {
type: DataTypes.INTEGER,
allowNull: false,
autoIncrement: true,
primaryKey: true,
},
firstName: {
type: DataTypes.STRING,
allowNull: false,
},
lastName: {
type: DataTypes.STRING,
allowNull: false,
},
},
{
sequelize,
scopes: {
// Works because the agent-role.js file / model comes before agent.js in the file tree
[camelCase(permissionNames.readAgentRoles)]: {
include: [
{
model: sequelize.models.AgentRole,
},
],
},
// Doesn't work due to import order in models/index.js, i.e., agent.js is before role.js in the file tree
// [camelCase(permissionNames.readRoles)]: {
// include: [
// {
// model: sequelize.models.Role,
// },
// ],
// },
},
}
);
Agent.associate = function (models) {
Agent.belongsToMany(models.Role, {
through: 'AgentRole',
onDelete: 'CASCADE', // default for belongsToMany
onUpdate: 'CASCADE', // default for belongsToMany
foreignKey: {
name: 'agentId',
type: DataTypes.INTEGER,
allowNull: false,
},
});
Agent.hasMany(models.AgentRole, {
onDelete: 'CASCADE',
onUpdate: 'CASCADE',
foreignKey: {
name: 'agentId',
type: DataTypes.INTEGER,
allowNull: false,
},
});
};
// Add a custom `addScopes` function to call after initializing all models in `index.js`
Agent.addScopes = function (models) {
Agent.addScope(camelCase(permissionNames.readRoles), {
include: [
{
model: models.Role,
},
],
});
};
return Agent;
};
models/index.js
'use strict';
const fs = require('fs');
const path = require('path');
const Sequelize = require('sequelize');
const basename = path.basename(__filename);
const config = require('../database-config.js');
const db = {};
const sequelize = new Sequelize(config.database, config.username, config.password, config);
/**
* Import and attach all of the model definitions within this 'models' directory to the sequelize instance.
*/
fs.readdirSync(__dirname)
.filter((file) => {
return file.indexOf('.') !== 0 && file !== basename && file.slice(-3) === '.js';
})
.forEach((file) => {
// Here is where file tree order matters... the sequelize const may not have the required model added to it yet
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);
}
// We need to add scopes that reference other tables once they have all been initialized
if (db[modelName].addScopes) {
db[modelName].addScopes(db);
}
});
db.sequelize = sequelize;
db.Sequelize = Sequelize;
module.exports = db;
Goodluck!

Able to make migration with associations but not able to query with associations (association not found)

I've built a database with a couple tables that are associated with each other: Auction and Bids. Each Auction should have many Bids and each Bid should have only one Auction. I made a migration fine to add foreignKeys but when I try to look up a certain Bid on an Auction I receive a SequelizeEagerLoadingError: Bids is not associated to Auctions! error.
migrations file:
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.addColumn(
'Auctions', // name of target model
'BidId',
{
type: Sequelize.INTEGER,
references:{
model: "bids",
key: "bid_id",
},
},
);
},
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('Auction');
}
};
bids.js
const Sequelize = require('sequelize');
// const resolver = require('graphql-sequelize');
const sequelize = require('../config/database');
const Auction = require('./Auction');
const tableName = 'bids';
const Bids = sequelize.define('Bids', {
bid_id: {
type: Sequelize.INTEGER,
autoIncrement: true,
primaryKey: true
},
createdAt: {
type: Sequelize.DATE,
// defaultValue: Sequelize.NOW
},
updatedAt: {
type: Sequelize.DATE,
defaultValue: Sequelize.NOW,
},
amount: {
type: Sequelize.INTEGER
},
bid_amount: {
type:Sequelize.STRING
},
bid_no: {
type: Sequelize.UUID,
defaultValue: Sequelize.UUIDV4,
},
},
{tableName})
Bids.associate = () => {
Bids.hasOne(Auction, {foreignKey:"BidId"})
};
auction.js
const Sequelize = require('sequelize');
const sequelize = require('../config/database');
const tableName = 'Auctions';
const Auction = sequelize.define('Auctions', {
auc_id: {
type: Sequelize.UUID,
defaultValue: Sequelize.UUIDV4, // generate the UUID automatically
primaryKey: true,
},
features: {
type: Sequelize.JSONB,
},
bid_amount: {
type:Sequelize.STRING
},
BidId: {
type: Sequelize.UUID,
}
}, { tableName });
Auction.hasMany(Bids, {foreignKey: 'BidId'})
module.exports = Auction
query.js
const findBidOnAuction = () => {Auction.findOne({where:{BidId:2}, include:[{model:Bids}]}).then(data => console.log("result", data))}
How do I properly associate these tables?
edit: Also on pgAdmin I can that the relationship exists, BidId is a foreignKey on Auction linked to bid_id on Bids
I do the following for add a foreignKey on migrations:
1.I create a migration for the parent model with a ref function.
const TABLE_NAME = 'parent';
function up(queryInterface, Sequelize) {
return queryInterface.createTable(
TABLE_NAME, //Bid model
{
//attributes
},
);
}
function down(queryInterface, Sequelize) {
return queryInterface.dropTable(TABLE_NAME);
}
function ref(Sequelize) {
return {type: Sequelize.STRING(2), references: {model: TABLE_NAME, key: 'id'}}; // 'id' here is your parent (Bids) primary key
}
module.exports = {up, down, ref};
2.On the child model where you are going to add the reference, you import the ref function and added like this, so your migration actually knows which model are you refering:
const {ref: REF_Model} = require('name-of-your-migration-file'); //import model
async function up(queryInterface, Sequelize){
await queryInterface.addColumn('Auctions', 'BidId', {...REF_Model(Sequelize), allowNull: true});
}
async function down(queryInterface){
await queryInterface.removeColumn('Auctions', 'BidId');
}
module.exports = {up, down};