@mazeltov/model
v1.0.6
Published
Mazeltov model library
Downloads
1
Maintainers
Readme
Requirements
This tool is designed to work with postgres (even though knexjs is used). It may work just fine with MySQL but
Defining a Model (Basic Example)
You have to make sure a table exists in your Postgres DB with these columns ahead of time.
const {
modelFromContext,
// these are action producers
creator,
getter,
lister,
updator,
remover,
iser,
} = require('./modelLib');
const iface = modelFromContext({
// DB must be knexjs using postgres. Also should handle snake to camel case conversion
// See Mazeltov app for example
db,
// it is recommended instead to pass @mazeltov/logger from higher context
logger: global.console,
entityName: 'fooOwner',
// default of 'id' if not specified. Can be array for composite keys
key: 'personId',
selectColumns: [
'personId',
'favoriteFoo',
'fooName',
'createdAt',
'updatedAt',
],
createColumns: [
'personId',
'fooName',
'favoriteFoo',
],
updateColumns: [
'favoriteFoo',
],
}, [
creator,
getter,
lister,
updator,
remover,
iser,
]);
(async () => {
const newFoo = await iface.create({
personId: 12,
fooName: 'Giuseppe',
favoriteFoo: 'Test',
});
console.log(newFoo);
})();
What is a model?
Lets start with MVC.
In the MVC paradigm, you have three components, a Model, a View, and a Controller
- A controller accepts input from a previous view
- The controller maps the input from the view into what the model needs
- The model uses the data to "model" business logic and write to DB
- A new view is produced (could be html, json, shell output)
- The cycle repeats when the view passes data back to the controller (user submits html form or types shell input)
In this paradigm, the model should be the same regardless of whether
- input comes from a CLI interface
- input comes from an HTTP request
- input comes from a message broker (like RabbitMQ)
I Need To Do X
There are always going to be special cases where much much more is needed and that is okay! There are plenty of ways to override each generated model method and insert hooks to change the arguments and result from the method call.
Transactions
This is the recommended pattern for transaction handling (when using multiple actions across models.
// someOtherModel would be passed from ctx.models
const someOtherModel = /*...*/
const fooModel = modelFromContext(/*...*/);
const createFoo = async ( args = {}, passedTrx = null) => {
const trx = passedTrx === null ? await db.transaction() : passedTrx;
try {
await fooModel.create(args, trx);
await someOtherModel.create(args, trx);
trx.commit();
} catch (e) {
trx.rollback();
}
};
Decorator Pattern (Recommended Approach)
If you need to extend the default behavior of the actions (create, get, update, remove, list), you can decorate default model
const {
modelFromContext,
creator,
getter,
lister,
updator,
remover,
iser,
} = require('./modelLib');
module.exports = (ctx) => {
const iface modelFromContext({
...ctx,
entityName: 'fooOwner',
key: 'personId',
selectColumns: [
'personId',
'favoriteFoo',
'fooName',
'createdAt',
'updatedAt',
],
createColumns: [
'fooName',
'favoriteFoo',
],
updateColumns: [
'favoriteFoo',
],
}, [
creator,
getter,
lister,
updator,
remover,
iser,
]);
// You can wrap the default method here. This is what's meant by decorating. While ES6 class decorators
// would be nice, the functional paradigm is generally used in code based.
const createFooOwner = async ( args = {} ) => {
// do something custom with the args
args.favoriteFoo = args.favoriteFoo + ' is da best!';
return iface.create(args);
};
// You must splat the default interface here
return {
...iface,
// It is advised to export a shorthand and fully qualified method name
createFooOwner,
create: createFooOwner,
};
}
Action Hooks (An Alternative)
onWill{Action} : This will transform args before getting sent to database. For example, you can change which columns are inserted/updated for create/update. You can change what gets used in where clause of list. You can also use this just for general side-effects before the action is performed, but you MUST return the first argument of your callback.
on{Action}Result This modifies the result returned. This shouldn't be used for standard actions like list, get, create, update, remove, but could be used for a very custom action. (isMostSpecial)
Overwriting Context (ctx)
When calling modelFromContext
, you can override the context JUST for a specific method:
module.exports = (ctx) => modelFromContext({
...ctx,
entityName: 'fooOwner',
key: 'personId',
selectColumns: [
'personId',
],
}, [
creator,
getter,
lister,
remover,
iser,
// here is where you pass an array with the function as key 0
// and the context override as key 1
[
updator,
{
fnName: 'markFinished',
defaultUpdateArgs: { isfinished: true }
}
],
[
updator,
{
fnName: 'markUnfinished',
defaultUpdateArgs: { isfinished: false }
}
]
]);
``
Roadmap
Things that could always help
- More warnings for misconfigurations
- More integration and unit test coverage