Defining RuleBooks
Write a RuleBook class, run it, and read the result.
On this page
Defining RuleBooks
A RuleBook is a class that holds a list of rules. Extend
rulebox.models.RuleBook and add your rules in a defineRules() method.
A HelloWorld example
class extends="rulebox.models.RuleBook"{
function defineRules(){
addRule(
newRule( "SayHello" )
.then( ( facts, result ) => result.setValue( "Hello " & facts.name ) )
)
}
}
newRule() takes an optional name, which you can use to identify the
rule later, for auditing. Now run it:
var greeting = getInstance( "HelloWorld" )
.run( { name : "World" } )
.getResult()
.getValue()
// "Hello World"
getInstance( "HelloWorld" ) finds the class in your app's models
folder by name. Use the full path (path.to.HelloWorld) for a class
that lives somewhere else.
Passing facts
run( facts ) takes a struct of facts directly. It is a shortcut for
calling given() once per key and then run(). Facts you pass replace
any fact of the same name; pass false as the second argument
(run( facts, false )) to keep the ones already set. See
Facts & Results.
Reading the outcome
run() returns the RuleBook itself, not the answer. That keeps calls
chainable. To get the answer, chain .getResult().getValue() after
run(), as the example above does. If you are not sure any rule set a
value, check getResult().isPresent() first. More in
Facts & Results.
Why rules are defined on the first run
defineRules() does not run when the RuleBook is created. It runs the
first time you call run() (or dryRun()), and only when the RuleBook
has no rules yet. By then WireBox has finished injecting your
dependencies, so you can use them inside defineRules(). Later runs on
the same instance reuse the rules instead of building them again.
Other ways to add a rule
You can also pass a closure to addRule() and configure the rule inside
it:
addRule( ( rule ) => {
rule
.setName( "SayHello" )
.then( ( facts, result ) => result.setValue( "Hello " & facts.name ) )
} )
A RuleBook has a name too, which you can set with setName( name ).
It is handy for auditing and the Visualizer.
You can also build rulebooks on the fly, with no class at all, using
The Builder.
Rule names must be unique within a RuleBook, because the
audit trail is keyed by name. Adding a rule whose name is
already taken throws a RuleBox.DuplicateRuleNameException; unnamed rules
are never affected.
Describing a RuleBook
Give a RuleBook a description to say what it decides. It is shown in the
Rule Visualizer and returned by getDescription(), and it
has no effect on how the rulebook runs. The simplest way is the class's
docblock:
/**
* Decides a loan application from the applicant's credit score.
*/
class extends="rulebox.models.RuleBook"{
// ...
}
getDescription() looks in this order and uses the first one it finds:
- a description set with
withDescription( text ), a rule file or config - the class's
@descriptionannotation, then its@hintannotation - the class's docblock, with its line breaks folded into spaces
@description( "Decides a loan application from the applicant's credit score" )
class extends="rulebox.models.RuleBook"{
}
// Or on any instance, including one from the Builder
getInstance( "LoanApproval" ).withDescription( "Decides loans for the spring promotion" )
A plain RuleBook, such as one from the Builder, has no
class hint, so its description is empty until you set one.
Rules can be described too, with
withDescription().
Retrieving a RuleBook
A RuleBook class is a normal WireBox-mapped class, so you retrieve it like any other transient:
var homeLoans = getInstance( "HomeLoanRateRuleBook" )
RuleBooks are transient: every getInstance() call returns a new one,
and each one remembers its own facts. Ask for a new RuleBook each time
you need one, and do not share one between requests or threads. See
Thread Safety.
Next
- The RuleBook DSL:
when,except,then,using,stopand priority. - Declared rulebooks:
get a ready-made RuleBook by name with
ruleBook( "name" ). - Externalized Rule Definitions: load rules from JSON, YAML, or a database.