i have a strange effekt at a m:n relation..
this are the model definitions:
Role Model:
'use strict';
module.exports = (sequelize, DataTypes) => {
const Role = sequelize.define('Role', {
uuid: {
allowNull: false,
primaryKey: true,
unique: true,
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
},
....
}, {});
/** #param models.User */
Role.associate = function(models) {
Role.belongsToMany(
models.User, {
through: 'user_role',
foreignKey: 'userId',
}
);
};
return Role;
};
User Model:
'use strict';
module.exports = (sequelize, DataTypes) => {
const User = sequelize.define('User', {
uuid: {
allowNull: false,
primaryKey: true,
unique: true,
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
},
....
}, {});
/** #param models.Role */
User.associate = function(models) {
User.belongsToMany(
models.Role, {
through: 'user_role',
foreignKey: 'roleId',
}
);
};
return User;
};
the migration is the following:
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('user', {
uuid: {
allowNull: false,
unique: true,
primaryKey: true,
type: Sequelize.UUIDV4,
defaultValue: Sequelize.UUIDV4,
},
....
}).then(() => {
queryInterface.createTable('role', {
uuid: {
allowNull: false,
unique: true,
primaryKey: true,
type: Sequelize.UUIDV4,
defaultValue: Sequelize.UUIDV4,
},
....
});
}).then(() => {
queryInterface.createTable('user_role', {
userId: {
type: Sequelize.UUIDV4,
references: {
model: 'User',
key: 'uuid',
},
allowNull: false,
},
roleId: {
type: Sequelize.UUIDV4,
references: {
model: 'Role',
key: 'uuid',
},
allowNull: false,
},
....
});
}).then(() => {
return queryInterface.addConstraint('user_role', ['UserId', 'RoleId'], {
unique: true,
type: 'primary key',
name: 'userrole_pkey',
});
});
},
down: (queryInterface, Sequelize) => {
....
},
};
if i try to insert now a user with a new role:
let models = require('../models');
models.Role.create({
role: 'Administrator',
description: 'Administrator Gruppe',
}).then(role => {
models.User.create({
login: 'admin',
password: '123',
nick: 'Admini',
mail: 'admin#localhost.com',
}).then(user => {
user.addRole(role);
user.save().then(() => {
console.log('admin created');
}).catch(err => {
console.log(err);
});
}).catch(err => {
console.log(err);
});
}).catch(err => {
console.log(err);
});
it tries to add the role uuid in the userid and the user uuid in the roleid.. and for that the constraint fails...
any hints or tips where i made a mistake?
found the mistake myself (with help of a college)
at
models.User, {
through: 'user_role',
foreignKey: 'userId',
}
i set the wrong foreign key, it's not the field in the helper table, it's needed to be the source table (in this case uuid of user model) or leave it blank for sequelize's default behaviour to use the primary key.
Related
I have a Node.js application with Express, Sequelize as ORM and PostgreSQL for the database. In this app I have candidate model and mission model as below.
'use strict';
const {
Model
} = require('sequelize');
module.exports = (sequelize, DataTypes) => {
class candidat extends Model {
static associate(models) {
this.belongsToMany(models.mission, {
through: "candidat_mission",
foreignKey: "candidatId",
otherKey: "idMission",
});
}
}
candidat.init({
candidatId: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: DataTypes.INTEGER
},
lastName: {
type: DataTypes.STRING,
allowNull: false,
},
firstName: {
type: DataTypes.STRING,
allowNull: false,
},
email: {
isEmail: true,
allowNull: false,
type: DataTypes.STRING,
unique: true,
},
}, {
sequelize,
modelName: 'candidat',
tableName: 'candidat',
freezeTableName: true,
});
return candidat;
};
'use strict';
const {
Model
} = require('sequelize');
module.exports = (sequelize, DataTypes) => {
class mission extends Model {
static associate(models) {
this.belongsToMany(models.candidat, {
through: "candidat_mission",
foreignKey: "idMission",
otherKey: "candidatId",
})
}
}
mission.init({
idMission: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: DataTypes.INTEGER
},
title: {
type: DataTypes.STRING,
allowNull: false
},
aliasTitle: {
type: DataTypes.STRING,
allowNull: true
},
description: {
type: DataTypes.TEXT,
allowNull: true
}
}, {
sequelize,
modelName: 'mission',
tableName: 'mission',
freezeTableName: true,
});
return mission;
};
These two models are linked in many-to-many by a candidate_mission join table. In this model, I added fields like a foreign key which points to another table, that of users.
'use strict';
const {
Model
} = require('sequelize');
module.exports = (sequelize, DataTypes) => {
class candidat_mission extends Model {
static associate(models) {
this.belongsTo(models.user, { foreignKey: "fk_user" });
}
}
candidat_mission.init({
candidatMissionId: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: DataTypes.INTEGER
},
candidatId: {
type: DataTypes.INTEGER,
allowNull: false,
references: {
model: { tableName: 'candidat' },
key: "candidatId",
},
},
idMission: {
type: DataTypes.INTEGER,
allowNull: false,
references: {
model: { tableName: 'mission' },
key: "idMission",
},
},
fk_user: {
type: DataTypes.INTEGER,
allowNull: true,
references: {
model: { tableName: 'user' },
key: "userId",
},
},
}, {
sequelize,
modelName: 'candidat_mission',
tableName: 'candidat_mission',
timestamps: true,
freezeTableName: true,
});
return candidat_mission;
};
When I make a "GET" request, I do have the information from the candidate_mission table (if a candidate is linked to this mission), but for the user it only returns the ID and I would like it to return all the fields present in the Users model, what can I do?
Here, my function in the mission controller which allows to add a candidate to this mission :
const addCandidats = async (req, res) => {
try {
const mission = await Mission.findByPk(req.body.idMission);
if (mission) {
const candidat = await Candidat.findByPk(req.body.candidatId);
if (candidat) {
mission.addCandidat(candidat,
{through: {
fk_user: req.body.fk_user && req.body.fk_user
}});
return res.status(200).send(mission);
} else {
console.log("Candidat non trouvé");
return null;
}
} else {
console.log("Mission non trouvée!")
return null;
}
} catch (error) {
console.log(error);
}
};
Currently, my query returns me this :
"candidat_mission":
{
"candidatMissionId": 2,
"candidatId": 1,
"idMission": 7,
"fk_user": 1,
"createdAt": "2023-02-14T10:34:08.302Z",
"updatedAt": "2023-02-14T15:06:10.232Z"
},
And i want it to come back to me :
"candidat_mission":
{
"candidatMissionId": 2,
"candidatId": 1,
"idMission": 7,
"fk_user": {
"userId": 1,
"email": "blabla#gmail.com",
"name": "blabla"
},
"createdAt": "2023-02-14T10:34:08.302Z",
"updatedAt": "2023-02-14T15:06:10.232Z"
},
After associating 2 models, we have to query again to get the object along with the relationship.
await mission.addCandidat(candidat,
{through: {
fk_user: req.body.fk_user && req.body.fk_user
}});
const result = await CandidatMission.findOne({
where: { candidatId: req.body.candidatId, idMission: req.body.idMission },
include: models.user,
})
return res.status(200).send(result);
I'm working on a REST API for the backend of a simple e-commerce app using Node JS, PostgreSQL and Sequelize, and I'm facing an issue with Sequelize when I try to add a product to the shopping cart. It returns an error "column Nan does not exist"
Initially I was using Integer for the user Id as the primary key, then I changed for UUID to better suit the purpose.
The code I'm using for the models and migrations is the following:
//User model
export default (sequelize, DataTypes) => {
const User = sequelize.define(
'User',
{
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
allowNull: false,
primaryKey: true,
unique: true,
},
name: {
type: DataTypes.STRING,
allowNull: false
},
password: {
type: DataTypes.STRING,
allowNull: false
},
email: {
type: DataTypes.STRING,
allowNull: false,
unique: {
name: 'user_email',
msg: 'A user with this email already exists.'
}
},
},
User.associate = models => {
User.hasOne(models.Cart, {
foreignKey: 'userId',
as: 'cart',
onDelete: 'cascade'
});
};
User.associate = models => {
User.hasMany(models.Order, {
foreignKey: 'userId',
as: 'orders',
onDelete: 'cascade'
});
};
return User;
};
//User migration
export const up = (queryInterface, Sequelize) =>
queryInterface.createTable('Users', {
id: {
type: Sequelize.UUID,
defaultValue: Sequelize.UUIDV4,
allowNull: false,
primaryKey: true,
unique: true,
},
name: {
allowNull: false,
type: Sequelize.STRING
},
password: Sequelize.STRING,
email: {
allowNull: false,
type: Sequelize.STRING,
unique: true
},
createdAt: {
allowNull: false,
type: Sequelize.DATE,
defaultValue: Sequelize.fn('now')
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE,
defaultValue: Sequelize.fn('now')
},
});
export const down = queryInterface => queryInterface.dropTable('Users');
Cart model
export default (sequelize, DataTypes) => {
const Cart = sequelize.define('Cart', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
allowNull: false,
primaryKey: true,
},
userId: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
allowNull: false
},
cartItem: {
type: DataTypes.TEXT,
allowNull: false,
get(value) {
return JSON.parse(this.getDataValue(value));
},
set(value) {
this.setDataValue('cartItem', JSON.stringify(value));
}
}
});
Cart.associate = models => {
Cart.belongsTo(models.User, {
foreignKey: 'userId',
as: 'owner'
});
};
return Cart;
};
Cart migration
export const up = (queryInterface, Sequelize) =>
queryInterface.createTable('Carts', {
id: {
allowNull: false,
primaryKey: true,
type: Sequelize.UUID,
defaultValue: Sequelize.UUIDV4,
},
userId: {
type: Sequelize.UUID,
defaultValue: Sequelize.UUIDV4,
allowNull: false
},
cartItem: {
type: Sequelize.TEXT,
allowNull: false
},
createdAt: {
allowNull: false,
type: Sequelize.DATE,
defaultValue: Sequelize.fn('now')
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE,
defaultValue: Sequelize.fn('now')
}
});
export const down = queryInterface => queryInterface.dropTable('Carts');
Code to handle the add to cart:
addToCart() {
return this.asyncWrapper(async (req, res) => {
const { body, user } = req;
body.userId = user.id;
const cart = await this.service.addToCart(body);
this.sendResponse(res, cart, undefined, 201);
});
}
Add to cart service
async cart(userId, options = {}) {
const cart = await super.find({ userId }, options);
return cart;
}
async addToCart(data, options) {
const { userId, productId, qty } = data;
const [result] = await this.model.findOrCreate({
where: { userId: +userId },
defaults: { cartItem: new CartItem() }
});
const cartData = JSON.parse(result.dataValues.cartItem);
const cartItem = new CartItem(cartData);
const product = await ProductService.getById(productId, { plain: true });
ExceptionHandler.throwErrorIfNull(product);
const cart = cartItem.addToCart(product, qty);
result.cartItem = cart;
result.save();
return result;
}
The SQL query generated by Sequelize is the following:
SELECT "id","userId","cartItem","createdAt","updatedAt" FROM "Carts" AS "Cart" WHERE "Cart"."userId" = NaN LIMIT 1;
The goal is to use UUID as primary key in the database.
This issue started when I changed the Datatype from Integer for UUID and I can't see what is wrong with the code.
Any advice on how to solve this?
Sequelize version: "^5.21.9" with "pg": "^8.2.0" and "pg-hstore": "^2.3.3".
If you switched a data type from INTEGER to UUID you shouldn't try to convert UUID-string to a number doing where: { userId: +userId }.
Pass userId as is:
where: { userId }
I'm getting this error:
AggregateError: aggregate error
at Promise.try.then
...
when trying to setup a many to many table in sequelize with this code:
let _document
db.document.create(payload, { include: ['documentAttributes', 'documentChildren'] })
.then(document => {
_document = document
const promises = []
// Add tags if there are any
if (req.body.tags) {
req.body.tags.forEach(tag => {
promises.push(db.tag.findOrCreate({ where: { key: tag.key } }))
})
}
return Promise.all(promises)
})
.then(tags => {
if (tags.length > 0) {
const allTags = tags.map(tag => tag[0])
return _document.setTags(allTags) // THIS LINE CAUSES THE ISSUE
}
document.js model:
'use strict'
module.exports = (sequelize, DataTypes) => {
const document = sequelize.define('document', {
id: {
allowNull: false,
primaryKey: true,
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4
},
...
lastUpdatedBy: {
allowNull: false,
type: DataTypes.UUID
}
},
{
updatedAt: 'lastUpdatedAt'
})
document.associate = function (models) {
document.hasMany(models.documentAttribute)
document.hasMany(models.documentChildren)
document.belongsToMany(models.tag, { through: 'documentTags' })
}
return document
}
tag.js model:
'use strict'
module.exports = (sequelize, DataTypes) => {
const tag = sequelize.define('tag', {
id: {
allowNull: false,
primaryKey: true,
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4
},
key: {
allowNull: false,
type: DataTypes.STRING
},
value: {
allowNull: false,
type: DataTypes.STRING
},
lastUpdatedBy: {
allowNull: false,
type: DataTypes.UUID
}
},
{
updatedAt: 'lastUpdatedAt'
})
tag.associate = function (models) {
tag.belongsToMany(models.document, { through: 'documentTags' })
}
return tag
}
documenttags.js model:
'use strict'
module.exports = (sequelize, DataTypes) => {
const documentTags = sequelize.define('documentTags', {
id: {
allowNull: false,
primaryKey: true,
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4
},
documentId: {
allowNull: false,
type: DataTypes.UUID
},
tagId: {
allowNull: false,
type: DataTypes.UUID
},
lastUpdatedBy: {
allowNull: false,
type: DataTypes.UUID
}
},
{
updatedAt: 'lastUpdatedAt'
})
documentTags.associate = function (models) {
// associations can be defined here
}
return documentTags
}
After reviewing the docs and discussing in the chat. The through property is necessary to set extra properties on the many-to-many table.
http://docs.sequelizejs.com/class/lib/associations/belongs-to-many.js~BelongsToMany.html
return _document.setTags(allTags, { through: { lastUpdatedBy: tag.lastUpdatedBy }})
I'm having difficulty understanding why bulkCreate will not include my two newly created columns, perhaps it's the migration?
My new migration is as follows:
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
queryInterface.addColumn('users', 'brand_id', {
allowNull: true,
type: Sequelize.INTEGER,
defaultValue: null,
});
queryInterface.addColumn('users', 'store_id', {
allowNull: true,
type: Sequelize.INTEGER,
defaultValue: null,
});
return true;
},
down: (queryInterface, Sequelize) => {
queryInterface.removeColumn('users', 'brand_id');
queryInterface.removeColumn('users', 'store_id');
return true;
}
};
I have a helper function for creating multiple users for the purpose of testing that looks something like this:
const properties = { brand_id: 123 };
const user = [];
users.push(Object.assign({}, {
name: chance.last(),
email: chance.email(),
password,
access_key: uuid(),
}, properties));
const newUsers = await models.user.bulkCreate(users, { returning: true, logging: console.log });
The output of the logging is:
INSERT INTO "users" ("id","name","email","password","access_key","created_at","updated_at") VALUES (DEFAULT,'Valente','letubdo#iwefa.fm','$2a$08$B5riQzA82ChwuH1q8HpGxOBK2uQj2m.BiHcEjytiox5yD.8u1fT5W','e62bf96c-0117-490f-9c80-b60e406238b0','2018-09-25 18:30:04.666 +00:00','2018-09-25 18:30:04.666 +00:00') RETURNING *;
You'll see that brand_id is completely ignored in the query, even if I change the following:
users.push(Object.assign({}, {
name: chance.last(),
email: chance.email(),
password,
access_key: uuid(),
brand_id: 123,
}, properties));
Any idea what could be wrong?
It turns out I did not add the two new columns to the user model:
'use strict';
module.exports = (sequelize, DataTypes) => {
var user = sequelize.define('user', {
name: {
allowNull: false,
type: DataTypes.STRING
},
email: {
allowNull: false,
type: DataTypes.STRING
},
password: {
allowNull: false,
type: DataTypes.STRING
},
access_key: {
allowNull: false,
type: DataTypes.STRING
},
brand_id: {
allowNull: true,
true: DataTypes.INTEGER,
},
store_id: {
allowNull: true,
true: DataTypes.INTEGER,
}
}, {
underscored: true,
});
return user;
};
Model EmployeeView
module.exports = function(sequelize, DataTypes) {
var _this = sequelize.define('EmployeeView', {
employeeId: {
type: DataTypes.INTEGER,
field: 'code'
},
username: DataTypes.STRING,
email: {
type: DataTypes.STRING,
field: 'emailaddress'
},
department: {
type: DataTypes.STRING,
field: 'department_name'
},
departmentId: {
type: DataTypes.STRING,
field: 'departments_id'
}
}, {
timestamps: false,
freezeTableName: true,
tableName: 'employees_view',
classMethods: {
associate: function(models) {
_this.belongsTo(models.EmployeeCategory, {
foreignKey: {
name: 'employeecategories_id'
}
});
_this.hasMany(models.EmployeeFile, {
foreignKey: 'employees_code'
});
}
}
});
return _this;
};
Model EmployeeFile
module.exports = function(sequelize, DataTypes) {
var _this = sequelize.define("EmployeeFile", {
employeeId: {
type: DataTypes.INTEGER,
field: 'employees_code'
},
filename: {
type: DataTypes.STRING,
filed: 'filename'
},
employeeFileTypeId: {
type: DataTypes.INTEGER,
field: 'employee_file_types_id'
}
}, {
timestamps: false,
freezeTableName: true,
tableName: 'employee_files'
});
return _this;
};
Router
router.get('/employee', function(req, res) {
models.EmployeeView.findAll({
where: {
active: req.query.active
}
include: [{
model: models.EmployeeCategory
}, {
model: models.EmployeeFile,
}]
}).then(function(employee) {
res.json(employee);
});
});
What do I expect to happen?
I have two tables 'employee_view' (it is a view) and 'employee_files' which map to the 'EmployeeView' and 'EmployeeFile'. 'employee_view' has 'id' field as the primary key and 'code' field as the employee number.'employee_files' has 'employees_code' as its primary key and foreignKey which bindings with the 'code' field. So I want to get 'employee_files' data through this relation.
What is actually happening?
Actually,I got nothing. Because the sequelize will execute "EmployeeView.id == EmployeeFile.employees_code". But I want the sequelize to execute "EmployeeView.code == EmployeeFile.employees_code" .What should I do?
just add primaryKey: true to your employeeId field to link the EmployeeView to EmployeeFiles since the belongsToMany relationship will only link to the primary key of it's parent
employeeId: {
type: sequelize.INTEGER,
field: 'code',
primaryKey: true
},