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!
}
}