4 This is a rule engine inspired by the "BDRuleEngine" available from
5 bDistributed.com (www.bdistributed.com) which in turn is inspired by the
6 direct to web framework which is part of WebObjects.
8 We have choosen different class names, so that NGExtensions can be used
9 together with the BDRuleEngine framework.
11 It's a nice application of EOControl qualifiers and key-value coding to
12 implement a simple rule evaluation system. It consists of just five small
24 The rule engine is an evaluator for a set of rules which map to values. It can
25 be used to make all kinds of actions configurable without being required to
30 "context.soRequestType='WebDAV' => renderer = 'SoWebDAVRenderer' ; high",
31 "context.soRequestType='XML-RPC' => renderer = 'SoXmlRpcRenderer' ; high",
32 "context.soRequestType='SOAP' => renderer = 'SoSOAPRenderer' ; high",
33 "context.soRequestType='WCAP' => renderer = 'SoWCAPRenderer' ; high",
34 "*true* => renderer = 'SoDefaultRenderer' ; fallback",
37 This is a rule from SOPE which selects the render class for SoObjects. As you
38 can see a rule has a left hand side, eg:
40 context.soRequestType='WebDAV'
42 and a right hand side, eg:
44 renderer = 'SoWebDAVRenderer'
46 further control specifiers like the priority of the rule in the set (high) can
49 The left hand side is just a regular EOQualifier which is evaluated against
50 a rule context (an object of the NGRuleContext class). A rule context is the
51 entry object for all rule processing.
53 To configure rule evaluation, you need to set some variables in the context,
54 those variables are basically the "parameters" of the rule. Eg in the above
56 [self->dispatcherRules reset];
57 [self->dispatcherRules takeValue:_rq forKey:@"request"];
58 [self->dispatcherRules takeValue:[_rq headers] forKey:@"headers"];
59 [self->dispatcherRules takeValue:[_rq method] forKey:@"method"];
60 [self->dispatcherRules takeValue:_ctx forKey:@"context"];
62 'dispatcherRules' is the NGRuleContext object. Because we reuse the same
63 context for each WORequest, we need to 'reset' the context to remove all old
66 As you can see the rule context gets set the 'context' variable which is used
67 in the qualifier - "context.soRequestType='WebDAV'". If this left hand side
68 (LHS) qualifier evaluates to true, the RHS will be run.
71 So lets get to the right hand side. It is the so called "Assignment" and is
72 actually someone similiar to a WOAssociation. The actual operation is triggered
73 by some subclass of NGRuleAssignment in the -fireInContext: method.
75 In the above example the RHS is
77 renderer = 'SoWebDAVRenderer'
79 this says that if the rule context is asked for a value of 'renderer', the
80 assignment will return the 'SoWebDAVRenderer' string constant.
82 Note: the assignment does _not_ set the value in the rule context.
83 TODO: should it set the value in the context? ;-)
85 You can have as many assignment as you like. Assignments are only run if the
86 user asks for a key which is set by the assignment!
89 Now that we have the basics, how do we use the rule context? Here is a small
93 NGRuleContext *context;
96 model = [[NGRuleModel alloc] initWithContentsOfFile:@"myrules.plist"];
97 context = [NGRuleContext ruleContextWithModel:model];
99 /* fill in parameters */
100 [context takeValue:@"10" forKey:@"age"];
102 /* query values that depend on the parameter */
103 [context valueForKey:@"color"];
105 A sample myrules.plist:
107 ( "age < 5 => color = 'white'", "age > 4 => color = 'green'" )
109 This would return 'green' in the above example (because age = 10 is >4).
111 Note that the cool aspect of the rule context is that the rule evaluation is
112 queried using regular key/value coding methods! This way you can easily bind
113 values to SOPE templates, eg:
115 TableCell: WOGenericContainer {
117 bgcolor = rules.color;
120 This assumes that the component returns a rule context in the 'rules' method.
121 A component setup like this can be easily customized just by changing the rules
122 avoiding the requirement to hack code.
125 Another neat application for rules is the selection of the "next page", that
126 is, to control the flow of a web application.
127 Consider a ruleset like this:
129 ( "document.status = 'saved' => pageName = 'MyReviewPage'",
130 "document.status = 'created' => pageName = 'MyReviewPage'",
131 "document.status = 'reviewed' => pageName = 'MyPublishPage'" )
136 return [self pageWithName:[rules valueForKey:@"pageName"]];
139 This code will automatically determine the correct page to be shown depending
140 on the rules and the state of an object. Eg if you later decide that the
141 publish page should also been selected for saved docuents which are green, just
144 ( "document.status = 'saved' => pageName = 'MyReviewPage'",
145 "document.status = 'created' => pageName = 'MyReviewPage'",
146 "document.status = 'reviewed' => pageName = 'MyPublishPage'",
147 "document.status = 'saved' AND document.color = 'green'
148 => pageName = 'MyPublishPage'; priority = high",
151 The rule context has a neat shortcut method in case you want to store rules in
154 context = [NGRuleContext ruleContextWithModelInUserDefault:@"MyRules"];
156 Since rules are often used to customize the behaviour of an application, this
160 Another shortcut method you can use is the evaluation of a ruleset for a set
161 of objects. Eg if you want to get the color of a set of objects with the 'age'
162 property, you can run:
164 colors = [rules valuesForKeyPath:@"color"
165 takingSuccessiveValues:ageObjects
168 This will walk over the 'ageObjects' array and perform a
170 [rules takeValue:ageObject forKey:@"document"]
172 for each object and add the result of
174 [rules valueForKeyPath:@"color"]
179 Finally remember that assignment results do not need to be base values, they
180 can also be complex objects, eg:
182 [rules takeValue:bossObject forKey:@"boss"];
183 [rules takeValue:secretary forKey:@"secretary"];
185 contactEMail = [rules valuesForKeyPath:@"contact.email"
186 takingSuccessiveValues:mailObjects
191 ( "mail.priority = 'high' => contact = boss",
192 "mail.priority = 'normal' => contact = secretary",
193 "mail.priority = 'low' => contact = secretary" )
195 Note that another speciality with the above ruleset is that it uses
196 NGRuleKeyAssignment assignments, that is, it retrieves the value of the
197 assignment from the rule context (the boss or secretary objects previously
204 You should normally use one of the predefined priorities:
205 - important (override)
212 If you need fine-grained control, you can use priority numbers which should
213 be between 50 (low) and 150 (high).