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');
}
Related
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'm practicing some Angular/Ionic and am having a bit of a hard time figuring out how i get all offresList of all users
this is my database look like :
This is my providre offre.ts
export class OffreProvider {
public offreListRef: firebase.database.Reference;
constructor() {
firebase.auth().onAuthStateChanged(user => {
if (user) {
this.offreListRef = firebase
.database()
.ref(`/userProfile/${user.uid}/offreList`);
}
});
}
createOffre(
offreTitre: string,
offreSecteur: string,
offreVille: string,
offreDispo: string,
offreDescrip: string,
offreDate : string,
): firebase.database.ThenableReference {
return this.offreListRef.push({
titre: offreTitre,
secteur: offreSecteur,
ville: offreVille,
dispo: offreDispo,
descrip: offreDescrip,
date : offreDate
});
}
getOffreList(): firebase.database.Reference {
return this.offreListRef;
}
}
and this is how i get all offres of currentUser
mesOffres.ts
export class MesOffresPage {
public offreList: Array<any>;
constructor(public navCtrl: NavController, public navParams: NavParams,
public offreProvider: OffreProvider) {
}
creatOffre(): void {
this.navCtrl.push('CreateOffrePage');
}
ionViewDidLoad() {
this.offreProvider.getOffreList().on("value", offreListSnapshot => {
this.offreList = [];
offreListSnapshot.forEach(snap => {
this.offreList.push({
id: snap.key,
titre: snap.val().titre,
secteur: snap.val().secteur,
ville: snap.val().ville
});
return false;
});
});
}
}
now how i can get all offreslist of all Users with alloffres.ts
and display it in alloffres.html
I m stacking for 2 days
To get the offers across all users, you will have to load all user data. Something like this:
ionViewDidLoad() {
let ref = firebase.database().ref("userProfile");
ref.on("value", userListSnapshot => {
this.offreList = [];
userListSnapshot.forEach(userSnapshot => {
let offerListSnapshot = userSnapshot.child("offreList");
offerListSnapshot.forEach(offerSnapshot => {
this.offreList.push({
id: snap.key,
titre: snap.val().titre,
secteur: snap.val().secteur,
ville: snap.val().ville
});
});
})
});
}
Note that this code also loads the profile data for each user, which the it doesn't use. This is one of the many reasons why experienced Firebase developers recommend to keep separate entity types in separate top-level lists. So in your case, I'd recommend having:
userProfiles: {
uid1: {
...
},
uid2: {
...
}
},
userOffers: {
uid1: {
"-LRZ...": {
...
}
},
uid2: {
"-LRQ...": {
...
}
}
}
With the above structure you can load just the offers for all users with code very similar to what I shared above. But with the updated structure, you won't be loading the user profile data unnecessarily. And just in case you need the profile data and offers for a user, you can easily load both in separate calls. The performance will be very similar, since Firebase pipelines the requests over a single connection.
I would like some kind of Model for my Meteor collections. The two options I could find for this would be to use collection helpers:
Users = new Mongo.Collection('users')
Users.helpers({
fullName() {
return `${this.firstName} ${this.lastName}`
}
})
or to map the returned objects to class instances:
class UserCollection extends Mongo.Collection {
fetch() : User[] {
return super.fetch().map(user => new User(user))
}
}
let users = new UserCollection('users')
class User {
collection: Mongo.Collection
_id: string
firstName: string
lastName: string
constructor({firstName: string, lastName: string) {
this.firstName = firstName
this.lastName = lastName
this.collection = users
}
get fullname () {
return `${this.firstName} ${this.lastName}`
}
save () {
if(this._id) {
this.collection.update({ $set: { ...this }})
}
else {
this.collection.insert({...this})
}
}
}
Which variant is preferable? I like B more, but rarely found any examples using it.
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();
}
}
});