Last active
April 10, 2018 18:25
-
-
Save jkantr/87a62b5629d616bb9f814be1b0de0494 to your computer and use it in GitHub Desktop.
Monstrosity of Objection.js model configuration and injection
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// app.js | |
// ... | |
const models = require('./models/'); | |
const state = { | |
models, | |
logger, | |
errorReporter, | |
}; | |
// ... |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// db/model.js | |
const { Model } = require('objection'); | |
const knex = require('./connection'); | |
Model.knex(knex); | |
// for global / base class configuration things | |
class BaseModel extends Model { | |
$beforeInsert() { | |
this.createdAt = new Date().toISOString(); | |
} | |
$beforeUpdate() { | |
this.updatedAt = new Date().toISOString(); | |
} | |
} | |
module.exports = { Model, BaseModel }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// models/index.js | |
const baseModels = require('../db/model'); | |
// list of models to initiate and configure - add to this array to add a new model file in this dir | |
const modelNames = ['User', 'Foo', 'Bar', 'Baz', 'Qux']; | |
/** | |
* require all of the models with their base configuration (no relations), | |
* this is the only place in the application models should be required directly. | |
*/ | |
const bareModels = modelNames.reduce((acc, modelName) => { | |
return { ...acc, [modelName]: require(`./${modelName}`)(baseModels) }; | |
}, {}); | |
/** | |
* now we can configure the model's relations with no circular depedencies | |
* because they were all previously initiated. | |
*/ | |
const configuredModels = Object.keys(bareModels).reduce((acc, modelName) => { | |
return { ...acc, [modelName]: bareModels[modelName].configure(bareModels) }; | |
}, {}); | |
/** | |
* now we can patch on the Model and BaseModel from objection so they can | |
* be reused throughout the application | |
*/ | |
module.exports = { ...configuredModels, ...baseModels }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// models/User/index.js | |
const $beforeInsert = require('./$beforeInsert'); | |
const relationMappings = require('./relationMappings'); | |
module.exports = ({ Model, BaseModel }) => { | |
class User extends BaseModel {} | |
User.tableName = 'users'; | |
User.idColumn = 'user_id'; | |
User.jsonSchema = { | |
type: 'object', | |
required: ['email', 'password', 'invitation', 'firstName', 'lastName'], | |
properties: { | |
userId: { type: 'integer' }, | |
type: { type: 'string' }, | |
role: { type: ['string', 'null'] }, | |
email: { type: 'string' }, | |
hash: { type: 'string' }, | |
firstName: { type: 'string' }, | |
lastName: { type: 'string' }, | |
}, | |
}; | |
User.configure = (models) => { | |
const Models = { ...models, Model }; | |
User.relationMappings = relationMappings(Models); | |
User.prototype.$beforeInsert = $beforeInsert(Models); | |
return User; | |
}; | |
return User; | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// models/User/relationMapping.js | |
module.exports = (models) => { | |
const { Model, Foo, Bar, Baz, Qux } = models; | |
return { | |
foos: { | |
relation: Model.HasManyRelation, | |
modelClass: Foo, | |
join: { | |
from: 'users.user_id', | |
to: 'fooss.user_id', | |
}, | |
}, | |
bars: { | |
relation: Model.HasManyRelation, | |
modelClass: Bar, | |
join: { | |
from: 'users.user_id', | |
to: 'bars.user_id', | |
}, | |
}, | |
bazs: { | |
relation: Model.HasManyRelation, | |
modelClass: Baz, | |
join: { | |
from: 'users.user_id', | |
to: 'basz.user_id', | |
}, | |
}, | |
quxs: { | |
relation: Model.HasManyRelation, | |
modelClass: Qux, | |
join: { | |
from: 'users.user_id', | |
to: 'quxs.user_id', | |
}, | |
}, | |
}; | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// models/User/$beforeInsert.js | |
const scrypt = require('scrypt-for-humans'); | |
const { InvalidInvitation, UsedInvitation } = require('../../errors'); | |
module.exports = models => async function userBeforeInsert({ transaction: trx }) { | |
const { Invitation, User } = models; | |
// find the invitation | |
const inv = await Invitation.query(trx).findOne('code', this.invitation); | |
if (!inv) { | |
// if no invitation is found | |
throw new InvalidInvitation(); | |
} else if (inv.isUsed === true) { | |
// if the invitation was already redeemed | |
throw new UsedInvitation(); | |
} | |
await Invitation | |
.query(trx) | |
.patch({ isUsed: true }) | |
.where('invitationId', inv.invitationId); | |
this.type = inv.type; | |
const hash = await scrypt.hash(this.password); | |
this.hash = hash; | |
this.$omit(['password', 'passwordAgain', 'invitation']); | |
return Object.getPrototypeOf(User.prototype).$beforeInsert(); | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment