HomeLearnArticleTriggers Treats and Tricks: Cascade Document Delete Using Triggers Preimage

Triggers Treats and Tricks: Cascade Document Delete Using Triggers Preimage

Updated: Aug 30, 2021 |

Published: Aug 16, 2021

  • Atlas
  • Realm
  • JavaScript
  • ...

By Pavel Duchovny

Rate this article

#
Triggers Tricks and Treats: Cascade Document Delete Using Triggers Preimage


In this blog series, we are trying to inspire you with some reactive Realm trigger use cases. We hope these will help you bring your application pipelines to the next level.

Essentially, triggers are components in our Atlas projects/Realm apps that allow a user to define a custom function to be invoked on a specific event.

  • Database triggers: We have triggers that can be triggered based on database events—like deletes, inserts, updates, and replaces—called database triggers.
  • Scheduled triggers: We can schedule a trigger based on a cron expression via scheduled triggers.
  • Authentication triggers: These triggers are only relevant for Realm authentication. They are triggered by one of the Realm auth providers' authentication events and can be configured only via a Realm application.

Relationships are an important part of any data design. Relational databases use primary and foreign key concepts to form those relationships when normalizing the data schema. Using those concepts, it allows a “cascading'' delete, which means a primary key parent delete will delete the related siblings.

MongoDB allows you to form relationships in different ways—for example, by embedding documents or arrays inside a parent document. This allows the document to contain all of its relationships within itself and therefore it does the cascading delete out of the box. Consider the following example between a user and the assigned tasks of the user:

1{
2userId : "abcd",
3username : "user1@example.com"
4Tasks : [
5 { taskId : 1,
6 Details : ["write","print" , "delete"]
7 },
8 { taskId : 1,
9 Details : ["clean","cook" , "eat"]
10 }
11}

Delete of this document will delete all the tasks.

However, in some design cases, we will want to separate the data of the relationship into Parent and Sibling collections—for example, games collection holding data for a specific game including ids referencing a quests collection holding a per game quest. As amount of quest data per game can be large and complex, we’d rather not embed it in games but reference:

Games collection

1{
2 _id: ObjectId("60f950794a61939b6aac12a4"),
3 userId: 'xxx',
4 gameId: 'abcd-wxyz',
5 gameName: 'Crash',
6 quests: [
7 {
8 startTime: ISODate("2021-01-01T22:00:00.000Z"),
9 questId: ObjectId("60f94b7beb7f78709b97b5f3")
10 },
11 {
12 questId: ObjectId("60f94bbfeb7f78709b97b5f4"),
13 startTime: ISODate("2021-01-02T02:00:00.000Z")
14 }
15 ]
16 }

Each game has a quest array with a start time of this quest and a reference to the quests collection where the quest data reside.

Quests collection

1{
2 _id: ObjectId("60f94bbfeb7f78709b97b5f4"),
3 questName: 'War of fruits ',
4 userId: 'xxx',
5 details: {
6 lastModified: ISODate("2021-01-01T23:00:00.000Z"),
7 currentState: 'in-progress'
8 },
9 progressRounds: [ 'failed', 'failed', 'in-progress' ]
10},
11{
12 _id: ObjectId("60f94b7beb7f78709b97b5f3"),
13 questName: 'War of vegetable ',
14 userId: 'xxx',
15 details: {
16 lastModified: ISODate("2021-01-01T22:00:00.000Z"),
17 currentState: 'failed'
18 },
19 progressRounds: [ 'failed', 'failed', 'failed' ]
20}

When a game gets deleted, we would like to purge the relevant quests in a cascading delete. This is where the Preimage trigger feature comes into play.

#Preimage Trigger Option

The Preimage option allows the trigger function to receive a snapshot of the deleted/modified document just before the change that triggered the function. This feature is enabled by enriching the oplog of the underlying replica set to store this snapshot as part of the change. Read more on our documentation.

In our case, we will use this feature to capture the parent deleted document full snapshot (games) and delete the related relationship documents in the sibling collection (quests).

#Building the Trigger

When we define the database trigger, we will point it to the relevant cluster and parent namespace to monitor and trigger when a document is deleted—in our case, GamesDB.games.

Trigger  Def

To enable the “Preimage” feature, we will toggle Document Preimage to “ON” and specify our function to handle the cascade delete logic.

Preimage Toggle

deleteCascadingQuests - Function

1exports = async function(changeEvent) {
2
3 // Get deleted document preImage using "fullDocumentBeforeChange"
4 var deletedDocument = changeEvent.fullDocumentBeforeChange;
5
6
7 // Get sibling collection "quests"
8 const quests = context.services.get("mongodb-atlas").db("GamesDB").collection("quests");
9
10 // Delete all relevant quest documents.
11 deletedDocument.quests.map( async (quest) => {
12 await quests.deleteOne({_id : quest.questId});
13 })
14};

As you can see, the function gets the fully deleted “games” document present in “changeEvent.fullDocumentBeforeChange” and iterates over the “quests” array. For each of those array elements, the function runs a “deleteOne” on the “quests” collection to delete the relevant quests documents.

#Deleting the Parent Document

Now let's put our trigger to the test by deleting the game from the “games” collection: https://mongodb-devhub-cms.s3.us-west-1.amazonaws.com/Delete_Game_df2f0c096c.png

Once the document was deleted, our trigger was fired and now the “quests” collection is empty as it had only quests related to this deleted game: https://mongodb-devhub-cms.s3.us-west-1.amazonaws.com/Odocs_d929e3c940.png Our cascade delete works thanks to triggers “Preimages.”

#Wrap-Up

The ability to get a modified or deleted full document opens a new world of opportunities for trigger use cases and abilities. We showed here one option to use this new feature but this can be used for many other scenarios, like tracking complex document state changes for auditing or cleanup images storage using the deleted metadata documents.

We suggest that you try this new feature considering your use case and look forward to the next trick along this blog series.

Want to keep going? Join the conversation over at our community forums!

Rate this article

More from this series

Triggers Tricks and Treats
  • Realm Triggers Treats and Tricks - Document-Based Trigger Scheduling
  • Realm Triggers Treats and Tricks - Auto Increment a Running ID Field
  • Triggers Treats and Tricks: Cascade Document Delete Using Triggers Preimage
MongoDB Icon
  • Developer Hub
  • Documentation
  • University
  • Community Forums

© MongoDB, Inc.