bali 1.1.0 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +87 -9
- data/lib/bali.rb +8 -0
- data/lib/bali/dsl/map_rules_dsl.rb +27 -0
- data/lib/bali/exceptions/bali_error.rb +2 -0
- data/lib/bali/exceptions/dsl_error.rb +2 -1
- data/lib/bali/exceptions/objection_error.rb +3 -0
- data/lib/bali/objector.rb +140 -15
- data/lib/bali/version.rb +1 -1
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bfdb9274e88935206ba786522f65dba0cb1ddfb4
|
4
|
+
data.tar.gz: f3dc6a29a69a526540bfd1fab705acdade594773
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4801d9b6dc47bd70b75689663485487533c82d1de7ae2cb37c8657972837f5894d03cc7598fad9f87adea0312672ccd81ce00458012ae7aa157d95895bba17a0
|
7
|
+
data.tar.gz: 1a0597385c6bded708cba7d9bac719921706465df49d9a29b445910e086859e3da5b0b9e123da4ce38d63d11b1c3dae033854ca76c539d5b57077da57243c709
|
data/README.md
CHANGED
@@ -85,6 +85,9 @@ class My::Employee
|
|
85
85
|
|
86
86
|
# working experience in the company
|
87
87
|
attr_accessor :exp_years
|
88
|
+
|
89
|
+
# role/roles of this employee
|
90
|
+
attr_accessor :roles
|
88
91
|
end
|
89
92
|
```
|
90
93
|
|
@@ -143,6 +146,72 @@ Thus, if we have:
|
|
143
146
|
|
144
147
|
That is, we can check `can?` and `cant?` with multiple roles by passing array of roles to it.
|
145
148
|
|
149
|
+
### Using subtarget's instance for Can and Cant testing
|
150
|
+
|
151
|
+
You may pass in real subtarget instance rather than (1) a symbol, (2) string or (3) array of string/symbol for can/cant testing.
|
152
|
+
|
153
|
+
In order to do so, you need to specify the field/method in the subtarget that will be used to evaluating the subtarget's role(s). To do that, we define `roles_for` as follow inside `Bali.map_rules` block:
|
154
|
+
|
155
|
+
```ruby
|
156
|
+
Bali.map_rules do
|
157
|
+
roles_for My::Employee, :roles
|
158
|
+
roles_for My::AdminUser, :admin_roles
|
159
|
+
roles_for My::BankUser, :roles
|
160
|
+
|
161
|
+
# rules definition
|
162
|
+
# may follow
|
163
|
+
end
|
164
|
+
```
|
165
|
+
|
166
|
+
`roles_for` accept two parameters, namely the class of the subtarget, and the field/method that will be invoked on it to gain data about its role(s).
|
167
|
+
|
168
|
+
By doing so, we can now perform authorisation testing as follow:
|
169
|
+
|
170
|
+
```ruby
|
171
|
+
txn = My::Transaction.new
|
172
|
+
current_employee = My::Employee.find_by_id(1)
|
173
|
+
txn.can?(current_employee, :print)
|
174
|
+
```
|
175
|
+
|
176
|
+
### Rule clause if/unless
|
177
|
+
|
178
|
+
Rule clause may contain `if` and `unless` (decider) proc as already seen before. This `if` and `unless` `proc` have three variants that can be used to express your rule in sufficient detail:
|
179
|
+
|
180
|
+
1. Zero arity
|
181
|
+
2. One arity
|
182
|
+
3. Two arity
|
183
|
+
|
184
|
+
When rule is very brief, use zero-arity rule clause as below:
|
185
|
+
|
186
|
+
```ruby
|
187
|
+
Bali.map_rules do
|
188
|
+
rules_for My::Transaction do
|
189
|
+
describe(:staff) { can :cancel }
|
190
|
+
describe(:finance) { can :cancel }
|
191
|
+
end
|
192
|
+
end
|
193
|
+
```
|
194
|
+
|
195
|
+
Say that (for staff) to cancel a transaction, the transaction must have not been settled yet, you need to define the rule by using one-arity rule clause decider:
|
196
|
+
|
197
|
+
```ruby
|
198
|
+
describe :staff do
|
199
|
+
can :cancel, if: { |txn| txn.is_settled? }
|
200
|
+
end
|
201
|
+
```
|
202
|
+
|
203
|
+
Good, but, in addition to that, how to allow transaction cancellation only to staff who has 3 years or so experience in working with the company?
|
204
|
+
|
205
|
+
In order to do that, we need to resort to 2-arity decider, as follow:
|
206
|
+
|
207
|
+
```ruby
|
208
|
+
describe :staff do
|
209
|
+
can :cancel, if: { |txn, usr| txn.is_settled? && usr.exp_years >= 3 }
|
210
|
+
end
|
211
|
+
```
|
212
|
+
|
213
|
+
This way, we can keep our controller/logic/model clean from unnecessary and un-DRY role-testing logic.
|
214
|
+
|
146
215
|
## Contributing
|
147
216
|
|
148
217
|
Bug reports and pull requests are welcome on GitHub at https://github.com/saveav/bali. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](contributor-covenant.org) code of conduct.
|
@@ -153,19 +222,23 @@ Bali is proudly available as open source under the terms of the [MIT License](ht
|
|
153
222
|
|
154
223
|
## Changelog
|
155
224
|
|
156
|
-
|
225
|
+
== Version 1.0.0beta1
|
226
|
+
|
157
227
|
1. Initial version
|
158
228
|
|
159
|
-
|
229
|
+
== Version 1.0.0rc1
|
230
|
+
|
160
231
|
1. Fix bug where user can't check on class
|
161
232
|
2. Adding new clause: cant_all
|
162
233
|
|
163
|
-
|
234
|
+
== Version 1.0.0rc2
|
235
|
+
|
164
236
|
1. [Fix bug when class's name, as a constant, is reloaded](http://stackoverflow.com/questions/2509350/rails-class-object-id-changes-after-i-make-a-request) (re-allocated to different address in the memory)
|
165
237
|
2. Allow describing rule for `nil`, useful if user is not authenticated thus role is probably `nil`
|
166
238
|
3. Remove pry from development dependency
|
167
239
|
|
168
|
-
|
240
|
+
== Version 1.0.0rc3
|
241
|
+
|
169
242
|
1. Each target class should includes `Bali::Objector`, for the following reasons:
|
170
243
|
- Makes it clear that class do want to include the Bali::Objector
|
171
244
|
- Transparant, and thus less confusing as to where "can?" and "cant" come from
|
@@ -173,18 +246,23 @@ Bali is proudly available as open source under the terms of the [MIT License](ht
|
|
173
246
|
2. Return `true` to any `can?` for undefined target/subtarget alike
|
174
247
|
3. Return `false` to any `cant?` for undefined target/subtarget alike
|
175
248
|
|
176
|
-
|
249
|
+
== Version 1.0.0
|
250
|
+
|
177
251
|
1. Released the stable version of this gem
|
178
252
|
|
179
|
-
|
253
|
+
== Version 1.1.0rc1
|
254
|
+
|
180
255
|
1. Ability for rule class to be parsed later by passing `later: true` to rule class definition
|
181
256
|
2. Add `Bali.parse` and `Bali.parse!` (`Bali.parse!` executes "later"-tagged rule class, Bali.parse executes automatically after all rules are defined)
|
182
257
|
3. Added more thorough testing specs
|
183
258
|
4. Proc can be served under `unless` for defining the rule's decider
|
184
259
|
|
185
|
-
|
260
|
+
== Version 1.1.0rc2
|
261
|
+
|
186
262
|
1. Ability to check `can?` and `cant?` for subtarget with multiple roles
|
187
263
|
2. Describe multiple rules at once for multiple subtarget
|
188
264
|
|
189
|
-
|
190
|
-
|
265
|
+
== Version 1.2.0
|
266
|
+
|
267
|
+
1. Passing real object as subtarget's role, instead of symbol or array of symbol
|
268
|
+
2. Clause can also yielding user, along with the object in question
|
data/lib/bali.rb
CHANGED
@@ -13,7 +13,9 @@ require_relative "bali/dsl/rules_for_dsl.rb"
|
|
13
13
|
require_relative "bali/objector"
|
14
14
|
|
15
15
|
# exception classes
|
16
|
+
require_relative "bali/exceptions/bali_error"
|
16
17
|
require_relative "bali/exceptions/dsl_error"
|
18
|
+
require_relative "bali/exceptions/objection_error"
|
17
19
|
|
18
20
|
module Bali
|
19
21
|
# mapping class to a RuleClass
|
@@ -24,6 +26,12 @@ module Bali
|
|
24
26
|
|
25
27
|
# from full class name to symbol
|
26
28
|
REVERSE_ALIASED_RULE_CLASS_MAP = {}
|
29
|
+
|
30
|
+
# {
|
31
|
+
# User: :roles,
|
32
|
+
# AdminUser: :admin_roles
|
33
|
+
# }
|
34
|
+
TRANSLATED_SUBTARGET_ROLES = {}
|
27
35
|
end
|
28
36
|
|
29
37
|
module Bali
|
@@ -6,6 +6,7 @@ class Bali::MapRulesDsl
|
|
6
6
|
@@lock = Mutex.new
|
7
7
|
end
|
8
8
|
|
9
|
+
# defining rules
|
9
10
|
def rules_for(target_class, target_alias_hash = {}, &block)
|
10
11
|
@@lock.synchronize do
|
11
12
|
self.current_rule_class = Bali::RuleClass.new(target_class)
|
@@ -18,7 +19,33 @@ class Bali::MapRulesDsl
|
|
18
19
|
end
|
19
20
|
end
|
20
21
|
|
22
|
+
# subtarget_class is the subtarget's class definition
|
23
|
+
# field_name is the field that will be consulted when instantiated object of this class is passed in can? or cant?
|
24
|
+
def roles_for(subtarget_class, field_name)
|
25
|
+
raise Bali::DslError, "Subtarget must be a class" unless subtarget_class.is_a?(Class)
|
26
|
+
raise Bali::DslError, "Field name must be a symbol/string" if !(field_name.is_a?(Symbol) || field_name.is_a?(String))
|
27
|
+
|
28
|
+
Bali::TRANSLATED_SUBTARGET_ROLES[subtarget_class.to_s] = field_name
|
29
|
+
nil
|
30
|
+
end
|
31
|
+
|
21
32
|
def describe(*params)
|
22
33
|
raise Bali::DslError, "describe block must be within rules_for block"
|
23
34
|
end
|
35
|
+
|
36
|
+
def can(*params)
|
37
|
+
raise Bali::DslError, "can block must be within describe block"
|
38
|
+
end
|
39
|
+
|
40
|
+
def cant(*params)
|
41
|
+
raise Bali::DslError, "cant block must be within describe block"
|
42
|
+
end
|
43
|
+
|
44
|
+
def can_all(*params)
|
45
|
+
raise Bali::DslError, "can_all block must be within describe block"
|
46
|
+
end
|
47
|
+
|
48
|
+
def cant_all(*params)
|
49
|
+
raise Bali::DslError, "cant_all block must be within describe block"
|
50
|
+
end
|
24
51
|
end
|
data/lib/bali/objector.rb
CHANGED
@@ -16,10 +16,46 @@ end
|
|
16
16
|
|
17
17
|
module Bali::Objector::Statics
|
18
18
|
|
19
|
+
# will return array
|
20
|
+
def __translate_subtarget_roles__(_subtarget_roles)
|
21
|
+
if _subtarget_roles.is_a?(String) || _subtarget_roles.is_a?(Symbol) || _subtarget_roles.is_a?(NilClass)
|
22
|
+
return [_subtarget_roles]
|
23
|
+
elsif _subtarget_roles.is_a?(Array)
|
24
|
+
return _subtarget_roles
|
25
|
+
else
|
26
|
+
# this case, _subtarget_roles is an object but not a symbol or a string
|
27
|
+
# let's try to deduct subtarget's roles
|
28
|
+
|
29
|
+
_subtarget = _subtarget_roles
|
30
|
+
_subtarget_class = _subtarget.class.to_s
|
31
|
+
|
32
|
+
# variable to hold deducted role of the passed object
|
33
|
+
deducted_roles = nil
|
34
|
+
|
35
|
+
Bali::TRANSLATED_SUBTARGET_ROLES.each do |subtarget_class, roles_field_name|
|
36
|
+
if _subtarget_class == subtarget_class
|
37
|
+
deducted_roles = _subtarget.send(roles_field_name)
|
38
|
+
if deducted_roles.is_a?(String) || deducted_roles.is_a?(Symbol)
|
39
|
+
deducted_roles = [deducted_roles]
|
40
|
+
break
|
41
|
+
elsif deducted_roles.is_a?(Array)
|
42
|
+
break
|
43
|
+
else
|
44
|
+
# keep it nil if _subtarget_roles is not either String, Symbol or Array
|
45
|
+
deducted_roles = nil
|
46
|
+
end
|
47
|
+
end # if matching class
|
48
|
+
end # each TRANSLATED_SUBTARGET_ROLES
|
49
|
+
|
50
|
+
return deducted_roles
|
51
|
+
end # if
|
52
|
+
end
|
53
|
+
|
19
54
|
### options passable to __can__? and __cant__? are:
|
20
55
|
### cross_action: if set to true wouldn't call its counterpart so as to prevent
|
21
56
|
### overflowing stack
|
22
|
-
###
|
57
|
+
### original_subtarget: the original passed to can? and cant? before
|
58
|
+
### processed by __translate_subtarget_roles__
|
23
59
|
|
24
60
|
def __can__?(subtarget, operation, record = self, options = {})
|
25
61
|
# if performed on a class-level, don't call its class or it will return
|
@@ -58,15 +94,56 @@ module Bali::Objector::Statics
|
|
58
94
|
# default if can? for undefined rule is false, after related clause
|
59
95
|
# cannot be found in cant?
|
60
96
|
return false if options[:cross_check]
|
61
|
-
|
97
|
+
options[:cross_check] = true
|
98
|
+
return !self.cant?(subtarget, operation, record, options)
|
62
99
|
else
|
63
100
|
if rule.has_decider?
|
64
101
|
# must test first
|
65
102
|
decider = rule.decider
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
103
|
+
original_subtarget = options.fetch(:original_subtarget)
|
104
|
+
case decider.arity
|
105
|
+
when 0
|
106
|
+
if rule.decider_type == :if
|
107
|
+
if decider.()
|
108
|
+
return true
|
109
|
+
else
|
110
|
+
return false
|
111
|
+
end
|
112
|
+
elsif rule.decider_type == :unless
|
113
|
+
unless decider.()
|
114
|
+
return true
|
115
|
+
else
|
116
|
+
return false
|
117
|
+
end
|
118
|
+
end
|
119
|
+
when 1
|
120
|
+
if rule.decider_type == :if
|
121
|
+
if decider.(record)
|
122
|
+
return true
|
123
|
+
else
|
124
|
+
return false
|
125
|
+
end
|
126
|
+
elsif rule.decider_type == :unless
|
127
|
+
unless decider.(record)
|
128
|
+
return true
|
129
|
+
else
|
130
|
+
return false
|
131
|
+
end
|
132
|
+
end
|
133
|
+
when 2
|
134
|
+
if rule.decider_type == :if
|
135
|
+
if decider.(record, original_subtarget)
|
136
|
+
return true
|
137
|
+
else
|
138
|
+
return false
|
139
|
+
end
|
140
|
+
elsif rule.decider_type == :unless
|
141
|
+
unless decider.(record, original_subtarget)
|
142
|
+
return true
|
143
|
+
else
|
144
|
+
return false
|
145
|
+
end
|
146
|
+
end
|
70
147
|
end
|
71
148
|
else
|
72
149
|
# rule is properly defined
|
@@ -110,14 +187,55 @@ module Bali::Objector::Statics
|
|
110
187
|
# can? is defined exactly for the same target, and subtarget, and record (if given)
|
111
188
|
if rule.nil?
|
112
189
|
return true if options[:cross_check]
|
113
|
-
|
190
|
+
options[:cross_check] = true
|
191
|
+
return !self.can?(subtarget, operation, record, options)
|
114
192
|
else
|
115
193
|
if rule.has_decider?
|
116
194
|
decider = rule.decider
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
195
|
+
original_subtarget = options.fetch(:original_subtarget)
|
196
|
+
case decider.arity
|
197
|
+
when 0
|
198
|
+
if rule.decider_type == :if
|
199
|
+
if decider.()
|
200
|
+
return true
|
201
|
+
else
|
202
|
+
return false
|
203
|
+
end
|
204
|
+
elsif rule.decider_type == :unless
|
205
|
+
unless decider.()
|
206
|
+
return true
|
207
|
+
else
|
208
|
+
return false
|
209
|
+
end
|
210
|
+
end
|
211
|
+
when 1
|
212
|
+
if rule.decider_type == :if
|
213
|
+
if decider.(record)
|
214
|
+
return true
|
215
|
+
else
|
216
|
+
return false
|
217
|
+
end
|
218
|
+
elsif rule.decider_type == :unless
|
219
|
+
unless decider.(record)
|
220
|
+
return true
|
221
|
+
else
|
222
|
+
return false
|
223
|
+
end
|
224
|
+
end
|
225
|
+
when 2
|
226
|
+
if rule.decider_type == :if
|
227
|
+
if decider.(record, original_subtarget)
|
228
|
+
return true
|
229
|
+
else
|
230
|
+
return false
|
231
|
+
end
|
232
|
+
elsif rule.decider_type == :unless
|
233
|
+
unless decider.(record, original_subtarget)
|
234
|
+
return true
|
235
|
+
else
|
236
|
+
return false
|
237
|
+
end
|
238
|
+
end
|
121
239
|
end
|
122
240
|
else
|
123
241
|
return true # rule is properly defined
|
@@ -125,23 +243,30 @@ module Bali::Objector::Statics
|
|
125
243
|
end # if rule is nil
|
126
244
|
end
|
127
245
|
|
128
|
-
def can?(
|
129
|
-
subs = (
|
246
|
+
def can?(subtarget_roles, operation, record = self, options = {})
|
247
|
+
subs = __translate_subtarget_roles__(subtarget_roles)
|
248
|
+
# well, it is largely not used unless decider's is 2 arity
|
249
|
+
options[:original_subtarget] = options[:original_subtarget].nil? ? subtarget_roles : options[:original_subtarget]
|
130
250
|
|
131
251
|
subs.each do |subtarget|
|
132
252
|
can_value = __can__?(subtarget, operation, record, options)
|
133
253
|
return true if can_value == true
|
134
254
|
end
|
135
255
|
false
|
256
|
+
rescue => e
|
257
|
+
raise Bali::ObjectionError, e.message
|
136
258
|
end
|
137
259
|
|
138
|
-
def cant?(
|
139
|
-
subs =
|
260
|
+
def cant?(subtarget_roles, operation, record = self, options = {})
|
261
|
+
subs = __translate_subtarget_roles__ subtarget_roles
|
262
|
+
options[:original_subtarget] = options[:original_subtarget].nil? ? subtarget_roles : options[:original_subtarget]
|
140
263
|
|
141
264
|
subs.each do |subtarget|
|
142
265
|
cant_value = __cant__?(subtarget, operation, record, options)
|
143
266
|
return false if cant_value == false
|
144
267
|
end
|
145
268
|
true
|
269
|
+
rescue => e
|
270
|
+
raise Bali::ObjectionError, e.message
|
146
271
|
end
|
147
272
|
end
|
data/lib/bali/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: bali
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Adam Pahlevi
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-08-
|
11
|
+
date: 2015-08-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -75,7 +75,9 @@ files:
|
|
75
75
|
- lib/bali.rb
|
76
76
|
- lib/bali/dsl/map_rules_dsl.rb
|
77
77
|
- lib/bali/dsl/rules_for_dsl.rb
|
78
|
+
- lib/bali/exceptions/bali_error.rb
|
78
79
|
- lib/bali/exceptions/dsl_error.rb
|
80
|
+
- lib/bali/exceptions/objection_error.rb
|
79
81
|
- lib/bali/foundations/bali_statics.rb
|
80
82
|
- lib/bali/foundations/rule.rb
|
81
83
|
- lib/bali/foundations/rule_class.rb
|