Initializing entity variables
Each ruleset installed in a pico can endow it with entity variables, which are specific to that pico and that ruleset. Together, the entity variables of all of the rulesets installed in a pico provide the pico with its persistent state.
Until a value is first assigned to an entity variable in a rule postlude, the value of the variable will be null
. There are various techniques which can be used to deal with this issue.
The defaultsTo
operator
The defaultsTo
operator can provide a default value when an entity variable is used in a read-only context. For example, suppose ent:owners
will be a map, but might not have been initialized. This code checks the map for a specific key.
nameExists = function(ownername){
ent:owners.defaultsTo({}) >< ownername
}
Before the ent:owners
variable has been assigned its value as a map, this ensures that it will behave as a map for the purposes of the membership operator ><
.
The first time ent:owners
is required to store a value, the defaultsTo
operator can be used like this to ensure that it will hold a map, which contains an entry keyed by name
with the value new_owner
.
rule owner_token{
select when owner token_created
pre {
name = ...
new_owner = ...
}
fired {
ent:owners := ent:owners.defaultsTo({}).put(name, new_owner)
}
}
Every mention of ent:owners
within the ruleset must be operated on by defaultsTo
when this technique is used.
Using guard rules
An alternative is to have a specific rule which will be evaluated before any other rules which assigns a value to the entity variable. Reading the entity variable would still be done using defaultsTo
in case a query comes in before any rule has been selected for evaluation.
rule guard_owners_map {
select when owner token_created
if ent:owners.isnull() then noop()
fired {
ent:owners := {}
}
}
This rule would be placed in the ruleset before any other ruleset that uses ent:owners
. It will be selected in the same cases, and since it appears first it will be evaluated first. This means that ent:owners
is guaranteed to be a map when subsequent rules are evaluated. So, the other rule can be simplified to this.
This form is also more efficient, especially for a map with a large number of entries.
Initializing when the ruleset is installed
Another alternative is to initialize all entity variables when the ruleset is installed in the pico, and thus before any part of it can be used.
This is done by a rule in the ruleset which selects on the wrangler:ruleset_installed
event. This rule can be placed anywhere convenient within the ruleset, because it will be evaluated as soon as the ruleset is installed, before any other party has a chance to use any part of it, whether in a function or in another rule.
It is very important that the select
clause include the clause where event:attr("rids") >< ctx:rid
because many rulesets might be installed in a pico during its existence, and we don't want the intialization
rule in this ruleset to respond when other rulesets are installed later on. Making sure the rule fires only when the entity variable is not already defined is also good policy.
Copyright Picolabs | Licensed under Creative Commons.