Skip to content

Lifecycle hook overhaul #185

@koskimas

Description

@koskimas

There are some issues with way the hooks currently work (#71, #81 #94, #112).

The hooks are somewhat counter intuitive because of the way objection.js works. Objection.js is not a full ORM in the sense that you don't always work with model instances. Sometimes (most of the time) you just write queries that result in models, but before the query you don't actually have the model instances that get modified. For example:

Person
  .query()
  .patch({a: 10})
  .where('b', '<', 100)

It would be intuitive to call the $beforeUpdate and $afterUpdate hook for all rows that get updated, but we don't have them and I certainly think we shouldn't fetch them implicitly just so that we can call the hooks for them. In this example objection simply calls the hooks for the patch object {a:10}. This allows you to do common stuff like

$beforeUpdate(options, context) {
  this.updatedAt = Date.now();
}

and it just works, since all the updated rows now get the new updatedAt value.

Confusion comes from the instance query methods (ones started with $query()). For example:

somePerson
  .$query()
  .patch({a: 10})

Now it would be intuitive that this would point to the model being updated in the hooks, but then the hook would work different from the non-instance query hooks. The $beforeUpdate example above would not work.

Currently there is no way to get the model being updated in the hooks. What I propose is that we pass in the model being updated in the options object like this:

$beforeUpdate(options, context) {
  if (options.old) {
    // Do stuff with the old instance.
    this.fullName = (this.firstName || options.old.firstName) + ' ' 
      + (this.lastName || options.old.lastName);
  }
  this.updatedAt = Date.now();
}

This way the hooks would work for with both query types.

Here is a list of how the hooks would work in each case. The new stuff (not currently implemented) is marked with NEW.

$beforeUpdate

  • arguments (options, context)
  • this points to the update object.
  • NEW for instance queries ($query()) the old values are passed in through options.old

$afterUpdate

  • arguments (options, context)
  • this points to the update object.
  • NEW for instance queries ($query()) the old values are passed in through options.old

$beforeValidate

  • arguments (jsonSchema, json, options)
  • This hook is different form all the other in the way that this always points
    to an empty model instance. $beforeValidate is called before the model instance
    is populated with fields. This is because the the object is validated before it is created.
    The object to be validated is passed in as the json argument.
  • jsonSchema can be modified in this hook and it should be returned from the hook.
  • NEW for instance queries ($query()) the old values are passed in through options.old

$afterValidate

  • arguments (json, options)
  • This hook is different form all the other in the way that this always points
    to an empty model instance. $afterValidate is called before the model instance
    is populated with fields. This is because the the object is validated before it is created.
    The validated object is passed in as the json argument.
  • NEW for instance queries ($query()) the old values are passed in through options.old

$beforeInsert

  • arguments (context)
  • this points to the model about to be inserted.

$afterInsert

  • arguments (context)

*this points to the model that was inserted.

NEW $beforeDelete

  • arguments (context)
  • this points to the model being deleted.
  • Only called for instance queries started with $query()!. Hooks are called for model instances.
    In a query().delete().where('id', 'foo') query there is no object to call it for unless we first fetch
    the objects to be deleted, but that is not something we should do automatically. You can use
    $query().delete() instead.

NEW $afterDelete

  • arguments (context)
  • this points to the model being deleted.
  • Only called for instance queries started with $query()!. Hooks are called for model instances.
    In a query().delete().where('id', 'foo') query there is no object to call it for unless we first fetch
    the objects to be deleted, but that is not something we should do automatically. You can use
    $query().delete() instead.

$afterGet

  • arguments (context)
  • this points to the fetched model instance.
  • Called for each fetched model.

What do you think? Please add any comments/changes/points you might think of! @purepear @drew-r @getvega @gerhardsletten

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions