Class: Credentials::Rule
- Inherits:
-
Object
- Object
- Credentials::Rule
- Defined in:
- lib/credentials/rule.rb
Overview
Specifies an individual ‘line’ in a Rulebook. Trivially subclassed as Credentials::Rule::AllowRule and Credentials::Rule::DenyRule, but this is just to allow you to create your own, more complex rule types.
Instance Attribute Summary collapse
-
#options ⇒ Object
Returns the value of attribute options.
-
#parameters ⇒ Object
Returns the value of attribute parameters.
Instance Method Summary collapse
-
#arity ⇒ Object
Returns the number of arguments expected in a test to this rule.
-
#evaluate_condition(conditions, op, *args) ⇒ Object
Evaluates an
if
orunless
condition. - #evaluate_preposition(object, expected, actual) ⇒ Object
-
#initialize(*args) ⇒ Rule
constructor
A new instance of Rule.
-
#match?(*args) ⇒ Boolean
Returns
true
if the given arguments match this rule.
Constructor Details
#initialize(*args) ⇒ Rule
Returns a new instance of Rule.
10 11 12 13 |
# File 'lib/credentials/rule.rb', line 10 def initialize(*args) self. = args.last.is_a?(Hash) ? args.pop : {} self.parameters = args end |
Instance Attribute Details
#options ⇒ Object
Returns the value of attribute options.
8 9 10 |
# File 'lib/credentials/rule.rb', line 8 def @options end |
#parameters ⇒ Object
Returns the value of attribute parameters.
7 8 9 |
# File 'lib/credentials/rule.rb', line 7 def parameters @parameters end |
Instance Method Details
#arity ⇒ Object
Returns the number of arguments expected in a test to this rule. This is really basic, but allows a quick first-pass filtering of the rules.
18 19 20 |
# File 'lib/credentials/rule.rb', line 18 def arity parameters.length end |
#evaluate_condition(conditions, op, *args) ⇒ Object
Evaluates an if
or unless
condition.
conditions
-
One or more conditions to evaluate. Can be symbols or procs.
op
-
Operator used to combine the results (+|+ for
if
, & forunless
). args
-
List of arguments to test with/against
75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 |
# File 'lib/credentials/rule.rb', line 75 def evaluate_condition(conditions, op, *args) receiver = args.shift args.reject! { |arg| arg.is_a? Symbol } Array(conditions).inject(op == :| ? false : true) do |memo, condition| memo = memo.send op, case condition when Symbol return false unless receiver.respond_to? condition !!receiver.send(condition, *args[0, receiver.method(condition).arity]) when Proc raise ArgumentError, "wrong number of arguments to condition (#{args.size + 1} to #{condition.arity})" unless args.size + 1 == condition.arity !!condition.call(receiver, *args) else raise ArgumentError, "invalid :if or :unless option (expected Symbol or Proc, or array thereof; got #{condition.class})" end end end |
#evaluate_preposition(object, expected, actual) ⇒ Object
92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 |
# File 'lib/credentials/rule.rb', line 92 def evaluate_preposition(object, expected, actual) return true if expected.nil? return false if actual.nil? return true if expected === actual single = expected.to_s plural = single.respond_to?(:pluralize) ? single.pluralize : single + "s" lclass, rclass = [ object.class.name, actual.class.name ].map do |s| s.gsub(/^.*::/, ''). gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2'). gsub(/([a-z\d])([A-Z])/,'\1_\2'). tr("-", "_"). downcase end if object.respond_to?(:id) && actual.respond_to?(:"#{lclass}_id") return true if actual.send(:"#{lclass}_id").to_i == object.id.to_i end if actual.respond_to?(:id) && object.respond_to?(:"#{rclass}_id") return true if object.send(:"#{rclass}_id").to_i == actual.id.to_i end (object.respond_to?(single) && (object.send(single) == actual)) || (object.respond_to?(plural) && (object.send(plural).include?(actual))) end |
#match?(*args) ⇒ Boolean
Returns true
if the given arguments match this rule. Rules mostly use the same matching criteria as Ruby’s case
statement: that is, the ===
operator. Remember:
User === User.new # a class matches an instance
/\w+/ === "abc" # a RegExp matches a valid string
(1..5) === 3 # a Range matches a number
:foo === :foo # anything matches itself
There are two exceptions to this behaviour. Firstly, if the rule specifies an array, then the argument will match any element of that array:
class User
credentials do |user|
user.can [ :punch, :fight ], [ :shatner, :gandhi ]
end
end
user.can? :fight, :gandhi # => true
Secondly, specifying :self
in a rule is a nice shorthand for specifying an object’s permissions on itself:
class User
credentials do |user|
user.can :fight, :self # SPOILER ALERT
end
end
49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
# File 'lib/credentials/rule.rb', line 49 def match?(*args) values = args.last.is_a?(Hash) ? args.pop : {} return false unless arity == args.length parameters.zip(args).each do |expected, actual| case expected when :self then return false unless actual == args.first when Array then return false unless expected.any? { |item| (item === actual) || (item == :self && actual == args.first) } else return false unless expected == actual || expected === actual || (expected.is_a?(Class) && actual.is_a?(Class) && actual.ancestors.include?(expected)) end end result = true result = result && (.keys & Credentials::Prepositions).inject(true) { |memo, key| memo && evaluate_preposition(args.first, [key], values[key]) } result = result && evaluate_condition([:if], :|, *args) unless [:if].nil? result = result && !evaluate_condition([:unless], :&, *args) unless [:unless].nil? result end |