Module: TypedEAV::ScopeTuple

Defined in:
lib/typed_eav/scope_tuple.rb

Overview

Localized semantics for the (scope, parent_scope) tuple.

Five files used to smear tuple shape/coercion/invariant logic across themselves (lib/typed_eav.rb, Config, Partition, HasTypedEav#resolve_scope, Field::Base#for_entity). This module gathers them in one place:

  • normalize_permissive(value) — the v0.1.x-BC scalar-friendly coercer used by the with_scope block surface and by callers that already know they may receive a loose value (single scalar, AR record, or 2-element Array). Mirrors what TypedEAV.normalize_scope did before this refactor; the public method is now a 1-line BC alias to this.

  • normalize_strict(value) — the contract-surface coercer used by TypedEAV.current_scope when consuming a configured Config.scope_resolver return. Bare scalars / 1-element / 3-element Arrays raise ArgumentError — this is the Phase-1 strict-contract chokepoint that makes a misshaped resolver fail loudly instead of silently coercing.

  • invariant_satisfied?(scope, parent_scope) — Boolean orphan-parent check. Returns false only when parent_scope is present and scope is blank (the dead-letter shape: a parent-scope predicate with no scope predicate). Each caller picks its own response policy (raise / AR error / silent narrow) on a false result; this helper never raises.

The split between permissive and strict normalization is the Phase-1 asymmetric contract preserved verbatim: with_scope block input is BC-permissive (scalars are sugar for [scalar, nil]), scope_resolver callable return is strict (scalars are a contract violation).

Class Method Summary collapse

Class Method Details

.invariant_satisfied?(scope, parent_scope) ⇒ Boolean

Orphan-parent invariant predicate. Returns true when the tuple is internally coherent, false ONLY when parent_scope is present while scope is blank — the dead-letter shape that cannot match any row under the partition predicates.

Truth table:

(nil, nil)     → true
("t1", nil)    → true
("t1", "w1")   → true
(nil, "w1")    → false
("",  "w1")    → false   (empty string treated as blank on scope axis)
(nil, "")      → true    (empty string treated as blank on parent axis)

Callers each pick their own response policy on a false result: Partition.visible_fields raises ArgumentError; Field's model validator adds an AR error; HasTypedEAV#resolve_scope silently narrows the query. Returning a Boolean (not raising) keeps the per-caller decision local.

Returns:

  • (Boolean)


98
99
100
101
102
# File 'lib/typed_eav/scope_tuple.rb', line 98

def invariant_satisfied?(scope, parent_scope)
  return true if parent_scope.blank?

  scope.present?
end

.normalize_permissive(value) ⇒ Object

BC-permissive normalizer for with_scope block input and explicit tuple inputs. Always returns either nil or a 2-element tuple [scope, parent_scope] where each element is a String or nil.

Accepted inputs:

  • nilnil (sentinel: nothing resolved).
  • [a, b] (2-element Array) → [normalize_one(a), normalize_one(b)]. Canonical Phase-1 input shape. [scope, nil] is the "scope-only" tuple; [nil, "ps1"] (orphan-parent) is intentionally accepted at this layer — orphan-parent rejection happens at the calling site (Field validator → AR error, Partition query → ArgumentError, resolve_scope → silent narrow). Keeping normalize permissive lets tests construct invalid states intentionally.
  • any other value (scalar / AR record) → [normalize_one(value), nil]. BC path for with_scope(scalar): single-arg block usage continues to mean "scope=scalar, parent_scope=nil".


54
55
56
57
58
59
# File 'lib/typed_eav/scope_tuple.rb', line 54

def normalize_permissive(value)
  return nil if value.nil?
  return [normalize_one(value[0]), normalize_one(value[1])] if value.is_a?(Array) && value.size == 2

  [normalize_one(value), nil]
end

.normalize_strict(value) ⇒ Object

Strict-contract normalizer for Config.scope_resolver return values. Accepts ONLY nil or a 2-element Array. Bare scalars, 1-element Arrays, and 3-element Arrays raise ArgumentError quoting the bad input and pointing at the migration note — this is the chokepoint that makes a v0.1.x bare-scalar resolver fail loudly under Phase 1.



66
67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/typed_eav/scope_tuple.rb', line 66

def normalize_strict(value)
  return nil if value.nil?

  unless value.is_a?(Array) && value.size == 2
    raise ArgumentError,
          "TypedEAV.config.scope_resolver must return a 2-element " \
          "[scope, parent_scope] Array (or nil). Got: #{value.inspect}. " \
          "v0.1.x resolvers returning a bare scalar must be updated — " \
          "see CHANGELOG and the README migration note."
  end

  [normalize_one(value[0]), normalize_one(value[1])]
end