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.
Related
I am developping a JHipster blueprint and I need to use EJS to template the files I want to generate. Since this is my first time using EJS, all I am trying to do for now is use an answer from one of the generated question and create a java interface with its name.
This is the template I got:
public interface <%= databaseURL %> {
}
prompts.js:
function askForDatabaseURL(meta) {
const applicationType = this.applicationType;
const prompts = [
{
type: 'string',
name: 'databaseURL',
message:
'Quel est l\'URL de votre base de données ?',
default: 'URL'
}
];
if (meta) return PROMPTS;
const done = this.async();
this.prompt(prompts).then(prompt => {
this.log(this.databaseURL);
this.databaseURL = prompt.databaseURL;
this.log(this.databaseURL);
done();
});
}
module.exports = {
askForDatabaseURL
};
index.js:
const chalk = require('chalk');
const AppGenerator = require('generator-jhipster/generators/app');
const prompts = require('./prompts');
module.exports = class extends AppGenerator {
constructor(args, opts) {
super(args, { fromBlueprint: true, ...opts }); // fromBlueprint variable is important
this.databaseURL = "Hello";
}
get initializing() {
return super._initializing();
}
_prompting() {
return {
askForDatabaseURL: prompts.askForDatabaseURL
}
}
get prompting() {
const defaultPhaseFromJHipster = super._prompting();
const myPrompting = this._prompting();
return Object.assign(defaultPhaseFromJHipster, myPrompting);
}
get configuring() {
return super._configuring();
}
get default() {
return super._default();
}
_writing() {
this.fs.copyTpl(
this.templatePath(`src/main/java/package/repository/JOOQRepository.java.ejs`),
this.destinationPath(`${this.databaseURL}.java`),
{ databaseURL : this.databaseURL}
)
}
get writing() {
const defaultPhaseFromJHipster = super._writing();
const myWriting = this._writing()
return Object.assign(defaultPhaseFromJHipster, myWriting);
}
get install() {
return super._install();
}
get end() {
return super._end();
}
};
The problem is, after the prompting phase, this.databaseURL always has a value of "Hello" which is the default value in the constructor, meaning the file generated is always Hello.java.
I tried to add this.log(this.databaseURL); before and after this.databaseURL = prompt.databaseURL so I'd get an idea if this line does what it's supposed to and it does:
I am fairly new to JavaScript so I might have missed something very basic, but I don't understand why this.databaseURL returns "Hello" after assigning it the user's answer to it.
Any help is welcomed!
I'm trying to make a relationship between two tables.
My relationship is belongsToMany between user => user_bet_match => matchs.
A user can have many user_bet_match and matchs can have many user_bet_match.
My database migration is :
Matchs table :
this.create('matchs', (table) => {
table.increments()
table.integer('round_id').unsigned()
table.integer('league_id').unsigned()
table.integer('hometeam_id').unsigned()
table.integer('awayteam_id').unsigned()
table.string('final_score_hometeam_goal')
table.string('final_score_awayteam_goal')
table.string('halftime_score_hometeam_goal')
table.string('halftime_score_awayteam_goal')
table.date('event_date')
table.integer('event_timestamp')
table.boolean('betailable').defaultTo(false)
table.boolean('is_finish').defaultTo(false)
table.timestamps()
})
User table:
this.create('users', (table) => {
table.increments()
table.string('username', 80).notNullable().unique()
table.string('email', 254).notNullable().unique()
table.string('password', 60).notNullable()
table.timestamps()
})
user_bet_match table :
this.create('user_bet_match', (table) => {
table.increments()
table.integer('user_id').unsigned()
table.integer('match_id').unsigned()
table.string('choice').notNullable()
table.timestamps()
})
My user model:
class User extends Model {
static boot () {
super.boot()
this.addHook('beforeSave', async (userInstance) => {
if (userInstance.dirty.password) {
userInstance.password = await Hash.make(userInstance.password)
}
})
}
tokens () {
return this.hasMany('App/Models/Token')
}
match () {
return this.belongsToMany('App/Models/Match').pivotTable('user_bet_match')
}
My user bet match module:
'use strict'
/** #type {typeof import('#adonisjs/lucid/src/Lucid/Model')} */
const Model = use('Model')
const Database = use('Database')
class UserBetMatch extends Model {
user () {
return this.hasOne('App/Models/User')
}
matchs () {
return this.hasOne('App/Models/Match')
}
}
module.exports = UserBetMatch
And my matchs module:
'use strict'
/** #type {typeof import('#adonisjs/lucid/src/Lucid/Model')} */
const Model = use('Model')
class Match extends Model {
userbetmatchs () {
return this.hasMany('App/Models/UserBetMatch')
}
}
module.exports = Match
And when I make :
let k = user.match().fetch()
With this relation :
match () {
return this.belongsToMany('App/Models/Match').pivotTable('user_bet_match')
}
It's returning me sqlMessage: "Table 'bo7jjjccwliucibms5pf.matches' doesn't exist"
But I never mention of a table "matches"..
I don't know why..
I noticed that you changed the name of the tables in the migration (by default with adonis cli : matches; user_bet_matches)
Try to use this in your models:
static get table () {
return 'matchs' // Your table name
}
^ https://adonisjs.com/docs/4.0/lucid#_table
Lucid does not take into account the migrations.
It's therefore necessary to specify the name of the table if it is not the default one (with adonis cli).
Don't hesitate to tell me if it's not fair.
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.
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');
}
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();
}
}
});