bali 2.1.2 → 2.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +8 -8
- data/CHANGELOG.md +6 -0
- data/README.md +7 -6
- data/lib/bali/dsl/map_rules_dsl.rb +9 -5
- data/lib/bali/dsl/rules_for_dsl.rb +126 -104
- data/lib/bali/foundations/all_foundations.rb +6 -0
- data/lib/bali/foundations/exceptions/authorization_error.rb +22 -3
- data/lib/bali/foundations/judger/judge.rb +329 -0
- data/lib/bali/foundations/judger/negative_judge.rb +40 -0
- data/lib/bali/foundations/judger/positive_judge.rb +41 -0
- data/lib/bali/foundations/role_extractor.rb +61 -0
- data/lib/bali/foundations/rule/rule_class.rb +3 -2
- data/lib/bali/foundations/rule/rule_group.rb +3 -0
- data/lib/bali/objector.rb +58 -359
- data/lib/bali/version.rb +1 -1
- metadata +6 -2
checksums.yaml
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
---
|
2
2
|
!binary "U0hBMQ==":
|
3
3
|
metadata.gz: !binary |-
|
4
|
-
|
4
|
+
ZTcxZjc2ZmMyNDgzYWVmMDNjOWNhMzNmNjZiZmNjYTcwMjkzZGE0YQ==
|
5
5
|
data.tar.gz: !binary |-
|
6
|
-
|
6
|
+
ZTYwYzIwMWE2Nzg1ZjM5YmIwM2RjNTcwY2E2MDQ4OTQ0ZTUzNWZmOA==
|
7
7
|
SHA512:
|
8
8
|
metadata.gz: !binary |-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
MDdkNTNhODc2NTRkNmY1NTBmNDk3MTQ3Mzc5YWUwMjBkNmQzODkzYTliOTA3
|
10
|
+
MGJkMjBkMWE0ZTE1ODZkYTY0MmFhMmJmMzMxNTMwM2I0ZWU1ZDE1Y2ZmOWUz
|
11
|
+
N2I0NDg5MGYxZjU1NzMyOWU3MzcwOTJhMmUxOTBkNTQ0YWEzNGQ=
|
12
12
|
data.tar.gz: !binary |-
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
YmI1YjcxMmNmOGFjOWFmZjk1NDA0MGIzZWFlMmEwNTk3ODgxNzVkYzRmZjQz
|
14
|
+
ZTNkNTU4ZWYwYTBjNWNiZDdjZTNjODA2ZTBjNzc0NDRhZTU3MmQ0NWJiZmI0
|
15
|
+
MGViMWE0ZjllNzZjM2U0N2ViYjNjZDYzY2ZlOWRkOTJkNWVhNjM=
|
data/CHANGELOG.md
CHANGED
@@ -72,3 +72,9 @@
|
|
72
72
|
|
73
73
|
1. `nil` will be printed <nil> when objecting with can! or cannot! for better readability.
|
74
74
|
2. Bug fixes on declaring multiple rules with decider. Previously, others were ignored--now, every single rule will get defined whether decider is present or not.
|
75
|
+
|
76
|
+
== Version 2.2.0
|
77
|
+
|
78
|
+
1. Deprecating `describe` block in favour of `role` block, `describe` is to be deprecated in version 3.0.
|
79
|
+
2. Using strategy pattern, heavy refactoring
|
80
|
+
3. Human-readable authorisation error message when invoking !-version of can/cannot, for eg: Role general_user is not allowed to perform operation `update` on My::Transaction
|
data/README.md
CHANGED
@@ -28,6 +28,7 @@ And then execute:
|
|
28
28
|
|
29
29
|
1. `cant` and `cant_all` which are used to declare rules will be deprecated on version 3.0, in favor of `cannot` and `cannot_all`. The reason behind this is that `can` and `cant` only differ by 1 letter, it is thought to be better to make it less ambiguous.
|
30
30
|
2. `cant?` and subsequently new-introduced `cant!` will be deprecated on version 3.0, in favor of `cannot?` and `cannot!` for the same reason as above.
|
31
|
+
3. Since version 2.1.3, `describe` block is replaced with `role` block. `describe` block will be deprecated on version 3.0.
|
31
32
|
|
32
33
|
## Usage
|
33
34
|
|
@@ -88,21 +89,21 @@ The specification above seems very terrifying, but with Bali, those can be defin
|
|
88
89
|
```ruby
|
89
90
|
Bali.map_rules do
|
90
91
|
rules_for My::Transaction do
|
91
|
-
|
92
|
-
|
92
|
+
role(:supreme_user) { can_all }
|
93
|
+
role :admin_user do
|
93
94
|
can_all
|
94
95
|
# a more specific rule would be executed even if can_all is present
|
95
96
|
can :cancel,
|
96
97
|
if: proc { |record| record.payment_channel == "CREDIT_CARD" &&
|
97
98
|
!record.is_settled? }
|
98
99
|
end
|
99
|
-
|
100
|
-
|
100
|
+
role "general user", can: [:download]
|
101
|
+
role "finance" do
|
101
102
|
can :delete, if: proc { |record| record.is_settled? }
|
102
103
|
can :cancel, unless: proc { |record| record.is_settled? }
|
103
104
|
end # finance_user description
|
104
|
-
|
105
|
-
|
105
|
+
role :guest, nil { can :report_fraud }
|
106
|
+
role :client do
|
106
107
|
can :create
|
107
108
|
end
|
108
109
|
others do
|
@@ -42,8 +42,12 @@ class Bali::MapRulesDsl
|
|
42
42
|
raise Bali::DslError, "describe block must be within rules_for block"
|
43
43
|
end
|
44
44
|
|
45
|
+
def role(*params)
|
46
|
+
raise Bali::DslError, "role block must be within rules_for block"
|
47
|
+
end
|
48
|
+
|
45
49
|
def can(*params)
|
46
|
-
raise Bali::DslError, "can block must be within
|
50
|
+
raise Bali::DslError, "can block must be within role block"
|
47
51
|
end
|
48
52
|
|
49
53
|
def cant(*params)
|
@@ -52,15 +56,15 @@ class Bali::MapRulesDsl
|
|
52
56
|
end
|
53
57
|
|
54
58
|
def cannot(*params)
|
55
|
-
raise Bali::DslError, "cant block must be within
|
59
|
+
raise Bali::DslError, "cant block must be within role block"
|
56
60
|
end
|
57
61
|
|
58
62
|
def can_all(*params)
|
59
|
-
raise Bali::DslError, "can_all block must be within
|
63
|
+
raise Bali::DslError, "can_all block must be within role block"
|
60
64
|
end
|
61
65
|
|
62
66
|
def clear_rules
|
63
|
-
raise Bali::DslError, "clear_rules must be called within
|
67
|
+
raise Bali::DslError, "clear_rules must be called within role block"
|
64
68
|
end
|
65
69
|
|
66
70
|
def cant_all(*params)
|
@@ -69,6 +73,6 @@ class Bali::MapRulesDsl
|
|
69
73
|
end
|
70
74
|
|
71
75
|
def cannot_all(*params)
|
72
|
-
raise Bali::DslError, "cant_all block must be within
|
76
|
+
raise Bali::DslError, "cant_all block must be within role block"
|
73
77
|
end
|
74
78
|
end
|
@@ -4,6 +4,11 @@ class Bali::RulesForDsl
|
|
4
4
|
attr_accessor :map_rules_dsl
|
5
5
|
attr_accessor :current_rule_group
|
6
6
|
|
7
|
+
# all to be processed subtargets
|
8
|
+
attr_accessor :current_subtargets
|
9
|
+
# rules defined with hash can: [] and cannot: []
|
10
|
+
attr_accessor :shortcut_rules
|
11
|
+
|
7
12
|
def initialize(map_rules_dsl)
|
8
13
|
@@lock = Mutex.new
|
9
14
|
self.map_rules_dsl = map_rules_dsl
|
@@ -12,123 +17,43 @@ class Bali::RulesForDsl
|
|
12
17
|
def current_rule_class
|
13
18
|
self.map_rules_dsl.current_rule_class
|
14
19
|
end
|
20
|
+
protected :current_rule_class
|
15
21
|
|
16
|
-
def
|
22
|
+
def role(*params)
|
17
23
|
@@lock.synchronize do
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
params.each do |passed_argument|
|
22
|
-
if passed_argument.is_a?(Symbol) || passed_argument.is_a?(String)
|
23
|
-
subtargets << passed_argument
|
24
|
-
elsif passed_argument.is_a?(NilClass)
|
25
|
-
subtargets << passed_argument
|
26
|
-
elsif passed_argument.is_a?(Array)
|
27
|
-
subtargets += passed_argument
|
28
|
-
elsif passed_argument.is_a?(Hash)
|
29
|
-
rules = passed_argument
|
30
|
-
else
|
31
|
-
raise Bali::DslError, "Allowed argument for describe: symbol, string, nil and hash"
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
|
-
target_class = self.map_rules_dsl.current_rule_class.target_class
|
36
|
-
|
37
|
-
subtargets.each do |subtarget|
|
38
|
-
rule_group = self.current_rule_class.rules_for(subtarget)
|
39
|
-
if rule_group.nil?
|
40
|
-
rule_group = Bali::RuleGroup.new(target_class, subtarget)
|
41
|
-
end
|
42
|
-
|
43
|
-
self.current_rule_group = rule_group
|
44
|
-
|
24
|
+
bali_scrap_actors(*params)
|
25
|
+
bali_scrap_shortcut_rules(*params)
|
26
|
+
current_subtargets.each do |subtarget|
|
45
27
|
if block_given?
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
rules.each do |auth_val, operations|
|
50
|
-
if operations.is_a?(Array)
|
51
|
-
operations.each do |op|
|
52
|
-
rule = Bali::Rule.new(auth_val, op)
|
53
|
-
self.current_rule_group.add_rule(rule)
|
54
|
-
end
|
55
|
-
else
|
56
|
-
operation = operations # well, basically is 1 only
|
57
|
-
rule = Bali::Rule.new(auth_val, operation)
|
58
|
-
self.current_rule_group.add_rule(rule)
|
59
|
-
end
|
60
|
-
end # each rules
|
61
|
-
end # block_given?
|
62
|
-
|
63
|
-
# add current_rule_group
|
64
|
-
self.map_rules_dsl.current_rule_class.add_rule_group(self.current_rule_group)
|
65
|
-
end # each subtarget
|
66
|
-
end # sync block
|
67
|
-
end # describe
|
68
|
-
|
69
|
-
# others block
|
70
|
-
def others(*params)
|
71
|
-
@@lock.synchronize do
|
72
|
-
rules = {}
|
73
|
-
|
74
|
-
params.each do |passed_argument|
|
75
|
-
if passed_argument.is_a?(Hash)
|
76
|
-
rules = passed_argument
|
28
|
+
bali_process_subtarget(subtarget) do
|
29
|
+
yield
|
30
|
+
end
|
77
31
|
else
|
78
|
-
|
32
|
+
bali_process_subtarget(subtarget)
|
79
33
|
end
|
80
34
|
end
|
35
|
+
end
|
36
|
+
end # role
|
81
37
|
|
82
|
-
|
83
|
-
|
84
|
-
|
38
|
+
def describe(*params)
|
39
|
+
puts "Bali Deprecation Warning: describing rules using describe will be deprecated on major release 3.0, use role instead"
|
40
|
+
if block_given?
|
41
|
+
role(*params) do
|
85
42
|
yield
|
86
|
-
else
|
87
|
-
rules.each do |auth_val, operations|
|
88
|
-
if operations.is_a?(Array)
|
89
|
-
operations.each do |op|
|
90
|
-
rule = Bali::Rule.new(auth_val, op)
|
91
|
-
self.current_rule_group.add_rule(rule)
|
92
|
-
end
|
93
|
-
else
|
94
|
-
operation = operations
|
95
|
-
rule = Bali::Rule.new(auth_val, operation)
|
96
|
-
self.current_rule_group.add_rule(rule)
|
97
|
-
end
|
98
|
-
end # each rules
|
99
|
-
end # block_given?
|
100
|
-
end # synchronize
|
101
|
-
end # others
|
102
|
-
|
103
|
-
# to define can and cant is basically using this method
|
104
|
-
def bali_process_auth_rules(auth_val, args)
|
105
|
-
conditional_hash = nil
|
106
|
-
operations = []
|
107
|
-
|
108
|
-
# scan args for options
|
109
|
-
args.each do |elm|
|
110
|
-
if elm.is_a?(Hash)
|
111
|
-
conditional_hash = elm
|
112
|
-
else
|
113
|
-
operations << elm
|
114
43
|
end
|
44
|
+
else
|
45
|
+
role(*params)
|
115
46
|
end
|
47
|
+
end
|
116
48
|
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
rule.decider = conditional_hash[:if] || conditional_hash["if"]
|
123
|
-
rule.decider_type = :if
|
124
|
-
elsif conditional_hash[:unless] || conditional_hash[:unless]
|
125
|
-
rule.decider = conditional_hash[:unless] || conditional_hash["unless"]
|
126
|
-
rule.decider_type = :unless
|
127
|
-
end
|
49
|
+
# others block
|
50
|
+
def others(*params)
|
51
|
+
if block_given?
|
52
|
+
role("__*__") do
|
53
|
+
yield
|
128
54
|
end
|
129
|
-
self.current_rule_group.add_rule(rule)
|
130
55
|
end
|
131
|
-
end #
|
56
|
+
end # others
|
132
57
|
|
133
58
|
# clear all defined rules
|
134
59
|
def clear_rules
|
@@ -163,4 +88,101 @@ class Bali::RulesForDsl
|
|
163
88
|
puts "Deprecation Warning: declaring rules with cant_all will be deprecated on major release 3.0, use cannot_all instead"
|
164
89
|
cannot_all
|
165
90
|
end
|
91
|
+
|
92
|
+
private
|
93
|
+
def bali_scrap_actors(*params)
|
94
|
+
self.current_subtargets = []
|
95
|
+
params.each do |passed_argument|
|
96
|
+
if passed_argument.is_a?(Symbol) || passed_argument.is_a?(String)
|
97
|
+
self.current_subtargets << passed_argument
|
98
|
+
elsif passed_argument.is_a?(NilClass)
|
99
|
+
self.current_subtargets << passed_argument
|
100
|
+
elsif passed_argument.is_a?(Array)
|
101
|
+
self.current_subtargets += passed_argument
|
102
|
+
end
|
103
|
+
end
|
104
|
+
nil
|
105
|
+
end
|
106
|
+
|
107
|
+
def bali_scrap_shortcut_rules(*params)
|
108
|
+
self.shortcut_rules = {}
|
109
|
+
params.each do |passed_argument|
|
110
|
+
if passed_argument.is_a?(Hash)
|
111
|
+
self.shortcut_rules = passed_argument
|
112
|
+
end
|
113
|
+
end
|
114
|
+
nil
|
115
|
+
end
|
116
|
+
|
117
|
+
def bali_process_subtarget(subtarget)
|
118
|
+
target_class = self.map_rules_dsl.current_rule_class.target_class
|
119
|
+
rule_class = self.map_rules_dsl.current_rule_class
|
120
|
+
|
121
|
+
rule_group = rule_class.rules_for(subtarget)
|
122
|
+
|
123
|
+
if rule_group.nil?
|
124
|
+
rule_group = Bali::RuleGroup.new(target_class, subtarget)
|
125
|
+
end
|
126
|
+
|
127
|
+
self.current_rule_group = rule_group
|
128
|
+
|
129
|
+
if block_given?
|
130
|
+
yield
|
131
|
+
else
|
132
|
+
# auth_val is either can or cannot
|
133
|
+
shortcut_rules.each do |auth_val, operations|
|
134
|
+
if operations.is_a?(Array)
|
135
|
+
operations.each do |op|
|
136
|
+
rule = Bali::Rule.new(auth_val, op)
|
137
|
+
rule_group.add_rule(rule)
|
138
|
+
end
|
139
|
+
else
|
140
|
+
operation = operations # well, basically is 1 only
|
141
|
+
rule = Bali::Rule.new(auth_val, operation)
|
142
|
+
rule_group.add_rule(rule)
|
143
|
+
end
|
144
|
+
end # each rules
|
145
|
+
end # block_given?
|
146
|
+
|
147
|
+
# add current_rule_group
|
148
|
+
rule_class.add_rule_group(rule_group)
|
149
|
+
|
150
|
+
nil
|
151
|
+
end
|
152
|
+
|
153
|
+
# to define can and cant is basically using this method
|
154
|
+
def bali_process_auth_rules(auth_val, args)
|
155
|
+
conditional_hash = nil
|
156
|
+
operations = []
|
157
|
+
|
158
|
+
# scan args for options
|
159
|
+
args.each do |elm|
|
160
|
+
if elm.is_a?(Hash)
|
161
|
+
conditional_hash = elm
|
162
|
+
else
|
163
|
+
operations << elm
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
# add operation one by one
|
168
|
+
operations.each do |op|
|
169
|
+
rule = Bali::Rule.new(auth_val, op)
|
170
|
+
bali_embed_conditions(rule, conditional_hash)
|
171
|
+
self.current_rule_group.add_rule(rule)
|
172
|
+
end
|
173
|
+
end # bali_process_auth_rules
|
174
|
+
|
175
|
+
# process conditional statement in rule definition
|
176
|
+
def bali_embed_conditions(rule, conditional_hash = nil)
|
177
|
+
return if conditional_hash.nil?
|
178
|
+
|
179
|
+
condition_type = conditional_hash.keys[0].to_s.downcase
|
180
|
+
condition_type_symb = condition_type.to_sym
|
181
|
+
|
182
|
+
if condition_type_symb == :if || condition_type_symb == :unless
|
183
|
+
rule.decider = conditional_hash.values[0]
|
184
|
+
rule.decider_type = condition_type_symb
|
185
|
+
end
|
186
|
+
nil
|
187
|
+
end
|
166
188
|
end # class
|
@@ -9,3 +9,9 @@ require_relative "rule/rule"
|
|
9
9
|
require_relative "rule/rule_class"
|
10
10
|
require_relative "rule/rule_group"
|
11
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"
|
@@ -11,9 +11,28 @@ class Bali::AuthorizationError < Bali::Error
|
|
11
11
|
# whether a class or an object
|
12
12
|
attr_accessor :target
|
13
13
|
|
14
|
+
def target_proper_class
|
15
|
+
if target.is_a?(Class)
|
16
|
+
target
|
17
|
+
else
|
18
|
+
target.class
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
14
22
|
def to_s
|
15
|
-
|
16
|
-
|
17
|
-
|
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
|
18
32
|
end
|
33
|
+
|
34
|
+
private
|
35
|
+
def humanise_value(val)
|
36
|
+
val.nil? ? "<nil>" : val
|
37
|
+
end
|
19
38
|
end
|
@@ -0,0 +1,329 @@
|
|
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::Integrators::Rule.rule_group_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::Integrators::Rule.rule_group_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
|