Skip to content

JayRule (Guarded Giraffe)

JayRule version v20190819 (Guarded Giraffe) & later

jaybird

What is a JayRule Ruleset

A JayRule ruleset is one which defines the ruleset's behaviours in a JSON object called "ruleset". This structure is processed by a script from an included RulesetInclude called "JayRule Ruleset Overlay JS", to perform as if it were a fully scripted ruleset.

Prior to JayRule, rulesets are constructed as a JSON/javascript object, but the structure is much looser, more prone to error, and requiring a lot of common "infrastructural" scripting, which JayRule now takes care of behind the scenes.

This version of the documentation relates to versions 20190819 (Guarded Giraffe) and later; for earlier versions see the previous JayRule documentation.

Features of a JayRule Ruleset

  • Formats each rule in a distinct structured block .
  • Handles synchronous and asynchronous processes behind the scenes.
  • Handles logging for:
    • Start of ruleset
    • Completion of ruleset
    • Start of rule
    • Completion of rule (and whether okay or in error)
  • Handles the callback topology needed to maintain a correct ruleset
  • Asynchronous rules only require a single "callback()"
  • hasRun flags automatically set internally.
  • Better error handling and logging.
  • All logging (ntf.logger.info, etc) prefixed with "RSLog:" or a developer specified prefix.

Added Features

The following are ready made state properties of the ntf object

Property Description
ntf.isNew whether document is new or existing
(nb -- this is incorrect for PostSave, use following in initialisation:
ntf.isNew = (sh && sh.previousVersionId) ? false : true;
ntf.isClientEvent whether the ruleset is running as a client event or a serverside event
ntf.userId the documentId of the current user's account document

Basic Syntax

{

#include "JayRule Ruleset Overlay JS",

  ruleset : {

    name : 'name-of-ruleset',

    rule-name : {

      ruleCondition : rule-condition-function | boolean,

      or

      fieldChanged : boolean,

      ruleAction : rule-action-function

    },

    rule-name : {

      ...

  }

}

NB: This is a simplified syntax -- other optional features of the syntax are explained in later documents.

ruleset

The main object. Must be named "ruleset".

name : name-of-ruleset

The name of the ruleset to use in the logging of the ruleset, eg

name : 'Acme Case - OnLoad',

rule-name

Identifier/key of the rule. Must be distinctly named. Must begin with lowercase "rule". Eg

ruleInitialiseFacts : {

Each rule will be run in the sequence as ordered within the ruleset.

rule-condition-function

A function that returns true (or non-empty/non-zero) if the rule is to proceed.

This function must be of the format

ruleCondition : function(ntf) {...}

Note: this function must not contain asynchronous processes.

Eg

ruleCondition : function(ntf) {
    return (ntf.context.fieldChanged === 'checkVenomous');
},

ruleCondition can also be assigned simply true (or false) for rules that must always (never) run.

Eg

ruleCondition : true,

fieldChanged

In the case of OnFieldChange rulesets only, we can specify just the name of the field changed. This replaces ruleCondition.

Eg

fieldChanged : 'customerName',

This has the same effect as:

ruleCondition : function(ntf) { 
    return (ntf.context.fieldChanged === 'customerName');
}

rule-action-function

A function that performs the rule's action.

If asynchronous, the function must be of the format:

function(ntf, callback) {...}

By adding the callback argument, we are telling the ruleset that we want it to wait for an asynchronous process within this rule to complete, before going onto the next rule.

Eg

  ruleAction : function(ntf,callback) {
    var ft3 = ntf.scope;
    var eqry = {"query": {"bool": {"filter": [
      {"term" : {"documentId" : ntf.user.documentId}}
    ]}}};

    ft3.findDocumentsByElastic(eqry, ntf.userId, function(err,result) {
      if (result) {
        var udoc = result.data.hits.hits[0]._source;
        ntf.document.description = 'Email fetched: ' + udoc.email;
      }
      callback();
    });
  }

If not asynchronous, it should be of the format:

function(ntf) {...}

With this format, the ruleset will simply run the contained code through, and then proceed to the next rule.

Eg

    ruleAction : function(ntf) {
        var ft3 = ntf.scope;
        var flag = (ntf.document.checkVenomous === true);
        ft3.showField('shWarning', flag);
    }

Example

The following is a simple ruleset including two non-asynchronous rules, plus one asynchronous rule.

// RuleSet: Dexwise Spider - OnFieldChange
// Updated by: peter.dexter@fieldtec.com 2018-03-03 17:23:49 +11:00
{
#include "JayRule Ruleset Overlay JS",

    ruleset : {

        name : "Dexwise Spider - OnFieldChange",

        // ---------------------------------------------------------------------
        // ruleVenomous
        // Shows/hides a warning based on the checkbox "Venomous"
        // ---------------------------------------------------------------------
        ruleVenomous : {

            fieldChanged : 'checkVenomous',

            ruleAction : function(ntf) {
                var ft3 = ntf.scope;
                var flag = (ntf.document.checkVenomous === true);
                ft3.showField('shWarning', flag);
            }
        },

        // ---------------------------------------------------------------------
        // rulePopGenusSpecies
        // If entered description is 'abc123', 
        //   set genus to 'Arceteuthis' and species to 'Duxiformi'
        // ---------------------------------------------------------------------
        rulePopGenusSpecies : {

            ruleCondition : function(ntf) {
                return (
                    ntf.context.fieldChanged === 'description'
                    && ntf.document.description === 'abc123'
                );
            },

            ruleAction : function(ntf) {
                delete ntf.document.description;
                ntf.document.genus = 'Arceteuthis';
                ntf.document.species = 'Duxiformi';
            }
        },

        // ---------------------------------------------------------------------
        // ruleGetUserByQuery
        // Queries for the user account document and outputs the email to the 
        // description.
        // NB - we could just use the object ntf.user, but we want to demonstrate asynchronous query usage here
        // ---------------------------------------------------------------------
        ruleGetUserByQuery : {

            ruleCondition : function(ntf) {
                return (
                    ntf.context.fieldChanged === 'commonName'
                    && ntf.context.newValue === 'ubug666'
                );
            },

            ruleAction : function(ntf,callback) {
                var ft3 = ntf.scope;
                var eqry = {"query": {"bool": {"filter": [
                    {"term" : {"documentId" : ntf.user.documentId}}    
                ]}}};

                ft3.findDocumentsByElastic(eqry, ntf.userId, function(err,result) {
                    if (err) {
                        var msg = err.message || JSON.stringify(err);
                        ntf.logger.error('Error on query: ' + msg);
                        ntf.errorMessage = 'Error on query: ' + msg;
                    }
                    else if (result && result.data && result.data.hits && result.data.hits.total) {
                        var udoc = result.data.hits.hits[0]._source;

                        ntf.document.description = 'Email fetched: ' + udoc.email;

                        delete ntf.document.commonName ;
                    }
                    callback();
                });
            }
        } 
    }
}

JayRule Extended Syntax

This document outlines the further main properties that may be set on the JayRule "ruleset" object

Syntax

{

#include "JayRule Ruleset Overlay JS",

  ruleset : {

    name : 'name-of-ruleset',

    timeout: 'timeout-in-milliseconds',

    logging: {

      showUncalledRules : true| false,

      debugEmail : 'email-address',

      debugEmailAll : 'true|false',

      logPrefix: 'logging-prefix',

    },

    rule-name : { ...   }

}

ruleset

The main object. Must be named "ruleset".

name : name-of-ruleset

The name of the ruleset to use in the logging of the ruleset, eg

name : 'CWW Case - OnLoad',

NB -- If you set this to '{{{rulesetName}}}' (as is default for a new ruleset), then on saving, the name/object event descriptor of the ruleset will be substituted in.

logging.debugEmail : 'email-address'

When on a server side ruleset, an email containing the logger lines will be sent to the target email address on completion of each run of the ruleset. Any lines issued to the log via ntf.logger.info or .debug or .error will be sent. Use for debugging purposes only, of course.

Requires that the server is configured to send emails, and is working. Depending on any syntax errors, may fail to send.

logging : {
    debugEmail : 'adrian.sharples@formbird.com',
    ...
}

JayRule, as of v20190819 (Guarded Giraffe) and later, will only send debug emails when the ruleset is triggered by the matching user, ie the login email address must match the debugEmail setting. This was done to reduce the risk of unexpected floods of emails when done by other parties.

This can be overridden with logging.debugEmailAll (see below).

NB - the direct setting (not in logging) will still work with newer versions of JayRule.

logging.debugEmailAll : true|false

When debugEmail has been configured, restores the old feature that emails will be sent on any user triggering the ruleset, not just the specified user as per the debugEmail setting.

JayRule, as of v20190819 (Guarded Giraffe) and later, will only send debug emails when the ruleset is triggered by the matching user, ie the login email address must match the debugEmail setting. This was done to reduce the risk of unexpected floods of emails when done by other parties.

logging : {
    debugEmail : 'adrian.sharples@formbird.com',
    debugEmailAll : true
}

timeout: 'timeout-in-milliseconds',

Sets the maximum time that should be allowed for the whole ruleset to run, in milliseconds.

This can be useful for tracking processes that fail to complete, or where a rule requires a callback, but it has not been made.

If not set, timeout defaults to 120000 (120 seconds).
To disable timeout, set to -1.

timeout : 60000  // Set to 60 seconds

Please note - having a definite short or medium timeout can be problematic when the ruleset needs to process input from the user, eg a dialog is presented with either OK or Cancel buttons, and separate process is required on each option.

logging.logPrefix : 'logging_prefix'

Changes the general logging prefix to a custom one, in case you need to filter logging specifically to this ruleset. Default prefix is "RSLog:"

logPrefix : 'RSLog[CWX]:',

logging : {
    logPrefix : 'RSLog[CWX]:',
    ...
}

logging.showUncalledRules : true|false

Sets whether to show a logging for rules that do not get called due to their condition not being met. Prior to this version, logging of uncalled rules was always shown.

Defaults to false from the above version.

JayRule - Using shared functions/objects

The ntf.scope, or ft3, now carries all the "shared" functions and objects available to rulesets, including

  • the "global" functions, eg showField(...)
  • functions and objects scripted before/after the ruleset definition
  • the objects from #includes

These are available in both the ruleCondition and the ruleAction.

NOTE:

In earlier versions of JayRule, it was necessary to assign ft3 = ntf.scope at the beginning of each function that needs to use it, either the ruleCondition function or the ruleAction function.

In recent and current versions, ft3 has been made a global object, so this is no longer necessary. You may however see rulesets which do this for each function block -- there is no harm done here, it still works perfectly.

Formbird Functions for Rulesets

The explicitly coded functions provided by the FormBird application to the rulesets are available through ntf.scope, or ft3, as previously practised.

Eg

{
#include "JayRule Ruleset Overlay JS",

    ruleset : {
        ruleDoThingA : {
            ruleCondition : ...,

            ruleAction : function(ntf) {
                ft3.showField('dateInitiated', true);
                ...
            }

Functions/objects from #include

Functions and objects defined within a #include rulesetInclude are available in ft3 (ntf.scope).

Eg

{
#include "JayRule Ruleset Overlay JS",
#include "Basic Functions JS", // provides object "basicFunctions"
...

ruleset : {
    ruleDoThingA : {
        ruleCondition : function(ntf) {
            // Perform function from #include "Basic Functions
            var x = ft3.basicFunctions.datePrecedes(ntf.document.date1, ntf.document.date2);
            ...
        }

Functions/objects from the ruleset

Functions and objects defined within the main block of the script (sibling to the object "ruleset") are available in ft3 (ntf.scope).

Eg

{
#include "JayRule Ruleset Overlay JS",

    getFullName : function(field) {
        var name = field.firstName + ' ' + field.surname;
        return name;
    },

    ruleset : {
        ruleDoThingA : {
            ruleCondition : function(ntf) {
                // Test full name
                var x = ft3.getFullName(ntf.document.contact);
                return (x === 'Peter Dexter');
            }
            ...
        }
        ... 
   }
}

JayRule - Raising Errors

If a ruleAction process results in the ruleset needing to abort, with an error statement, eg on validating fields in OnFieldChange, the process is:

  • set ntf.errorMessage to the error you want displayed/reported

To terminate the block at that point:

  • callback(), if the ruleAction has a callback in its function signature, ie is asynchronous; if not, ignore.
  • return; (for either asynchronous/non-asynchronous)

Example

if (ntf.userType === 'forbidden') {
  ntf.errorMessage = 'Cannot perform this action for forbidden User Type';
  return;
}

That's it.

If there is no return/callback, the ruleset will terminate at the completion of that rule block.

JayRule - Ending a Ruleset (without error)

In order to end a ruleset, simply set ntf.rulesetEnd to true:

ruleNoCascade : {
    ruleCondition : function(ntf) {
        return (ntf.document.flagNoCascade);
    },

    ruleAction : function(ntf) { 
        delete ntf.document.flagNoCascade;
        ntf.rulesetEnd = true;
    }
}

The ruleset will return a success flag to the Formbird system, indicating that the ruleset ended successfully. (ala old method callbackSuccess()). No further rules will be executed.

JayRule - Running Asynchronous Rules

Rules that require to run an asynchronous process should be formatted as below:


ruleName : {
    ruleCondition : function(ntf) {
        ...
    },

    ruleAction : function(ntf, callback) {
        ...
        asyncFunction(..., function(args) {
            ...
            callback();
        }
    }
}


Note the salient features:

  • ruleAction has two arguments -- ntf and callback
  • callback() is called at the endpoint of the rule, typically within the callback of an asynchronous function

CAUTION

It is VITAL that your rule only calls callback() once in the course of a rule. It may be written several times within the rule, but only one should ever be called in any one running of the rule.

Current versions of the JayRule overlay will only process for the first callback, and prevent 2nd , 3rd, 4th, etc callbacks from proceeding. The logging will display "RSLog:!!! Multiple callbacks detected - aborting"

Example

    // ---------------------------------------------------------------------
    // ruleGetUserByQuery
    // Queries for the user account document and outputs the email to the 
    // description.
    // NB - we could just use the object ntf.user, but we want to 
    // demonstrate asynchronous query usage here
    // ---------------------------------------------------------------------
    ruleGetUserByQuery : {

        ruleCondition : function(ntf) {
            return (
                ntf.context.fieldChanged === 'commonName'
                && ntf.context.newValue === 'ubug666'
            );
        },

        ruleAction : function(ntf,callback) {        // <<<<<<<<<<<<<<<<<<<<<<<<<<<<
            var ft3 = ntf.scope;
            var eqry = {"query": {"bool": {"filter": [
                {"term" : {"documentId" : ntf.user.documentId}}    
            ]}}};

            ft3.findDocumentsByElastic(eqry, ntf.userId, function(err,result) {
                if (err) {
                    ntf.errorMessage = 'Error on query: ' + err.message;
                }
                else if (result && result.data && result.data.hits && result.data.hits.total) {
                    var udoc = result.data.hits.hits[0]._source;

                    ntf.document.description = 'Email fetched: ' + udoc.email;

                    delete ntf.document.commonName ;
                }
                callback();    // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
            });
        }
    }