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,
}),
]);
});
},
};
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)
I have the following user model:
var model = module.exports = {
autoPK: false,
attributes: {
id: {
type: 'string',
primaryKey: true
},
email: {
type: 'string',
required: true
},
hash: {
type: 'string',
required: true
},
}
}
And the following query on it:
User.findOne({_id: req.param('user_id')}).populate('drafts').then(function(user) {
console.log("USER: " + JSON.stringify(user) + " FOR: " + req.param('user_id'));
res.send(user.drafts, 200);
});
From the print statement, I know nothing is turning being returned for the ID "Rfrq8un5f," but the mongodb command line outputs this:
> db.user.find();
{ "email" : "m#m.com", "hash" : "[...]", "createdAt" : ISODate("2014-05-18T16:32:21.023Z"), "updatedAt" : ISODate("2014-05-18T16:32:21.023Z"), "_id" : "9PTIqHxEc" }
What's going on?
To solve the id: null when use waterline with mongo adapter you must add to the model: autoPK: false, schema: true. You need both, isn't enough with autoPK false or schema true.
This is a model example solving that issue (user model):
module.exports = {
schema: true,
autoPK: false,
attributes: {
name: {
type: 'string',
required: true
},
email: {
type: 'string',
email: true,
required: true,
unique: true
},
password: {
type: 'string',
minLength: 6,
maxLength: 15,
columnName: 'encrypted_password',
required: true
},
toJSON: function() {
var obj = this.toObject();
delete obj.password;
return obj;
}
},
beforeCreate: function(values, next) {
require('bcrypt').hash(values.password, 10, function passwordEncrypted(err, encryptedPassword) {
if(err) console.log(err);
values.password = encryptedPassword;
next();
});
}
};
There is my code:
var fileModel = context.models.File,
query = {
_id: context.models.ObjectId("532083358ab1654c0c8b4ced") // TODO: for debug, change after update fix
},
update = {
description: context.data.description,
userId: context.data.userId ?
context.models.ObjectId(context.data.userId) : undefined,
isAdded: true
};
fileModel.update(query, update, { multi: true }, function (err) {
if (err) {
console.log('update');
console.log(err);
context.sendJson({ success: false, err: err });
}
else {
context.sendJson({ success: true });
}
});
There is my Schema:
var fileSchema = new schema({
path: { type: String, required: true, validate: [validateName, 'a path is required'] },
isApproved: { type: Boolean, default: false },
isAdded: { type: Boolean, default: false },
name: { type: String, required: true, validate: [validateName, 'a name is required'] },
description: { type: String },
userId: { type: schema.Types.ObjectId },
updated: { type: Date, default: Date.now },
size: { type: Number }
}, { autoIndex: false });
When I try to update document by id I see this messages in console:
update
[TypeError: Cannot read property '_id' of undefined]
I think problem in
userId: context.data.userId ?
context.models.ObjectId(context.data.userId) : undefined,
But I don't understand how fix it.
I solve this by separate part of my code. But I can't understand what's wrong in my first solution. That's working code:
var fileModel = context.models.File,
query = {
_id: {
$in: context.data.files.map(function (el) {
return context.models.ObjectId(el);
})
}
},
update = {
description: context.data.description,
isAdded: true
};
if (context.data.userId){
update.userId = context.models.ObjectId(context.data.userId);
}
fileModel.update(query, update, { multi: true }, function (err) {
if (err) {
console.log('update');
console.log(err);
context.sendJson({ success: false, err: err });
}
else {
context.sendJson({ success: true });
}
});