Class: CurrentScope::GatingReflection

Inherits:
Object
  • Object
show all
Defined in:
lib/current_scope/gating_reflection.rb

Overview

Answers one question about a controller path: is it PROVEN that current_scope_check! never runs there? True only when the callback is ABSENT from the class's callback chain — the one thing the chain states unconditionally. Everything else is false:

- callback present and unconditional → gated → false
- callback present wearing a condition (skip_before_action only:) →
UNPROVABLE without evaluating ActionFilter internals, which this class
must never do (KTD-3) → false. The naive any?/none? presence check
inverts exactly here — see the ProblemFrame characterization in
test/gating_reflection_test.rb — and per-action guessing is how a
fail-open gets reported as gated.
- no controller class routed at that path → nothing to prove → false

Absence is an EFFECT, not a cause: a bare skip_before_action, an inherited bare skip (#62), and never including Guard at all are the same verdict, because the chain looks the same. That is deliberate — the question is "does the gate run?", not "why not?".

Named beside GatingTripwire: both interrogate the same subject (is this action gated?), the tripwire at request time, this one by reflection.

Instance Method Summary collapse

Instance Method Details

#controller_class(controller_path) ⇒ Object

The controller class routed at this path, or nil when none is (a stale route, a not-yet-written controller). PUBLIC because SodPreflight (#133) asks the same path→class question, and a second copy would have to reimplement the NameError triage described below — the one thing that keeps a broken controller from being reported as a clean one.

This is not a second way to ask the same question, and the difference is the whole point: controller_class_for RAISES MissingController — every private caller above rescues it into its own answer (ungated? → false, missing_controller? → true, declared_skip_reason → nil), because those three mean different things by "no class here". This one is the fourth such answer, spelled nil, and it delegates rather than reimplements, so there is still exactly one path→class rule. What must NOT be added is a public method that swallows the NameError too. (#133 review — cubic)



82
83
84
85
86
# File 'lib/current_scope/gating_reflection.rb', line 82

def controller_class(controller_path)
  controller_class_for(controller_path)
rescue ActionDispatch::MissingController
  nil
end

#declared_skip_reason(controller_path) ⇒ Object

Reason from current_scope_skip_gate!(reason:), or nil when ungated without a declaration (bare skip / never included). Meaningful when #ungated? is true (#76).



59
60
61
62
63
64
65
66
# File 'lib/current_scope/gating_reflection.rb', line 59

def declared_skip_reason(controller_path)
  klass = controller_class_for(controller_path)
  return nil unless klass.respond_to?(:current_scope_gate_skip_reason)

  klass.current_scope_gate_skip_reason.presence
rescue ActionDispatch::MissingController
  nil
end

#missing_controller?(controller_path) ⇒ Boolean

True when the path is routed but no controller class loads (stale route / typo). Distinct from ungated?: there is nothing to gate. Hitting the route is ActionDispatch::MissingController (a 500 in production), so the role grid flags the row (#43). Uses the same Rails path→class rule as #ungated? so the two answers cannot disagree on loadability.

Returns:

  • (Boolean)


49
50
51
52
53
54
# File 'lib/current_scope/gating_reflection.rb', line 49

def missing_controller?(controller_path)
  controller_class_for(controller_path)
  false
rescue ActionDispatch::MissingController
  true
end

#ungated?(controller_path) ⇒ Boolean

Returns:

  • (Boolean)


24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# File 'lib/current_scope/gating_reflection.rb', line 24

def ungated?(controller_path)
  klass = controller_class_for(controller_path)
  # A controller without the callbacks API at all (a bare ActionController::
  # Metal without AbstractController::Callbacks) cannot run a before_action
  # — the gate PROVABLY never runs there. Without this check the reflection
  # would raise NoMethodError instead of answering. (#79 review)
  return true unless klass.respond_to?(:_process_action_callbacks)

  klass
    ._process_action_callbacks
    .none? { |callback| callback.kind == :before && callback.filter == :current_scope_check! }
rescue ActionDispatch::MissingController
  # A routed path with no controller class (a scaffolding leftover, a
  # not-yet-written controller) proves nothing about gating. Silent false,
  # matching the prove-or-stay-silent discipline of
  # warn_on_cross_controller_derivation. Surface that shape via
  # #missing_controller? instead — the grid badges it (#43).
  false
end