bali 2.4.0 → 6.0.0rc1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. checksums.yaml +5 -13
  2. data/.rspec +1 -0
  3. data/lib/bali.rb +23 -14
  4. data/lib/bali/activerecord.rb +8 -0
  5. data/lib/bali/authorizer.rb +24 -0
  6. data/lib/bali/config.rb +12 -0
  7. data/lib/bali/dsl_error.rb +3 -0
  8. data/lib/bali/{foundations/exceptions/bali_error.rb → error.rb} +0 -0
  9. data/lib/bali/judge.rb +239 -0
  10. data/lib/bali/printer.rb +16 -26
  11. data/lib/bali/railtie.rb +13 -0
  12. data/lib/bali/role.rb +79 -0
  13. data/lib/bali/rule.rb +17 -0
  14. data/lib/bali/ruler.rb +36 -0
  15. data/lib/bali/rules.rb +68 -0
  16. data/lib/bali/tasks/bali/print_rules.rake +9 -0
  17. data/lib/bali/version.rb +1 -1
  18. data/lib/generators/rails/USAGE +8 -0
  19. data/lib/generators/rails/rules_generator.rb +17 -0
  20. data/lib/generators/rails/templates/rules.rb +4 -0
  21. data/lib/generators/rspec/rules_generator.rb +12 -0
  22. data/lib/generators/rspec/templates/rules_spec.rb +7 -0
  23. metadata +104 -47
  24. data/lib/bali/dsl/map_rules_dsl.rb +0 -75
  25. data/lib/bali/dsl/rules_for_dsl.rb +0 -130
  26. data/lib/bali/foundations/all_foundations.rb +0 -17
  27. data/lib/bali/foundations/exceptions/authorization_error.rb +0 -38
  28. data/lib/bali/foundations/exceptions/dsl_error.rb +0 -3
  29. data/lib/bali/foundations/exceptions/objection_error.rb +0 -3
  30. data/lib/bali/foundations/judger/judge.rb +0 -329
  31. data/lib/bali/foundations/judger/negative_judge.rb +0 -40
  32. data/lib/bali/foundations/judger/positive_judge.rb +0 -41
  33. data/lib/bali/foundations/role_extractor.rb +0 -61
  34. data/lib/bali/foundations/rule/rule.rb +0 -55
  35. data/lib/bali/foundations/rule/rule_class.rb +0 -54
  36. data/lib/bali/foundations/rule/rule_group.rb +0 -91
  37. data/lib/bali/integrators/all_integrators.rb +0 -8
  38. data/lib/bali/integrators/rule_class_integrator.rb +0 -27
  39. data/lib/bali/integrators/rule_group_integrator.rb +0 -29
  40. data/lib/bali/integrators/rule_integrator.rb +0 -56
  41. data/lib/bali/objector.rb +0 -173
@@ -1,75 +0,0 @@
1
- # grand scheme of things begin here
2
- class Bali::MapRulesDsl
3
- attr_accessor :current_rule_class
4
-
5
- def initialize
6
- @@lock ||= Mutex.new
7
- end
8
-
9
- # defining rules
10
- def rules_for(target_class, options_hash = {}, &block)
11
- @@lock.synchronize do
12
- self.current_rule_class = Bali::RuleClass.new(target_class)
13
-
14
- parent_class = options_hash[:inherits] || options_hash["inherits"]
15
- if parent_class
16
- # in case there is inherits specification
17
- parent_is_class = parent_class.class
18
- fail Bali::DslError, 'inherits must take a class' unless parent_is_class
19
- rule_class_from_parent = Bali::Integrator::RuleClass.for(parent_class)
20
- fail Bali::DslError, "not yet defined a rule class for #{parent_class}" if rule_class_from_parent.nil?
21
- self.current_rule_class = rule_class_from_parent.clone(target_class: target_class)
22
- end
23
-
24
- Bali::RulesForDsl.new(self).instance_eval(&block)
25
-
26
- # done processing the block, now add the rule class
27
- Bali::Integrator::RuleClass.add(self.current_rule_class)
28
- end
29
- end
30
-
31
- # subtarget_class is the subtarget's class definition
32
- # field_name is the field that will be consulted when instantiated object of this class is passed in can? or cant?
33
- def roles_for(subtarget_class, field_name)
34
- Bali::TRANSLATED_SUBTARGET_ROLES[subtarget_class.to_s] = field_name
35
- nil
36
- end
37
-
38
- def describe(*params)
39
- fail Bali::DslError, "describe block must be within rules_for block"
40
- end
41
-
42
- def role(*params)
43
- fail Bali::DslError, "role block must be within rules_for block"
44
- end
45
-
46
- def can(*params)
47
- fail Bali::DslError, "can block must be within role block"
48
- end
49
-
50
- def cant(*params)
51
- puts "Deprecation Warning: declaring rules with cant will be deprecated on major release 3.0, use cannot instead"
52
- cannot *params
53
- end
54
-
55
- def cannot(*params)
56
- fail Bali::DslError, "cant block must be within role block"
57
- end
58
-
59
- def can_all(*params)
60
- fail Bali::DslError, "can_all block must be within role block"
61
- end
62
-
63
- def clear_rules
64
- fail Bali::DslError, "clear_rules must be called within role block"
65
- end
66
-
67
- def cant_all(*params)
68
- puts "Deprecation Warning: declaring rules with cant_all will be deprecated on major release 3.0, use cannot instead"
69
- cannot_all *params
70
- end
71
-
72
- def cannot_all(*params)
73
- fail Bali::DslError, "cant_all block must be within role block"
74
- end
75
- end
@@ -1,130 +0,0 @@
1
- # this class is used to define DSL after rules_for is invoked.
2
- # @author Adam Pahlevi Baihaqi
3
- class Bali::RulesForDsl
4
- attr_accessor :map_rules_dsl
5
- attr_accessor :current_rule_group
6
-
7
- # all to be processed subtargets
8
- attr_accessor :current_subtargets
9
-
10
- def initialize(map_rules_dsl)
11
- @@lock ||= Mutex.new
12
- self.map_rules_dsl = map_rules_dsl
13
- end
14
-
15
- def current_rule_class
16
- self.map_rules_dsl.current_rule_class
17
- end
18
- protected :current_rule_class
19
-
20
- def role(*params)
21
- @@lock.synchronize do
22
- bali_scrap_actors(*params)
23
- current_subtargets.each do |subtarget|
24
- bali_set_subtarget(subtarget)
25
-
26
- if block_given?
27
- yield
28
- else
29
- # if no block, then rules are defined using shortcut notation, eg:
30
- # role :user, can: [:edit]
31
- # the last element of which params must be a hash
32
- shortcut_rules = params[-1]
33
- unless shortcut_rules.is_a?(Hash)
34
- raise Bali::DslError, "Pass a hash for shortcut notation"
35
- end
36
-
37
- shortcut_can_rules = shortcut_rules[:can] || shortcut_rules["can"]
38
- shortcut_cannot_rules = shortcut_rules[:cannot] || shortcut_rules["cannot"]
39
-
40
- shortcut_rules.each do |auth_val, args|
41
- Bali::Integrator::Rule.add(auth_val, self.current_rule_group, *args)
42
- end # each shortcut rules
43
- end # whether block is given or not
44
- end # each subtarget
45
- end # sync
46
- end # role
47
-
48
- def describe(*params)
49
- puts "Bali Deprecation Warning: describing rules using describe will be deprecated on major release 3.0, use role instead"
50
- if block_given?
51
- role(*params) do
52
- yield
53
- end
54
- else
55
- role(*params)
56
- end
57
- end
58
-
59
- # others block
60
- def others(*params)
61
- if block_given?
62
- role("__*__") do
63
- yield
64
- end
65
- end
66
- end # others
67
-
68
- # clear all defined rules
69
- def clear_rules
70
- self.current_rule_group.clear_rules
71
- true
72
- end
73
-
74
- def can(*args)
75
- Bali::Integrator::Rule.add_can(self.current_rule_group, *args)
76
- end
77
-
78
- def cannot(*args)
79
- Bali::Integrator::Rule.add_cannot(self.current_rule_group, *args)
80
- end
81
-
82
- def cant(*operations)
83
- puts "Deprecation Warning: declaring rules with cant will be deprecated on major release 3.0, use cannot instead"
84
- cannot(*operations)
85
- end
86
-
87
- def can_all
88
- Bali::Integrator::RuleGroup.make_zeus(self.current_rule_group)
89
- end
90
-
91
- def cannot_all
92
- Bali::Integrator::RuleGroup.make_plant(self.current_rule_group)
93
- end
94
-
95
- def cant_all
96
- puts "Deprecation Warning: declaring rules with cant_all will be deprecated on major release 3.0, use cannot_all instead"
97
- cannot_all
98
- end
99
-
100
- private
101
- def bali_scrap_actors(*params)
102
- self.current_subtargets = []
103
- params.each do |passed_argument|
104
- if passed_argument.is_a?(Symbol) || passed_argument.is_a?(String)
105
- self.current_subtargets << passed_argument
106
- elsif passed_argument.is_a?(NilClass)
107
- self.current_subtargets << passed_argument
108
- elsif passed_argument.is_a?(Array)
109
- self.current_subtargets += passed_argument
110
- end
111
- end
112
- nil
113
- end
114
-
115
- # set the current processing on a specific subtarget
116
- def bali_set_subtarget(subtarget)
117
- rule_class = self.map_rules_dsl.current_rule_class
118
- target_class = rule_class.target_class
119
-
120
- rule_group = rule_class.rules_for(subtarget)
121
-
122
- if rule_group.nil?
123
- rule_group = Bali::RuleGroup.new(target_class, subtarget)
124
- end
125
-
126
- rule_class.add_rule_group rule_group
127
- self.current_rule_group = rule_group
128
- end
129
-
130
- end # class
@@ -1,17 +0,0 @@
1
- # ./exceptions
2
- require_relative "exceptions/bali_error"
3
- require_relative "exceptions/dsl_error"
4
- require_relative "exceptions/objection_error"
5
- require_relative "exceptions/authorization_error"
6
-
7
- # rule
8
- require_relative "rule/rule"
9
- require_relative "rule/rule_class"
10
- require_relative "rule/rule_group"
11
-
12
- # role extractor
13
- require_relative "role_extractor"
14
-
15
- require_relative "judger/judge"
16
- require_relative "judger/negative_judge"
17
- require_relative "judger/positive_judge"
@@ -1,38 +0,0 @@
1
- # Error that will be raised when subtarget cannot do something he wanted to do,
2
- # or when subtarget do something he should not be allowed to do.
3
- class Bali::AuthorizationError < Bali::Error
4
- attr_accessor :operation
5
- attr_accessor :auth_level
6
- attr_accessor :role
7
-
8
- # it may be nil, depends on whether rule checking is using symbol/user
9
- attr_accessor :subtarget
10
-
11
- # whether a class or an object
12
- attr_accessor :target
13
-
14
- def target_proper_class
15
- if target.is_a?(Class)
16
- target
17
- else
18
- target.class
19
- end
20
- end
21
-
22
- def to_s
23
- role = humanise_value(self.role)
24
- operation = humanise_value(self.operation)
25
- auth_level = humanise_value(self.auth_level)
26
-
27
- if auth_level == :can
28
- "Role #{role} is not allowed to perform operation `#{operation}` on #{target_proper_class}"
29
- else
30
- "Role #{role} is allowed to perform operation `#{operation}` on #{target_proper_class}"
31
- end
32
- end
33
-
34
- private
35
- def humanise_value(val)
36
- val.nil? ? "<nil>" : val
37
- end
38
- end
@@ -1,3 +0,0 @@
1
- # this error is thrown when there is incorrect syntax in the DSL
2
- class Bali::DslError < Bali::Error
3
- end
@@ -1,3 +0,0 @@
1
- # this error is thrown when objection is cannot be executed
2
- class Bali::ObjectionError < Bali::Error
3
- end
@@ -1,329 +0,0 @@
1
- module Bali::Judger
2
- # FUZY-ed value is happen when it is not really clear, need further cross checking,
3
- # whether it is really allowed or not. It happens for example in block with others, such as this:
4
- #
5
- # role :finance do
6
- # cannot :view
7
- # end
8
- # others do
9
- # can :view
10
- # can :index
11
- # end
12
- #
13
- # In the example above, objecting cannot view on finance will result in STRONG_FALSE, but
14
- # objecting can index on finance will result in FUZY_TRUE.
15
- #
16
- # Eventually, all FUZY value will be normal TRUE or FALSE if no definite counterpart
17
- # is found/defined
18
- BALI_FUZY_FALSE = -2
19
- BALI_FUZY_TRUE = 2
20
- BALI_FALSE = -1
21
- BALI_TRUE = 1
22
-
23
- class Judge
24
- attr_accessor :original_subtarget
25
- attr_accessor :subtarget
26
- attr_accessor :operation
27
- # record can be the class, or an instance of a class
28
- attr_accessor :record
29
-
30
- # determine if this judger should not call other judger
31
- attr_accessor :cross_checking
32
-
33
- # this class is abstract, shouldn't be initialized
34
- def initialize(unconstructable = true)
35
- if unconstructable
36
- raise Bali::Error, "Bali::Judge::Judger is unconstructable, properly construct by using build to get a concrete class!"
37
- end
38
- self
39
- end
40
-
41
- def self.build(auth_level, options = {})
42
- judge = nil
43
- if auth_level == :can
44
- judge = Bali::Judger::PositiveJudge.new
45
- elsif auth_level == :cannot
46
- judge = Bali::Judger::NegativeJudge.new
47
- else
48
- raise Bali::Error, "Unable to find judge for `#{auth_level}` case"
49
- end
50
-
51
- judge.original_subtarget = options[:original_subtarget]
52
- judge.subtarget = options[:subtarget]
53
- judge.operation = options[:operation]
54
- judge.record = options[:record]
55
- judge.cross_checking = false
56
-
57
- judge
58
- end
59
-
60
- def clone(options = {})
61
- if options[:reverse]
62
- new_judge = Bali::Judger::Judge.build(self.reverse_auth_level)
63
- else
64
- new_judge = Bali::Judger::Judge.build(self.auth_level)
65
- end
66
-
67
- new_judge.subtarget = subtarget
68
- new_judge.operation = operation
69
- new_judge.record = record
70
- new_judge.cross_checking = cross_checking
71
- new_judge.original_subtarget = original_subtarget
72
-
73
- new_judge
74
- end
75
-
76
- def record_class
77
- record.is_a?(Class) ? record : record.class
78
- end
79
-
80
- def rule_group
81
- unless @rule_group_checked
82
- @rule_group = Bali::Integrator::RuleGroup.for(record_class, subtarget)
83
- @rule_group_checked = true
84
- end
85
- @rule_group
86
- end
87
-
88
- def other_rule_group
89
- unless @other_rule_group_checked
90
- @other_rule_group = Bali::Integrator::RuleGroup.for(record_class, "__*__")
91
- @other_rule_group_checked = true
92
- end
93
- @other_rule_group
94
- end
95
-
96
- def rule
97
- unless @rule_checked
98
- # rule group may be nil, for when user checking for undefined rule group
99
- if rule_group
100
- @rule = rule_group.get_rule(auth_level, operation)
101
- else
102
- self.rule = nil
103
- end
104
- end
105
- @rule
106
- end
107
-
108
- def rule=(the_rule)
109
- @rule = the_rule
110
- @rule_checked = true
111
- @rule
112
- end
113
-
114
- def otherly_rule
115
- unless @otherly_rule_checked
116
- if other_rule_group
117
- # retrieve rule from others group
118
- @otherly_rule = other_rule_group.get_rule(auth_level, operation)
119
- @otherly_rule_checked = true
120
- end
121
- end
122
- @otherly_rule
123
- end
124
-
125
- # return either true or false
126
- # options can specify if returning raw, by specifying holy: true
127
- def judgement(options = {})
128
- # the divine judgement will come to thee, O Thou
129
- # the doer of truth. return raw, untranslated to true/false.
130
- our_holy_judgement = nil
131
-
132
- # default of can? is false whenever RuleClass for that class is undefined
133
- # or RuleGroup for that subtarget is not defined
134
- if rule_group.nil?
135
- if other_rule_group.nil?
136
- # no more chance for checking
137
- our_holy_judgement = natural_value
138
- end
139
- end
140
-
141
- if our_holy_judgement.nil? && need_to_check_for_intervention?
142
- our_holy_judgement = check_intervention
143
- end
144
-
145
- if our_holy_judgement.nil? &&
146
- rule_group && rule_group.plant? &&
147
- rule.nil? && otherly_rule.nil?
148
- our_holy_judgement = natural_value
149
- end
150
-
151
- if our_holy_judgement.nil? && rule.nil?
152
- cross_check_value = nil
153
- # default if can? for undefined rule is false, after related clause
154
- # cannot be found in cannot?
155
- unless cross_checking
156
- reversed_self = self.clone reverse: true
157
- reversed_self.cross_checking = true
158
- cross_check_value = reversed_self.judgement holy: true
159
- end
160
-
161
- # if cross check value nil, then the reverse rule is not defined,
162
- # let's determine whether he is zeus or plant
163
- if cross_check_value.nil?
164
- # rule_group can be nil for when user checking under undefined rule-group
165
- if rule_group
166
- if rule_group.plant?
167
- our_holy_judgement = plant_return_value
168
- end
169
-
170
- if rule_group.zeus?
171
- our_holy_judgement = zeus_return_value
172
- end
173
- end # if rule_group exist
174
- else
175
- # process value from cross checking
176
-
177
- if can_use_otherly_rule?(cross_check_value, cross_checking)
178
- # give chance to check at others block
179
- self.rule = otherly_rule
180
- else
181
- our_holy_judgement = cross_check_reverse_value(cross_check_value)
182
- end
183
- end
184
- end # if our judgement nil and rule is nil
185
-
186
- # if our holy judgement is still nil, but rule is defined
187
- if our_holy_judgement.nil? && rule
188
- if rule.has_decider?
189
- our_holy_judgement = get_decider_result(rule, original_subtarget, record)
190
- else
191
- our_holy_judgement = default_positive_return_value
192
- end
193
- end
194
-
195
- # return fuzy if otherly rule defines contrary to this auth_level
196
- if our_holy_judgement.nil? && rule.nil? && (other_rule_group && other_rule_group.get_rule(reverse_auth_level, operation))
197
- if rule_group && (rule_group.zeus? || rule_group.plant?)
198
- # don't overwrite our holy judgement with fuzy value if rule group
199
- # zeus/plant, because zeus/plant is more definite than any fuzy values
200
- # eventhough the rule is abstractly defined
201
- else
202
- our_holy_judgement = default_negative_fuzy_return_value
203
- end
204
- end
205
-
206
- # if at this point still nil, well,
207
- # return the natural value for this judge
208
- if our_holy_judgement.nil?
209
- if otherly_rule
210
- our_holy_judgement = BALI_FUZY_TRUE
211
- else
212
- our_holy_judgement = natural_value
213
- end
214
- end
215
-
216
- holy = !!options[:holy]
217
- return holy ? our_holy_judgement : translate_holy_judgement(our_holy_judgement)
218
- end
219
-
220
- private
221
- # translate response for value above to traditional true/false
222
- # holy judgement refer to non-standard true/false being used inside Bali
223
- # which need to be translated from other beings to know
224
- def translate_holy_judgement(bali_bool_value)
225
- unless bali_bool_value.is_a?(Integer)
226
- raise Bali::Error, "Expect bali value to be an Integer, got: `#{bali_bool_value}`"
227
- end
228
- if bali_bool_value < 0
229
- return false
230
- elsif bali_bool_value > 0
231
- return true
232
- end
233
- end
234
-
235
- def can_use_otherly_rule?(cross_check_value, is_cross_checking)
236
- # either if rule from others block is defined, and the result so far is fuzy
237
- # or, otherly rule is defined, and it is still a cross check
238
- # plus, the result is not a definite BALI_TRUE/BALI_FALSE
239
- #
240
- # rationalisation:
241
- # 1. Definite answer such as BALI_TRUE and BALI_FALSE is to be prioritised over
242
- # FUZY answer, because definite answer is not gathered from others block where
243
- # FUZY answer is. Therefore, it is an intended result
244
- # 2. If the answer is FUZY, otherly_rule only be considered if the result
245
- # is either FUZY TRUE or FUZY FALSE, or
246
- # 3. Or, when already in cross check mode, we cannot retrieve cross_check_value
247
- # what we can is instead, if otherly rule is available, just to try the odd
248
- (!otherly_rule.nil? && cross_check_value && !(cross_check_value == BALI_TRUE || cross_check_value == BALI_FALSE)) ||
249
- (!otherly_rule.nil? && (cross_check_value == BALI_FUZY_FALSE || cross_check_value == BALI_FUZY_TRUE)) ||
250
- (!otherly_rule.nil? && is_cross_checking && cross_check_value.nil?)
251
- end
252
-
253
- # if after cross check (checking for cannot) the return is false,
254
- # meaning for us, (checking for can), the return have to be true
255
- def cross_check_reverse_value(cross_check_value)
256
- # either the return is not fuzy, or otherly rule is undefined
257
- if cross_check_value == BALI_TRUE
258
- return BALI_FALSE
259
- elsif cross_check_value == BALI_FALSE
260
- return BALI_TRUE
261
- elsif cross_check_value == BALI_FUZY_FALSE
262
- return BALI_FUZY_TRUE
263
- elsif cross_check_value == BALI_FUZY_TRUE
264
- return BALI_FUZY_FALSE
265
- else
266
- raise Bali::Error, "Unknown how to process cross check value: `#{cross_check_value}`"
267
- end
268
- end # cross_check_reverse_value
269
-
270
- def check_intervention
271
- if rule.nil?
272
- self_clone = self.clone reverse: true
273
- self_clone.cross_checking = true
274
-
275
- check_val = self_clone.judgement holy: true
276
-
277
- # check further whether contradicting rule is defined to overwrite this
278
- # super-power either can_all or cannot_all rule
279
- if check_val == BALI_TRUE
280
- # it is defined, must overwrite
281
- return BALI_FALSE
282
- else
283
- # futher inspection said no such overwriting value is exist
284
- return BALI_TRUE
285
- end # check_val
286
- end # if rule nil
287
- end # check intervention
288
-
289
- # what is the result when decider is executed
290
- # rule: the rule object
291
- # original subtarget: raw, unprocessed arugment passed as subtarget
292
- def get_decider_result(rule, original_subtarget, record)
293
- # must test first
294
- decider = rule.decider
295
- case decider.arity
296
- when 0
297
- if rule.decider_type == :if
298
- return decider.() ? BALI_TRUE : BALI_FALSE
299
- elsif rule.decider_type == :unless
300
- unless decider.()
301
- return BALI_TRUE
302
- else
303
- return BALI_FALSE
304
- end
305
- end
306
- when 1
307
- if rule.decider_type == :if
308
- return decider.(record) ? BALI_TRUE : BALI_FALSE
309
- elsif rule.decider_type == :unless
310
- unless decider.(record)
311
- return BALI_TRUE
312
- else
313
- return BALI_FALSE
314
- end
315
- end
316
- when 2
317
- if rule.decider_type == :if
318
- return decider.(record, original_subtarget) ? BALI_TRUE : BALI_FALSE
319
- elsif rule.decider_type == :unless
320
- unless decider.(record, original_subtarget)
321
- return BALI_TRUE
322
- else
323
- return BALI_FALSE
324
- end
325
- end
326
- end
327
- end
328
- end # class
329
- end # module