consul 0.12.0 → 0.12.1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of consul might be problematic. Click here for more details.

checksums.yaml CHANGED
@@ -1,15 +1,7 @@
1
1
  ---
2
- !binary "U0hBMQ==":
3
- metadata.gz: !binary |-
4
- NGJjOTg0MzEzODg3Mzc2Mjk1OGIyYzhmNTRlMDgwYzczZDFjNGY5Mw==
5
- data.tar.gz: !binary |-
6
- NjEwOTM0ZjMwNzllMWQxMTgzNzZmMDAwZWQ3ODk0NTA0NTU0N2U3YQ==
2
+ SHA1:
3
+ metadata.gz: 0400eaba4100fccd2fa6ce219fbef662b0539a68
4
+ data.tar.gz: c37bcc13bc449b5fdbfd75e0cd10921450d93145
7
5
  SHA512:
8
- metadata.gz: !binary |-
9
- NmY4NzcxNDM2YTFmOGQxOGJhNzY2Mzc1M2UyZWFhNzMzZWEwNzVjNjNjYjU5
10
- ODg0NTUzNmE4MmI3NzM5YWNmMjkzMjI3NjUzMzA3N2U5ZTZlNjM4YmE1OWJk
11
- YTg5NGQ5NjNlYjM3Y2RhODZiZTUyZDNlZWIwZjQ3OGYyNTI2MWM=
12
- data.tar.gz: !binary |-
13
- Nzg4Nzc2NGU2Y2FlZjA5N2Y1YzBmMGYyZjhlMjIxZTU0ZDgyOGQ3YjZkNTM5
14
- MWZkNDA0OTJhNjVjZTVkMTZlM2E0MDg4NWIwODFkZjQ5MGJiYzE4NWEyYTA2
15
- ZjQ2NjJjMjc2N2RhZGE1NmZiZWVlZDdmYmY4NmIyOGRmMzQ3Mjg=
6
+ metadata.gz: c311180fbd19ac398075e151e8b734edca9dabe4a53de83bdf80cbe16498764b3726b856a36d9ab6926e541b9b4f5ddefe52a8aa42173ad204e3e79d6fe5989a
7
+ data.tar.gz: 5524169c519ddf6461b6c9925e718b0b560408d0514cad3cba6fd881f576963528b2b54d0e417adfa9446c25135b9c924da2ceff70e586145a9b579ca3be5571
data/README.md CHANGED
@@ -23,26 +23,28 @@ Inside your `Power` you can talk about what is accessible for the current user,
23
23
 
24
24
  A `Power` might look like this:
25
25
 
26
- class Power
27
- include Consul::Power
26
+ ```rb
27
+ class Power
28
+ include Consul::Power
28
29
 
29
- def initialize(user)
30
- @user = user
31
- end
30
+ def initialize(user)
31
+ @user = user
32
+ end
32
33
 
33
- power :users do
34
- User if @user.admin?
35
- end
34
+ power :users do
35
+ User if @user.admin?
36
+ end
36
37
 
37
- power :notes do
38
- Note.by_author(@user)
39
- end
38
+ power :notes do
39
+ Note.by_author(@user)
40
+ end
40
41
 
41
- power :dashboard do
42
- true # not a scope, but a boolean power. This is useful to control access to stuff that doesn't live in the database.
43
- end
42
+ power :dashboard do
43
+ true # not a scope, but a boolean power. This is useful to control access to stuff that doesn't live in the database.
44
+ end
44
45
 
45
- end
46
+ end
47
+ ```
46
48
 
47
49
  There are no restrictions on the name or constructor arguments of your this class.
48
50
 
@@ -60,39 +62,51 @@ A typical use case in a Rails application is to restrict access to your ActiveRe
60
62
 
61
63
  You do this by making your powers return an ActiveRecord scope (or "relation"):
62
64
 
63
- class Power
64
- ...
65
+ ```rb
66
+ class Power
67
+ ...
65
68
 
66
- power :notes do
67
- Note.by_author(@user)
68
- end
69
+ power :notes do
70
+ Note.by_author(@user)
71
+ end
69
72
 
70
- power :users do
71
- User if @user.admin?
72
- end
73
+ power :users do
74
+ User if @user.admin?
75
+ end
73
76
 
74
- end
77
+ end
78
+ ```
75
79
 
76
80
  You can now query these powers in order to retrieve the scope:
77
81
 
78
- power = Power.new(user)
79
- power.notes # => returns an ActiveRecord::Scope
82
+ ```rb
83
+ power = Power.new(user)
84
+ power.notes # => returns an ActiveRecord::Scope
85
+ ```
80
86
 
81
87
  Or you can ask if the power is given (meaning it's not `nil`):
82
88
 
83
- power.notes? # => returns true if Power#notes returns a scope and not nil
89
+ ```rb
90
+ power.notes? # => returns true if Power#notes returns a scope and not nil
91
+ ```
84
92
 
85
93
  Or you can raise an error unless a power its given, e.g. to guard access into a controller action:
86
94
 
87
- power.notes! # => raises Consul::Powerless unless Power#notes returns a scope (even if it's empty)
95
+ ```rb
96
+ power.notes! # => raises Consul::Powerless unless Power#notes returns a scope (even if it's empty)
97
+ ```
88
98
 
89
99
  Or you ask whether a given record is included in its scope (can be [optimized](#optimizing-record-checks-for-scope-powers)):
90
100
 
91
- power.note?(Note.last) # => returns whether the given Note is in the Power#notes scope. Caches the result for subsequent queries.
101
+ ```rb
102
+ power.note?(Note.last) # => returns whether the given Note is in the Power#notes scope. Caches the result for subsequent queries.
103
+ ```
92
104
 
93
105
  Or you can raise an error unless a given record is included in its scope:
94
106
 
95
- power.note!(Note.last) # => raises Consul::Powerless unless the given Note is in the Power#notes scope
107
+ ```rb
108
+ power.note!(Note.last) # => raises Consul::Powerless unless the given Note is in the Power#notes scope
109
+ ```
96
110
 
97
111
  See our crash course video [Solving bizare authorization requirements with Rails](http://bizarre-authorization.talks.makandra.com/) for many different use cases you can cover with this pattern.
98
112
 
@@ -103,22 +117,24 @@ See our crash course video [Solving bizare authorization requirements with Rails
103
117
  If you have different access rights for e.g. viewing or updating posts, simply use different powers:
104
118
 
105
119
 
106
- class Power
107
- ...
120
+ ```rb
121
+ class Power
122
+ ...
108
123
 
109
- power :notes do
110
- Note.published
111
- end
124
+ power :notes do
125
+ Note.published
126
+ end
112
127
 
113
- power :updatable_notes do
114
- Note.by_author(@user)
115
- end
128
+ power :updatable_notes do
129
+ Note.by_author(@user)
130
+ end
116
131
 
117
- power :destroyable_notes do
118
- Note if @user.admin?
119
- end
132
+ power :destroyable_notes do
133
+ Note if @user.admin?
134
+ end
120
135
 
121
- end
136
+ end
137
+ ```
122
138
 
123
139
  There is also a [shortcut to map different powers to RESTful controller actions](#protect-entry-into-controller-actions).
124
140
 
@@ -128,20 +144,24 @@ There is also a [shortcut to map different powers to RESTful controller actions]
128
144
 
129
145
  Boolean powers are useful to control access to stuff that doesn't live in the database:
130
146
 
131
- class Power
132
- ...
147
+ ```rb
148
+ class Power
149
+ ...
133
150
 
134
- power :dashboard do
135
- true
136
- end
151
+ power :dashboard do
152
+ true
153
+ end
137
154
 
138
- end
155
+ end
156
+ ```
139
157
 
140
158
  You can query it like the other powers:
141
159
 
142
- power = Power.new(@user)
143
- power.dashboard? # => true
144
- power.dashboard! # => raises Consul::Powerless unless Power#dashboard? returns true
160
+ ```rb
161
+ power = Power.new(@user)
162
+ power.dashboard? # => true
163
+ power.dashboard! # => raises Consul::Powerless unless Power#dashboard? returns true
164
+ ```
145
165
 
146
166
 
147
167
  ### Powers that give no access at all
@@ -151,23 +171,27 @@ If you want to express that a user has no access at all, make the respective pow
151
171
 
152
172
  Note how the power in the example below returns `nil` unless the user is an admin:
153
173
 
154
- class Power
155
- ...
174
+ ```rb
175
+ class Power
176
+ ...
156
177
 
157
- power :users do
158
- User if @user.admin?
159
- end
178
+ power :users do
179
+ User if @user.admin?
180
+ end
160
181
 
161
- end
182
+ end
183
+ ```
162
184
 
163
185
  When a non-admin queries the `:users` power, she will get the following behavior:
164
186
 
165
- power = Power.new(@user)
166
- power.users # => returns nil
167
- power.users? # => returns false
168
- power.users! # => raises Consul::Powerless
169
- power.user?(User.last) # => returns false
170
- power.user!(User.last) # => raises Consul::Powerless
187
+ ```rb
188
+ power = Power.new(@user)
189
+ power.users # => returns nil
190
+ power.users? # => returns false
191
+ power.users! # => raises Consul::Powerless
192
+ power.user?(User.last) # => returns false
193
+ power.user!(User.last) # => raises Consul::Powerless
194
+ ```
171
195
 
172
196
 
173
197
 
@@ -179,97 +203,113 @@ Sometimes you only want to store a method that checks whether a given object is
179
203
  To do so, simply define a power that ends in a question mark:
180
204
 
181
205
 
182
- class Power
183
- ...
206
+ ```rb
207
+ class Power
208
+ ...
184
209
 
185
- power :updatable_post? do |post|
186
- post.author == @user
187
- end
210
+ power :updatable_post? do |post|
211
+ post.author == @user
212
+ end
188
213
 
189
- end
214
+ end
215
+ ```
190
216
 
191
217
  You can query such an power as always:
192
218
 
193
- power = Power.new(@user)
194
- power.updatable_post?(Post.last) # return true if the author of the post is @user
195
- power.updatable_post!(Post.last) # raises Consul::Powerless unless the author of the post is @user
219
+ ```rb
220
+ power = Power.new(@user)
221
+ power.updatable_post?(Post.last) # return true if the author of the post is @user
222
+ power.updatable_post!(Post.last) # raises Consul::Powerless unless the author of the post is @user
223
+ ```
196
224
 
197
225
 
198
226
  ### Other types of powers
199
227
 
200
228
  A power can return any type of object. For instance, you often want to return an array:
201
229
 
202
- class Power
203
- ...
204
-
205
- power :assignable_note_states do
206
- if admin?
207
- %w[draft pending published retracted]
208
- else
209
- %w[draft pending]
210
- end
211
- end
230
+ ```rb
231
+ class Power
232
+ ...
212
233
 
234
+ power :assignable_note_states do
235
+ if admin?
236
+ %w[draft pending published retracted]
237
+ else
238
+ %w[draft pending]
213
239
  end
240
+ end
241
+
242
+ end
243
+ ```
214
244
 
215
245
  You can query it like any other power. E.g. if a non-admin queries this power she will get the following behavior:
216
246
 
217
- power.assignable_note_states # => ['draft', 'pending']
218
- power.assignable_note_states? # => returns true
219
- power.assignable_note_states! # => does nothing (because the power isn't nil)
220
- power.assignable_note_state?('draft') # => returns true
221
- power.assignable_note_state?('published') # => returns false
222
- power.assignable_note_state!('published') # => raises Consul::Powerless
247
+ ```rb
248
+ power.assignable_note_states # => ['draft', 'pending']
249
+ power.assignable_note_states? # => returns true
250
+ power.assignable_note_states! # => does nothing (because the power isn't nil)
251
+ power.assignable_note_state?('draft') # => returns true
252
+ power.assignable_note_state?('published') # => returns false
253
+ power.assignable_note_state!('published') # => raises Consul::Powerless
254
+ ```
223
255
 
224
256
 
225
257
  ### Defining multiple powers at once
226
258
 
227
259
  You can define multiple powers at once by giving multiple power names:
228
260
 
229
- class Power
230
- ...
261
+ ```rb
262
+ class Power
263
+ ...
231
264
 
232
- power :destroyable_users, :updatable_users do
233
- User if admin?
234
- end
265
+ power :destroyable_users, :updatable_users do
266
+ User if admin?
267
+ end
235
268
 
236
- end
269
+ end
270
+ ```
237
271
 
238
272
 
239
273
  ### Powers that require context (arguments)
240
274
 
241
275
  Sometimes it can be useful to define powers that require context. To do so, just take an argument in your `power` block:
242
276
 
243
- class Power
244
- ...
277
+ ```rb
278
+ class Power
279
+ ...
245
280
 
246
- power :client_notes do |client|
247
- client.notes.where(:state => 'published')
248
- end
249
-
250
- end
281
+ power :client_notes do |client|
282
+ client.notes.where(:state => 'published')
283
+ end
284
+
285
+ end
286
+ ```
251
287
 
252
288
  When querying such a power, you always need to provide the context, e.g.:
253
289
 
254
- client = ...
255
- note = ...
256
- Power.current.client_note?(client, note)
290
+ ```rb
291
+ client = ...
292
+ note = ...
293
+ Power.current.client_note?(client, note)
294
+ ```
257
295
 
258
296
 
259
297
  ### Optimizing record checks for scope powers
260
298
 
261
299
  You can query a scope power for a given record, e.g.
262
300
 
263
- class Power
264
- ...
301
+ ```rb
302
+ class Power
303
+ ...
265
304
 
266
- power :posts do |post|
267
- Post.where(:author_id => @user.id)
268
- end
269
- end
305
+ power :posts do |post|
306
+ Post.where(:author_id => @user.id)
307
+ end
308
+ end
270
309
 
271
- power = Power.new(@user)
272
- power.post?(Post.last)
310
+ power = Power.new(@user)
311
+ power.post?(Post.last)
312
+ ```
273
313
 
274
314
  What Consul does internally is fetch **all** the IDs of the `power.posts` scope and test if the given
275
315
  record's ID is among them. This list of IDs is cached for subsequent calls, so you will only touch the database once.
@@ -279,18 +319,20 @@ however be the point where you want to optimize this.
279
319
 
280
320
  What you can do in Consul is to define a second power that checks a given record in plain Ruby:
281
321
 
282
- class Power
283
- ...
322
+ ```rb
323
+ class Power
324
+ ...
284
325
 
285
- power :posts do |post|
286
- Post.where(:author_id => @user.id)
287
- end
326
+ power :posts do |post|
327
+ Post.where(:author_id => @user.id)
328
+ end
288
329
 
289
- power :post? do |post|
290
- post.author_id == @user.id
291
- end
330
+ power :post? do |post|
331
+ post.author_id == @user.id
332
+ end
292
333
 
293
- end
334
+ end
335
+ ```
294
336
 
295
337
  This way you do not need to touch the database at all.
296
338
 
@@ -300,27 +342,29 @@ Role-based permissions
300
342
 
301
343
  Consul has no built-in support for role-based permissions, but you can easily implement it yourself. Let's say your `User` model has a string column `role` which can be `"author"` or `"admin"`:
302
344
 
303
- class Power
304
- include Consul::Power
345
+ ```rb
346
+ class Power
347
+ include Consul::Power
305
348
 
306
- def initialize(user)
307
- @user = user
308
- end
349
+ def initialize(user)
350
+ @user = user
351
+ end
309
352
 
310
- power :notes do
311
- case role
312
- when :admin then Note
313
- when :author then Note.by_author
314
- end
315
- end
353
+ power :notes do
354
+ case role
355
+ when :admin then Note
356
+ when :author then Note.by_author
357
+ end
358
+ end
316
359
 
317
- private
360
+ private
318
361
 
319
- def role
320
- @user.role.to_sym
321
- end
362
+ def role
363
+ @user.role.to_sym
364
+ end
322
365
 
323
- end
366
+ end
367
+ ```
324
368
 
325
369
 
326
370
  Controller integration
@@ -328,76 +372,90 @@ Controller integration
328
372
 
329
373
  It is convenient to expose the power for the current request to the rest of the application. Consul will help you with that if you tell it how to instantiate a power for the current request:
330
374
 
331
- class ApplicationController < ActionController::Base
332
- include Consul::Controller
375
+ ```rb
376
+ class ApplicationController < ActionController::Base
377
+ include Consul::Controller
333
378
 
334
- current_power do
335
- Power.new(current_user)
336
- end
379
+ current_power do
380
+ Power.new(current_user)
381
+ end
337
382
 
338
- end
383
+ end
384
+ ```
339
385
 
340
386
  You now have a helper method `current_power` for your controller and views. Everywhere else, you can access it from `Power.current`. The power will be instantiated when the request is handed over from routing to `ApplicationController`, and will be nilified once the request was processed.
341
387
 
342
388
  You can now use power scopes to control access:
343
389
 
344
- class NotesController < ApplicationController
390
+ ```rb
391
+ class NotesController < ApplicationController
345
392
 
346
- def show
347
- @note = current_power.notes.find(params[:id])
348
- end
393
+ def show
394
+ @note = current_power.notes.find(params[:id])
395
+ end
349
396
 
350
- end
397
+ end
398
+ ```
351
399
 
352
400
 
353
401
  ### Protect entry into controller actions
354
402
 
355
403
  To make sure a power is given before every action in a controller:
356
404
 
357
- class NotesController < ApplicationController
358
- power :notes
359
- end
405
+ ```rb
406
+ class NotesController < ApplicationController
407
+ power :notes
408
+ end
409
+ ```
360
410
 
361
411
  You can use `:except` and `:only` options like in before filters.
362
412
 
363
413
  You can also map different powers to different actions:
364
414
 
365
- class NotesController < ApplicationController
366
- power :notes, :map => { [:edit, :update, :destroy] => :changeable_notes }
367
- end
415
+ ```rb
416
+ class NotesController < ApplicationController
417
+ power :notes, :map => { [:edit, :update, :destroy] => :changeable_notes }
418
+ end
419
+ ```
368
420
 
369
421
  Actions that are not listed in `:map` will get the default action `:notes`.
370
422
 
371
423
  Note that in moderately complex authorization scenarios you will often find yourself writing a map like this:
372
424
 
373
- class NotesController < ApplicationController
374
- power :notes, :map => {
375
- [:edit, :update] => :updatable_notes,
376
- [:new, :create] => :creatable_notes,
377
- [:destroy] => :destroyable_notes
378
- }
379
- end
425
+ ```rb
426
+ class NotesController < ApplicationController
427
+ power :notes, :map => {
428
+ [:edit, :update] => :updatable_notes,
429
+ [:new, :create] => :creatable_notes,
430
+ [:destroy] => :destroyable_notes
431
+ }
432
+ end
433
+ ```
380
434
 
381
435
  Because this pattern is so common, there is a shortcut `:crud` to do the same:
382
436
 
383
- class NotesController < ApplicationController
384
- power :crud => :notes
385
- end
437
+ ```rb
438
+ class NotesController < ApplicationController
439
+ power :crud => :notes
440
+ end
441
+ ```
386
442
 
387
443
 
388
444
  And if your power [requires context](#powers-that-require-context-arguments) (is parametrized), you can give it using the `:context` method:
389
445
 
390
- class ClientNotesController < ApplicationController
446
+ ```rb
447
+ class ClientNotesController < ApplicationController
391
448
 
392
- power :client_notes, :context => :load_client
449
+ power :client_notes, :context => :load_client
393
450
 
394
- private
451
+ private
395
452
 
396
- def load_client
397
- @client ||= Client.find(params[:client_id])
398
- end
453
+ def load_client
454
+ @client ||= Client.find(params[:client_id])
455
+ end
399
456
 
400
- end
457
+ end
458
+ ```
401
459
 
402
460
 
403
461
 
@@ -405,15 +463,17 @@ And if your power [requires context](#powers-that-require-context-arguments) (is
405
463
 
406
464
  It is often convenient to map a power scope to a private controller method:
407
465
 
408
- class NotesController < ApplicationController
466
+ ```rb
467
+ class NotesController < ApplicationController
409
468
 
410
- power :notes, :as => note_scope
469
+ power :notes, :as => note_scope
411
470
 
412
- def show
413
- @note = note_scope.find(params[:id])
414
- end
471
+ def show
472
+ @note = note_scope.find(params[:id])
473
+ end
415
474
 
416
- end
475
+ end
476
+ ```
417
477
 
418
478
  This is especially useful when you are using a RESTful controller library like [resource_controller](https://github.com/jamesgolick/resource_controller). The mapped method is aware of the `:map` option.
419
479
 
@@ -425,47 +485,53 @@ checks and method mappings: One for the parent resource, another for the child r
425
485
 
426
486
  Say you have the following routes:
427
487
 
428
- resources :clients do
429
- resources :notes
430
- end
488
+ ```rb
489
+ resources :clients do
490
+ resources :notes
491
+ end
492
+ ```
431
493
 
432
494
  And the following power definitions:
433
495
 
434
- class Power
435
- ...
496
+ ```rb
497
+ class Power
498
+ ...
436
499
 
437
- power :clients do |client|
438
- Client.active if signed_in?
439
- end
500
+ power :clients do |client|
501
+ Client.active if signed_in?
502
+ end
440
503
 
441
- power :client_notes do |client|
442
- client.notes.where(:state => 'published')
443
- end
504
+ power :client_notes do |client|
505
+ client.notes.where(:state => 'published')
506
+ end
444
507
 
445
- end
508
+ end
509
+ ```
446
510
 
447
511
  You can now check and map both powers in the nested `NotesController`:
448
512
 
449
- class NotesController < ApplicationController
513
+ ```rb
514
+ class NotesController < ApplicationController
450
515
 
451
- power :clients, :as => :client_scope
452
- power :client_notes, :context => :load_client, :as => :note_scope
516
+ power :clients, :as => :client_scope
517
+ power :client_notes, :context => :load_client, :as => :note_scope
453
518
 
454
- def show
455
- load_note
456
- end
519
+ def show
520
+ load_note
521
+ end
457
522
 
458
- private
523
+ private
459
524
 
460
- def load_client
461
- @client ||= client_scope.find(params[:client_id])
462
- end
525
+ def load_client
526
+ @client ||= client_scope.find(params[:client_id])
527
+ end
463
528
 
464
- def load_note
465
- @note ||= note_scope.find(params[:id])
466
- end
529
+ def load_note
530
+ @note ||= note_scope.find(params[:id])
531
+ end
467
532
 
468
- end
533
+ end
534
+ ```
469
535
 
470
536
  Note how we provide the `Client` parameter for the `:client_notes` power by using the `:context => :load_client`
471
537
  option in the `power` directive.
@@ -474,16 +540,20 @@ option in the `power` directive.
474
540
 
475
541
  You can force yourself to use a `power` check in every controller. This will raise `Consul::UncheckedPower` if you ever forget it:
476
542
 
477
- class ApplicationController < ActionController::Base
478
- include Consul::Controller
479
- require_power_check
480
- end
543
+ ```rb
544
+ class ApplicationController < ActionController::Base
545
+ include Consul::Controller
546
+ require_power_check
547
+ end
548
+ ```
481
549
 
482
550
  Should you for some obscure reason want to forego the power check:
483
551
 
484
- class ApiController < ApplicationController
485
- skip_power_check
486
- end
552
+ ```rb
553
+ class ApiController < ApplicationController
554
+ skip_power_check
555
+ end
556
+ ```
487
557
 
488
558
 
489
559
  Validating assignable values
@@ -495,59 +565,68 @@ Consul leverages the [assignable_values](https://github.com/makandra/assignable_
495
565
 
496
566
  You can enable the authorization layer by using the macro `authorize_values_for`:
497
567
 
498
- class Story < ActiveRecord::Base
499
- authorize_values_for :state
500
- endy
568
+ ```rb
569
+ class Story < ActiveRecord::Base
570
+ authorize_values_for :state
571
+ end
572
+ ```
501
573
 
502
574
  The macro defines an accessor `power` on instances of `Story`. If that field is set to a power, the values of `state` will be validated against a whitelist of values provided by that power. If that field is `nil`, the validation is skipped.
503
575
 
504
576
  Here is a power implementation that can provide a list of assignable values for the example above:
505
577
 
506
- class Power
507
- ...
508
-
509
- def assignable_story_states(story)
510
- if admin?
511
- ['delivered', 'accepted', 'rejected']
512
- else
513
- ['delivered']
514
- end
515
- end
578
+ ```rb
579
+ class Power
580
+ ...
516
581
 
582
+ def assignable_story_states(story)
583
+ if admin?
584
+ ['delivered', 'accepted', 'rejected']
585
+ else
586
+ ['delivered']
517
587
  end
588
+ end
589
+
590
+ end
591
+ ```
518
592
 
519
593
  Here you can see how to activate the authorization layer and use the new validations:
520
594
 
521
- story = Story.new
522
- Power.current = Power.new(:role => :guest) # activate the authorization layer
595
+ ```rb
596
+ story = Story.new
597
+ Power.current = Power.new(:role => :guest) # activate the authorization layer
523
598
 
524
- story.assignable_states # ['delivered'] # apparently we're not admins
599
+ story.assignable_states # ['delivered'] # apparently we're not admins
525
600
 
526
- story.state = 'accepted' # a disallowed value
527
- story.valid? # => false
601
+ story.state = 'accepted' # a disallowed value
602
+ story.valid? # => false
528
603
 
529
- story.state = 'delivered' # an allowed value
530
- story.valid? # => true
604
+ story.state = 'delivered' # an allowed value
605
+ story.valid? # => true
606
+ ```
531
607
 
532
608
  You can not only authorize scalar attributes like strings or integers that way, you can also authorize `belongs_to` associations:
533
609
 
534
- class Story < ActiveRecord::Base
535
- belongs_to :project
536
- authorize_values_for :project
537
- end
610
+ ```rb
611
+ class Story < ActiveRecord::Base
612
+ belongs_to :project
613
+ authorize_values_for :project
614
+ end
538
615
 
539
- class Power
540
- ...
616
+ class Power
617
+ ...
541
618
 
542
- power :assignable_story_projects do |story|
543
- user.account.projects
544
- end
545
- end
619
+ power :assignable_story_projects do |story|
620
+ user.account.projects
621
+ end
622
+ end
623
+ ```
546
624
 
547
625
  The `authorize_values_for` macro comes with many useful options and details best explained in the [assignable_values README](https://github.com/makandra/assignable_values), so head over there for more. The macro is basically a shortcut for this:
548
626
 
549
- assignable_values_for :field, :through => lambda { Power.current }
550
-
627
+ ```rb
628
+ assignable_values_for :field, :through => lambda { Power.current }
629
+ ```
551
630
 
552
631
  Dynamic power access
553
632
  --------------------
@@ -556,15 +635,17 @@ Consul gives you a way to dynamically access and query powers for a given name,
556
635
  A common use case for this are generic helper methods, e.g. a method to display an "edit" link for any given record
557
636
  if the user is authorized to change that record:
558
637
 
559
- module CrudHelper
560
-
561
- def edit_record_action(record)
562
- if current_power.include_record?(:updatable, record)
563
- link_to 'Edit', [:edit, record]
564
- end
565
- end
638
+ ```rb
639
+ module CrudHelper
566
640
 
641
+ def edit_record_action(record)
642
+ if current_power.include_record?(:updatable, record)
643
+ link_to 'Edit', [:edit, record]
567
644
  end
645
+ end
646
+
647
+ end
648
+ ```
568
649
 
569
650
  You can find a full list of available dynamic calls below:
570
651
 
@@ -599,43 +680,49 @@ Querying a power that might be nil
599
680
 
600
681
  You will often want to access `Power.current` from another model, to e.g. iterate through the list of accessible users:
601
682
 
602
- class UserReport
603
-
604
- def data
605
- Power.current.users.collect do |user|
606
- [user.name, user.email, user.income]
607
- end
608
- end
683
+ ```rb
684
+ class UserReport
609
685
 
686
+ def data
687
+ Power.current.users.collect do |user|
688
+ [user.name, user.email, user.income]
610
689
  end
690
+ end
691
+
692
+ end
693
+ ```
611
694
 
612
695
  Good practice is for your model to not crash when `Power.current` is `nil`. This is the case when your model isn't
613
696
  called as part of processing a browser request, e.g. on the console, during tests and during batch processes.
614
697
  In such cases your model should simply skip authorization and assume that all users are accessible:
615
698
 
616
- class UserReport
617
-
618
- def data
619
- accessible_users = Power.current.present? ? Power.current.users : User
620
- accessible_users.collect do |user|
621
- [user.name, user.email, user.income]
622
- end
623
- end
699
+ ```rb
700
+ class UserReport
624
701
 
702
+ def data
703
+ accessible_users = Power.current.present? ? Power.current.users : User
704
+ accessible_users.collect do |user|
705
+ [user.name, user.email, user.income]
625
706
  end
707
+ end
708
+
709
+ end
710
+ ```
626
711
 
627
712
  Because this pattern is so common, the `Power` class comes with a number of class methods you can use to either query
628
713
  `Power.current` or, if it is not set, just assume that everything is accessible:
629
714
 
630
- class UserReport
631
-
632
- def data
633
- Power.for_model(User).collect do |user|
634
- [user.name, user.email, user.income]
635
- end
636
- end
715
+ ```rb
716
+ class UserReport
637
717
 
718
+ def data
719
+ Power.for_model(User).collect do |user|
720
+ [user.name, user.email, user.income]
638
721
  end
722
+ end
723
+
724
+ end
725
+ ```
639
726
 
640
727
  There is a long selection of class methods that behave neutrally in case `Power.current` is `nil`:
641
728
 
@@ -665,19 +752,23 @@ This section Some hints for testing authorization with Consul.
665
752
 
666
753
  You can say this in any controller spec:
667
754
 
668
- describe CakesController do
755
+ ```rb
756
+ describe CakesController do
669
757
 
670
- it { should check_power(:cakes) }
758
+ it { should check_power(:cakes) }
671
759
 
672
- end
760
+ end
761
+ ```
673
762
 
674
763
  You can test against all options of the `power` macro:
675
764
 
676
- describe CakesController do
765
+ ```rb
766
+ describe CakesController do
677
767
 
678
- it { should check_power(:cakes, :map => { [:edit, :update] => :updatable_cakes }) }
768
+ it { should check_power(:cakes, :map => { [:edit, :update] => :updatable_cakes }) }
679
769
 
680
- end
770
+ end
771
+ ```
681
772
 
682
773
  ### Temporarily change the current power
683
774
 
@@ -685,28 +776,34 @@ When you set `Power.current` to a power in an RSpec example, you must remember t
685
776
 
686
777
  A better way is to use the `.with_power` method to change the current power for the duration of a block:
687
778
 
688
- admin = User.new(:role => 'admin')
689
- admin_power = Power.new(admin)
779
+ ```rb
780
+ admin = User.new(:role => 'admin')
781
+ admin_power = Power.new(admin)
690
782
 
691
- Power.with_power(admin_power) do
692
- # run code that uses Power.current
693
- end
783
+ Power.with_power(admin_power) do
784
+ # run code that uses Power.current
785
+ end
786
+ ```
694
787
 
695
788
  `Power.current` will be `nil` (or its former value) after the block has ended.
696
789
 
697
790
  A nice shortcut is that when you call `with_power` with an argument that is not already a `Power`, Consul will instantiate a `Power` for you:
698
791
 
699
- admin = User.new(:role => 'admin')
792
+ ```rb
793
+ admin = User.new(:role => 'admin')
700
794
 
701
- Power.with_power(admin) do
702
- # run code that uses Power.current
703
- end
795
+ Power.with_power(admin) do
796
+ # run code that uses Power.current
797
+ end
798
+ ```
704
799
 
705
800
  There is also a method `.without_power` that runs a block without a current Power:
706
801
 
707
- Power.without_power do
708
- # run code that should not see a Power
709
- end
802
+ ```rb
803
+ Power.without_power do
804
+ # run code that should not see a Power
805
+ end
806
+ ```
710
807
 
711
808
 
712
809
  Installation
@@ -714,7 +811,9 @@ Installation
714
811
 
715
812
  Add the following to your `Gemfile`:
716
813
 
717
- gem 'consul'
814
+ ```
815
+ gem 'consul'
816
+ ```
718
817
 
719
818
  Now run `bundle install` to lock the gem into your project.
720
819
 
@@ -724,7 +823,9 @@ Development
724
823
 
725
824
  Test applications for various Rails versions lives in `spec`. You can run specs from the project root by saying:
726
825
 
727
- bundle exec rake all:spec
826
+ ```
827
+ bundle exec rake all:spec
828
+ ```
728
829
 
729
830
  If you would like to contribute:
730
831