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();
}
}
});
Related
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.
what exactly I want to do is, I have a file say permissions.js with contents:
Permissions.js
module..exports = {
Student: {
Read: true,
Service: "StudentService",
Teach: false
},
Teacher: {
Read: false,
Service: "TeacherService",
Teach: true
}
}
and another file functions.js:
functions.js
// functions.js
module.exports = {
StudentService: {
someFunc1: function() {...},
someFunc2: function() {...},
someFunc3: function() {...}
},
TeacherService: {
someFunc1: function() {...},
someFunc2: function() {...},
someFunc3: function() {...}
}
}
and importing both the files in school.js:
school.js
// school.js
import functions from "./path-to/functions.js";
import { Student, Teacher } from "./path-to/permissions.js";
// Making it very simple
let service = Student.Service; // service = "StudentService"
service.someFunc1() // returns someFunc1 is not a function
So what I want to do is use the service variable as a reference for the StudentService in functions.js. Cannot use require.
This doesn't isn't specific to react-native at all, just modern JS modules. This will work:
import * as functions from "./path-to/functions.js"
import { Student } from "./path-to/permissions.js"
let service = Student.Service;
functions[service].someFunc1
A better approach though would be this:
perissions.js
import { StudentService, TeacherService } from "./path-to/functions.js";
module.exports = {
Student: {
Read: true,
Service: StudentService,
Teach: false
},
Teacher: {
Read: false,
Service: TeacherService,
Teach: true
}
}
And then you never have to mess about referencing things with strings.
I'm fighting hard with relations inside my bookshelf model. What I'm trying to do is to create schema which contains 4 tables:
users
roles
privileges
privileges_roles
With relations between them like this:
users manyTOone roles manyTOone privileges_roles oneTOmany privileges
I've easily achieved relations between privileges and roles with:
Privileges
class Privileges {
constructor() {
this.model = bookshelf.Model.extend({
tableName: 'privileges',
roles: function () {
return this.belongsToMany(Roles.model).through(PrivilegeRole.model);
}
});
};
}
Roles
class Roles {
constructor() {
this.model = bookshelf.Model.extend({
tableName: 'roles',
privileges: function() {
return this.belongsToMany(Privileges.model).through(PrivilegeRole.model);
},
users: function() {
return this.hasMany(Users.model);
}
});
};
}
PrivilegeRole
class PrivilegeRole {
constructor() {
this.model = bookshelf.Model.extend({
tableName: 'privileges_roles',
role: function() {
return this.belongsTo(Roles.model);
},
privileges: function() {
return this.belongsTo(Privileges.model);
}
});
};
}
Those works really fine. Unfortunately when I'm trying to fetch Privileges from User model it keep inserting id instead of role_id to query.
class Users {
constructor() {
this.model = bookshelf.Model.extend({
tableName: 'users',
role: function () {
return this.belongsTo(Role.model);
},
privileges: function () {
// return this.belongsToMany(Privileges.model).through(PrivilegeRole.model);
return this.belongsToMany(Privileges.model, 'privileges_roles', 'role_id', 'privilege_id', 'role_id');
}
});
};
}
So at the end whatever I do, bookshelf is creating query like this:
select privileges.*, privileges_roles.id as _pivot_id,
privileges_roles.role_id as _pivot_role_id,
privileges_roles.privilege_id as _pivot_privilege_id from
privileges inner join privileges_roles on
privileges_roles.privilege_id = privileges.id where
privileges_roles.role_id in (1)
Instead of role_id in (3) like it's in a record fetched.
Alright so I've finally found a solution. Instead of previously used:
privileges: function () {
return this.belongsToMany(Privileges.model, 'privileges_roles', 'role_id', 'privilege_id', 'role_id');
}
I had to simply use:
privileges: function () {
return this.belongsToMany(Privileges.model).through(PrivilegeRole.model, 'role_id', 'privilege_id', 'role_id');
}
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.
How can I run only a single function in a generator (and end the generator) if a flag is set? What is the preferred way to achieve this?
var MyGenerator = yeoman.generators.Base.extend({
constructor: function () {
yeoman.generators.Base.apply(this, arguments);
this.option('flag', {
desc: 'Do something',
type: String,
required: false,
defaults: null
});
},
runOnlyThisIfFlagIsSet: function() {
if(this.options.flag) {
// do stuff and end the generator so that it does all the things defined here
}
},
doNotRunThis: function() {
// I don't want this to run if the flag is set
},
iCouldDoThisButItIsTooRepetitive: function() {
if(!this.options.flag) {
// do stuff
}
}
});
module.exports = MyGenerator;
yo myGeneratorName --flag
Maybe you're over thinking this? Based on http://yeoman.io/authoring/running-context.html, I would recommend only having maybe one function in your generator anyway (default?). Read the section called "Helper and private methods" if you want to break your generator into additional methods.
var MyGenerator = yeoman.generators.Base.extend({
constructor: function () {
yeoman.generators.Base.apply(this, arguments);
this.option('flag', {
desc: 'Do something',
type: String,
required: false,
defaults: null
});
},
default: function() {
if(this.options.flag) {
// do stuff and end the generator so that it does all the things defined here. Use the documentation link above to figure out how to create private methods you can call from here.
}
}
});
module.exports = MyGenerator;
If the flag is not set, the generator will just exit.