diff options
Diffstat (limited to 'lib/declarative_policy/rule.rb')
-rw-r--r-- | lib/declarative_policy/rule.rb | 312 |
1 files changed, 0 insertions, 312 deletions
diff --git a/lib/declarative_policy/rule.rb b/lib/declarative_policy/rule.rb deleted file mode 100644 index 964d35cde9e..00000000000 --- a/lib/declarative_policy/rule.rb +++ /dev/null @@ -1,312 +0,0 @@ -# frozen_string_literal: true - -module DeclarativePolicy - module Rule - # A Rule is the object that results from the `rule` declaration, - # usually built using the DSL in `RuleDsl`. It is a basic logical - # combination of building blocks, and is capable of deciding, - # given a context (instance of DeclarativePolicy::Base) whether it - # passes or not. Note that this decision doesn't by itself know - # how that affects the actual ability decision - for that, a - # `Step` is used. - class Base - def self.make(*args) - new(*args).simplify - end - - # true or false whether this rule passes. - # `context` is a policy - an instance of - # DeclarativePolicy::Base. - def pass?(context) - raise 'abstract' - end - - # same as #pass? except refuses to do any I/O, - # returning nil if the result is not yet cached. - # used for accurately scoring And/Or - def cached_pass?(context) - raise 'abstract' - end - - # abstractly, how long would it take to compute - # this rule? lower-scored rules are tried first. - def score(context) - raise 'abstract' - end - - # unwrap double negatives and nested and/or - def simplify - self - end - - # convenience combination methods - def or(other) - Or.make([self, other]) - end - - def and(other) - And.make([self, other]) - end - - def negate - Not.make(self) - end - - alias_method :|, :or - alias_method :&, :and - alias_method :~@, :negate - - def inspect - "#<Rule #{repr}>" - end - end - - # A rule that checks a condition. This is the - # type of rule that results from a basic bareword - # in the rule dsl (see RuleDsl#method_missing). - class Condition < Base - def initialize(name) - @name = name - end - - # we delegate scoring to the condition. See - # ManifestCondition#score. - def score(context) - context.condition(@name).score - end - - # Let the ManifestCondition from the context - # decide whether we pass. - def pass?(context) - context.condition(@name).pass? - end - - # returns nil unless it's already cached - def cached_pass?(context) - condition = context.condition(@name) - return unless condition.cached? - - condition.pass? - end - - def description(context) - context.class.conditions[@name].description - end - - def repr - @name.to_s - end - end - - # A rule constructed from DelegateDsl - using a condition from a - # delegated policy. - class DelegatedCondition < Base - # Internal use only - this is rescued each time it's raised. - MissingDelegate = Class.new(StandardError) - - def initialize(delegate_name, name) - @delegate_name = delegate_name - @name = name - end - - def delegated_context(context) - policy = context.delegated_policies[@delegate_name] - raise MissingDelegate if policy.nil? - - policy - end - - def score(context) - delegated_context(context).condition(@name).score - rescue MissingDelegate - 0 - end - - def cached_pass?(context) - condition = delegated_context(context).condition(@name) - return unless condition.cached? - - condition.pass? - rescue MissingDelegate - false - end - - def pass?(context) - delegated_context(context).condition(@name).pass? - rescue MissingDelegate - false - end - - def repr - "#{@delegate_name}.#{@name}" - end - end - - # A rule constructed from RuleDsl#can?. Computes a different ability - # on the same subject. - class Ability < Base - attr_reader :ability - def initialize(ability) - @ability = ability - end - - # We ask the ability's runner for a score - def score(context) - context.runner(@ability).score - end - - def pass?(context) - context.allowed?(@ability) - end - - def cached_pass?(context) - runner = context.runner(@ability) - return unless runner.cached? - - runner.pass? - end - - def description(context) - "User can #{@ability.inspect}" - end - - def repr - "can?(#{@ability.inspect})" - end - end - - # Logical `and`, containing a list of rules. Only passes - # if all of them do. - class And < Base - attr_reader :rules - def initialize(rules) - @rules = rules - end - - def simplify - simplified_rules = @rules.flat_map do |rule| - simplified = rule.simplify - case simplified - when And then simplified.rules - else [simplified] - end - end - - And.new(simplified_rules) - end - - def score(context) - return 0 unless cached_pass?(context).nil? - - # note that cached rules will have score 0 anyways. - @rules.map { |r| r.score(context) }.inject(0, :+) - end - - def pass?(context) - # try to find a cached answer before - # checking in order - cached = cached_pass?(context) - return cached unless cached.nil? - - @rules.all? { |r| r.pass?(context) } - end - - def cached_pass?(context) - @rules.each do |rule| - pass = rule.cached_pass?(context) - - return pass if pass.nil? || pass == false - end - - true - end - - def repr - "all?(#{rules.map(&:repr).join(', ')})" - end - end - - # Logical `or`. Mirrors And. - class Or < Base - attr_reader :rules - def initialize(rules) - @rules = rules - end - - def pass?(context) - cached = cached_pass?(context) - return cached unless cached.nil? - - @rules.any? { |r| r.pass?(context) } - end - - def simplify - simplified_rules = @rules.flat_map do |rule| - simplified = rule.simplify - case simplified - when Or then simplified.rules - else [simplified] - end - end - - Or.new(simplified_rules) - end - - def cached_pass?(context) - @rules.each do |rule| - pass = rule.cached_pass?(context) - - return pass if pass.nil? || pass == true - end - - false - end - - def score(context) - return 0 unless cached_pass?(context).nil? - - @rules.map { |r| r.score(context) }.inject(0, :+) - end - - def repr - "any?(#{@rules.map(&:repr).join(', ')})" - end - end - - class Not < Base - attr_reader :rule - def initialize(rule) - @rule = rule - end - - def simplify - case @rule - when And then Or.new(@rule.rules.map(&:negate)).simplify - when Or then And.new(@rule.rules.map(&:negate)).simplify - when Not then @rule.rule.simplify - else Not.new(@rule.simplify) - end - end - - def pass?(context) - !@rule.pass?(context) - end - - def cached_pass?(context) - case @rule.cached_pass?(context) - when nil then nil - when true then false - when false then true - end - end - - def score(context) - @rule.score(context) - end - - def repr - "~#{@rule.repr}" - end - end - end -end |