Imho it would be useful to allow a $beforeInsertGraph / $beforeUpdateGraph method on the model, like $beforeInsert. This would allow me to setup relational references; something like:
class User extends Model {
static async $beforeInsertGraph(graph) {
return merge(
{
email: {
relation: {
"#ref": "root",
},
},
person: {
relation: {
"#id": "root",
name: `${graph.person.first_name} ${graph.person.last_name}`,
},
},
},
graph
);
}
static get tableName() {
return "users";
}
}
I currently use a class enhancer (with a proxy) to achieve this:
module.exports = Model =>
class with_graph_helpers extends Model {
static query(...args) {
return new Proxy(Model.query.apply(this, args), {
get(target, propKey, receiver) {
switch (propKey) {
case "insertGraph":
return async function(graph) {
if (Model.$beforeInsertGraph) {
graph = await Model.$beforeInsertGraph.call(target, graph);
}
return target.insertGraph.call(this, graph);
};
case "insertGraphAndFetch":
case "insertWithRelatedAndFetch":
return async function(graph) {
if (Model.$beforeInsertGraph) {
graph = await Model.$beforeInsertGraph.call(target, graph);
}
return target.insertGraphAndFetch.call(this, graph);
};
case "updateGraph":
return async function(graph) {
if (Model.$beforeUpdateGraph) {
graph = await Model.$beforeUpdateGraph.call(target, graph);
}
return target.updateGraph.call(this, graph);
};
case "updateGraphAndFetch":
case "updateWithRelatedAndFetch":
return async function(graph) {
if (Model.$beforeInsertGraph) {
graph = await Model.$beforeUpdateGraph.call(target, graph);
}
return target.updateGraphAndFetch.call(this, graph);
};
default:
return Reflect.get(target, propKey, receiver);
}
},
});
}
};
This works,but I dislike using proxies and think this would be a nice feature to add. Please let me know if it's worth a PR.
You don't need to use a proxy. You can extend the query builder:
class CustomQueryBuilder extends Model.QueryBuilder {
insertGraph(graph, options) {
graph = this.modelClass().$beforeInsertGraph(graph);
return super.insertGraph(graph, options);
}
}
class BaseModel extends Model {
static get QueryBuilder() {
return CustomQueryBuilder;
}
}
class Person extends BaseModel {
}
Since this is pretty easy to implement, I don't think it should be added to objection.
Oh wow, that's a great way to solve this; thanks!
@koskimas : I have problems getting this to work with Typescript. Any ideas?
The insertGraph method has two signatures in the typings:
interface InsertGraph<QM extends Model> {
(modelsOrObjects?: Partial<QM>[], options?: InsertGraphOptions): QueryBuilder<QM, QM[]>;
(modelOrObject?: Partial<QM>, options?: InsertGraphOptions): QueryBuilder<QM, QM>;
(): this;
}
So, no idea how I would overload this function and typescript liking it:
class CustomQueryBuilder<A extends Model,B,C> extends QueryBuilder<A,B,C> {
insertGraph(graph: ?, options:?):? {
return super.insertGraph(graph, options);
}
}
Most helpful comment
You don't need to use a proxy. You can extend the query builder: