]> err.no Git - sope/blob - sope-core/NGExtensions/NGRuleEngine.subproj/README
added some NGRule documentation
[sope] / sope-core / NGExtensions / NGRuleEngine.subproj / README
1 NGRuleEngine
2 ============
3
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.
7
8 We have choosen different class names, so that NGExtensions can be used
9 together with the BDRuleEngine framework.
10
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
13 classes:
14   NGRuleAssignment
15   NGRuleKeyAssignment
16   NGRule
17   NGRuleContext
18   NSRuleModel
19
20
21 How does it work?
22 =================
23
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
26 write code.
27
28 Example Ruleset:
29   (
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",
35   )
36
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:
39
40   context.soRequestType='WebDAV'
41
42 and a right hand side, eg:
43
44   renderer = 'SoWebDAVRenderer'
45
46 further control specifiers like the priority of the rule in the set (high) can
47 be attached.
48
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.
52
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
55 case we use:
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"];
61
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
64 information.
65
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.
69
70
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.
74
75 In the above example the RHS is
76
77   renderer = 'SoWebDAVRenderer'
78
79 this says that if the rule context is asked for a value of 'renderer', the
80 assignment will return the 'SoWebDAVRenderer' string constant.
81
82 Note: the assignment does _not_ set the value in the rule context.
83 TODO: should it set the value in the context? ;-)
84
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!
87
88
89 Now that we have the basics, how do we use the rule context? Here is a small
90 example:
91
92   NGRuleModel   *model;
93   NGRuleContext *context;
94   
95   /* setup */
96   model   = [[NGRuleModel alloc] initWithContentsOfFile:@"myrules.plist"];
97   context = [NGRuleContext ruleContextWithModel:model];
98
99   /* fill in parameters */
100   [context takeValue:@"10" forKey:@"age"];
101
102   /* query values that depend on the parameter */
103   [context valueForKey:@"color"];
104
105 A sample myrules.plist:
106
107   ( "age < 5 => color = 'white'", "age > 4 => color = 'green'" )
108
109 This would return 'green' in the above example (because age = 10 is >4).
110
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:
114
115   TableCell: WOGenericContainer {
116     elementName = "td";
117     bgcolor     = rules.color;
118   }
119
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.
123
124
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:
128
129   ( "document.status = 'saved'    => pageName = 'MyReviewPage'",
130     "document.status = 'created'  => pageName = 'MyReviewPage'",
131     "document.status = 'reviewed' => pageName = 'MyPublishPage'" )
132
133 and code like this:
134
135   - (id)showNextPage {
136     return [self pageWithName:[rules valueForKey:@"pageName"]];
137   }
138
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
142 enhance the rule to:
143
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",
149   )
150
151 The rule context has a neat shortcut method in case you want to store rules in
152 the defaults system:
153   
154   context = [NGRuleContext ruleContextWithModelInUserDefault:@"MyRules"];
155
156 Since rules are often used to customize the behaviour of an application, this
157 is quite useful.
158
159
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:
163   
164   colors = [rules valuesForKeyPath:@"color"
165                   takingSuccessiveValues:ageObjects
166                   forKey:@"document"];
167
168 This will walk over the 'ageObjects' array and perform a
169
170   [rules takeValue:ageObject forKey:@"document"]
171
172 for each object and add the result of
173
174   [rules valueForKeyPath:@"color"]
175
176 to the result array.
177
178
179 Finally remember that assignment results do not need to be base values, they
180 can also be complex objects, eg:
181
182   [rules takeValue:bossObject forKey:@"boss"];
183   [rules takeValue:secretary  forKey:@"secretary"];
184
185   contactEMail = [rules valuesForKeyPath:@"contact.email"
186                         takingSuccessiveValues:mailObjects
187                         forKey:@"mail"];
188
189 with the ruleset:
190
191   ( "mail.priority = 'high'   => contact = boss",
192     "mail.priority = 'normal' => contact = secretary",
193     "mail.priority = 'low'    => contact = secretary" )
194
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
198 set as parameters).
199
200
201 Priorities
202 ==========
203
204 You should normally use one of the predefined priorities:
205   - important (override)
206   - very high
207   - high
208   - normal/default
209   - low
210   - very low
211   - fallback
212 If you need fine-grained control, you can use priority numbers which should
213 be between 50 (low) and 150 (high).