edit column/attributes in sequelize migration - javascript

Hi I'm new to migrations in sequelize, and I'm not sure how to add field/property for attributes. So my scenario is that I have two attributes sku & barcode, but I forgot to add unique: true. Now I need to edit the table, I have tried with addColumn, changeColumn and addIndex, but nothing works. I don't know if my approach is correct or not, please help.
Here my migration approach
"use strict";
module.exports = {
async up(queryInterface, Sequelize) {
/**
* Add altering commands here.
*
* Example:
* await queryInterface.createTable('users', { id: Sequelize.INTEGER });
*/
// *********** received duplicate error ***********
// await queryInterface.addColumn("Products", "sku", {
// unique: true,
// type: Sequelize.STRING,
// allowNull: false,
// validate: {
// notNull: {
// msg: "Product SKU cannot be empty",
// },
// notEmpty: {
// msg: "Product SKU cannot be empty",
// },
// },
// });
// await queryInterface.addColumn("Products", "barcode", {
// unique: true,
// type: Sequelize.STRING,
// allowNull: false,
// validate: {
// notNull: {
// msg: "Product barcode cannot be empty",
// },
// notEmpty: {
// msg: "Product barcode cannot be empty",
// },
// },
// });
// ***************** received Cannot create property 'fields' ***********
// await queryInterface.addIndex("Products", "sku", {
// unique: true,
// });
// await queryInterface.addIndex("Products", "barcode", {
// unique: true,
// });
// **************** received Validation error *************
await queryInterface.changeColumn("Products", "sku", {
unique: true,
type: Sequelize.STRING,
allowNull: false,
validate: {
notNull: {
msg: "Product SKU cannot be empty",
},
notEmpty: {
msg: "Product SKU cannot be empty",
},
},
});
await queryInterface.changeColumn("Products", "barcode", {
unique: true,
type: Sequelize.STRING,
allowNull: false,
validate: {
notNull: {
msg: "Product barcode cannot be empty",
},
notEmpty: {
msg: "Product barcode cannot be empty",
},
},
});
},
async down(queryInterface, Sequelize) {
/**
* Add reverting commands here.
*
* Example:
* await queryInterface.dropTable('users');
*/
// await queryInterface.removeColumn("Products", "sku");
// await queryInterface.removeColumn("Products", "barcode");
},
};
Here how my model looks like:
"use strict";
const { Model } = require("sequelize");
module.exports = (sequelize, DataTypes) => {
class Product extends Model {
/**
* Helper method for defining associations.
* This method is not a part of Sequelize lifecycle.
* The `models/index` file will call this method automatically.
*/
static associate(models) {
// define association here
Product.belongsTo(models.Brand, {
foreignKey: "brandId",
});
Product.belongsTo(models.Category, {
foreignKey: "categoryId",
as: "Category",
});
Product.belongsTo(models.Category, {
foreignKey: "subcategoryId",
as: "Subcategory",
});
Product.belongsToMany(models.Tag, {
through: models.ProductTag,
foreignKey: "productId",
});
Product.hasMany(models.ProductInfo, {
foreignKey: "productId",
});
Product.hasMany(models.ProductPhoto, {
foreignKey: "productId",
});
Product.hasMany(models.ProductStockHistory, {
foreignKey: "productId",
});
Product.hasMany(models.Trolley, {
foreignKey: "productId",
targetKey: "id",
});
Product.hasMany(models.Wishlist, {
foreignKey: "productId",
targetKey: "id",
});
Product.belongsToMany(models.PromoPrice, {
through: models.ProductPromoPrice,
foreignKey: "ProductId",
});
Product.hasOne(models.ProductPromoPrice, {
foreignKey: "ProductId",
as: "SpecialPrice",
});
}
}
Product.init(
{
sku: {
type: DataTypes.STRING,
allowNull: false,
validate: {
notNull: {
msg: "Product SKU cannot be empty",
},
notEmpty: {
msg: "Product SKU cannot be empty",
},
},
},
barcode: {
type: DataTypes.STRING,
allowNull: false,
validate: {
notNull: {
msg: "Product barcode cannot be empty",
},
notEmpty: {
msg: "Product barcode cannot be empty",
},
},
},
name: {
type: DataTypes.STRING,
allowNull: false,
validate: {
notNull: {
msg: "Product name cannot be empty",
},
notEmpty: {
msg: "Product name cannot be empty",
},
},
},
description: {
type: DataTypes.TEXT,
allowNull: false,
validate: {
notNull: {
msg: "Product description cannot be empty",
},
notEmpty: {
msg: "Product description cannot be empty",
},
},
},
brandId: {
type: DataTypes.INTEGER,
allowNull: false,
validate: {
notNull: {
msg: "Product brandId cannot be empty",
},
notEmpty: {
msg: "Product brandId cannot be empty",
},
},
},
categoryId: {
type: DataTypes.INTEGER,
allowNull: false,
validate: {
notNull: {
msg: "Product categoryId cannot be empty",
},
notEmpty: {
msg: "Product categoryId cannot be empty",
},
},
},
subcategoryId: {
type: DataTypes.INTEGER,
allowNull: false,
validate: {
notNull: {
msg: "Product subcategoryId cannot be empty",
},
notEmpty: {
msg: "Product subcategoryId cannot be empty",
},
},
},
unit: {
type: DataTypes.STRING,
allowNull: false,
validate: {
notNull: {
msg: "Product unit cannot be empty",
},
notEmpty: {
msg: "Product unit cannot be empty",
},
},
},
price: {
type: DataTypes.INTEGER,
allowNull: false,
validate: {
notNull: {
msg: "Product price cannot be empty",
},
notEmpty: {
msg: "Product price cannot be empty",
},
},
},
notes: DataTypes.STRING,
photoURL: {
type: DataTypes.STRING,
allowNull: false,
validate: {
notNull: {
msg: "Product photo URL cannot be empty",
},
notEmpty: {
msg: "Product photo URL cannot be empty",
},
isUrl: {
msg: "Invalid product photo URL",
},
},
},
videoURL: {
type: DataTypes.STRING,
validate: {
isUrl: {
msg: "Invalid product video URL",
},
},
},
isActive: {
type: DataTypes.BOOLEAN,
allowNull: false,
validate: {
notNull: {
msg: "Product isActive cannot be empty",
},
notEmpty: {
msg: "Product isActive cannot be empty",
},
},
},
stock: {
type: DataTypes.INTEGER,
allowNull: false,
validate: {
notNull: {
msg: "Product stock cannot be empty",
},
notEmpty: {
msg: "Product stock cannot be empty",
},
},
},
reservedStock: DataTypes.INTEGER,
sold: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: 0,
},
firestoreId: DataTypes.STRING,
buyPrice: {
type: DataTypes.INTEGER,
defaultValue: 0,
},
},
{
hooks: {
beforeCreate(Product, options) {
Product.stock = Product.stock || 0;
Product.reservedStock = 0;
},
},
sequelize,
modelName: "Product",
// edited here
paranoid: true,
}
);
return Product;
};
I just need to add unique: true in sku & barcode, please help. Thanks
EDITED
here is what I received in terminal when I use addConstraint

Refer to the queryInterface API here addConstraint removeConstraint
A transaction was made to ensure that all the queries succeed together. The name field in the second parameter of queryInterface.addConstraint can be anything. If any two columns have the same name of constraint, then a composite constraint will be created.
module.exports = {
async up(queryInterface, Sequelize) {
return queryInterface.sequelize.transaction((transaction) => {
return Promise.all([
queryInterface.addConstraint("Products", {
fields: ["barcode"],
type: "unique",
name: "Products_barcode_uk",
transaction,
}),
queryInterface.addConstraint("Products", {
fields: ["sku"],
type: "unique",
name: "Products_sku_uk",
transaction,
}),
]);
});
},
async down(queryInterface, Sequelize) {
return queryInterface.sequelize.transaction((transaction) => {
return Promise.all([
queryInterface.removeConstraint("Products", "Products_barcode_uk", {
fields: ["barcode"],
transaction,
}),
queryInterface.removeConstraint("Products", "Products_sku_uk", {
fields: ["sku"],
transaction,
}),
]);
});
},
};

Related

How to Update a DataTypes.ENUM datatype in Sequelize

I have this sequelize model:
var TableName = sequelize.define(
"TableName",
{
id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true },
name: { type: DataTypes.STRING, allowNull: false },
approvedTenure: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: 0,
},
status: {
type: DataTypes.ENUM,
values: ["PENDING", "YES", "NO", "DON'T KNOW"],
allowNull: false,
defaultValue: "PENDING",
},
narrative: { type: DataTypes.STRING, allowNull: false, defaultValue: "" },
},
{
timestamps: true,
tableName: "table_name",
freezeTableName: true,
}
);
And when I run the following code trying to update the table:
const data = {id: 23, status: "YES", narrative: "I am good", approvedTenure: 2}
async updateTable(data) {
return await TableName.update(
{
status: data.status,
narrative: data.narrative,
approvedTenure: data.approvedTenure,
},
{
where: { id: data.id },
returning: true,
}
);
}
The table gets updated except the status column with DataTypes.ENUM as the data type. How do I go about updating the status column with the DataTypes.ENUM data type?

Sequelize mixes camel case and snake case keys in result object

Am trying to get the resulting plain object from Sequelize create (or any Sequelize query) to be snake case keys throughout. The result objects are mixed however. Here's an example query:
const object = await models.Account.create({
userId,
name,
accountNumber
})
console.log(object.get({ plain: true }))
The result object is mixed keys of camel case and snake case:
{
"id": 2,
"userId": 1,
"name": "baller",
"accountNumber": "1234-123-1234",
"updated_at": "2019-01-07T02:23:41.305Z",
"created_at": "2019-01-07T02:23:41.305Z",
"balance": "0.00",
"deleted_at": null
}
Any idea how to get the result plain object or nested objects to be completely snake case keys only? Upgrade sequelize from ^4.42.0 to ^5.0.0-beta in package.json and happens for both. Not sure what else to try?
Accounts table is all snake case column names:
return queryInterface.createTable('accounts', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
user_id: {
allowNull: false,
type: Sequelize.INTEGER,
references: {
model: 'users',
key: 'id'
}
},
name: {
allowNull: false,
type: Sequelize.STRING(50)
},
account_number: {
allowNull: false,
type: Sequelize.STRING(50)
},
Account model has has option underscored: true and camelCase attrs with field in snake case
const Account = sequelize.define('Account', {
userId: {
type: DataTypes.INTEGER,
field: 'user_id',
allowNull: false,
validate: {
notNull(value) {
if (value == null) {
throw new Error('Missing user id')
}
}
}
},
name: {
type: DataTypes.STRING,
field: 'name',
validate: {
notEmpty: true
}
},
accountNumber: {
type: DataTypes.STRING,
field: 'account_number',
validate: {
notEmpty: true
}
},
}, {
tableName: 'accounts',
paranoid: true,
underscored: true
})
For now I'm resolving it with:
import snakeCaseKeys from 'snakecase-keys'
let jsonObject = object.get({ plain: true })
jsonObject = snakeCaseKeys(jsonObject)

Custom validation error using Sequelize.js

Is possible to customize the error from the
Sequelize.ValidationError
Model:
var PaymentType = sequelize.define('payment_type' , {
id: {
type: DataTypes.INTEGER(11),
allowNull: false,
primaryKey: true,
autoIncrement: true,
field: 'id'
},
code: {
type: DataTypes.STRING,
allowNull: false,
validate:{
notEmpty: true
},
field: 'code'
},
name: {
type: DataTypes.STRING,
allowNull: false,
validate:{
notEmpty: true
},
field: 'name'
}
}, {
timestamps: true,
paranoid: false,
underscored: true,
freezeTableName: true,
tableName: 'payment_types'
});
My controller:
update(req, res) {
paymentType
.update(req.body, {
where: {
id: req.params.id
}
})
.then( updatedRecords => {
res.status(200).json(updatedRecords);
})
.catch(Sequelize.ValidationError, error => {
res.status(400).json(error);
})
.catch( error => {
res.status(500).json(error);
});
},
The errors I get, are this way:
{
"name": "SequelizeValidationError",
"message": "Validation error: Validation notEmpty failed",
"errors": [
{
"message": "Validation notEmpty failed",
"type": "Validation error",
"path": "name",
"value": {},
"__raw": {}
}
]
}
I want to pass the errors like this(only path and message):
{
"name":"The field cannot be empty",
"other_field":"custom error message"
}
I don't know if I can specify a custom message in the model or I have to create a function to build the errors messages.
If I have to build a function, how can I extract the path and customize the message?
Thanks in Advance.
You can catch Sequelize's ValidationError and loop through its ValidationErrorItem's and write a custom message depending on the error type (determined by ValidationErrorItem.validatorKey). Here is a code snippet to get started.
try {
// sequelize custom logic here
} catch(e) {
const messages = {};
if (e instanceof ValidationError) {
e.errors.forEach((error) => {
let message;
switch (error.validatorKey) {
case 'isEmail':
message = 'Please enter a valid email';
break;
case 'isDate':
message = 'Please enter a valid date';
break;
case 'len':
if (error.validatorArgs[0] === error.validatorArgs[1]) {
message = 'Use ' + error.validatorArgs[0] + ' characters';
} else {
message = 'Use between ' + error.validatorArgs[0] + ' and ' + error.validatorArgs[1] + ' characters';
}
break;
case 'min':
message = 'Use a number greater or equal to ' + error.validatorArgs[0];
break;
case 'max':
message = 'Use a number less or equal to ' + error.validatorArgs[0];
break;
case 'isInt':
message = 'Please use an integer number';
break;
case 'is_null':
message = 'Please complete this field';
break;
case 'not_unique':
message = error.value + ' is taken. Please choose another one';
error.path = error.path.replace("_UNIQUE", "");
}
messages[error.path] = message;
});
}
}
You can specify a custom message for sequelize Validation, Your code will look something like this
`var PaymentType = sequelize.define('payment_type' , {
id: {
type: DataTypes.INTEGER(11),
allowNull: false,
primaryKey: true,
autoIncrement: true,
field: 'id'
},
code: {
type: DataTypes.STRING,
allowNull: false,
validate:{
notEmpty: {
args: true,
msg: “code cannot be empty"
}
},
field: 'code'
},
name: {
type: DataTypes.STRING,
allowNull: false,
validate:{
notEmpty: {
args: true,
msg: “code cannot be empty"
}
},
field: 'name'
}
I specified the validation rule as an object, in the above case, notEmpty should be true so I refactored it to an object setting the args property to true i.e notEmpty should be true, and the second property is msg which contains our custom message
notEmpty: {
args: true,
msg: “code cannot be empty"
}
Do you try to change Model like this:
var PaymentType = sequelize.define('payment_type' , {
id: {
type: DataTypes.INTEGER(11),
allowNull: false,
primaryKey: true,
autoIncrement: true,
field: 'id'
},
code: {
type: DataTypes.STRING,
allowNull: false,
validate:{
notEmpty: {
msg: "The field cannot be empty"
}
},
field: 'code'
},
name: {
type: DataTypes.STRING,
allowNull: false,
validate:{
notEmpty: {
msg: "The field cannot be empty"
}
},
field: 'name'
}
}, {
timestamps: true,
paranoid: false,
underscored: true,
freezeTableName: true,
tableName: 'payment_types'
});
Reference this post

sequelize hasMany, how to use customized foreginKey

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

On sequelize, "include" of "findOne" not working

I made a simple test that is to search for an address (id = 4) and retrieve the user who is linked to that address.
Here are my Models:
user.js
module.exports = function(sequelize, DataTypes) {
return sequelize.define('User', {
id: {
type: DataTypes.INTEGER(10).UNSIGNED,
allowNull: false,
field: 'id',
//primaryKey: true,
},
name: {
type: DataTypes.STRING,
allowNull: false,
field: 'name',
},
}, {
freezeTableName: true,
tableName: 'user',
createdAt: false,
updatedAt: false,
classMethods: {
associate: function(models) {
models.User.hasMany(models.UserAddress, { foreignKey: 'userId' });
},
},
});
};
user_address.js
module.exports = function(sequelize, DataTypes) {
return sequelize.define('UserAddress', {
id: {
type: DataTypes.INTEGER(10).UNSIGNED,
allowNull: false,
field: 'id',
},
userId: {
type: DataTypes.INTEGER(10).UNSIGNED,
allowNull: false,
field: 'user_id',
},
title: {
type: DataTypes.STRING,
allowNull: true,
field: 'title',
},
address: {
type: DataTypes.STRING,
allowNull: true,
field: 'address',
},
}, {
freezeTableName: true,
tableName: 'user_address',
createdAt: false,
updatedAt: false,
classMethods: {
associate: function(models) {
models.UserAddress.hasOne(models.User, { foreignKey: 'id' });
},
},
});
};
And here is my test file:
db.UserAddress.findOne({
where: { id: 4 },
include: [db.User],
}).then(function(address) {
console.log('------------------------------ Address by "include"');
console.log('Address title: '+address.title);
console.log('User id: '+address.userId);
if(address.User !== null) {
console.log('User name: '+address.User.name);
} else {
console.log('User name: NO USER');
}
console.log('');
address.getUser().then(function(user) {
console.log('------------------------------ Address by "getUser"');
console.log('Address title: '+address.title);
console.log('User id: '+address.userId);
if(user !== null) {
console.log('User name: '+address.user.name);
} else {
console.log('User name: NO USER');
}
console.log('');
});
});
I do a query with two tests:
The first aims to recover the user directly via the variable "user", so thanks to "include" of the request.
And the other also retrieve the user but this time via "getUser()".
Here is the result:
$ node test.js
Executing (default): SELECT `UserAddress`.`id`, `UserAddress`.`user_id` AS `userId`, `UserAddress`.`title`, `UserAddress`.`address`, `User`.`id` AS `User.id`, `User`.`name` AS `User.name` FROM `user_address` AS `UserAddress` LEFT OUTER JOIN `user` AS `User` ON `UserAddress`.`id` = `User`.`id` WHERE `UserAddress`.`id`=4;
------------------------------ Address by "include"
Address title: Test
User id: 3
User name: NO USER
Executing (default): SELECT `id`, `name` FROM `user` AS `User` WHERE (`User`.`id`=4);
------------------------------ Address by "getUser"
Address title: Test
User id: 3
User name: NO USER
One can observe that it is impossible to retrieve the result via "include" and "getUser()".
The error is visible in the log of SQL:
"include": LEFT OUTER JOIN `user` AS `User` ON `UserAddress`.`id` = `User`.`id`
and
"getUser()": SELECT `id`, `name` FROM `user` AS `User` WHERE (`User`.`id`=4);
While the correct answer should have been:
"include": LEFT OUTER JOIN `user` AS `User` ON `UserAddress`.`user_id` = `User`.`id`
and
"getUser()": SELECT `id`, `name` FROM `user` AS `User` WHERE (`User`.`id`=3);
So my question is, what is the configuration to put in my Model or my request for the result to be correct with "include" and "getUser()" ?
Thank you.
(Also posted on: https://github.com/sequelize/sequelize/issues/3182)
Answer from the github page - need to use belongsTo instead of hasOne.
user.js
module.exports = function(sequelize, DataTypes) {
return sequelize.define('User', {
id: {
type: DataTypes.INTEGER(10).UNSIGNED,
allowNull: false,
field: 'id',
//primaryKey: true,
},
name: {
type: DataTypes.STRING,
allowNull: false,
field: 'name',
},
}, {
freezeTableName: true,
tableName: 'user',
createdAt: false,
updatedAt: false,
classMethods: {
associate: function(models) {
models.User.hasMany(models.UserAddress, { foreignKey: 'userId' });
},
},
});
};
user_address.js
module.exports = function(sequelize, DataTypes) {
return sequelize.define('UserAddress', {
id: {
type: DataTypes.INTEGER(10).UNSIGNED,
allowNull: false,
field: 'id',
},
userId: {
type: DataTypes.INTEGER(10).UNSIGNED,
allowNull: false,
field: 'user_id',
},
title: {
type: DataTypes.STRING,
allowNull: true,
field: 'title',
},
address: {
type: DataTypes.STRING,
allowNull: true,
field: 'address',
},
}, {
freezeTableName: true,
tableName: 'user_address',
createdAt: false,
updatedAt: false,
classMethods: {
associate: function(models) {
models.UserAddress.belongsTo(models.User, { foreignKey: 'userId' });
},
},
});
};

Categories

Resources