Skip to main content

lifecycle hooks

BeforeCreate

BeforeCreate hooks are run whenever a new record is being saved to the database. A decorator pattern is leveraged, so no arguments need to be passed to the hook, it simply needs to live above the property, like so:

export default class User extends ApplicationModel {
@deco.BeforeCreate()
public async hashPassword() {
if (this.password) this.passwordDigest = await Hash.gen(this.password)
this.password = undefined
}
}

BeforeUpdate

BeforeUpdate hooks are run whenever an existing (meaning, previously-saved) record is updated, and can be leveraged like so:

export default class Post extends ApplicationModel {
...

@deco.BeforeUpdate()
public async doSomething() {
// ... do whatever you want here!
}

@deco.BeforeUpdate({ ifChanging: ['color'] })
public async doSomethingWhenColorIsChanging() {
// ... do whatever you want here!
}
}

BeforeSave

BeforeSave hooks are run whenever either an existing OR new record is written to the DB, and can be leveraged like so:

export default class Post extends ApplicationModel {
...

@deco.BeforeSave()
public async doSomething() {
// ... do whatever you want here!
}
}

changing the same record in a hook

If a hook needs to change attributes on the record being persisted, use a before hook and assign the values directly. Do not call save() or update() on this from inside one of its own lifecycle hooks; that starts another persistence cycle while the current one is still running.

export default class Place extends ApplicationModel {
@deco.BeforeSave()
public normalizeAddress() {
if (this.city) this.city = this.city.trim()
}

// Avoid this pattern. Use a before hook and direct assignment instead.
@deco.AfterSave()
public async normalizeAddressAgain() {
if (this.city) await this.update({ city: this.city.trim() })
}
}

Use after hooks for side effects that require the row to already exist, such as creating associated records or delegating to a service. If the side effect writes other records, pass the hook's transaction through. If the side effect queues background work, use a commit hook variant.

before destroy

BeforeDestroy hooks are run whenever either an existing record is destroyed from the DB, and can be leveraged like so:

export default class Post extends ApplicationModel {
...

@deco.BeforeDestroy()
public async doSomething() {
// ... do whatever you want here!
}
}

after create

AfterCreate hooks are run after a new record is saved to the database. A decorator pattern is leveraged, so no arguments need to be passed to the hook, it simply needs to live above the property, like so:

export default class Post extends ApplicationModel {
...

@deco.AfterCreate()
public async hashPassword() {
if (this.password)
this.password_digest = await Hash.gen(this.password)
this.password = undefined
}
}

after update

AfterUpdate hooks are run whenever an existing (meaning, previously-saved) record has been updated, and can be leveraged like so:

export default class Post extends ApplicationModel {
...

@deco.AfterUpdate()
public async doSomething() {
// ... do whatever you want here!
}

@deco.AfterUpdate({ ifChanged: ['color'] })
public async doSomethingWhenColorChanged() {
// ... do whatever you want here!
}
}

after save

AfterSave hooks are run whenever either an existing OR new record has been written to the DB, and can be leveraged like so:

export default class Post extends ApplicationModel {
...

@deco.AfterSave()
public async doSomething() {
// ... do whatever you want here!
}
}

after destroy

AfterDestroy hooks are run whenever either an existing record has been destroyed from the DB, and can be leveraged like so:

export default class Post extends ApplicationModel {
...

@deco.AfterDestroy()
public async doSomething() {
// ... do whatever you want here!
}
}

after commit hooks

In contrast to ordinary after hooks (like AfterCreate, AfterUpdate, etc...), we have commit variants. The implementation is similar to the approach taken by Ruby on Rails, whereby commit hooks are bound to an existing transaction if it exists, and will wait until after the commit has been made at the transaction level to run your callback.

IMPORTANT: any lifecycle hook that queues background work must use a commit hook, whether it calls a backgrounded service, calls this.background(...) on a backgrounded model, or delegates to another method that queues the job. This ensures the row exists after create and the latest persisted data are visible after update by the time the worker loads the record. Otherwise, the worker can race the transaction and either miss the row or read stale data.

after create commit

AfterCreateCommit hooks are run after a new record is saved to the database and the transaction is committed.

export default class Post extends ApplicationModel {
...

@deco.AfterCreateCommit()
public async yourThing() {
// ... your code
}
}

after update commit

AfterUpdateCommit hooks are run whenever an existing (meaning, previously-saved) record has been updated and the encapsulating transaction has been committed, and can be leveraged like so:

export default class Post extends ApplicationModel {
...

@deco.AfterUpdateCommit()
public async doSomething() {
// ... do whatever you want here!
}

@deco.AfterUpdateCommit({ ifChanged: ['color'] })
public async doSomethingWhenColorChanged() {
// ... do whatever you want here!
}
}

after save commit

AfterSaveCommit hooks are run whenever either an existing OR new record has been written to the DB and the encapsulating transaction has been committed, and can be leveraged like so:

export default class Post extends ApplicationModel {
...

@deco.AfterSaveCommit()
public async doSomething() {
// ... do whatever you want here!
}
}

after destroy commit

AfterDestroyCommit hooks are run whenever an existing record has been destroyed from the DB and the transaction has been committed, and can be leveraged like so:

export default class Post extends ApplicationModel {
...

@deco.AfterDestroyCommit()
public async doSomething() {
// ... do whatever you want here!
}
}