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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ace804a23b863182a963445bfa6c155019eb63fb
4
- data.tar.gz: 35aec1c6457269fac4f5e145b7714fa1509ffa09
3
+ metadata.gz: bfdb9274e88935206ba786522f65dba0cb1ddfb4
4
+ data.tar.gz: f3dc6a29a69a526540bfd1fab705acdade594773
5
5
  SHA512:
6
- metadata.gz: 8fdcf47c1d1222118e81bc425ec045729419e63ca834dced4ba9471102e29a420deb6618d111fd21324938e2c79ec3d079cc1d32161dbc52876959e608185140
7
- data.tar.gz: 0d0157c91b98396a0805742f619e124cce725bac14417af71afa2300fe234885e5c35dd5695fec498b07f80182b8d14ecaca9a01ca4a2663d4bdaddd251f16df
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
- ### Version 1.0.0beta1
225
+ == Version 1.0.0beta1
226
+
157
227
  1. Initial version
158
228
 
159
- ### Version 1.0.0rc1
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
- ### Version 1.0.0rc2
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
- ### Version 1.0.0rc3
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
- ### Version 1.0.0
249
+ == Version 1.0.0
250
+
177
251
  1. Released the stable version of this gem
178
252
 
179
- ### Version 1.1.0rc1
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
- ### Version 1.1.0rc2
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
- ### Version 1.1.0
190
- 1. Respecting precedence; rule that is defined first can be replaced with later-defined rule clause for the same operation
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
@@ -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
@@ -0,0 +1,2 @@
1
+ class Bali::Error < StandardError
2
+ end
@@ -1,2 +1,3 @@
1
- class Bali::DslError < StandardError
1
+ # this error is thrown when there is incorrect syntax in the DSL
2
+ class Bali::DslError < Bali::Error
2
3
  end
@@ -0,0 +1,3 @@
1
+ # this error is thrown when objection is cannot be executed
2
+ class Bali::ObjectionError < Bali::Error
3
+ end
@@ -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
- return !self.cant?(subtarget, operation, record, cross_check: true)
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
- if decider.arity == 0
67
- return (rule.decider_type == :if) ? decider.() == true : decider.() == false
68
- else
69
- return (rule.decider_type == :if) ? decider.(record) == true : decider.(record) == false
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
- return !self.can?(subtarget, operation, record, cross_check: true)
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
- if decider.arity == 0
118
- return (rule.decider_type == :if) ? decider.() == true : decider.() == false
119
- else
120
- return (rule.decider_type == :if) ? decider.(record) == true : decider.(record) == false
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?(subtargets, operation, record = self, options = {})
129
- subs = (subtargets.is_a?(Array)) ? subtargets : [subtargets]
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?(subtargets, operation, record = self, options = {})
139
- subs = (subtargets.is_a?(Array)) ? subtargets : [subtargets]
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
@@ -1,3 +1,3 @@
1
1
  module Bali
2
- VERSION = "1.1.0"
2
+ VERSION = "1.2.0"
3
3
  end
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.1.0
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-24 00:00:00.000000000 Z
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