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
:allowor:denyby default. -
#deny_rules ⇒ Object
Subset of rules that deny permission by exposing an
deny?method. -
#empty? ⇒ Boolean
Returns
trueif 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 = {} else @superklass_rulebook = superklass.credentials = 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 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
ALLOWEDto true if permission is specifically allowed by any allow_rules; otherwise, false. -
Set
DENIEDto 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:
-
posthas auser_idmethod matchinguser.id -
userhas apost_idmethod matchingpost.id -
userhas apostmethod matchingpost -
userhas apostsmethod 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 |