class DeclarativePolicy::Step

This object represents one step in the runtime decision of whether an ability is allowed. It contains a Rule and a context (instance of DeclarativePolicy::Base), which contains the user, the subject, and the cache. It also contains an “action”, which is the symbol :prevent or :enable.

Attributes

action[R]
context[R]
rule[R]

Public Class Methods

new(context, rule, action) click to toggle source
# File lib/declarative_policy/step.rb, line 12
def initialize(context, rule, action)
  @context = context
  @rule = rule
  @action = action
end

Public Instance Methods

==(other) click to toggle source

In the flattening process, duplicate steps may be generated in the same rule. This allows us to eliminate those (see Runner#steps_by_score and note its use of a Set)

# File lib/declarative_policy/step.rb, line 21
def ==(other)
  @context == other.context && @rule == other.rule && @action == other.action
end
enable?() click to toggle source
# File lib/declarative_policy/step.rb, line 44
def enable?
  @action == :enable
end
flattened(roots) click to toggle source

This rather complex method allows us to split rules into parts so that they can be sorted independently for better optimization

# File lib/declarative_policy/step.rb, line 54
def flattened(roots)
  case @rule
  when Rule::Or
    # A single `Or` step is the same as each of its elements as separate steps
    @rule.rules.flat_map { |r| Step.new(@context, r, @action).flattened(roots) }
  when Rule::Ability
    # This looks like a weird micro-optimization but it buys us quite a lot
    # in some cases. If we depend on an Ability (i.e. a `can?(...)` rule),
    # and that ability *only* has :enable actions (modulo some actions that
    # we already have taken care of), then its rules can be safely inlined.
    steps = @context.runner(@rule.ability).steps.reject { |s| roots.include?(s) }

    if steps.all?(&:enable?)
      # in the case that we are a :prevent step, each inlined step becomes
      # an independent :prevent, even though it was an :enable in its initial
      # context.
      steps.map! { |s| s.with_action(:prevent) } if prevent?

      steps.flat_map { |s| s.flattened(roots) }
    else
      [self]
    end
  else
    [self]
  end
end
pass?() click to toggle source
# File lib/declarative_policy/step.rb, line 81
def pass?
  @rule.pass?(@context)
end
prevent?() click to toggle source
# File lib/declarative_policy/step.rb, line 48
def prevent?
  @action == :prevent
end
repr() click to toggle source
# File lib/declarative_policy/step.rb, line 85
def repr
  "#{@action} when #{@rule.repr} (#{@context.repr})"
end
score() click to toggle source

In the runner, steps are sorted dynamically by score, so that we are sure to compute them in close to the optimal order.

See also Rule#score, ManifestCondition#score, and Runner#steps_by_score.

# File lib/declarative_policy/step.rb, line 29
def score
  # we slightly prefer the preventative actions
  # since they are more likely to short-circuit
  case @action
  when :prevent
    @rule.score(@context) * (7.0 / 8)
  when :enable
    @rule.score(@context)
  end
end
with_action(action) click to toggle source
# File lib/declarative_policy/step.rb, line 40
def with_action(action)
  Step.new(@context, @rule, action)
end