Transactions have two events we can bind handlers to.
Commit - when the transaction persists its changes to the database
Rollback - when the transaction reverts any changes applied to the database
Snippet Scenario
Let's say we have a Notification model. When new notification records are created we want to emit an event to send an email to the notification recipient.
However, our notification is created inside a transaction that's in charge of persisting other records besides our notification.
In case we run into any errors between our notification creation and our transaction commit, we'll only want to emit our event that sends the email when we know our transaction completes and our records are all persisted.
// inside a controller or service
const notification = new Notification()
if (trx) {
notification.useTransaction(trx)
}
notification.merge({
userId: post.author.id,
initiatorUserId: user?.id,
notificationTypeId: NotificationTypes.COMMENT,
table: Comment.table,
tableId: comment.id,
title: `${user.username} commented on your post`,
body: UtilityService.truncate(comment.body),
href: this.getGoPath(comment)
})
await notification.save()
await notification.trySendEmail(post.author.id, trx) // 👈 we'll dig into this
// ... other actions
await trx?.commit()
Here we're creating our notification record, which may or may not utilize a transaction.
// app/Models/Notification.ts
export default class Notification extends BaseModel {
// ... other stuff
public async trySendEmail(
userId: number,
trx: TransactionClientContract | undefined | null = undefined
) {
const user = await User.query()
.where({ id: userId })
.preload('profile')
.firstOrFail()
// user doesn't want notified, skip send
if (!this.isEmailEnabled(user.profile)) {
return
}
// no transaction, go ahead and send then exit
if (!trx) {
return Event.emit('notification:send', {
notification: this,
user
})
}
// otherwise, wait for transaction to commit, then send
trx.on('commit', () => {
Event.emit('notification:send', {
notification: this,
user
})
})
}
}