I am learning MEVN by watching this video https://youtu.be/H6hM_5ilhqw?t=38m28s
And I imitated to add a function to const User like below:
module.exports = (sequelize, Datatypes) =>{
const User = sequelize.define('User', {
email:{
type:Datatypes.STRING,
unique: true
},
password:Datatypes.STRING
}, {
hooks:{
beforeCreate: hashPassword,
beforeUpdate: hashPassword,
beforeSave: hashPassword
}
})
User.prototype.comparePassword = function(password) {
return bcrypt.compareAsync(password, this.password)
}
return User
}
But it always shows that TypeError: Cannot set property 'comparePassword' of undefined
(The tutorial video has no problem with it.)
I have tried to use "User.____proto____.comparePassword", and it was compiled successfully but cannot pass the function to its instance.
Hope someone can help me out, thanks!
You are using an older version of sequelize that doesn't yet support extending instances by using prototypes.
According to their documentation the older way would be to provide instanceMethods
const Model = sequelize.define('Model', {
...
}, {
classMethods: {
associate: function (model) {...}
},
instanceMethods: {
someMethod: function () { ...}
}
});
The 4.X works like you tried to make it to work.
Related
I'm new to Sails (and a jr. dev to boot) and have been working through the Sailsjs in Action book. Given some previous experience with TypeScript I wanted to experiment with the framework in that capacity. However I've hit a road block when trying to return response statuses and information via my API.
Every time I try to use the res in my controller I receive a:
TypeError: Cannot read property 'ok' of undefined. Based on the book and the documentation, I'm under the impression that would get set automatically per this example from the docs:
await User.create({name:'Finn'});
return res.ok();
and this example from the book:
signup: function(req, res) {
var options = {**grab request data**};
User.create(options).exec(function(err, createdUser){
if(err){
return res.negotiate(err);
}
return res.json(createdUser);
}
So I feel like I'm missing something pretty obvious but I'm not sure what. The project compiles just fine and I've got the documented typescript libraries installed/configured. Even matching that function there returns the same TypeError to me.
Another set of eyes would be greatly appreciated. Controller code below.
declare const sails: any;
import { boatInterface } from '../../interfaces/boat';
module.exports = {
friendlyName: 'new-boat',
description: 'create a new boat model',
inputs: {
modelName:{
required: true,
type: 'string',
},
yearBuilt:{
required: true,
type: 'number'
}
},
exits: {
success: {
description: 'New boat model was created successfully.'
},
invalid: {
responseType: 'badRequest',
description: 'The provided boat info was invalid.'
},
modelAlreadyCreated: {
statusCode: 409,
description: 'The provided boat model has already been
created.',
},
},
fn: async function (req: any, res: any){
console.log('building boat');
let boatRequest: boatInterface = {
modelName: req.modelName.toLowerCase(),
yearBuilt: req.yearBuilt
}
//confirming data has been formatted correctly
console.log(boatRequest);
let newBoat = await sails.models.boat.create(boatRequest)
.intercept('E_UNIQUE', 'modelAlreadyCreated')
.fetch();
//confirming new boat exists
console.log(newBoat);
console.log("request successful");
//res remains undefined and throws an error on attempted return
console.log(res);
return res.ok();
}
};
Here's the error with some console logs included. Thanks in advance!
building boat
{ modelName: 'kraken', yearBuilt: 1337 } <-- Request formats correctly
{ createdAt: 1566173040652,
updatedAt: 1566173040652,
id: 6,
modelName: 'kraken',
yearBuilt: 1337 } <-- new db entry is returned via fetch()
request successful
undefined <-- attempt to log the res returns undefined
(node:3738) UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'ok' of undefined
at Object.<anonymous> (/Users/pitterpatter/Repos/learn/sails-learn/freeform/api/controllers/boat/new-boat.ts:67:20)
It looks like you're using actions2.
Your handler function will be given 2 parameters - inputs and exits as you've defined in the objects above it.
fn: async function (inputs: any, exits: any) {
inputs will be an object that contains parmeters given by a user that you've defined in the inputs part of your action (form data/query parameters/route parameters).
In this case it'd contain a modelName and yearBuilt, something like
{ modelName: 'lemon', yearBuilt: 2012.3 }
exits will contain a few default methods - exits.success() and exits.error(), as well as the invalid and modelAlreadyCreated which you've defined.
TLDR try this
fn: async function (inputs: any, exits: any) {
let boatRequest: boatInterface = {
modelName: inputs.modelName.toLowerCase(),
yearBuilt: inputs.yearBuilt
}
let newBoat = await sails.models.boat.create(boatRequest)
.intercept('E_UNIQUE', 'modelAlreadyCreated')
.fetch();
return exits.success(newBoat);
}
You can access the "raw express response object" deely that you're looking for by using this.res.ok(...) or this.req.something, but it's recommended you use the appropriate "exit" instead.
I compiled vue-flash-message component from sources and got the following warning:
✘ http://eslint.org/docs/rules/no-param-reassign Assignment to property of function parameter 'Vue'
src\components\vue-flash-message\index.js:173:5
Vue.prototype[options.storage] = FlashBus;
in the following code:
export default {
install(Vue, config = {}) {
const defaults = {
method: 'flash',
storage: '$flashStorage',
createShortcuts: true,
name: 'flash-message',
};
const options = Object.assign(defaults, config);
...
const FlashBus = new Vue({
data() {
return {
storage: {
},
};
},
methods: {
flash(msg, type, opts) {
return new FlashMessage(FlashBus, msg, type, opts);
},
push(id, message) {
Vue.set(this.storage, id, message);
},
destroy(id) {
Vue.delete(this.storage, id);
},
destroyAll() {
Vue.set(this, 'storage', {});
},
},
});
...
Vue.prototype[options.storage] = FlashBus;
...
},
};
is it possible to correct the code and make it compile without warnings?
This is not an issue.
You have an ES Lint rule setup for no-param-reassign. This conflicts with Vue's way of creating plugins, where you are directed to write to the prototype directly. You can see my statement reinforced here
Your only choice is to fork that project, and ignore the line with your linter if it's bugging you that much.
I have two "models" in my application that are branched into different files:
ApplicationSession.model.js
module.exports = function(sequelize, DataTypes) {
const { INTEGER, DATE } = DataTypes;
var ApplicationSession = sequelize.define("applicationSession", {
sessionStart: DATE,
sessionEnd: DATE
}, {
associate: (models) => {
ApplicationSession.belongsTo(models.User, {
foreignKey: 'userId',
as: 'User',
});
}
});
return ApplicationSession;
};
User.model.js
module.exports = function(sequelize, DataTypes) {
const { STRING, INTEGER, DATE } = DataTypes;
var User = sequelize.define("user", {
name: STRING
}, {
associate: (models) => {
User.hasMany(models.ApplicationSession);
}
});
return User;
};
When saving the tables (DROPING/RECREATING) force: true and manual dropping just for sanity, there is never a field created for the user.
Here's how I'm loading my different models
import fs from 'fs';
import path from 'path';
import sequelize from '../connection';
var exports = {};
fs.readdirSync(__dirname).forEach(fileName => {
if(~fileName.indexOf('.model.js')) {
const subname = fileName.replace('.model.js', '');
const model = sequelize.import(path.join(__dirname, fileName));
exports[subname] = model;
}
});
export default exports;
When all of the models are declared in a single file, I can use X.belongsTo(Y) without any problems, so I thought I'd try adding this to the bottom of my sequelize.import calls
exports['ApplicationSession'].belongsTo(exports['User'], { as: 'User', foreignKey: 'userId' });
exports['User'].hasMany(exports['ApplicationSession']);
However, that generated a different error:
/node_modules/sequelize/lib/associations/mixin.js:96
throw new Error(this.name + '.' + Utils.lowercaseFirst(Type.toString()) + ' called with something that\'s not an instance of Sequelize.Model');
^
Error: applicationSession.function BelongsTo(source, target, options)
What do I do to make relationships work?
You need to add one more thing to your model loading module to make associations work.
fs.readdirSync(__dirname).forEach(fileName => {
if(~fileName.indexOf('.model.js')) {
const subname = fileName.replace('.model.js', '');
const model = sequelize.import(path.join(__dirname, fileName));
exports[subname] = model;
}
});
// You need to check for every model if it has `associate` class method
// and execute it
Object.keys(exports).forEach(function(modelName) {
if ("associate" in exports[modelName]) {
exports[modelName].associate(db);
}
});
I also saw another mistake in your code. The class method defined in your models associate should be wrapped around classMethods property.
It should look like this :
var ApplicationSession = sequelize.define("applicationSession", {
sessionStart: DATE,
sessionEnd: DATE
}, {
// Add this
classMethods: {
associate: (models) => {
ApplicationSession.belongsTo(models.User, {
foreignKey: 'userId',
as: 'User',
});
}
}
});
You can follow the examples by sequelize in GitHub.
So starting my adventure into all things Node. One of the tools I am trying to learn is Sequelize. So I will start off what I was trying to do:
'use strict';
var crypto = require('crypto');
module.exports = function(sequelize, DataTypes) {
var User = sequelize.define('User', {
username: DataTypes.STRING,
first_name: DataTypes.STRING,
last_name: DataTypes.STRING,
salt: DataTypes.STRING,
hashed_pwd: DataTypes.STRING
}, {
classMethods: {
},
instanceMethods: {
createSalt: function() {
return crypto.randomBytes(128).toString('base64');
},
hashPassword: function(salt, pwd) {
var hmac = crypto.createHmac('sha1', salt);
return hmac.update(pwd).digest('hex');
},
authenticate: function(passwordToMatch) {
return this.hashPassword(this.salt, passwordToMatch) === this.hashed_pwd;
}
}
});
return User;
};
I am confused on when to use classMethods vs instanceMethods. To me when I think about createSalt() and hashPassword() should be class methods. They are general and for the most part dont really have anything to do with the specific instance they are just used in general. But when I have createSalt() and hashPassword() in classMethods I cannot call them from instanceMethods.
I have tried variations of the following:
this.createSalt();
this.classMethods.createSalt();
createSalt();
Something like below wont work and I am probably just not understanding something simple.
authenticate: function(passwordToMatch) {
console.log(this.createSalt());
return this.hashPassword(this.salt, passwordToMatch) === this.hashed_pwd;
}
Any hints/tips/direction would be very much so appreciated!
All the method who don't modify or check any type of instance should be classMethod and the rest instanceMethod
ex:
// Should be a classMethods
function getMyFriends() {
return this.find({where{...}})
}
// Should be a instanceMethods
function checkMyName() {
return this.name === "george";
}
Although the basics are that instance methods should be used when you want to modify your instance ( ergo row ). I would rather not pollute the classMethods with methods that don't use the class ( ergo the table ) itself.
In your example I would put hashPassword function outside your class and leave it as a helper function somewhere in my utilities module ( or why not the same module but as a normal defined function ) ... like
var hashPassword = function(...) { ... }
...
...
instanceMethods: {
authenticate: function( ... ) { hashPassword( ... ) }
}
I found this worked for me as of sequelize 3.14
var myModel = sequelize.define('model', {
}, {
classMethods: {
someClassMethod: function() {
return true;
}
}, {
instanceMethods: {
callClassMethod: function() {
myModel.someClassMethod();
}
}
});
The question:
As I understand in sails.js during initialization process Services are initialized before Models.
Is there any possibility to change this behavior? To make Models load before Services.
If it's not, then how can I load particular settings from the database to use them to build instance of my class described in some Service during this Service initialization?
A little bit code for solidity:
api/models/Model.js
console.log("Model Identified");
module.exports = {
attributes: {
name: { type: 'string', required: true, size: 15 },
//Some extra secret fields
}
};
...
api/services/MyCoolService.js
console.log('service inits');
function MyCoolService(options){
//some extraordinary constructor logic may be ommited
}
MyCoolService.prototype.setOptions = function(options){
//Set values for MyCoolService fields.
}
//Some other methods
var myCoolServiceWithSettingsFromDb = new MyCoolService();
//That's the place
model.findOne(sails.config.myApplication.settingsId).exec(function(err,result){
if(!err)
myCoolServiceWithSettingsFromDb.setOptions(result);
});
module.exports = myCoolServiceWithSettingsFromDb;
It's because you instantiate object in service with constructor that needs sails that not exist. Try use this at MyCoolService;
module.exports = {
someOption: null,
method: function () {
var that = this;
sails.models.model.findOne(sails.config.myApplication.settingsId)
.exec(function (err, result) {
if (!err)
that.someOption = result;
});
}
};
that method can be called by sails.services.mycoolservice.method() or simply MyCoolService.method() to give your service some option from DB.
If you want to initiate them at Sails start, call that method at config/bootstrap.js
Thanks to Andi Nugroho Dirgantara,
I ended up with this solution (I still don't like it much, but it works):
api/services/MyCoolService.js
console.log('service inits');
function MyCoolService(options){
//some extraordinary constructor logic may be ommited
}
//All the same as in question
//The instance
var instance;
module.exports = module.exports = {
init: function(options) {
instance = new MyCoolService(options);
},
get: function() {
return instance;
},
constructor: MyCoolService
};
config/bootstrap.js
...
Model.findOrCreate({ id: 1 }, sails.config.someDefaultSettings).exec(function(err, result) {
if (err)
return sails.log.error(err);
result = result || sails.config.someDefaultSettings;
MyCoolService.init(result);
return sails.log.verbose("MyCoolService Created: ", TbcPaymentProcessorService.get());
});
...
tests/unit/service/MyCoolService.test.js
...
describe('MyCoolService', function() {
it('check MyCoolService', function(done) {
assert.notDeepEqual(MyCoolService.get(), sails.config.someDefaultSettings);
done();
});
});
...
It works: the service is instantiated once while bootstraping and it's instance is avaliable everywhere.
But to me this solution still weird... I still don't understand how to globally instantiate instance of my service (for use in a lot of controllers) and make it the best way.