bali 2.1.2 → 2.2.0
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.
- 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
|