Class: Credentials::Rulebook
- Inherits:
-
Object
- Object
- Credentials::Rulebook
- Defined in:
- lib/credentials/rulebook.rb
Overview
Represents a collection of rules for granting and denying permissions to instances of a class. This is the return type of a call to a class’s credentials
method.
Constant Summary collapse
- DEFAULT_OPTIONS =
{ :default => :deny }.freeze
Instance Attribute Summary collapse
-
#klass ⇒ Object
Returns the value of attribute klass.
-
#options ⇒ Object
Returns the value of attribute options.
-
#rules ⇒ Object
Returns the value of attribute rules.
-
#superklass_rulebook ⇒ Object
readonly
Returns the value of attribute superklass_rulebook.
Class Method Summary collapse
-
.for(klass) ⇒ Object
Creates a Rulebook for the given class.
Instance Method Summary collapse
-
#allow?(*args) ⇒ Boolean
Decides whether to allow the requested permission.
-
#allow_rules ⇒ Object
Subset of rules that grant permission by exposing an
allow?
method. -
#can(*args) ⇒ Object
Declaratively specify a permission.
-
#cannot(*args) ⇒ Object
Declaratively remove a permission.
-
#default ⇒ Object
Determines whether to
:allow
or:deny
by default. -
#deny_rules ⇒ Object
Subset of rules that deny permission by exposing an
deny?
method. -
#empty? ⇒ Boolean
Returns
true
if there are no rules defined in this Rulebook. -
#initialize(klass) ⇒ Rulebook
constructor
A new instance of Rulebook.
Constructor Details
#initialize(klass) ⇒ Rulebook
Returns a new instance of Rulebook.
20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
# File 'lib/credentials/rulebook.rb', line 20 def initialize(klass) if klass.to_s =~ /^#<Class:#<([\w_]+(?:\:\:[\w_]+)*)/ # there must be a better way self.klass = superklass = $1.constantize else self.klass = klass superklass = klass.superclass end @rules = [] if superklass == Object @superklass_rulebook = nil @options = {} else @superklass_rulebook = superklass.credentials @options = superklass_rulebook..dup end end |
Instance Attribute Details
#klass ⇒ Object
Returns the value of attribute klass.
11 12 13 |
# File 'lib/credentials/rulebook.rb', line 11 def klass @klass end |
#options ⇒ Object
Returns the value of attribute options.
12 13 14 |
# File 'lib/credentials/rulebook.rb', line 12 def @options end |
#rules ⇒ Object
Returns the value of attribute rules.
13 14 15 |
# File 'lib/credentials/rulebook.rb', line 13 def rules @rules end |
#superklass_rulebook ⇒ Object (readonly)
Returns the value of attribute superklass_rulebook.
14 15 16 |
# File 'lib/credentials/rulebook.rb', line 14 def superklass_rulebook @superklass_rulebook end |
Class Method Details
.for(klass) ⇒ Object
Creates a Rulebook for the given class. Should not be called directly: instead, use class.credentials
(q.v.).
41 42 43 44 |
# File 'lib/credentials/rulebook.rb', line 41 def self.for(klass) @rulebooks ||= {} @rulebooks[klass] ||= new(klass) end |
Instance Method Details
#allow?(*args) ⇒ Boolean
Decides whether to allow the requested permission.
Match algorithm
-
Set
ALLOWED
to true if permission is specifically allowed by any allow_rules; otherwise, false. -
Set
DENIED
to true if permission is specifically denied by any deny_rules; otherwise, false. -
The final result depends on the value of
default
:-
if
:allow
:ALLOWED OR !DENIED
-
if
:deny
:ALLOWED AND !DENIED
-
Expressed as a table: <table> <thead><tr><th>Matching rules</th><th>Default allow</th><th>Default deny</th></tr></thead> <tbody> <tr><td>None of the allow
or deny
rules matched.</td><td>allow</td><td>deny</td></tr> <tr><td>Some of the allow
rules matched, none of the deny
rules matched.</td><td>allow</td><td>allow</td></tr> <tr><td>None of the allow
rules matched, some of the deny
rules matched.</td><td>deny</td><td>deny</td></tr> <tr><td>Some of the allow
rules matched, some of the deny
rules matched.</td><td>allow</td><td>deny</td></tr> </tbody> </table>
195 196 197 198 199 200 201 202 203 204 |
# File 'lib/credentials/rulebook.rb', line 195 def allow?(*args) allowed = allow_rules.inject(false) { |memo, rule| memo || rule.allow?(*args) } denied = deny_rules.inject(false) { |memo, rule| memo || rule.deny?(*args) } if default == :allow allowed or !denied else allowed and !denied end end |
#allow_rules ⇒ Object
Subset of rules that grant permission by exposing an allow?
method.
207 208 209 210 |
# File 'lib/credentials/rulebook.rb', line 207 def allow_rules @allow_rules ||= (superklass_rulebook ? superklass_rulebook.allow_rules : []) + rules.select { |rule| rule.respond_to? :allow? } end |
#can(*args) ⇒ Object
Declaratively specify a permission. This is usually done in the context of a credentials
block (see Credentials::ObjectExtensions::ClassMethods#credentials). The examples below assume that context.
Simple (intransitive) permissions
class User
credentials do |user|
user.can :log_in
end
end
Permission is expressed as a symbol; usually an intransitive verb. Permission can be tested with:
user.can? :log_in
or
user.can_log_in?
Resource (transitive) permissions
class User
credentials do |user|
user.can :edit, Post
end
end
As above, but a resource type is specified. Permission can be tested with:
user.can? :edit, Post.first
or
user.can_edit? Post.first
if
and unless
You can specify complex conditions with the if
and unless
options. These options can be either a symbol (which is assumed to be a method of the instance under test), or a proc, which is passed any non-symbol arguments from the can?
method.
class User
credentials do |user|
user.can :create, Post, :if => :administrator?
user.can :edit, Post, :if => lambda { |user, post| user == post. }
end
end
user.can? :create, Post # checks user.administrator?
user.can? :edit, post # checks user == post.author
Both if
and unless
options can be specified for the same rule:
class User
credentials do |user|
user.can :eat, "chunky bacon", :if => :hungry?, :unless => :vegetarian?
end
end
So, only hungry users who are not vegetarian can eat chunky bacon.
You can also specify multiple options for if
and unless
. If there are multiple options for if
, any one match will do:
class User
credentials do |user|
user.can :go_backstage, :if => [ :crew?, :good_looking? ]
end
end
However, multiple options for unless
must all match:
class User
credentials(:default => :allow) do |user|
user.cannot :record, Album, :unless => [ :talented?, :dedicated? ]
end
end
You cannot record an album unless you are both talented and dedicated. Note that we have specified the default permission as allow
in this example: otherwise, the rule would never match.
If your rules are any more complicated than that, you might want to consider using the lambda form of arguments to if
and/or unless
.
Reflexive permissions (:self
)
The following two permissions are identical:
class User
credentials do |user|
user.can :edit, User, :if => lambda { |user, another| user == another }
user.can :edit, :self
end
end
Prepositions (:for
, :on
, etc)
You can do the following:
class User
credentials do |user|
user.can :delete, Comment, :on => :post
end
end
user.can? :delete, post.comments.first, :on => post
…means that Credentials will check if:
-
post
has auser_id
method matchinguser.id
-
user
has apost_id
method matchingpost.id
-
user
has apost
method matchingpost
-
user
has aposts
method that returns an array includingpost
See Credentials::Prepositions for the list of available prepositions.
149 150 151 |
# File 'lib/credentials/rulebook.rb', line 149 def can(*args) self.rules << AllowRule.new(klass, *args) end |
#cannot(*args) ⇒ Object
Declaratively remove a permission. This is handy to explicitly remove a permission in a child class that you have granted in a parent class. It is also useful if your default is set to allow
and you want to tighten up some permissions:
class User
credentials(:default => :allow) do |user|
user.cannot :delete, :self
end
end
See Credentials::Rulebook#can for more on specifying permissions: just remember that everything is backwards!
165 166 167 |
# File 'lib/credentials/rulebook.rb', line 165 def cannot(*args) self.rules << DenyRule.new(klass, *args) end |
#default ⇒ Object
Determines whether to :allow
or :deny
by default.
170 171 172 |
# File 'lib/credentials/rulebook.rb', line 170 def default [:default] && [:default].to_sym end |
#deny_rules ⇒ Object
Subset of rules that deny permission by exposing an deny?
method.
213 214 215 216 |
# File 'lib/credentials/rulebook.rb', line 213 def deny_rules @deny_rules ||= (superklass_rulebook ? superklass_rulebook.deny_rules : []) + rules.select { |rule| rule.respond_to? :deny? } end |
#empty? ⇒ Boolean
Returns true
if there are no rules defined in this Rulebook.
47 48 49 |
# File 'lib/credentials/rulebook.rb', line 47 def empty? rules.empty? end |