Objection.js: [Feature request]: $beforeInsertGraph / $beforeUpdateGraph

Created on 28 Oct 2018  路  4Comments  路  Source: Vincit/objection.js

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.

Most helpful comment

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 {

}

All 4 comments

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);
  }
}
Was this page helpful?
0 / 5 - 0 ratings