Post-Update Checks
Among the "create", "read", "update", and "delete" operations, "update" is a special one, because its data has two states: pre-update and post-update. When you write a policy rule for "update", by default, you're checking against the pre-update state. But sometimes, you may want to inspect the post-update state. For example, you may wish to prevent a user from setting a "revision" to a smaller value than the current one.
To define a post-update rule, you use the future()
function to access the post-update state. Here's an example:
model Post {
...
revision Int
@@allow('update', future().revision > revision)
}
Although there are two kinds of update rules, ZenStack uses a single "update" operation to represent both pre-update and post-update checks. The only difference is that you use the future()
function to access the post-update state. What happens when pre-update and post-update rules are mixed? For example:
model Post {
...
published Boolean
revision Int
@@allow('update', !published && future().revision > revision)
}
When ZenStack detects that a model has "update" rules involving future()
calls, it'll postpone the evaluation of all "update" rules of that model to the "post-update" stage. It analyzes what fields' pre-update state is needed and collects them before the update happens.
One of the use cases of post-update rules is to prevent modification of specific fields. For example, in our Todo app, we allow List
's owner to update its fields, but we shouldn't allow its owner to be changed. We can use a post-update rule to enforce this:
model List {
...
@@deny('update', future().owner != owner)
}
It works, but it feels a bit awkward for such a common use case. In the next chapter, we'll learn a better way to handle this.