sequelize use instance method after selecting result - node.js

I want to understand how sequelize instance methods works and if its possible to use returned object as instance for further usage. Basically I'm just selecting user by its user name, later I want to compare if password matches and if so - update data. But the error says
Unhandled rejection TypeError: user_data.validPassword is not a function
and I'm not even close to instance update..
my User model:
const bcrypt = require('bcryptjs');
module.exports = (sequelize, DataTypes) => {
const User = sequelize.define('User', {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true,
},
user_name: DataTypes.STRING,
user_password: DataTypes.STRING,
user_token: DataTypes.STRING,
user_alias_name: DataTypes.STRING,
}, {
tableName: 'oc_users',
instanceMethods: {
generateHash(password) {
return bcrypt.hash(password, bcrypt.genSaltSync(8));
},
validPassword(password) {
return bcrypt.compare(password, this.password);
}
}
});
return User;
};
my method:
...
loginAttempt(cookie) {
return models.User.findOne({
attributes: ['id', 'user_password', 'user_alias_name'],
where: {user_name: this.user}
}).then(user_data => {
if (!user_data) return 'No matching results for such a user';
return user_data.validPassword(this.password).then(result => {
if (result !== true) return 'Invalid password for selected user';
return this.updateAfterLogin(user_data, cookie);
})
})
}
updateAfterLogin(user, cookie) {
return user.update({
user_token: cookie
}).then(()=> {
return {data: 'updated'};
})
}
...

It depends on which version of sequelize you're using and probabily you're using Sequelize v4. On Sequelize v4 classMethods and instanceMethods were removed from sequelize.define.
You may check it at oficial docs for more informations:
http://docs.sequelizejs.com/manual/tutorial/upgrade-to-v4.html#config-options
Removed classMethods and instanceMethods options from sequelize.define. Sequelize models are now ES6 classes. You can set class / instance level methods like this
Old
const Model = sequelize.define('Model', {
...
}, {
classMethods: {
associate: function (model) {...}
},
instanceMethods: {
someMethod: function () { ...}
}
});
New
const Model = sequelize.define('Model', {
...
});
// Class Method
Model.associate = function (models) {
...associate the models
};
// Instance Method
Model.prototype.someMethod = function () {..}

Related

Sequelize class method works fine but instance method is not working

I've defined a user model in Sequelize, and also defined a custom class method and an instance method for it. I'm calling these two methods in my login api (which works fine). The problem is that the class method works perfectly, but the instance method results an error, and I cannot recognize what is wrong with my code. PLEASE HELP.
This is my user model and its methods:
const Sequelize = require("sequelize");
const sequelize = require("../db/db.config");
const bcrypt = require("bcryptjs");
const _ = require("lodash");
const jwt = require("jsonwebtoken");
const User = sequelize.define("user", {
id: {
type: Sequelize.INTEGER,
autoIncrement: true,
allowNull: false,
primaryKey: true,
},
first_name: {
type: Sequelize.STRING(50),
allowNull: false,
},
last_name: {
type: Sequelize.STRING(50),
allowNull: false,
},
email: {
type: Sequelize.STRING(50),
allowNull: false,
validate: {
isEmail: true,
},
},
password: {
type: Sequelize.STRING(100),
allowNull: false,
},
});
User.prototype.testMethod = function () {
console.log("THIS IS A TEST");
};
User.beforeCreate(async (user, options) => {
const hashedPassword = await bcrypt.hash(user.password, 10);
user.password = hashedPassword;
});
User.findByEmailAndPassword = async function (inputEmail, inputPassword) {
try {
const user = await User.findOne({ where: { email: inputEmail } });
if (user === null) {
return null;
}
const passwordMatch = await bcrypt.compare(inputPassword, user.password);
if (!passwordMatch) {
return null;
}
return _.pick(user, "id", "first_name", "last_name", "email");
} catch (error) {
console.log("FIND BY EMAIL AND PASSWORD ERROR: ", error);
}
};
module.exports = User;
And this is my login router:
const express = require("express");
const router = express.Router();
const User = require("../models/user.model");
router.post("/api/login", async (req, res) => {
try {
const user = await User.findByEmailAndPassword(
req.body.email,
req.body.password
);
console.log("USER: ", user);
await user.testMethod();
if (!user) {
return res.status(400).send({
errorMessage: "Username and password combination is not correct!",
});
}
return res.status(200).send(user);
} catch (error) {
res.status(400).send({ errorMessage: error });
}
});
module.exports = router;
Thanks.
First, the reason that you get the issue is that findByEmailAndPassword is returning the regular object from _.pick and you are defining the instance method for Sequelize instance. This instance method can be callable on Sequelize instance and not on regular object.
However, your goal is
What I'm trying to do here is to avoid sending user password in my response body.
defaultScope is great for this use case. It allows you to define some repetitive options on a model.
You can define your User model as
const User = sequelize.define("user", {
id: {
type: Sequelize.INTEGER,
autoIncrement: true,
allowNull: false,
primaryKey: true,
},
...
}, {
defaultScope: {
attributes: {
exclude: ['password']
}
}
});
Defining the defaultScope on the model, this will be applied to many Sequelize functions by default.
Scopes apply to .find, .findAll, .count, .update, .increment and .destroy.
I also tested that it also applied to .findByPk, .findOne.
So, how to use...
Call regular Sequelize findOne function.
const user = User.findOne({
where: {
email: req.body.email,
password: req.body.password
}
});
By default, since defaultScope is applied, this won't return password in response.
In some scenarios where you need to return the password, use unscoped to disable the defaultScope.
// This will return `password` in response.
User.unscoped().fineOne(...)
For reference: https://sequelize.org/master/manual/scopes.html

How to implement Instance Methods in Sequelize 6 with Node.js

So i was implementing a bit of change in my server application - switching databases from MongoDb to PostgreSQL (with Sequelize 6) and i had to change the controller functions i had created for mongoose to suit the current database but there was a problem implementing the instance methods, but as usual there was little to no helpful solutions online for this with Sequelize 6. But now there is. Below are the code samples for some of the problems and error messages you may be facing if you come across this post.
The function which calls the instance method:
userController.js (login function)
User.findByPk(req.body.id)
.then(user => {
if (!user) {
return res.status(401).send('Sorry!! You do not have an account with us.')
}
if (!user.validPassword(req.body.password)) {
return res.status(401).send('Invalid Password')
} else {
res.status(200).json({ user })
}
})
.catch(error => {
console.log(error)
res.status(500).send({
message: 'Some error occurred while logging in this User',
error: error.message
});
});
EXAMPLE CODE 1
user.js
'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(models) {
// define association here
}
};
User.init({
username: {
type: DataTypes.STRING,
allowNull: false,
unique: true
},
password: {
type: DataTypes.STRING,
allowNull: false,
unique: true
},
password_confirmation: {
type: DataTypes.STRING,
allowNull: false,
unique: true
}
}, {
hooks: {
beforeCreate: (User) => {
const salt = bcrypt.genSaltSync();
User.password = bcrypt.hashSync(User.password, salt);
User.password_confirmation = User.password;
}
},
instanceMethods: {
validatePassword: (password) => {
return bcrypt.compareSync(password, this.password);
}
}
sequelize,
modelName: 'User',
});
return User;
};
response (Server 500 error message)
{
"message": "Some error occurred while logging in this User",
"error": "user.validPassword is not a function"
}
The instance method in this case is not recognized and the function validPassword() is not run thus a 500 Server error. Let's move to example 2.
EXAMPLE CODE 2
user.js
'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(models) {
// define association here
}
};
User.init({
username: {
type: DataTypes.STRING,
allowNull: false,
unique: true
},
password: {
type: DataTypes.STRING,
allowNull: false,
unique: true
},
password_confirmation: {
type: DataTypes.STRING,
allowNull: false,
unique: true
}
}, {
hooks: {
beforeCreate: (User) => {
const salt = bcrypt.genSaltSync();
User.password = bcrypt.hashSync(User.password, salt);
User.password_confirmation = User.password;
}
},
instanceMethods: {
validatePassword: (password) => {
return bcrypt.compareSync(password, this.password);
}
}
sequelize,
modelName: 'User',
});
User.prototype.validPassword = (password) => {
return bcrypt.compareSync(password, this.password);
};
return User;
};
response (Server 500 error message)
{
"message": "Some error occurred while logging in this User",
"error": "Illegal arguments: string, undefined"
}
The instance method in this case is still not recognized and the function validPassword() is thus not run because over here the parameter this.password for the bcrypt.compareSync() function is not defined (or has been used outside the Model extension) thus a 500 Server error.
And now for the solution.
After around half a day of searching, I found out that for some reason the instanceMethods functionality has been removed in Sequelize v4. As a result, the only way to obtain this functionality is by one of the following:
declaring the function on the model class as Jeffrey Dabo suggested
adding the function on the prototype of the Sequelize model
Very important: If you go with the prototype approach, in order to have access to the this object, you need to declare the function using the function syntax and not as an arrow function, or else it will not work.
Example:
const User = sequelize.define('User', {...});
User.prototype.validatePassword = function (password) {
return bcrypt.compareSync(password, this.password);
}
Placing the instance method function right under the class method function (associations on models) would eventually allow your function validPassword() to be recognized, run and produce the desired response.
user.js
'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(models) {
// define association here
}
validPassword(password) => {
return bcrypt.compareSync(password, this.password);
};
};
User.init({
username: {
type: DataTypes.STRING,
allowNull: false,
unique: true
},
password: {
type: DataTypes.STRING,
allowNull: false,
unique: true
},
password_confirmation: {
type: DataTypes.STRING,
allowNull: false,
unique: true
}
}, {
hooks: {
beforeCreate: (User) => {
const salt = bcrypt.genSaltSync();
User.password = bcrypt.hashSync(User.password, salt);
User.password_confirmation = User.password;
}
},
instanceMethods: {
validatePassword: (password) => {
return bcrypt.compareSync(password, this.password);
}
}
sequelize,
modelName: 'User',
});
return User;
};
I don't think this will help you but I can see that you have a syntax error in your codes, try adding , before
sequelize, modelName: 'User'
{
hooks: {
beforeCreate: (User) => {
const salt = bcrypt.genSaltSync();
User.password = bcrypt.hashSync(User.password, salt);
User.password_confirmation = User.password;
}
},
instanceMethods: {
validatePassword: (password) => {
return bcrypt.compareSync(password, this.password);
}
},
sequelize,
modelName: 'User',
}

A is not associated to B

Looked on the internet about similar questions/errors, none of them helped me...
Unhandled rejection SequelizeEagerLoadingError: Task is not associated to User!
My users route
router.get('', function (req, res) {
models.User.findAll({
include: [
{
model: models.Task,
as: 'tasks'
}
]
}).then(function (users) {
res.send(users);
});
});
User model
'use strict';
module.exports = (sequelize, DataTypes) => {
const User = sequelize.define('User', {
first_name: DataTypes.STRING,
last_name: DataTypes.STRING,
email: DataTypes.STRING
}, {
underscored: true
});
User.associations = function (models) {
User.hasMany(models.Task, { as: 'tasks' });
};
return User;
};
Task model
'use strict';
module.exports = (sequelize, DataTypes) => {
const Task = sequelize.define('Task', {
name: DataTypes.STRING
}, {
underscored: true
});
Task.associations = function (models) {
Task.belongsTo(models.User);
};
return Task;
};
I associate them both, and made a bidirectional relationship..
As you are using the finder function for association with as option, Sequelize cannot find that alias, as it is not defined explicitly anywhere. Please try this one:
User.associations = function (models) {
User.hasMany(models.Task, { as: 'tasks' });
};
Hope this helps.

sequelize instanceMethods

I'm trying to configurate my database ,i want the data returned to user doesn't include some sensetive data, so i'm adding an instanceMethods named toPublicJSON that using a function named pick from underscoreJS , but when i use this function (toPublicJSON) I encounter an error : that toPublicJSON is not a function
this my database configuration:
var bcrypt = require("bcrypt");
var _ = require("underscore");
module.exports = function(sequelize, DataType) {
return sequelize.define('users', {
email: {
type: DataType.STRING,
allowNull: false,
unique: true,
validate: {
isEmail: true
}
},
salt: {
type: DataType.STRING
},
hassedPassword: {
type: DataType.STRING
},
password: {
type: DataType.VIRTUAL,
allowNull: false,
validate: {
len: [7, 100]
},
set: function(value) {
var salt = bcrypt.genSaltSync(10);
var hashed_password = bcrypt.hashSync(value, salt);
this.setDataValue('password', value);
this.setDataValue("salt", salt);
this.setDataValue("hassedPassword", hashed_password);
}
}
}, {
hooks: {
beforeValidate: function(user, option) {
if (typeof user.email == 'string')
user.email = user.email.toLowerCase();
}
},
instanceMethods:{
toPublicJSON: function() {
var json = this.toJSON();
return _.pick(json, 'id', 'email', 'createdAt', 'updatedAt');
}
}
});
}
my code :
app.post('/users',function(req,res){
var body=_.pick(req.body,"email","password");
db.users.create(body).then(function(user){
return res.send(user.toPublicJSON());
},function(e){
return res.status(400).json(e);
})
});
and this is the error :
Assuming you are using the latest version of Sequelize they changed slightly how to define instance and class methods. You need to assign functions to the prototype of the result of sequelize.define('users');. You will need to save the results of sequelize.define to a variable, similar to below...
const User = sequelize.define('users', {});
User.prototype.toPublicJSON = function() {
// Your code here
};
return User;
If you're on version 3 let me know.
Good luck :)
EDIT: Further reading material http://docs.sequelizejs.com/manual/tutorial/models-definition.html#expansion-of-models
EDIT 2: You may want to look into scopes. They are essentially pre-baked filters you can apply to your data when querying for them. http://docs.sequelizejs.com/manual/tutorial/scopes.html
If you did that then you could say
sequelize.define('users', {
// Attributes...
}, {
scopes: {
public: {
attributes: ['id', 'email', 'createdAt', 'updatedAt']
}
}
});
db.users.scope('public').findOne({...});

Cannot get Sequelize validation working

I'm trying to implement validation in my Sequelize models. The model is defined as follows
var model = sequelize.define('Model', {
from: {
type: DataTypes.STRING,
allowNull: false,
validate: {
isEmail: true
}
}
}
Then I'm trying to build an instance and validate it:
var m = Model.build({ from: 'obviously not a email' });
var err = m.validate();
But if I do console.log(err), I get { fct: [Function] } only. Defining a custom validator that throws an exception results in an unhandled exception.
How should I use validate() properly?
Here is how to get your problem solved with Sequelize v2.0.0:
var Sequelize = require("sequelize")
, sequelize = new Sequelize("sequelize_test", "root")
var Model = sequelize.define('Model', {
from: {
type: Sequelize.STRING,
allowNull: false,
validate: {
isEmail: true
}
}
})
Model.sync().success(function() {
Model.build({ from: "foo#bar" }).validate().success(function(errors) {
console.log(errors)
})
})
This will result in:
{ from: [ 'Invalid email' ] }
Side note: You can also skip the validate-call and just create the instance instead:
Model.sync().success(function() {
Model
.create({ from: "foo#bar" })
.success(function() {
console.log('ok')
})
.error(function(errors) {
console.log(errors)
})
})
The error method will receive the very same error object as in the previous code snippet.
Greetings,
sdepold.
An alternative approach for validating in Sequelize, use a hook instead of a model validation. I'm using the 'beforeValidate' hook and adding custom validation (using validator module) with Promises that are rejected when validation fails.
var validator = require('validator');
module.exports = function(sequelize, DataTypes) {
var User = sequelize.define("User", {
email: {
type:DataTypes.STRING
},
password: {
type:DataTypes.STRING
}
});
//validate here
User.hook('beforeValidate', function(user, options) {
if(validator.isEmail(user.email)){
return sequelize.Promise.resolve(user);
}else{
return sequelize.Promise.reject('Validation Error: invalid email');
}
});
return User;
};
This worked for me
In model use :-
var model = sequelize.define('Model', {
from: {
type: DataTypes.STRING,
allowNull: false,
validate: {
isEmail: true
}
}
}
In your control logic while you save model do this :-
var Sequelize = require('sequelize');
var Model = require('your_model_folderpath').model;
Model.create({from: 'not email'}).then(function(model) {
// if validation passes you will get saved model
}).catch(Sequelize.ValidationError, function(err) {
// responds with validation errors
}).catch(function(err) {
// every other error
});

Resources