Lifecycle hooks with sequelize on a different model - node.js

I've been looking through this but I couldn't find what I was looking for.
Basically I have two models.
"X" and "Y"
On lifecycle events of Y(which defines a belongsTo to X) I'm supposed to be updating(incrementing) the values of X. I'll illustrate with code in a while but this is the gist of it.
So whenever I create an entry of Y a value inside X must increment by 1.
Using a require to the sequelize generated Index.js function didn't seem to do anything and I don't want to add even more complexity to my main routes.js file.
This is the "X" model code(named User):
const bcrypt = require("bcrypt");
module.exports = (sequelize, DataTypes) => {
const user = sequelize.define('user', {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
unique: true,
autoIncrement: true,
required: true
},
username: {
type: DataTypes.STRING,
unique: true,
required: true,
validate: {
len: [4, 32]
}
},
password: {
type: DataTypes.STRING,
required: true,
protect: true,
validate: {
len: [4, 32]
}
},
likedByCount: {
type: DataTypes.INTEGER,
defaultValue: 0
},
user_likes:{
type: DataTypes.INTEGER,
defaultValue:0
}
});
user.associate = function (models) {
models.user.hasMany(models.like, {
onDelete: 'cascade'
});
};
user.beforeCreate((user) => {
return bcrypt.hash(user.password, 10).then(hashedPw => {
console.log(user.password + " hash " + hashedPw);
user.password = hashedPw;
});
});
user.beforeUpdate((user) => {
return bcrypt.hash(user.password, 10).then(hashedPw => {
console.log(user.password + " hash " + hashedPw);
user.password = hashedPw;
});
});
return user;
};
And this is the "Y" model definition:
const moduleFile = require("./index.js");
module.exports = (sequelize, DataTypes) => {
sequelize
.sync()
.then(function (err) {
console.log('Sync successful');
}, function (err) {
console.log('An error occurred while creating the table:', err);
});
const like = sequelize.define('like', {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
unique: true,
autoIncrement: true,
required: true,
},
target_username: {
type: DataTypes.STRING
},
source_username: {
type: DataTypes.STRING
},
target: {
type: DataTypes.INTEGER
}
});
//HasOne and BelongsTo insert the association key in different models from each other.
//HasOne inserts the association key in target model whereas BelongsTo inserts the association key in the source model.
like.associate = function (models) {
models.like.belongsTo(models.user, {
foreignKey: {
allowNull: false
}
});
};
""
//Update the field likedByCount of target user by +1, update the field likes of source user by 1
like.afterCreate((like) => {
const target_id = like.target_id;
const source_id = like.source_id;
moduleFile.user.findById(target_id).then(user => {
user.increment('likedByCount', {
by: 1
})
}).catch((err)=>console.log("Decrement error" + err.message));
});
return like;
};
My code obviously produces an error because "moduleFiles" isn't found correctly. But I do it the same way in router.js:
var models = require("../models/index.js");
Folder structure checks out:
/ :index.js(this just launches the server)
/models/: index.js,user.js,like.js
/routes/: routes.js(this is where the above code(var models=) is from

Related

Cannot read property 'findOne' of undefined"

I am writing an endpoint that would create a record but before then, I want to be able to validate if one of those data exist before allowing the data to be saved. This is being done using the custom validator in express-validator. I am also using Sequelize as well.
My migration files looks like this:
'use strict';
module.exports = {
up: async (queryInterface, Sequelize) => {
await queryInterface.createTable('merchant_temp', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
firstname: {
type: Sequelize.STRING,
allowNull: false
},
lastname: {
type: Sequelize.STRING,
allowNull: false
},
phone: {
type: Sequelize.STRING,
allowNull: false,
unique: true
},
email: {
type: Sequelize.STRING,
allowNull: false,
unique: true
},
password: {
type: Sequelize.STRING,
allowNull: false
},
ip_address: {
type: Sequelize.STRING
},
created_at: {
allowNull: false,
type: Sequelize.DATE
},
updated_at: {
allowNull: false,
type: Sequelize.DATE
}
});
},
down: async (queryInterface, Sequelize) => {
await queryInterface.dropTable('merchant_temp');
}
};
Model file (merchanttemp.js)
'use strict';
const {
Model, Sequelize
} = require('sequelize');
module.exports = (sequelize, DataTypes) => {
class MerchantTemp 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
}
};
MerchantTemp.init({
firstname: {
type: DataTypes.STRING,
allowNull: false
},
lastname: {
type: DataTypes.STRING,
allowNull: false
},
phone: {
type: DataTypes.STRING,
allowNull: false,
unique: true
},
email: {
type: DataTypes.STRING,
allowNull: false,
unique: true
},
password: {
type: DataTypes.STRING,
allowNull: false,
},
ip_address: DataTypes.STRING,
}, {
sequelize,
modelName: 'merchant_temp',
});
return MerchantTemp;
};
validator.js
const { check, body, validationResult } = require('express-validator')
const bcrypt = require('bcrypt')
const models = require('../app/models')
const MerchantTemp = db.rest.models.MerchantTemp
const signupValidation = () => {
return [
body('firstname')
.not().isEmpty().trim().withMessage('Firstname field is required'),
body('lastname')
.not().isEmpty().trim().withMessage('Lastname field is required'),
body('phone')
.not().isEmpty().trim().withMessage('Phone Number field is required')
.isNumeric().withMessage('Phone Number field can only contain Numbers')
.isLength({min: 11, max: 13}).withMessage('Phone Number field can only contain minimum of 11 and max of 13 digits respectively'),
body('email')
.not().isEmpty().trim().withMessage('Email Address field is required')
.isEmail().withMessage('Email field is not a valid format').normalizeEmail()
.custom((value, { req }) => {
/**
** THIS PART IS WHERE I AM VALIDATING IF IT EXIST
**/
MerchantTemp.findOne({ where: { email: req.body.email } })
.then((result) => {
console.log(result)
}).catch(error => {
console.log(error)
})
}),
body('password')
.not().isEmpty().trim().withMessage('Password field is required')
.isStrongPassword(
{
minLength: 6,
minLowercase: 1,
minUppercase: 1,
minSymbols: 1
}).withMessage('Password is too weak. Field must contain min. of 6 characters, 1 lowercase and uppercase character and a symbol')
]
}
const validate = (req, res, next) => {
const errors = validationResult(req)
if (errors.isEmpty()) {
return next()
}
const extractedErrors = []
errors.array().map(err => extractedErrors.push({ msg: err.msg }))
res.status(200).json({
statusCode: 400,
errors: extractedErrors
})
}
module.exports = {
signupValidation,
validate
}
router file
require('dotenv').config()
const router = require('express').Router()
const account = require('../controllers/account.controller')
const { signupValidation, validate } = require('../../helpers/validator')
router.get('/', (req, res) => {
let p = "This serves as a repository of API calls for application"
res.status(200).json({message:p, statusCode: 200})
})
//Endpoint to create new merchant
router.post('/account/create-merchant', signupValidation(), validate, async (req, res) => {
res.status(200).json({
statusCode: 201,
message: req.body
})
})
//Endpoint to login merchant
router.post('/account/login', (req, res) => {
})
module.exports = router
The validation on the /account/create-merchant route works well prior to when I included the findOne part in the validator.js. What exactly I am doing wrong?

Pulling associations values with Sequelize

I am using sequelize for the first time and I having trouble getting my head around the association / foreign key relationships when pulling data into my jsons. I have some experience with SQL joins when I used PHP and I want to display the values that relate to the integer values in my diveSchool model.
Is there an easy way to do this without creating a complicated API?
The integers with ID relate to other tables and I have the foreign keys already created in pgadmin. Obviously the integers will mean nothing by themselves when displayed on the front-end.
module.exports = (sequelize, Sequelize) => {
const diveSchool = sequelize.define("diveSchools", {
diveSchoolID: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true,
allowNull: false
},
diveSchoolName: {
type: Sequelize.TEXT
},
diveSchoolLocation: {
type: Sequelize.TEXT
},
diveSchoolRegionID: {
type: Sequelize.INTEGER
},
diveCertificatesOfferedID: {
type: Sequelize.INTEGER
},
diveSpotsOfferedID: {
type: Sequelize.INTEGER
},
diveSchoolGeo: {
type: Sequelize.GEOMETRY('POINT'),
allowNull: false
},
},
{
timestamps: false
}, {});
diveSchool.associate = function(models){
diveSchool.belongsTo(models.diveRegion, {foreignKey: 'diveSchoolRegionID', as: 'diveRegion'})
diveSchool.belongsTo(models.diveCertification, {foreignKey: 'diveCertification', as: 'diveCertification'})
diveSchool.belongsTo(models.diveSpot, {foreignKey: 'diveSpotID', as: 'diveSpot'})
};
return diveSchool;
};
diveRegion.js
module.exports = (sequelize, Sequelize) => {
const diveRegion = sequelize.define("diveRegions", {
diveRegionID: {
type: Sequelize.INTEGER,
primaryKey: true
},
diveRegion: {
type: Sequelize.TEXT
}},
{
timestamps: false
},{});
diveRegion.associate = function(models){
diveRegion.hasMany(models.diveSchool, {as: 'diveRegion'})
};
return diveRegion;
};
diveCertifications.js
module.exports = (sequelize, Sequelize) => {
const diveCertification = sequelize.define("diveCertifications", {
diveCertificationID: {
type: Sequelize.INTEGER,
primaryKey: true
},
diveCertificationName: {
type: Sequelize.TEXT
}},
{
timestamps: false
},{});
diveCertification.associate = function(models){
diveCertification.hasMany(models.diveSchool, {as: 'diveCertificatesOfferedID'})
diveCertification.hasMany(models.diveCertsCompleted, {as: 'diveCertsCompletedID'})
};
return diveCertification;
};
diveSchool.controller.js API
exports.allDiveSchools = (req, res) => {
approvedDivingSchool.findAll({})
.then((approvedDivingSchool) => {
const diveSchoolsList = [];
for (i = 0; i < approvedDivingSchool.length; i++) {
diveSchoolsList.push(approvedDivingSchool[i].dataValues);
}
if (!approvedDivingSchool) {
return res.status(404).send({ message: "No dive schools stored in this region." });
}
res.status(200).send({
data: diveSchoolsList,
});
})
.catch((err) => {
res.status(500).send({ message: err.message });
});
};
Check out eager loading and how to code associations in the Sequelize docs. Note that I already had associations in the style above in model that didn't work however there are are examples of how to code include statements with the "example1.belongsTo(example2) style and then the include statements inside the 'where' methods in API's.
https://sequelize.org/master/manual/eager-loading.html

hasMany called with something that's not an instance of Sequelize.Model

as you guys can see my issue is related to the title description, i created a User Model, and a Foto Model in sequelize, basicly a user can shoot many fotos, but each foto can be related to just 1 user.
My User model
"use strict";
var sequelize = require('./index');
var bcrypt = require('bcrypt-nodejs');
var Foto = require('./Foto');
module.exports = function (sequelize, DataTypes) {
var User = sequelize.define("User", {
username: {
type: DataTypes.STRING,
allowNull: false,
unique: true,
validate: {
isUnique: function (value, next) {
var self = this;
User.find({ where: { username: value } })
.then(function (user) {
// reject if a different user wants to use the same username
if (user && self.id !== user.id) {
return next('username already in use!');
}
return next();
})
.catch(function (err) {
return next(err);
});
}
}
},
email: {
type: DataTypes.STRING,
allowNull: false,
unique: true,
validate: {
isUnique: function (value, next) {
var self = this;
User.find({ where: { email: value } })
.then(function (user) {
// reject if a different user wants to use the same email
if (user && self.id !== user.id) {
return next('Email already in use!');
}
return next();
})
.catch(function (err) {
return next(err);
});
}
}
},
typeOfUser: {
type: DataTypes.INTEGER,
allowNull:true,
defaultValue:null
},
country: {
type: DataTypes.STRING,
allowNull:true,
defaultValue:null
},
birthDate:{
type: DataTypes.DATEONLY,
allowNull:true,
defaultValue:null
},
reports: {
type: DataTypes.INTEGER,
defaultValue: 0
},
points: {
type: DataTypes.INTEGER,
defaultValue: 0
},
password: {
type: DataTypes.STRING,
allowNull:false
},
numberFotos: {
type: DataTypes.INTEGER,
defaultValue: 0
}
}, {
classMethods: {
generateHash: function (password) {
return bcrypt.hashSync(password, bcrypt.genSaltSync(8), null);
},
},
instanceMethods: {
validPassword: function (password) {
return bcrypt.compareSync(password, this.password);
}
}
});
User.hasMany(Foto,{as: 'fotos', foreignKey: 'userId'})
return Foto;
}
My foto model
"use strict";
var sequelize = require('./index');
var bcrypt = require('bcrypt-nodejs');
var User = require('./User');
module.exports = function (sequelize, DataTypes) {
var Foto = sequelize.define("Foto", {
reports: {
type: DataTypes.INTEGER,
defaultValue: 0
},
image: {
type: DataTypes.STRING,
allowNull: false
},
date: {
type: DataTypes.DATE,
allowNull:true
},
position: {
type: DataTypes.RANGE,
allowNull: true
}
});
Foto.belongsTo(User, {foreignKey: 'userId'});
return Foto;
}
You don't need to declare the association on the Photo Model:
Foto.belongsTo(User, {foreignKey: 'userId'});
When you have a 1:N relation between models you only need to refer the id from the "1" model, on our case the User model, on the "N" model, Photos. So doing:
User.hasMany(Foto,{as: 'fotos', foreignKey: 'userId'})
Will create a column on your Foto table with name "userId" that refer to user table. On this way both models are associate as you want.
You can define relations for both models in one file. It doesn't throw any errors that way.
In your Foto.js, you can try:
...
Foto.belongsTo(User);
User.hasMany(Foto);
return Foto;
I had a similar problem. Sometimes it can be caused because in your index.js or app.js the files are loaded in a specific order, so for example if you have a relationship between A and B, A loads first and references B, and B in turn references A, the error will be thrown inside the B file because A has not been fully defined/executed yet.
The solution to this would be to remove all associations from the model files, and inside your app or index.js require them all, and then define their relationships.
Example
const entities = {
A: require('./src/Entity/A'),
B: require('./src/Entity/B'),
};
entities.A.belongsToMany(entities.B, {through: 'AB'});
entities.B.belongsToMany(entities.A, {through: 'AB'});
So I was getting this error and it took me some time to deal with the bug. I realised I was getting the Error because I was referencing the model wrongly. Sequelize is case sensitive so if you created the model with UpperCase ensure to keep it uniform throughout your referencing.
I would also point out you could try this out instead
User.hasMany(models.Foto ,{as: 'fotos', foreignKey: 'userId'})
It seems you need to define both ends of the relationship in the file containing the 1 part of the 1:many association. That is, the "User" file in your case.
So:
User.hasMany(Foto);
Foto.belongsTo(User);
None of the above solutions worked for my scenario (could work for other setups). I stumbled upon this article which states you have to have the models defined and exported prior to applying the associations. Using a separate extra-setup.js file to define the associations, worked for me.
https://github.com/sequelize/express-example/tree/master/express-main-example
I had lots of issues, but I switched to using the sequelize CLI which generated models in this format, I then found creating associations a lot easier as the index file took care of everything and the static associate({ PersonalDetail }) that is in the model itself already requires your models in one place all you need to do is deconstruct them, so no need to require anything at the top of the file.
This youtube video really helped me out... https://www.youtube.com/watch?v=3qlnR9hK-lQ
'use strict'
const { Model } = require('sequelize')
module.exports = (sequelize, DataTypes) => {
class User 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({ PersonalDetail }) {
// define association here
this.hasMany(PersonalDetail, {
foreignKey: 'userId',
//as: 'personalDetails',
})
}
}
User.init(
{
uuid: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
},
moredata below: {
type: DataTypes.STRING,
allowNull: false,
},
//createdAt/updatedAt is defined in migration and updated automatically
},
{
sequelize,
tableName: 'users',
modelName: 'User',
}
)
return User
}
I got the same type issue. All mappings were done perfectly as explained in the document.
Yet, I received the issue regarding the association.
Reason is given by Dorian in this forum.
https://stackoverflow.com/a/60760296/16790144
My approach:
models/company.js
const company = sequelize.define("company",{
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true,
},
companyName: {
type: DataTypes.STRING,
allowNull: false,
}
});
export default company;
models/client.js
const Client = sequelize.define("client", {
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true,
},
firstName: {
type: DataTypes.STRING,
allowNull: false,
}
});
export default Client;
models/clientCompany.js
const clientCompany = sequelize.define("client_company",{
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true,
},
companyId: {
type: DataTypes.INTEGER
},
clientId: {
type: DataTypes.INTEGER
}
});
export default clientCompany;
models/index.js
import Company from './company';
import Client from './client';
import ClientCompany from './clientCompany';
Company.belongsToMany(Client, { through : ClientCompany });
Client.belongsToMany(Company, { through : ClientCompany });
export {
Company,
Client,
ClientCompany,
};
handler.js
This file contains the business logic.
import { Client, Company } from '../../models';
const company = await Company.findOne({
where: { id: companyId },
include: Client,
});

Update with association using Sequelize.js

I am trying to update with association using sequelize.js.
I have tried give example on stackoverflow namely the following links:
Sequelize update with association
Sequelize update with association
Updating attributes in associated models using Sequelize
all of these links did not get me to the goal i am trying to accomplish.
My model is as follow, I have a country module and a city module. a country has many cities. please refer to the module bellow.
Please advise.
country.js file
module.exports = function (sequelize, DataTypes) {
var country= sequelize.define('COUNTRY', {
COUNTRY_ID: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
COUNTRY_NAME: DataTypes.STRING,
COUNTRY_CURRENCY: DataTypes.STRING
}, {
freezeTableName: true,
classMethods: {
associate: function (models) {
COUNTRY_ID.hasMany(models.CITIES, {
foreignKey: 'COUNTRY_ID'
})
}
},
instanceMethods: {
updateAssociation: function (onSuccess, onError) {
country.findAll({
where: {
COUNTRY_ID: req.params.country_id
},
include: [
{
model: sequelize.import('./cities.js'),
}
]
})
})
.then(country =>{
const updatePromises = country.map(countries =>{
return countries.updateAttributes(req.body);
});
const updatePromisescities = list.CITY.map(cities =>{
return cities.updateAttributes(req.body.CITYs[0]);
});
return sequelize.Promise.all([updatePromises, updatePromisescities ])
}).then(onSuccess).error(onError);
}
}
});
return country;
};
city.js file
module.exports = function (sequelize, DataTypes) {
var CITY = sequelize.define('LIST_CODE', {
CITY_ID: {
type: DataTypes.INTEGER,
primaryKey: true
},
COUNTRY_ID: {
type: DataTypes.INTEGER,
primaryKey: true
}
}, {
freezeTableName: true,
timestamps: false,
classMethods: {
associate: function (models) {
// associations can be defined here
CITY.belongsTo(models.COUNTRY, {
foreignKey: 'COUNTRY_ID'
})
}
}
});
return CITY;
};

Node Sequelize migrations/models is it possible to share the same code?

I'm new at Sequelize so be patient.
I started up a new project using Sequelize
and migrations so I've got like this:
migrations/20150210104840-create-my-user.js:
"use strict";
module.exports = {
up: function(migration, DataTypes, done) {
migration.createTable("MyUsers", {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: DataTypes.INTEGER
},
first_name: {
type: DataTypes.STRING
},
last_name: {
type: DataTypes.STRING
},
bio: {
type: DataTypes.TEXT
},
createdAt: {
allowNull: false,
type: DataTypes.DATE
},
updatedAt: {
allowNull: false,
type: DataTypes.DATE
}
}).done(done);
},
down: function(migration, DataTypes, done) {
migration.dropTable("MyUsers").done(done);
}
};
models/myuser.js:
"use strict";
module.exports = function(sequelize, DataTypes) {
var MyUser = sequelize.define("MyUser", {
first_name: DataTypes.STRING,
last_name: DataTypes.STRING,
bio: DataTypes.TEXT
}, {
classMethods: {
associate: function(models) {
// associations can be defined here
}
}
});
return MyUser;
};
as you can see the table definition
is both on the migration and the model file.
I'm wondering if there is a way to share
the code ?
I mean I don't like to have logic in two files
if a field change I've to update twice.
UPDATE
following the Yan Foto example below
a different way may be cleaner.
schemas/users
'use strict';
module.exports = {
name: 'users',
definition : function(DataTypes) {
return {
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true
},
firstname: {
type:DataTypes.STRING
},
lastname: {
type:DataTypes.STRING
},
email: {
type: DataTypes.STRING,
unique: true
},
username: {
type:DataTypes.STRING,
unique: true
}
};
}
};
models/users
'use strict';
var Schema = require('../schemas/users');
module.exports = function(sequelize, DataTypes) {
return sequelize.define(
Schema.name,
Schema.definition(DataTypes),
{
freezeTableName: true ,
instanceMethods: {
countTasks: function() {
// how to implement this method ?
}
}
}
);
};
migrations/20150720184716-users.js
'use strict';
var Schema = require('../schemas/users');
module.exports = {
up: function (queryInterface, Sequelize) {
return queryInterface.createTable(
Schema.name,
Schema.definition(Sequelize)
);
},
down: function (queryInterface, Sequelize) {
return queryInterface.dropTable(Schema.name);
}
};
I wondered the same thing as I started using sequelize and here is my solution. I define my models as bellow:
module.exports = {
def: function(DataTypes) {
return {
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true
},
username: DataTypes.STRING,
password: DataTypes.STRING,
createdAt: DataTypes.DATE,
updatedAt: DataTypes.DATE,
}
},
config: {}
};
Where def defines the attributes and config is the optional options object accepted by define or migration methods. And I import them using the following code:
fs.readdirSync(__dirname + '/PATH/TO/models')
.filter(function(file) {
return (file.indexOf('.') !== 0) && (file !== basename);
})
.forEach(function(file) {
var name = file.substring(0, file.lastIndexOf(".")),
definition = require(path.join(__dirname + '/models', file));
sequelize['import'](name, function(sequelize, DataTypes) {
return sequelize.define(
name,
definition.def(DataTypes),
definition.config
);
});
});
For the migrations I have a similar approach:
const path = require('path');
module.exports = {
up: function (queryInterface, Sequelize) {
return queryInterface.createTable(
'users',
require(path.join(__dirname + '/PATH/TO/models', 'user.js')).def(Sequelize)
);
},
down: function (queryInterface, Sequelize) {
return queryInterface.dropTable('users');
}
};

Resources