I was wandering if there are any extended tutorials on how to save a many to many relationship? I find the documentation more than basic. Its missing many use case examples.
I have two models: Client and Rule. They have the n:n relationship.
Client:
var Client = sequelize.define('client', {
title: {
type: DataTypes.STRING(200),
allowNull: false
},
company: {
type: DataTypes.STRING(200),
allowNull: false
},
vendor: {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: false
},
consumer: {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: true
},
address_id: {
type: DataTypes.INTEGER,
allowNull: true
}
},{
paranoid: true,
underscored: true,
classMethods: {
associate:function(models){
Client.hasMany(models.rule, { through: 'client_rules', onDelete: 'cascade'});
}
}
});
Rule:
var Rule = sequelize.define('rule', {
service_id: {
type: DataTypes.INTEGER,
allowNull: false
},
is_allowed: {
type: DataTypes.BOOLEAN,
defaultValue: false
},
valid_until: {
type: DataTypes.DATE,
allowNull: true,
},
rule: {
type: DataTypes.TEXT,
allowNull: true
},
type: {
type: DataTypes.INTEGER, // 1 for company rule, 2 for individual rule
allowNull: false,
},
active: {
type: DataTypes.BOOLEAN,
defaultValue: true
}
},{
underscored: true,
paranoid: true,
classMethods: {
associate:function(models){
Rule.belongsToMany(models.client, { through: 'client_rules', onDelete: 'cascade'});
Rule.belongsTo(models.service, { foreignKey: 'service_id' } );
}
}
});
Now I would like to create a new rule for client. So I would have to create the rule first and associate it then to the client through 'client_rules'.
How do I do that with sequelize? This doesn't work:
var clientID = req.user.client_id;
Client.find({ id: clientID })
.then(function(client){
return client.addRule(req.body)
})
.catch(function(err){
console.log(err)
})
[TypeError: Cannot read property 'replace' of undefined]
Ok I found out how to do it. The docs are very confusing.
var clientID = req.user.client_id;
return Rule.create(req.body)
.then(function(newRule){
var ruleToAdd = newRule;
return Client.findOne({ where: { id: clientID } })
.then(function(client){
return client.addRule(ruleToAdd)
.then(function(ans){
return ruleToAdd;
})
})
Related
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.
I am using sequelize as ORM in a node project and running into some issues when querying.
Below is the code for the sequelize models in question. The table names are singular--see the use of freezable: true in the model. But when I use include it does not work and fails with the following error message
Error: Association with alias "Client" does not exist on Invoice
Model - client
module.exports = (sequelize, DataTypes) => {
const Client = sequelize.define("Client", {
Id: {
primaryKey: true,
type: "INTEGER",
autoIncrement:true
},
Name:{
type: "VARCHAR(250)",
allowNull: false,
validate: {
notEmpty: true
}
},
AddressLine1:{
type: "VARCHAR(500)",
allowNull: false,
validate: {
notEmpty: true
}
},
AddressLine2:{
type: "VARCHAR(500)",
allowNull: true,
},
AddressLine3:{
type: "VARCHAR(500)",
allowNull: true,
},
Postcode:{
type: "VARCHAR(10)",
allowNull: false,
validate: {
notEmpty: true
}
},
City:{
type: "VARCHAR(100)",
allowNull: true,
},
County:{
type: "VARCHAR(50)",
allowNull: true,
},
Country:{
type: "VARCHAR(100)",
allowNull: true,
},
ContactNumber : {
type: "VARCHAR(20)",
allowNull: true,
},
Email : {
type: "VARCHAR(500)",
allowNull: true,
},
CreatedAt :{
type:"datetimeoffset(7) DEFAULT GETDATE()",
allowNull: false
},
UpdatedAt :{
type:"datetimeoffset(7)",
allowNull: true
}
},
{freezeTableName: true, createdAt: false,updatedAt: false}
);
Client.associate=models=>{
Client.hasMany(models.Invoice,{foreignKey:"ClientId"})
}
return Client;
}
Model Invoice
module.exports = (sequelize, DataTypes) => {
const Invoice = sequelize.define("Invoice", {
Id: {
primaryKey: true,
type: "INTEGER",
autoIncrement:true
},
ClientId: {
type: DataTypes.INTEGER,
},
CompanyId: {
type: DataTypes.INTEGER,
},
InvoiceNumber:{
type: "VARCHAR(10)",
allowNull: false,
validate: {
notEmpty: true
}
},
InvoiceDate : {
type: DataTypes.DATEONLY,
},
Total: {
type: DataTypes.DECIMAL(18,2),
allowNull: true
},
CreatedAt :{
type:"datetimeoffset(7) DEFAULT GETDATE()",
allowNull: false
},
UpdatedAt :{
type:"datetimeoffset(7)",
allowNull: true
}
},
{freezeTableName: true, createdAt: false,updatedAt: false} );
Invoice.associate=models=>{
Invoice.belongsTo(models.Client,{
foreignKey:{
name:"ClientId",
allowNull:false
}
})
};
Invoice.associate=models=>{
Invoice.belongsTo(models.Company,{
foreignKey:{
name:"CompanyId",
allowNull:false
}
})
}
Invoice.associate=models=>{
Invoice.hasMany(models.InvoiceDetails,{foreignKey:"InvoiceId"})
}
return Invoice;
}
Query
let invoiceResult= await db.Invoice.findOne({where: {"InvoiceNumber":"INV001"}
,include:"Client"});
The above query does not work. However, the following one works
let invoiceResult= await db.Invoice.findOne({where: {"InvoiceNumber":"INV001"}
,include:"InvoiceDetails"});
The model for InvoiceDetails is as follows
module.exports = (sequelize, DataTypes) => {
const InvoiceDetails = sequelize.define("InvoiceDetails", {
Id: {
primaryKey: true,
type: "INTEGER",
autoIncrement:true
},
InvoiceId: {
type: DataTypes.INTEGER,
},
Description:{
type: DataTypes.STRING(500),
allowNull: false,
validate: {
notEmpty: true
}
},
Quantity:{
type: DataTypes.INTEGER,
allowNull: false,
validate: {
notEmpty: true
}
},
AmountPerUnit : {
type: DataTypes.DECIMAL(18,2),
allowNull: false,
validate: {
notEmpty: true
}
},
SubTotal: {
type: DataTypes.DECIMAL(18,2),
allowNull: false,
validate: {
notEmpty: true
}
},
VatPercentage: {
type: DataTypes.DECIMAL(5,2),
allowNull: false,
validate: {
notEmpty: true
}
},
VatAmount: {
type: DataTypes.DECIMAL(18,2),
allowNull: false,
validate: {
notEmpty: true
}
}
,
CreatedAt :{
type:"datetimeoffset(7) DEFAULT GETDATE()",
allowNull: false
},
UpdatedAt :{
type:"datetimeoffset(7)",
allowNull: true
}
},
{freezeTableName: true, createdAt: false,updatedAt: false}
);
InvoiceDetails.associate=models=>{
InvoiceDetails.belongsTo(models.Invoice,{
foreignKey:{
name:"InvoiceId",
allowNull:false
}
})
}
return InvoiceDetails;
}
Any pointers will be much appreciated.
i fixed it with the following changes
in the client model changed the following
Client.associate=models=>{
Client.hasMany(models.Invoice,{as:'Invoice',foreignKey:"ClientId"})
}
and in the invoice model
Invoice.associate=models=>{
Invoice.belongsTo(models.Client,{
as: 'Client',
foreignKey:{
name:"ClientId",
allowNull:false
}
})
};
After this change both the following queries work. The point being when using include as alias needs to be present in both the models. Atleast that's how I got it working.
let invoiceResult= await db.Invoice.findOne({where: {"InvoiceNumber":invoiceNumber}
,include:[
"Client"
]
});
let client= await db.Client.findAll({include:[
"Invoice"
]
});
Thanks for the help and taking the time to post an answer
I think this two way can help you
1:
Sequelize pluralized models try "Clients" instead
let invoiceResult= await db.Invoice.findOne({where: {"InvoiceNumber":"INV001"}
,include:"Clients"});
2: if "Clients" not work try this
let invoiceResult= await db.Invoice.findOne({where: {"InvoiceNumber":"INV001"}
,include:[{model: Client, as: "client"}]});
When using the "include" option in a query, you need to include a model object and not just the name of the table/model.
As an example I'll use a mock controller "InvoiceController.js":
const {Invoice} = require("Path to the invoice model") // or = 'db.Invoice'
const {Client} = require("Path to the client model") // or '= db.Client'
module.exports = {
async indexInvoiceWithClient (req, res) {
try {
let invoiceResults = await Invoice.findOne({
where: {
InvoiceNumber: "INV001" // req.query.invoiceNum if you request it over http/s
},
include: {
model: Client
}
})
console.log(invoiceResults)
res.status(200).send(invoiceResults)
} catch (err) {
console.log(err)
res.status(500).send({
error: "An error occured while fetching the clients on this Invoice etc..."
})
}
}
}
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.
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.
I'm trying to make this connection between these two tables, but I'm not getting it and it's giving the following error: "UnhandledPromiseRejectionWarning: SequelizeEagerLoadingError: cfg_user is not associated to dn_coluna!"
follows below the controllers and models that I am using to inner join
This is my cfg_user.js
const sequelizePaginate = require("sequelize-paginate");
module.exports = function(sequelize, DataTypes) {
const cfg_user = sequelize.define("cfg_user", {
'id_cfg_user': {
type: DataTypes.INTEGER(11),
allowNull: false,
primaryKey: true,
autoIncrement: true
},
'id_cfg_grupo': {
type: DataTypes.INTEGER(11),
allowNull: true
},
'nome': {
type: DataTypes.CHAR(100),
allowNull: true
},
'email': {
type: DataTypes.CHAR(100),
allowNull: true
},
'arquivo': {
type: DataTypes.CHAR(255),
allowNull: false
},
'senha': {
type: DataTypes.CHAR(32),
allowNull: true
}
},{
tableName: "cfg_user",
freezeTableName: true,
timestamps: false
});
sequelizePaginate.paginate(cfg_user);
return cfg_user;
};
This is my dn_coluna.js
const sequelizePaginate = require("sequelize-paginate");
const cfg_user = require("./cfg_user");
module.exports = function(sequelize, DataTypes) {
const dn_coluna = sequelize.define("dn_coluna", {
'id_dn_coluna': {
type: DataTypes.INTEGER(10),
allowNull: false,
primaryKey: true,
autoIncrement: true
},
'id_cfg_user': {
type: DataTypes.INTEGER(10),
allowNull: false
},
'coluna': {
type: DataTypes.CHAR(255),
allowNull: false
},
'frase': {
type: DataTypes.CHAR(255),
allowNull: true,
},
'resumo': {
type: DataTypes.CHAR(255),
allowNull: true,
},
'url': {
type: DataTypes.CHAR(255),
allowNull: true
},
'arquivo': {
type: DataTypes.CHAR(255),
allowNull: true
},
'arquivo_topo': {
type: DataTypes.CHAR(255),
allowNull: true
},
'arquivo_bg': {
type: DataTypes.CHAR(255),
allowNull: true
},
'ordem': {
type: DataTypes.INTEGER(11),
allowNull: true
},
'destaque':{
type: DataTypes.TINYINT(1),
allowNull: true
},
'libera': {
type: DataTypes.TINYINT(1),
allowNull: true
}
},{
tableName: "dn_coluna",
freezeTableName: true,
timestamps: false
});
sequelizePaginate.paginate(dn_coluna);
return dn_coluna;
};
this is my Controller
const { dn_coluna } = require("../models");
const { cfg_user } = require("../models");
module.exports = {
async index(req, res){
const colunas = await dn_coluna.findAll({
include: [{
model: cfg_user,
attributes: []
}],
attributes: ['id_cfg_user'],
})
.then(colunas => {
return res.status(200).json(colunas);
});
},
};
Your answer lies in associations. You are not properly associating your tables together. Here is a link to the documentation. I am unsure of the type of relationship that cfg_user and dn_coluna have so I can't show you how to write the association but the below may help.
Remove the below from dn_coluna.js
'id_cfg_user': {
type: DataTypes.INTEGER(10),
allowNull: false
}
Add in to dn_coluna.js (just above sequelizePaginate.paginate(dn_coluna);):
dn_coluna.associate = (models) => {
dn_coluna.hasMany(models.cfg_user, {foreignKey: 'cfg_user_id'})
}
In cfg_user.js add (just above sequelizePaginate.paginate(cfg_user);):
cfg_user.associate = (models) => {
cfg_user.belongsTo(models.dn_coluna)
}
The above will create a one to many relationship between dn_coluna and cfg_user.