Sequelize assocation to same table - javascript

I have a table called documents that has a column called parentId which is a reference to another document record.
With my current code i'm getting the error
insert or update on table "documents" violates foreign key constraint "documents_parentId_fkey"
documents migration
'use strict'
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('documents', {
id: {
allowNull: false,
primaryKey: true,
type: Sequelize.UUID,
defaultValue: Sequelize.UUIDV4
},
parentId: {
allowNull: true,
type: Sequelize.UUID,
references: {
model: 'documents',
key: 'id'
}
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
lastUpdatedAt: {
allowNull: false,
type: Sequelize.DATE
},
lastUpdatedBy: {
allowNull: false,
type: Sequelize.UUID
}
})
},
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('documents')
}
}
document model
'use strict'
module.exports = (sequelize, DataTypes) => {
const document = sequelize.define('document', {
id: {
allowNull: false,
primaryKey: true,
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4
},
parentId: {
allowNull: true,
type: DataTypes.UUID,
references: {
model: 'documents',
key: 'id'
}
},
lastUpdatedBy: {
allowNull: false,
type: DataTypes.UUID
}
},
{
updatedAt: 'lastUpdatedAt'
})
document.associate = function (models) {
document.belongsTo(models.document, { foreignKey: 'parentId' })
}
return document
}
How do you properly do associations to the same table?

I have a self referencing table configured with the constraints: false setting.
MyModel.belongsTo(MyModel, {
as: 'parentMyModel',
foreignKey: 'parentId',
constraints: false,
});

Looks like the constraint is valid (and a good one). My payload that I was submitting had a parent uuid which didn't actually reference any document with that id.
So my code was right, the data I was submitting was wrong.

Related

Node JS API Sequelize PostgreSQL UUID as primary key return error "column Nan does not exist"

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 }

Why is Sequelize only returning one object using findAll()?

My goal is to be able to find all products by their brand name and model name. However, Sequelize is only returning one record out of many other similar records. If it does return more than one record, other records with identical attributes as the first record found will be null. For example, the first record in the array will have the attribute name: iPhone, the second record which has the exact same attribute will be shown as name: null when it should be name: iPhone.
In my database, I have the following tables:
Products, Brands, Models, and Suppliers. The Products table contains foreign keys such as brand_id, model_id, etc.. Brands, Models, and Suppliers have the attribute: id.
I have set the relationship up as the following:
Products.hasOne(Brands, { foreignKey: 'id' });
Products.hasOne(Models, { foreignKey: 'id' });
Products.hasOne(Suppliers, { foreignKey: 'id' });
Brands.belongsTo(Products);
Models.belongsTo(Products);
Suppliers.belongsTo(Products);
In my search function, I attempt to find all products by brand and model name that match my query.
const getSearch = (req, res) => {
const { query: { query } } = req;
Products.findAll({
where: Sequelize.where(Sequelize.fn('concat', Sequelize.col('Brand.name'), ' ', Sequelize.col('Model.name')), {
[Op.substring]: query
}),
include: [
{ model: Brands, attributes: ['name'] },
{ model: Models, attributes: ['name'] },
{ model: Suppliers, attributes: ['name'] },
],
attributes: ['id', 'price']
})
.then((data) => {
console.log(data);
res.send(data);
})
.catch((err) => (console.log(err)));
};
In my database, I have two product rows with the exact same data but different ids. When calling getSearch I expect to see two objects in the array as they have the same brand name and model name. Instead I see one.
Here's what my models look like:
Products
class Products extends Model {
static init(sequelize, DataTypes) {
return super.init(
{
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
url_id: {
type: DataTypes.INTEGER,
allowNull: true
},
brand_id: {
type: DataTypes.INTEGER,
allowNull: true
},
model_id: {
type: DataTypes.INTEGER,
allowNull: true
},
supplier_id: {
type: DataTypes.INTEGER,
allowNull: true
},
image: {
type: DataTypes.STRING,
allowNull: true
},
description: {
type: DataTypes.STRING,
allowNull: true
},
price: {
type: DataTypes.DOUBLE,
allowNull: true
}
},
{
modelName: 'Products',
timestamps: false,
sequelize
}
);
}
}
Models
class Models extends Model {
static init(sequelize, DataTypes) {
return super.init(
{
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
name: {
type: DataTypes.STRING,
allowNull: true
},
colour_id: {
type: DataTypes.INTEGER,
allowNull: true
},
storage_capacity_id: {
type: DataTypes.INTEGER,
allowNull: true
}
},
{
modelName: 'Models',
timestamps: false,
sequelize
}
);
}
}
Brands
class Brands extends Model {
static init(sequelize, DataTypes) {
return super.init(
{
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
name: {
type: DataTypes.STRING,
allowNull: true
}
},
{
modelName: 'Brands',
timestamps: false,
sequelize
}
);
}
}
Suppliers
class Suppliers extends Model {
static init(sequelize, DataTypes) {
return super.init(
{
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
name: {
type: DataTypes.STRING,
allowNull: true
}
},
{
modelName: 'Suppliers',
timestamps: false,
sequelize
}
);
}
}
What am I doing wrong here?
You have an error in associations. Just change hasOne to hasMany and you are done.

Sequelize bulkCreate not including newly added columns

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;
};

sequelize - Cannot add foreign key constraint

I'm trying to set a 1:1 relation between two tables. RefreshToken table will have two foreignKey releated to Users table, as in this image:
I used sequelize-auto to generate my sequelize models.
Users model:
module.exports = function(sequelize, DataTypes) {
return sequelize.define('Users', {
idUsers: {
type: DataTypes.INTEGER(11),
allowNull: false,
primaryKey: true
},
name: {
type: DataTypes.STRING(45),
allowNull: true
},
mail: {
type: DataTypes.STRING(45),
allowNull: false,
primaryKey: true
}
}, {
tableName: 'Users'
});
};
RefreshToken model:
module.exports = function(sequelize, DataTypes) {
return sequelize.define('RefreshToken', {
idRefreshToken: {
type: DataTypes.INTEGER(11),
allowNull: false,
primaryKey: true
},
token: {
type: DataTypes.TEXT,
allowNull: true
},
userId: {
type: DataTypes.INTEGER(11),
allowNull: false,
primaryKey: true,
references: {
model: 'Users',
key: 'idUsers'
}
},
userEmail: {
type: DataTypes.STRING(45),
allowNull: false,
primaryKey: true,
references: {
model: 'Users',
key: 'mail'
}
}
}, {
tableName: 'RefreshToken'
});
};
When I run the application, I receive this error:
Unhandled rejection Error: SequelizeDatabaseError:
ER_CANNOT_ADD_FOREIGN: Cannot add foreign key constraint
I tried to add explicit the relation, adding in Users table:
User.associate = (models) => {
User.hasOne(models.RefreshToken, {
foreignKey: 'userId'
});
User.hasOne(models.RefreshToken, {
foreignKey: 'userEmail'
});
};
and in RefreshToken:
RefreshToken.associate = (models) => {
RefreshToken.belongsTo(models.Users, {
foreignKey: 'userId'
});
RefreshToken.belongsTo(models.Users, {
foreignKey: 'userEmail'
});
};
But I receive again the same error. If I remove the references in the RefreshToken table I don't see any error, but when I check the database I don't see any foreign key relation constraint with email and id of the User
This is common type error mainly occurs because of
1. When the primary key data type and the foreign key data type did not matched
return sequelize.define('RefreshToken', {
userId: {
type: DataTypes.INTEGER(11), // The data type defined here and
references: {
model: 'Users',
key: 'idUsers'
}
},
return sequelize.define('Users', {
idUsers: {
type: DataTypes.INTEGER(11), // This data type should be the same
},
2. When the referenced key is not a primary or unique key.
You can not have two primary keys, so other referenced keys should be defined unique. unique:true
return sequelize.define('Users', {
idUsers: {
primaryKey: true
},
mail: {
type: DataTypes.STRING(45),
allowNull: false,
primaryKey: true // You should change this to 'unique:true'. you cant hv two primary keys in one table.
}
I see two issues:
No table should contain two primary keys and userId shouldn't be in integer it should be a UUID.
I had a foreign key set to INT and it gave me error:
Unhandled rejection SequelizeDatabaseError: foreign key constraint
"constraint_name_here" cannot be implemented
Try changing:
userId: {
type: DataTypes.INTEGER(11),
allowNull: false,
primaryKey: true,
references: {
model: 'Users',
key: 'idUsers'
}
},
To
userId: {
type: DataTypes.UUID,
allowNull: false,
foreignKey: true,
references: {
model: 'Users',
key: 'idUsers'
}
},

id: null when creating a new item in sequelize

When I try to create a new Conversation item Sequelize will return an object with id: null eventhough there is an valid id in the database. How can I get Sequelize to return the last inserted id to the newly created item?
Conversation.create({
type: 'private',
createdBy: 1,
}).then(conversation => {
reply(conversation);
});
Will return
{
"type": "conversations",
"id": null,
"createdBy": 1,
"created_at": "2016-03-18T01:47:48.000Z"
}
My code:
const Conversation = model.define('Conversation', {
id: {
type: Sequelize.INTEGER,
primaryKey: true,
},
type: {
type: Sequelize.ENUM,
values: ['private', 'group'],
validate: {
isIn: ['private', 'group'],
},
},
createdBy: {
type: Sequelize.INTEGER,
field: 'created_by',
},
}, {
tableName: 'conversations',
timestamps: true,
createdAt: 'created_at',
updatedAt: false,
getterMethods: {
type: () => 'conversations',
},
});
const User = model.define('User', {
id: {
type: Sequelize.INTEGER,
primaryKey: true,
},
firstName: {
type: Sequelize.STRING,
field: 'first_name',
allowNull: false,
},
lastName: {
type: Sequelize.STRING,
field: 'last_name',
allowNull: true,
},
email: {
type: Sequelize.STRING,
allowNull: false,
},
profileImg: {
type: Sequelize.STRING,
field: 'profile_img',
allowNull: false,
},
password: Sequelize.STRING,
}, {
tableName: 'users',
timestamps: true,
createdAt: 'created_at',
updatedAt: 'updated_at',
getterMethods: {
type: () => 'users',
},
});
Conversation.belongsToMany(User, {
foreignKey: 'conversation_id',
otherKey: 'user_id',
through: 'conversation_user',
timestamps: false,
});
User.belongsToMany(Conversation, {
as: 'conversations',
foreignKey: 'user_id',
otherKey: 'conversation_id',
through: 'conversation_user',
timestamps: false,
});
Yo need to put autoIncrement: true in id field:
id: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true,
}
Personally I would advice to skip the id column as sequalize does it automatically for you and works nicely.
hope it helps :)
Problem was my MySQL version (5.6 instead of 5.7), updated it and now I'm getting id's of the created items in the promise.
I'm not sure about how Sequelize is working with id field, I get null if I do instance.id, bug I can get the real value at DB if I do the following:
console.info(instance.id); // null
console.info(instance.get('id')); // 25 => Real ID
console.info(instance.getDataValue('id')); // 25 => Real ID
Something similar is happening with other fields like createdAt and updatedAt.
In order to get the real value at id field and other related fields, I added following logic to Model declaration:
class FooModel extends Model {
// ...
/**
* #inheritdoc
*/
public async save(options?: SaveOptions<TModelAttributes>): Promise<this> {
await super.save(options);
this.loadBaseData();
return this;
}
/**
* #inheritdoc
*/
public async reload(options?: FindOptions<TModelAttributes>): Promise<this> {
await super.reload(options);
this.loadBaseData();
return this;
}
private loadBaseData() {
this.id = this.getDataValue('id');
this.createdAt = this.getDataValue('createdAt');
this.updatedAt = this.getDataValue('updatedAt');
}
}
because if you only build without save it then:
instance.id // null
so you need:
instance.save()
instance.id // someNumber

Categories

Resources