RulesEngine icon indicating copy to clipboard operation
RulesEngine copied to clipboard

Access Rule(s) In Other Rules

Open sswapniljadhav opened this issue 3 years ago • 7 comments

Hello,

I am stuck with one scenario where I need to call multiple rules in 1 rule, here is simple example which I would like to achieve. Any help in solving the scenario or direction will be appreciated.

[ { "WorkflowName": "PaymentRule", "Rules": [ { "RuleName": "MinimumPayment", "SuccessEvent": "10", "ErrorMessage": "Error", "ErrorType": "Error", "RuleExpressionType": "LambdaExpression", "Operator": "OrElse", "Rules": [ { "RuleName": "IsPaymentEligible", "ErrorMessage": "Error", "ErrorType": "Error", "RuleExpressionType": "LambdaExpression", "Expression": "input1.PaymentAmount > 0 AND (input1.IsEligible=="true" AND input1.PaymentFrequency==input1.PaymentDuration ) AND input1.PaymentFrequency < input1.PaymentCycle ", "Actions": { "OnSuccess": { "Name": "OutputExpression", "Context": { "Expression": "(input1.PaymentAmount)" } } } }, { "RuleName": "elsePaymentEligible", "ErrorMessage": "Error", "ErrorType": "Error", "RuleExpressionType": "LambdaExpression", "Expression": "input1.PaymentAmount > 0 AND input1.IsEligible=="false" ", "Actions": { "OnSuccess": { "Name": "OutputExpression", "Context": { "Expression": "input1.PaymentFrequency" } } } } ]

  },
  {
    "RuleName": "BonusPayment",
    "SuccessEvent": "20",
    "ErrorMessage": "Error",
    "ErrorType": "Error",
    "RuleExpressionType": "LambdaExpression",
    "Operator": "OrElse",
    "Rules": [
      {
        "RuleName": "IsBonusEligible",
        "ErrorMessage": "Error",
        "ErrorType": "Error",
        "RuleExpressionType": "LambdaExpression",
        "Expression": "input1.BonusAmount > 0 AND (input1.IsBonusEligible==\"true\" AND input1.BonusFrequency==input1.BonusCycle)",
        "Actions": {
          "OnSuccess": {
            "Name": "OutputExpression",
            "Context": {
              "Expression": "(input1.BonusAmount)"
            }
          }
        }
      },
      {
        "RuleName": "elseBonusEligible",
        "ErrorMessage": "Error",
        "ErrorType": "Error",
        "RuleExpressionType": "LambdaExpression",
        "Expression": "input1.BonusAmount == 0 AND input1.IsBonusEligible==\"false\" ",
        "Actions": {
          "OnSuccess": {
            "Name": "OutputExpression",
            "Context": {
              "Expression": "input1.FlatBonus"
            }
          }
        }
      }
    ]

  },
  {
    "RuleName": "PaymentAmountToClient",
    "SuccessEvent": "30",
    "ErrorMessage": "Error",
    "ErrorType": "Error",
    "RuleExpressionType": "LambdaExpression",
    "Expression": "input1.PaymentDueDate == input1.PaymentDate",
    "Actions": {
      "OnSuccess": {
        "Name": "OutputExpression", 
        "Context": { 
          "Expression": "input1.BalanceAmount + {BonusPayment} + {MinimumPayment}"
        }
      }
    }

  }
]

} ]

sswapniljadhav avatar Aug 25 '21 13:08 sswapniljadhav

copy/paste and then nest? maybe not elegant but its a possible solution? although it would be nice to reference other rules like include a header file (c++) or use using (c#)

asulwer avatar Aug 26 '21 04:08 asulwer

@sswapniljadhav Are you looking for referencing Rules into another rule or are you looking at referencing output of Rules within another rule?

abbasc52 avatar Aug 26 '21 05:08 abbasc52

@sswapniljadhav Are you looking for referencing Rules into another rule or are you looking at referencing output of Rules within another rule?

Referencing output of rules in another rule

sswapniljadhav avatar Aug 26 '21 05:08 sswapniljadhav

@sswapniljadhav It sounds you should use ScopedParams (see also LocalParams). For across rules and accessing the expression via other rules (really just expressions) use a GlobalParam (inside the Workflow object) with the original rule as the expression and then reference the global param in the other rules. In the original rule, the expression would simply be the name of the global param and in the others it would be GlobalParam1 && Input1.Foo == 0. More info, here are the unit tests, take a look at the sample workflows at the bottom here.

alexreich avatar Aug 26 '21 15:08 alexreich

I have a fork that does this if anyone is interested. I'm not really sure if it's pull request material though.

widavies avatar Nov 24 '21 00:11 widavies

I have a fork that does this if anyone is interested. I'm not really sure if its pull request material though.

Is this what you mean? Can you put it in a new branch and create some unit tests for it?

alexreich avatar Nov 24 '21 00:11 alexreich

That's one small portion of it. I'm working on a project that required me to fork RulesEngine to add a few features I needed that weren't already baked in. As a disclaimer, I'm focused on moving my project forward so my implementations are "quick and dirty" so to speak, while they are functional, they aren't necessarily ideal.

With that said, my fork (in its current state) adds the following features:

  • Add ExclusiveOr operator, Value, and DefaultValue parameters to Rule. Value is technically an expression under the hood.
  • Rule referencing support: A Rule may reference the Value of another Rule in its Expression. To implement this, I used a DAG to store rule dependencies and I altered RuleEngine to execute rules sequentially, in order, according to the DAG. Things like circular dependencies are checked for.
  • I added quite a few more validation checks, they are a bit stricter (no duplicate names, only alphanumeric rule names, etc.) for my specific requirements.
  • I added RegexExpression and RegexCasedExpression expression types, which are mostly specific to my project, but they are shorthand for a regex match that assumes the first input is a string. This allows the Expression field to only list a regex pattern, and not any regex functions or equals signs.

The way I implement this is maybe a bit hacky, the DAG is traversed and rules are registered and executed one at a time. When a rule is complete, I package its result into a GlobalParam which is provided to the execution context of all subsequent rules. This GlobalParam method is a bit of a kludge, but it allowed me to implement the feature with minimal effort and changes to the code. I'm not sure if it's the best way as well. Secondly, I share a Value, not the Expression between rules, this is because my project has tags that are passed along instead of expressions so I'm not sure if this is something other people would want, or if we should just switch this to Expression references instead.

Here is an example of how my rule referencing system works:

[
  {
    "WorkflowName": "Workflow1",
    "Rules": [
      {
        "RuleName": "HasDiscount",
        "Expression": "FetchDiscount > 0"
      },
      {
        "RuleName": "FetchDiscount",
        "Operator": "ExclusiveOr",
        "DefaultValue": "0",
        "Rules": [
          {
            "RuleName": "Discount20",
            "Value": "20",
            "ExpressionType": "LambdaExpression",
            "Expression": "input1.loyaltyFactor <= 2"
          },
          {
            "RuleName": "Discount30",
            "Value": "30",
            "ExpressionType": "LambdaExpression",
            "Expression": "input1.loyaltyFactor >= 3"
          }
        ]
      }
    ]
  }
]

First, my altered RuleEngine noticies that the rule HasDiscount depends on FetchDiscount, so it will set up an execution plan where FetchDiscount is executed first, and HasDiscount is executed second. Secondly, the ExclusiveOr requires exactly one of its children to evaluate to true (IsSuccess = true). If this is not the case, the rule will fail — with one exception: if all child rules fail DefaultValue is returned. Value doesn't need to be a fixed value like it is here, it could be an Expression as well. I haven't added support for Values referencing other rules or values yet. Finally the Value evaluated from FetchDiscount is inserted into the expression HasDiscount and HasDiscount is registered and executed.

Let me know if you have any questions.

widavies avatar Nov 24 '21 02:11 widavies