Hooks (Calculated Field)

Hooks is a feature in Tailor Platform that offers functionality similar to calculated fields in traditional relational databases. When a record is created or modified, Hooks automatically update specified field values based on CEL Script expressions.

These expressions serve several purposes:

  1. Performing calculations:

You can perform calculations using other fields within the same record. For example, a field like totalPrice can be configured to automatically calculate the product of price and quantity fields.

  1. Adding user context:

Hooks allow the addition of current user context to the record. For instance, a field like createdById can be populated with the user.id. Additionally, you can utilize user attributes, an array of UUIDs configured in the AttributesFields in the Auth service. A typical use case for user.attributes involves validation. Refer to this example.

Furthermore, the field updates itself whenever a new record is created or an existing one is updated. This ensures data consistency without manual recalculations, similar to calculated fields in a database, and helps you avoid writing complex logic in Pipeline.

Events

The create and update events are supported as a trigger to run the script.

  • CreateExpr: Triggered when a new record is created.
  • UpdateExpr: Triggered when the target record is updated.

Examples

  1. Given an Order model, this is how you could for instance compute the total price of an item:
<span><span style="color: var(--shiki-color-text)">price: {</span></span>
<span><span style="color: var(--shiki-color-text)">  Type: tailordb.#TypeInt</span></span>
<span><span style="color: var(--shiki-color-text)">  Description: </span><span style="color: var(--shiki-color-text)">&quot;</span><span style="color: var(--shiki-token-string-expression)">Unit price of a certain product</span><span style="color: var(--shiki-color-text)">&quot;</span></span>
<span><span style="color: var(--shiki-color-text)">}</span></span>
<span><span style="color: var(--shiki-color-text)">quantity: {</span></span>
<span><span style="color: var(--shiki-color-text)">  Type: tailordb.#TypeInt</span></span>
<span><span style="color: var(--shiki-color-text)">  Description: </span><span style="color: var(--shiki-color-text)">&quot;</span><span style="color: var(--shiki-token-string-expression)">Order quantity of a certain product</span><span style="color: var(--shiki-color-text)">&quot;</span></span>
<span><span style="color: var(--shiki-color-text)">}</span></span>
<span><span style="color: var(--shiki-color-text)">totalPrice: {</span></span>
<span><span style="color: var(--shiki-color-text)">  Type: tailordb.#TypeInt</span></span>
<span><span style="color: var(--shiki-color-text)">  Description: </span><span style="color: var(--shiki-color-text)">&quot;</span><span style="color: var(--shiki-token-string-expression)">Total price of a certain product</span><span style="color: var(--shiki-color-text)">&quot;</span></span>
<span><span style="color: var(--shiki-color-text)">  Hooks: {</span></span>
<span><span style="color: var(--shiki-color-text)">    CreateExpr: </span><span style="color: var(--shiki-color-text)">&quot;&quot;&quot;</span></span>
<span><span style="color: var(--shiki-token-string-expression)">      _value.price * _value.quantity</span></span>
<span><span style="color: var(--shiki-token-string-expression)">    </span><span style="color: var(--shiki-color-text)">&quot;&quot;&quot;</span></span>
<span><span style="color: var(--shiki-color-text)">    UpdateExpr: </span><span style="color: var(--shiki-color-text)">&quot;&quot;&quot;</span></span>
<span><span style="color: var(--shiki-token-string-expression)">      _value.price * _value.quantity</span></span>
<span><span style="color: var(--shiki-token-string-expression)">    </span><span style="color: var(--shiki-color-text)">&quot;&quot;&quot;</span></span>
<span><span style="color: var(--shiki-color-text)">  }</span></span>
<span><span style="color: var(--shiki-color-text)">}</span></span>
<span></span>

With such hooks in place, the totalPrice field will be computed and stored whenever the order is created or changed.

  1. Here's how to add user context to the Supplier model
<span><span style="color: var(--shiki-color-text)">createdById: {</span></span>
<span><span style="color: var(--shiki-color-text)">  Type:        tailordb.#TypeUUID</span></span>
<span><span style="color: var(--shiki-color-text)">  Description: </span><span style="color: var(--shiki-color-text)">&quot;</span><span style="color: var(--shiki-token-string-expression)">User ID of the logged in user</span><span style="color: var(--shiki-color-text)">&quot;</span></span>
<span><span style="color: var(--shiki-color-text)">  Hooks: {</span></span>
<span><span style="color: var(--shiki-color-text)">    CreateExpr: </span><span style="color: var(--shiki-color-text)">&quot;&quot;&quot;</span></span>
<span><span style="color: var(--shiki-token-string-expression)">      user.id</span></span>
<span><span style="color: var(--shiki-token-string-expression)">    </span><span style="color: var(--shiki-color-text)">&quot;&quot;&quot;</span></span>
<span><span style="color: var(--shiki-color-text)">  }</span></span>
<span><span style="color: var(--shiki-color-text)">}</span></span>
<span></span>
  1. Example demonstrating how to set a default value to a field
<span><span style="color: var(--shiki-color-text)">quantity: {</span></span>
<span><span style="color: var(--shiki-color-text)">  Type: tailordb.#TypeInt</span></span>
<span><span style="color: var(--shiki-color-text)">  Description: </span><span style="color: var(--shiki-color-text)">&quot;</span><span style="color: var(--shiki-token-string-expression)">Order quantity of a certain product</span><span style="color: var(--shiki-color-text)">&quot;</span></span>
<span><span style="color: var(--shiki-color-text)">  Hooks: {</span></span>
<span><span style="color: var(--shiki-color-text)">    CreateExpr: </span><span style="color: var(--shiki-color-text)">&quot;&quot;&quot;</span></span>
<span><span style="color: var(--shiki-token-string-expression)">      _value != null ? _value : 2</span></span>
<span><span style="color: var(--shiki-token-string-expression)">    </span><span style="color: var(--shiki-color-text)">&quot;&quot;&quot;</span></span>
<span><span style="color: var(--shiki-color-text)">  }</span></span>
<span><span style="color: var(--shiki-color-text)">}</span></span>
<span></span>

Evaluation order

User input

Hooks run after input evaluation, having the consequence that any passed value may be overwritten by a hook's result, depending on the type of the hooks.

Example

<span><span style="color: var(--shiki-color-text)">price: {</span></span>
<span><span style="color: var(--shiki-color-text)">  Type: tailordb.#TypeInt</span></span>
<span><span style="color: var(--shiki-color-text)">  Description: </span><span style="color: var(--shiki-color-text)">&quot;</span><span style="color: var(--shiki-token-string-expression)">Unit price of a certain product</span><span style="color: var(--shiki-color-text)">&quot;</span></span>
<span><span style="color: var(--shiki-color-text)">}</span></span>
<span><span style="color: var(--shiki-color-text)">quantity: {</span></span>
<span><span style="color: var(--shiki-color-text)">  Type: tailordb.#TypeInt</span></span>
<span><span style="color: var(--shiki-color-text)">  Description: </span><span style="color: var(--shiki-color-text)">&quot;</span><span style="color: var(--shiki-token-string-expression)">Order quantity of a certain product</span><span style="color: var(--shiki-color-text)">&quot;</span></span>
<span><span style="color: var(--shiki-color-text)">}</span></span>
<span><span style="color: var(--shiki-color-text)">totalPrice: {</span></span>
<span><span style="color: var(--shiki-color-text)">  Type: tailordb.#TypeInt</span></span>
<span><span style="color: var(--shiki-color-text)">  Description: </span><span style="color: var(--shiki-color-text)">&quot;</span><span style="color: var(--shiki-token-string-expression)">Total price of a certain product</span><span style="color: var(--shiki-color-text)">&quot;</span></span>
<span><span style="color: var(--shiki-color-text)">  Hooks: {</span></span>
<span><span style="color: var(--shiki-color-text)">    CreateExpr: </span><span style="color: var(--shiki-color-text)">&quot;&quot;&quot;</span></span>
<span><span style="color: var(--shiki-token-string-expression)">      _value.price * _value.quantity</span></span>
<span><span style="color: var(--shiki-token-string-expression)">    </span><span style="color: var(--shiki-color-text)">&quot;&quot;&quot;</span></span>
<span><span style="color: var(--shiki-color-text)">    UpdateExpr: </span><span style="color: var(--shiki-color-text)">&quot;&quot;&quot;</span></span>
<span><span style="color: var(--shiki-token-string-expression)">      _value.price * _value.quantity</span></span>
<span><span style="color: var(--shiki-token-string-expression)">    </span><span style="color: var(--shiki-color-text)">&quot;&quot;&quot;</span></span>
<span><span style="color: var(--shiki-color-text)">  }</span></span>
<span><span style="color: var(--shiki-color-text)">}</span></span>
<span></span>

When passing totalPrice = 100, price = 5, and quantity = 10 as inputs, 50 is stored on the totalPrice field.

Validate vs Hooks

Validations are run after hooks.

Given an Order model with the following fields:

<span><span style="color: var(--shiki-color-text)">price: {</span></span>
<span><span style="color: var(--shiki-color-text)">  Type: tailordb.#TypeInt</span></span>
<span><span style="color: var(--shiki-color-text)">  Description: </span><span style="color: var(--shiki-color-text)">&quot;</span><span style="color: var(--shiki-token-string-expression)">Unit price of a certain product</span><span style="color: var(--shiki-color-text)">&quot;</span></span>
<span><span style="color: var(--shiki-color-text)">}</span></span>
<span><span style="color: var(--shiki-color-text)">quantity: {</span></span>
<span><span style="color: var(--shiki-color-text)">  Type: tailordb.#TypeInt</span></span>
<span><span style="color: var(--shiki-color-text)">  Description: </span><span style="color: var(--shiki-color-text)">&quot;</span><span style="color: var(--shiki-token-string-expression)">Order quantity of a certain product</span><span style="color: var(--shiki-color-text)">&quot;</span></span>
<span><span style="color: var(--shiki-color-text)">}</span></span>
<span><span style="color: var(--shiki-color-text)">totalPrice: {</span></span>
<span><span style="color: var(--shiki-color-text)">  Type: tailordb.#TypeInt</span></span>
<span><span style="color: var(--shiki-color-text)">  Description: </span><span style="color: var(--shiki-color-text)">&quot;</span><span style="color: var(--shiki-token-string-expression)">Total price of a certain product</span><span style="color: var(--shiki-color-text)">&quot;</span></span>
<span><span style="color: var(--shiki-color-text)">  Hooks: {</span></span>
<span><span style="color: var(--shiki-color-text)">    CreateExpr: </span><span style="color: var(--shiki-color-text)">&quot;&quot;&quot;</span></span>
<span><span style="color: var(--shiki-token-string-expression)">      _value.price * _value.quantity</span></span>
<span><span style="color: var(--shiki-token-string-expression)">    </span><span style="color: var(--shiki-color-text)">&quot;&quot;&quot;</span></span>
<span><span style="color: var(--shiki-color-text)">    UpdateExpr: </span><span style="color: var(--shiki-color-text)">&quot;&quot;&quot;</span></span>
<span><span style="color: var(--shiki-token-string-expression)">      _value.price * _value.quantity</span></span>
<span><span style="color: var(--shiki-token-string-expression)">    </span><span style="color: var(--shiki-color-text)">&quot;&quot;&quot;</span></span>
<span><span style="color: var(--shiki-color-text)">  }</span></span>
<span><span style="color: var(--shiki-color-text)">  Validate: [</span></span>
<span><span style="color: var(--shiki-color-text)">    </span><span style="color: var(--shiki-token-comment)">// totalPrice value must be less than 100</span></span>
<span><span style="color: var(--shiki-color-text)">    {</span></span>
<span><span style="color: var(--shiki-color-text)">      Expr: </span><span style="color: var(--shiki-color-text)">&quot;</span><span style="color: var(--shiki-token-string-expression)">_value &lt; 100</span><span style="color: var(--shiki-color-text)">&quot;</span></span>
<span><span style="color: var(--shiki-color-text)">      Action: tailordb.#Permit.Deny</span></span>
<span><span style="color: var(--shiki-color-text)">    }</span></span>
<span><span style="color: var(--shiki-color-text)">  ]</span></span>
<span><span style="color: var(--shiki-color-text)">}</span></span>
<span></span>

Passing an initial price and quantity resulting in a total price lesser than "100" will successfully create the record and store the computed total price. However, if the total price exceeds "100", the validation will fail and the record won't be created.

Advanced use

createdAt, updatedAt field

The datetime each record was created and updated can be stored as follows

<span><span style="color: var(--shiki-color-text)">createdAt: {</span></span>
<span><span style="color: var(--shiki-color-text)">  Type: tailordb.#TypeDateTime</span></span>
<span><span style="color: var(--shiki-color-text)">  Description: </span><span style="color: var(--shiki-color-text)">&quot;</span><span style="color: var(--shiki-token-string-expression)">The time when this record is created</span><span style="color: var(--shiki-color-text)">&quot;</span></span>
<span><span style="color: var(--shiki-color-text)">  Hooks: {</span></span>
<span><span style="color: var(--shiki-color-text)">    CreateExpr: </span><span style="color: var(--shiki-color-text)">&quot;</span><span style="color: var(--shiki-token-string-expression)">now()</span><span style="color: var(--shiki-color-text)">&quot;</span></span>
<span><span style="color: var(--shiki-color-text)">  }</span></span>
<span><span style="color: var(--shiki-color-text)">}</span></span>
<span><span style="color: var(--shiki-color-text)">updatedAt: {</span></span>
<span><span style="color: var(--shiki-color-text)">  Type: tailordb.#TypeDateTime</span></span>
<span><span style="color: var(--shiki-color-text)">  Description: </span><span style="color: var(--shiki-color-text)">&quot;</span><span style="color: var(--shiki-token-string-expression)">The time when this record is updated</span><span style="color: var(--shiki-color-text)">&quot;</span></span>
<span><span style="color: var(--shiki-color-text)">  Hooks: {</span></span>
<span><span style="color: var(--shiki-color-text)">    UpdateExpr: </span><span style="color: var(--shiki-color-text)">&quot;</span><span style="color: var(--shiki-token-string-expression)">now()</span><span style="color: var(--shiki-color-text)">&quot;</span></span>
<span><span style="color: var(--shiki-color-text)">  }</span></span>
<span><span style="color: var(--shiki-color-text)">}</span></span>
<span></span>

In this example, the createdAt field is evaluated only on create events, and the updatedAt field is evaluated only on update events.

When the record is created, the current datetime value is stored in the createdAt field, but not in the updatedAt field.

When the record is updated, the value in the createdAt field will remain unchanged, while the updatedAt field will be updated with the current datetime.

Conditional default values

The default value of a field can be set in two ways: using the defaultValue property or using hooks.

If the default value should be set based on dynamic conditions, Hooks is the recommended method to define the default value.

In this example, the default value of the field price is determined by the value of the type field.

<span><span style="color: var(--shiki-color-text)">type: {</span></span>
<span><span style="color: var(--shiki-color-text)">  Type: tailordb.#TypeEnum</span></span>
<span><span style="color: var(--shiki-color-text)">  Description: </span><span style="color: var(--shiki-color-text)">&quot;</span><span style="color: var(--shiki-token-string-expression)">Item category</span><span style="color: var(--shiki-color-text)">&quot;</span></span>
<span><span style="color: var(--shiki-color-text)">  AllowedValues: [</span></span>
<span><span style="color: var(--shiki-color-text)">				{ Value: </span><span style="color: var(--shiki-color-text)">&quot;</span><span style="color: var(--shiki-token-string-expression)">ITEMA</span><span style="color: var(--shiki-color-text)">&quot;</span><span style="color: var(--shiki-token-punctuation)">,</span><span style="color: var(--shiki-color-text)"> Description: </span><span style="color: var(--shiki-color-text)">&quot;</span><span style="color: var(--shiki-token-string-expression)">Item A</span><span style="color: var(--shiki-color-text)">&quot;</span><span style="color: var(--shiki-color-text)"> }</span><span style="color: var(--shiki-token-punctuation)">,</span></span>
<span><span style="color: var(--shiki-color-text)">				{ Value: </span><span style="color: var(--shiki-color-text)">&quot;</span><span style="color: var(--shiki-token-string-expression)">ITEMB</span><span style="color: var(--shiki-color-text)">&quot;</span><span style="color: var(--shiki-token-punctuation)">,</span><span style="color: var(--shiki-color-text)"> Description: </span><span style="color: var(--shiki-color-text)">&quot;</span><span style="color: var(--shiki-token-string-expression)">Item B</span><span style="color: var(--shiki-color-text)">&quot;</span><span style="color: var(--shiki-color-text)"> }</span></span>
<span><span style="color: var(--shiki-color-text)">			]</span></span>
<span><span style="color: var(--shiki-color-text)">}</span></span>
<span><span style="color: var(--shiki-color-text)">price: {</span></span>
<span><span style="color: var(--shiki-color-text)">  Type: tailordb.#TypeInt</span></span>
<span><span style="color: var(--shiki-color-text)">  Description: </span><span style="color: var(--shiki-color-text)">&quot;</span><span style="color: var(--shiki-token-string-expression)">Unit price of a certain product</span><span style="color: var(--shiki-color-text)">&quot;</span></span>
<span><span style="color: var(--shiki-color-text)">  Hooks: {</span></span>
<span><span style="color: var(--shiki-color-text)">    CreateExpr: </span><span style="color: var(--shiki-color-text)">&quot;&quot;&quot;</span></span>
<span><span style="color: var(--shiki-token-string-expression)">      _value.type == &quot;ITEMA&quot; ? 100 : null</span></span>
<span><span style="color: var(--shiki-token-string-expression)">    </span><span style="color: var(--shiki-color-text)">&quot;&quot;&quot;</span></span>
<span><span style="color: var(--shiki-color-text)">  }</span></span>
<span><span style="color: var(--shiki-color-text)">}</span></span>
<span></span>

In this example, the price field is evaluated only on create events, and if the type is ITEMA, then the value 100 is applied to the price field.

However, if the type is ITEMB, no value is applied to the price field.

Additionally, on the update event, regardless of whether the type is ITEMA or ITEMB, no value is applied.