How can I restore lifecycle callbacks in sails? - sails.js

After testing model creation, I noticed that lifecycle callbacks were not getting called and upon reading Waterline's documentation I found:
NOTE: When using custom adapter methods the features of Waterline are not used. You no longer get the Lifecycle Callbacks and Validations as you would when using a defined Waterline method.
Though, I haven't knowingly used a custom adapter method, and that is the only reference I could find in the documentation about lifecycle callbacks getting disabled.
What criteria/setting of whichever files in config/* should I have to absolutely ensure that lifecycle callbacks are not disabled?
Here is a copy of my model for which the only lifecycle callback I use does not get called:
/**
* User.js
*
*/
var bcrypt = require('bcrypt');
module.exports = {
attributes: {
'email': {
type: 'email',
required: true,
unique: true
},
'username': {
type: 'string',
required: true,
unique: true,
minLength: 5,
maxLength: 16
},
'password': {
type: 'string',
required: true
},
'family': {
model: 'family'
},
'lastlogin': {
type: 'datetime',
defaultsTo: function() {return new Date().toISOString();}
},
beforeCreate: function(obj, cb) {
console.log("In beforeCreate");
bcrypt.hash(obj.password, 10, function(err, hash) {
if (err) {
console.log(err);
return cb(err);
}
obj.password = hash;
cb();
});
}
}
};`

Your callback need to be on the exports object, its not an attribute.
/**
* User.js
*
*/
var bcrypt = require('bcrypt');
module.exports = {
attributes: {
'email': {
type: 'email',
required: true,
unique: true
},
'username': {
type: 'string',
required: true,
unique: true,
minLength: 5,
maxLength: 16
},
'password': {
type: 'string',
required: true
},
'family': {
model: 'family'
},
'lastlogin': {
type: 'datetime',
defaultsTo: function() {return new Date().toISOString();}
},
},
beforeCreate: function(obj, cb) {
console.log("In beforeCreate");
bcrypt.hash(obj.password, 10, function(err, hash) {
if (err) {
console.log(err);
return cb(err);
}
obj.password = hash;
cb();
});
}
};

Related

How do I get sequelize to not use timestamp fields when generating SQL for getting n:m associated models?

My models:
'use strict';
module.exports = (sequelize, DataTypes) => {
const Vendor = sequelize.define('Vendor', {
id: { type: DataTypes.INTEGER, allowNull: false, autoIncrement: true, primaryKey: true },
// other fields
}, {});
Vendor.associate = function (models) {
Vendor.belongsToMany(models.Corporate, { through: 'VendorCorporates', foreignKey: 'vendorId' });
};
return Vendor;
};
and
'use strict';
module.exports = (sequelize, DataTypes) => {
const Corporate = sequelize.define('Corporate', {
id: { type: DataTypes.INTEGER, allowNull: false, autoIncrement: true, primaryKey: true },
// other fields
}, {});
Corporate.associate = function (models) {
Corporate.belongsToMany(models.Vendor, { through: 'VendorCorporates', foreignKey: 'corporateId' });
};
return Corporate;
};
and the migration for the association (link) table:
'use strict';
const vendorCorps = 'VendorCorporates', vendorId = 'vendorId', corpId = 'corporateId';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable(vendorCorps, {
vendorId: {
type: Sequelize.INTEGER,
references: {
model: 'Vendors',
key: 'id'
},
onUpdate: 'CASCADE',
onDelete: 'CASCADE'
},
corporateId: {
type: Sequelize.INTEGER,
references: {
model: 'Corporates',
key: 'id'
},
onUpdate: 'CASCADE',
onDelete: 'CASCADE'
},
// notice no timestamps
});
},
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('VendorCorporates');
}
};
Now when I try to get the Corporates that are associated with a Vendor:
let vendor = await models.Vendor.findByPk(pk);
let corporates = await vendor.getCorporates();
the query fails because sequelize generates SQL with the fields updatedAt and createdAt. How do I tell sequelize to generate SQL without the timestamp fields?
Clarification: I want timestamps in the Corporate & Vendor models, but none in the link/association table, and I want the join query (vendor.getCorporates()) to be generated accordingly.
You can indicate this using the timestamps option. For instance:
const Corporate = sequelize.define('Corporate', {
id: { type: DataTypes.INTEGER, allowNull: false, autoIncrement: true, primaryKey: true },
// other fields
}, {
timestamps: false
});
Finally solved this by writing a model for the n:m association itself:
'use strict';
module.exports = (sequelize, DataTypes) => {
const VendorCorporate = sequelize.define('VendorCorporate', {
vendorId: DataTypes.INTEGER,
corporateId: DataTypes.INTEGER
}, { timestamps: false });
VendorCorporate.associate = function (models) {
// associations can be defined here
};
return VendorCorporate;
};
and changing:
// db/models/vendor.js
Vendor.belongsToMany(models.Corporate, { through: 'VendorCorporates', foreignKey: 'vendorId' });
to:
Vendor.belongsToMany(models.Corporate, { through: models.VendorCorporate, foreignKey: 'vendorId' });
and making a similar change in the Corporate model file.

How can I refer to the schema I was trying to save in nestjs/mongoose?

I am trying to encrypt some passwords and get its salt before saving my model to mongoose in Nestjs, but simply using this to refer to the schema itself doesn't yield any results as it refers to the UserSchemaProvider object itself, instead of the current model I'm trying to save.
My schema provider:
export const UserSchemaProvider = {
name: 'User',
useFactory: (): mongoose.Model<User> => {
const UserSchema = new mongoose.Schema({
name: { type: String, required: true },
email: { type: String, required: true, unique: true },
password: { type: String, required: true },
birthday: { type: Date, required: true },
celphoneNumber: String,
whatsapp: Boolean,
promo: Object,
status: String
});
UserSchema.pre<User>('save', async (next) => {
const user = this;
console.log(user);
if (user.password) {
const salt = await bcrypt.genSalt();
bcrypt.hash(user.password, salt, (err, hash) => {
if (err) return next(err);
user.password = hash;
next();
});
}
});
return UserSchema;
},
};
and my user Module comes below:
#Module({
imports: [
MongooseModule.forFeatureAsync([
UserSchemaProvider]),
HttpModule
],
controllers: [UsersController],
providers: [UsersService, Validator, ValidationPipe, IsEmailInUseConstraint, GoogleRecaptchaV3Constraint],
})
:Nest Platform Information:
platform-express version: 6.10.14
mongoose version: 6.3.1
common version: 6.10.14
core version: 6.10.14
Your pre hook handler shouldn't be an arrow function () => {}. mongoose handler will need to have the execution context to point to a current document being saved. When using arrow function, your execution context of the pre hook is no longer the document, hence, this inside of the handler isn't the document itself anymore.
export const UserSchemaProvider = {
name: 'User',
useFactory: (): mongoose.Model<User> => {
const UserSchema = new mongoose.Schema({
name: { type: String, required: true },
email: { type: String, required: true, unique: true },
password: { type: String, required: true },
birthday: { type: Date, required: true },
celphoneNumber: String,
whatsapp: Boolean,
promo: Object,
status: String
});
UserSchema.pre<User>('save', async function(next) { // <-- change to a function instead
const user = this;
console.log(user);
if (user.password) {
const salt = await bcrypt.genSalt();
bcrypt.hash(user.password, salt, (err, hash) => {
if (err) return next(err);
user.password = hash;
next();
});
}
});
return UserSchema;
},
};

Multiple chained promises is sailsjs

This is my first attempt at attempting to chain multiple finds together. The debug running shows that all the code executes correctly but there is a delay in receiving the users array back and therefore unable to present the data back.
The concept is a user may belong to multiple organizations, and there may be more than one user (other than the current user) that may belong to organizations. The function is trying to receive all users for all the organizations the current user belongs to.
getUserOrganizationsUsers: function (userId) {
var users = [];
sails.log.info('Getting the current users organizations [' + userId + ']');
return UserOrganization.find({ user_id: userId, deleted: null })
.populate('organization_id', { deleted: null })
.populate('user_id', { deleted: null })
.then(function (userorganization) {
return userorganization;
})
.then(function (userorgs) {
/* From all the organizations I want to get all the users from those organizations */
_.forEach(userorgs, function (userorg) {
UserOrganization.find({ organization_id: userorg.organization_id.id })
.populate('organization_id', { deleted: null })
.populate('user_id', { deleted: null })
.then(function (otherusrs) {
_.forEach(otherusrs, function (otherusr) {
sails.log.info('other userss each loop ');
var users = _.find(otherusrs, {id: otherusr.organization_id.id});
users.push(users);
})
})
});
return Q.when(employees);
})
},
Organization.js
module.exports = {
attributes: {
companyName: {
type: 'string',
required: true
},
Address: {
type: 'string'
},
ABN: {
type: 'string'
},
City: {
type: 'string'
},
contactNumber: {
type: 'string'
},
country: {
type: 'string'
},
icon: {
type: 'string'
},
users:
{ collection: 'userorganization',
via : 'user_id'
},
deleted: {
type: 'date',
defaultsTo: null
},
toJSON: function () {
var obj = this.toObject();
obj = _.pick(obj, Organization.publicFields);
return obj;
}
},
editableFields: [
'companyName',
'users'
// 'industries'
],
publicFields: [
'id',
'companyName',
'users'
],
};
UserOrganization.js
module.exports = {
attributes: {
organization_id: {
model : 'organization',
required: true
},
user_id: {
model: 'user',
required: true
},
organizationRole: {
type: 'string',
required: true
},
deleted: {
type: 'date',
defaultsTo: null
},
toJSON: function () {
var obj = this.toObject();
obj = _.pick(obj, UserOrganization.publicFields);
return obj;
}
},
editableFields: [
'organization_id',
'user_id',
'organizationRole',
],
publicFields: [
'id',
'organization_id',
'user_id',
'organizationRole'
],
};
and the user.js
var bcrypt = require('bcrypt-nodejs');
module.exports = {
attributes: {
email: {
type: 'email',
required: true,
unique: true
},
password: {
type: 'string',
required: true
},
firstName: {
type: 'string'
},
lastName: {
type: 'string'
},
verified: {
type: 'boolean',
defaultsTo: false
},
organizations:
{ collection: 'userorganization',
via : 'user_id'
}, deleted: {
type: 'date',
defaultsTo: null
},
fullName: function () {
return this.firstName + ' ' + this.lastName;
},
toJSON: function () {
var obj = this.toObject();
obj = _.pick(obj, User.publicFields);
return obj;
}
},
// TODO: Add initialFields
editableFields: [
'password',
'email',
'firstName',
'lastName',
'organizations'],
publicFields: [
'id',
'email',
'verified',
'firstName',
'lastName',
'fullName',
'organizations'
],
comparePassword: function (password, user, cb) {
bcrypt.compare(password, user.password, function (err, match) {
if(err) return cb(err);
cb(null, match);
})
},
beforeCreate: function (user, cb) {
bcrypt.genSalt(10, function (err, salt) {
bcrypt.hash(user.password, salt, function () {}, function (err, hash) {
if (err) {
sails.log.error(err);
return cb(err);
}
user.password = hash;
cb(null, user);
});
});
}
};
Okay, I think I understand what you're doing. It would be a lot simpler to have the User belong to an organization directly.
Anyways, if I understood your model structure correctly, something like this should work:
getUserOrganizationsUsers: function (userId) {
UserOrganization.find({ user_id: userId, deleted: null })
.then(function (userOrgs) {
// return array of organization IDs
return _.map(userOrgs, function(org){
return org.id;
});
})
.then(function (userOrgs) {
Organization.find(userOrgs)
.populate('users') // users is a collection of UserOrganization
.exec(function(err, orgs){ // lookup organizations
if(err) //handle error
else {
return _.flatten( // return basic array for next promise handler
_.map(orgs, function(org){ // for each organization
return _.map(org.users, function(user){ // return an array of user_ids
return user.user_id;
})
})
)
}
})
})
.then(function(allUserOrgs){
UserOrganization.find(allUserOrgs)
.populate('user_id')
.exec(function(err, userOrgsList){
return _.map(userOrgsList, function(user){
return user.user_id;
})
})
})
.then(function(users){
// users should be an array of all the users form allt he organizations that the current users belongs to
})
},

Eager Loading : How to disable specific fields of included table

I am trying to do Eager Loading in Sequelize with PostgreSQL where I need to find the Users which have a given specific Mail id or basically, i am performing find operation on Mail model while using include to include User model
UserModel :
module.exports = function (sequelize, Sequelize) {
var User = sequelize.define('User', {
userId: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true
},
firstname: {
type: Sequelize.STRING,
require: true
},
lastname: {
type: Sequelize.STRING,
require: true
},
age: {
type: Sequelize.INTEGER,
require: true
},
phone: {
type: Sequelize.STRING,
require: true
},
location: {
type: Sequelize.STRING,
require: true
},
createdAt: {
type: Sequelize.DATE,
defaultValue: Sequelize.NOW
},
updatedAt: {
type: Sequelize.DATE,
defaultValue: Sequelize.NOW
}
});
return User;
};
MailModel :
module.exports = function (sequelize, Sequelize) {
var User = require('./User.js')(sequelize, Sequelize)
var Mail = sequelize.define('Mail', {
name: {
type: Sequelize.STRING,
require: true
}
});
Mail.belongsTo(User);
return Mail;
};
MailController :
var db = require('../services/db.js');
module.exports = {
create: function (req, res, next) {
var Mailm = db.MailModel;
var name = req.body;
try {
db.sequelize.sync().then(function () {
Mailm.create(name).then(function (found) {
return res.json({
success: true,
message: found.get({
plain: true
})
});
})
});
} catch (ex) {
res.json({
success: false,
exception: ex
});
return;
}
},
query: function (req, res, next) {
var Mailm = db.MailModel;
var Userm = db.UserModel;
var name = req.body;
var option = {};
option.where = name;
option.include = [{
model: Userm
}];
try {
Mailm.findAll(option).then(function (found) {
console.log(found);
return res.json({
success: true,
message: found
});
});
} catch (ex) {
res.json({
success: false,
exception: ex
});
return;
}
}
};
It is returning me the records of both User and Mail table in exactly the right way .
Output :
{
"success": true,
"message":[
{
"id": 2,
"name": "Mailb2",
"createdAt": "2015-07-30T07:32:51.807Z",
"updatedAt": "2015-07-30T07:32:51.807Z",
"UserUserId": 2,
"User":{
"userId": 2,
"firstname": "Prerna",
"lastname": "Jain",
"age": 20,
"phone": "9812123456",
"location": "Sirsa",
"createdAt": "2015-07-30T07:30:48.000Z",
"updatedAt": "2015-07-30T07:30:48.000Z"
}
}
]
}
But I want to disable createdAt and updatedAt fields of User table so that it does not give me these two fields in the output for User.
I have tried a lot as of how to do this but still in vain.Can anyone please help me out.
I bet this is coming late, add attribute/properties to your models called timestamps, it accepts a boolean as a value. For example:
module.exports = function (sequelize, Sequelize) {
var User = require('./User.js')(sequelize, Sequelize)
var Mail = sequelize.define('Mail', {
name: {
type: Sequelize.STRING,
require: true
}
},
{
// This does the magic
timestamps: false,
});
Mail.belongsTo(User);
return Mail;
};
Also, add it to the User model:
var User = sequelize.define('User', {
userId: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true
},
firstname: {
type: Sequelize.STRING,
require: true
},
lastname: {
type: Sequelize.STRING,
require: true
},
age: {
type: Sequelize.INTEGER,
require: true
},
phone: {
type: Sequelize.STRING,
require: true
},
location: {
type: Sequelize.STRING,
require: true
},
createdAt: {
type: Sequelize.DATE,
defaultValue: Sequelize.NOW
},
updatedAt: {
type: Sequelize.DATE,
defaultValue: Sequelize.NOW
}
},
{
timestamps: false
});
return User;
};
You can use
Model.findAll({
attributes: { exclude: ['baz'] }
});
more examples with attributes - http://docs.sequelizejs.com/en/latest/docs/querying/#attributes

Sails.js 0.10.0-rc5 many-to-many association: remove

i'm developing an app with sails.js beta and mongodb.
I've two models in a many-to-many association, i can successfully associate and populate instances of these models using .add() and .populate() methods. My problem is now that the .remove() method seems to do nothing.
here the models:
//Menu.js
module.exports = {
schema : true,
attributes: {
name: {
type: 'string',
minLength: 3,
required: true
},
dishes: {
collection: 'dish',
via: 'menus',
dominant: true
}
}
};
//Dish.js
module.exports = {
schema : true,
attributes: {
name: {
type: 'string',
minLength: 3,
required: true
},
description: 'string',
menus: {
collection: 'menu',
via: 'dishes'
}
}
};
And here the controller actions...
addDishToMenu: function(req,res,next){
Menu.findOne(req.param('menu')).populate('dishes').exec(function(err,bean){
if(err) return next(err);
if(!bean) return next();
bean.dishes.add(req.param('dish'));
bean.save(function(err) {
if(err) return next(err);
res.redirect('/main/dishes/');
})
})
},
removeDishFromMenu: function(req,res,next){
Menu.findOne(req.param('menu')).populate('dishes').exec(function(err,bean){
if(err) return next(err);
if(!bean) return next();
bean.dishes.remove(req.param('dish'));
bean.save(function(err) {
if(err) return next(err);
res.redirect('/main/menu/' + req.param('menu'));
})
})
}
I can't figure out what i'm doing wrong. Any ideas?
This issue has been fixed and I confirmed it with the repo I sent earlier. If you update your sails, waterline, and sails-mongo versions you should be good to go.