Module: TypedEAV
- Extended by:
- ActiveSupport::Autoload
- Defined in:
- lib/typed_eav.rb,
lib/typed_eav/config.rb,
lib/typed_eav/engine.rb,
lib/typed_eav/version.rb,
lib/typed_eav/registry.rb,
lib/typed_eav/bulk_read.rb,
lib/typed_eav/partition.rb,
lib/typed_eav/versioned.rb,
lib/typed_eav/bulk_write.rb,
lib/typed_eav/csv_mapper.rb,
lib/typed_eav/versioning.rb,
lib/typed_eav/scope_tuple.rb,
app/models/typed_eav/value.rb,
lib/typed_eav/entity_query.rb,
lib/typed_eav/filter_query.rb,
app/models/typed_eav/option.rb,
lib/typed_eav/has_typed_eav.rb,
lib/typed_eav/query_builder.rb,
app/models/typed_eav/section.rb,
app/models/typed_eav/field/url.rb,
lib/typed_eav/event_dispatcher.rb,
app/models/typed_eav/field/base.rb,
app/models/typed_eav/field/date.rb,
app/models/typed_eav/field/file.rb,
app/models/typed_eav/field/json.rb,
app/models/typed_eav/field/text.rb,
app/models/typed_eav/field/color.rb,
app/models/typed_eav/field/email.rb,
app/models/typed_eav/field/image.rb,
lib/typed_eav/schema_portability.rb,
app/models/typed_eav/field/select.rb,
lib/typed_eav/field/typed_storage.rb,
app/models/typed_eav/field/boolean.rb,
app/models/typed_eav/field/decimal.rb,
app/models/typed_eav/field/integer.rb,
app/models/typed_eav/value_version.rb,
app/models/typed_eav/field/currency.rb,
lib/typed_eav/versioning/subscriber.rb,
app/models/typed_eav/field/date_time.rb,
app/models/typed_eav/field/long_text.rb,
app/models/typed_eav/field/reference.rb,
app/models/typed_eav/field/date_array.rb,
app/models/typed_eav/field/optionable.rb,
app/models/typed_eav/field/percentage.rb,
app/models/typed_eav/field/text_array.rb,
app/models/typed_eav/application_record.rb,
app/models/typed_eav/field/multi_select.rb,
app/models/typed_eav/field/decimal_array.rb,
app/models/typed_eav/field/integer_array.rb,
app/models/typed_eav/field/range_bounded.rb,
app/models/typed_eav/field/validated_string.rb,
lib/typed_eav/has_typed_eav/instance_methods.rb,
lib/typed_eav/schema_portability/import_index.rb,
lib/generators/typed_eav/install/install_generator.rb,
lib/generators/typed_eav/scaffold/scaffold_generator.rb
Defined Under Namespace
Modules: BulkWrite, CSVMapper, EntityQuery, EventDispatcher, Field, Generators, HasTypedEAV, Partition, SchemaPortability, ScopeTuple, Versioned, Versioning Classes: ApplicationRecord, BulkRead, Config, Engine, FilterQuery, Option, QueryBuilder, Registry, ScopeRequired, Section, Value, ValueVersion
Constant Summary collapse
- VERSION =
"0.6.0"
Class Method Summary collapse
- .config {|Config| ... } ⇒ Object (also: configure)
-
.current_context ⇒ Object
Returns the current thread's top-of-stack context Hash, or a shared frozen empty Hash when no
with_contextblock is active. -
.current_scope ⇒ Object
Current ambient scope tuple.
-
.normalize_scope(value) ⇒ Object
BC-permissive normalizer for
with_scopeblock input and explicit tuple inputs. - .registry ⇒ Object
-
.unscoped ⇒ Object
Run the block with scope enforcement disabled.
-
.unscoped? ⇒ Boolean
True when inside an
unscoped { }block. -
.with_context(**kwargs) ⇒ Object
Run the block with
kwargsmerged into the ambient event context, restoring the prior stack on exit (exception-safe). -
.with_scope(value) ⇒ Object
Run the block with
valueas the ambient scope, restoring the prior stack on exit (exception-safe).
Class Method Details
.config {|Config| ... } ⇒ Object Also known as: configure
50 51 52 53 |
# File 'lib/typed_eav.rb', line 50 def config yield Config if block_given? Config end |
.current_context ⇒ Object
Returns the current thread's top-of-stack context Hash, or a shared
frozen empty Hash when no with_context block is active. The return
value is ALWAYS frozen — callers can rely on read-only semantics
regardless of whether a block is active. NEVER returns nil.
192 193 194 |
# File 'lib/typed_eav.rb', line 192 def current_context Thread.current[THREAD_CONTEXT_STACK]&.last || EMPTY_FROZEN_CONTEXT end |
.current_scope ⇒ Object
Current ambient scope tuple. Resolution order:
1. Inside `unscoped { }` → nil (hard bypass)
2. Innermost `with_scope(v)` → tuple stored on the stack
3. Configured `scope_resolver` callable
4. nil
Return-value contract (Phase 1, breaking change from v0.1.x)
Returns either nil (no ambient scope) or a 2-element Array
[scope, parent_scope] where each element is a String or nil.
Never returns a bare scalar.
scope_resolver contract (strict)
The resolver lambda configured via Config.scope_resolver = ->{ ... }
MUST return either nil or a 2-element Array. Both elements may be
nil. Any other shape — most importantly a bare scalar (the v0.1.x
shape) — raises ArgumentError directly inside current_scope,
BEFORE any normalization is applied. We deliberately do NOT auto-coerce
a bare-scalar return into [scalar, nil]; the BC-shim path was
rejected during Phase 1 design (see .vbw-planning/phases/01-*/01-CONTEXT.md
§ "Deferred Ideas"). The strict raise is the chokepoint that makes
the breaking change visible — silent coercion here would hide a
contract violation in user-supplied resolver code.
parent_scope non-nil + scope nil (orphan parent) is invalid; the
check belongs to model-level validators added by plans 03/04, NOT
to this resolver layer. The resolver is a contract surface, not a
validation surface.
with_scope(scalar) block API remains BC-permissive and is a
DIFFERENT surface from the resolver-callable contract — see
with_scope doc and normalize_scope doc.
92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 |
# File 'lib/typed_eav.rb', line 92 def current_scope return nil if Thread.current[THREAD_UNSCOPED] stack = Thread.current[THREAD_SCOPE_STACK] # The stack stores tuples already (with_scope normalized on push), so # reads bypass coercion entirely — no risk of double-normalization. return stack.last if stack.present? # Resolver-callable strict-contract path. We route through # `ScopeTuple.normalize_strict`, which raises `ArgumentError` on any # shape other than `nil` or a 2-element Array — most importantly on # the v0.1.x bare-scalar shape. The strict raise is the chokepoint # that makes a misshaped resolver fail loudly; the BC-shim # (silent coercion) alternative was rejected during Phase 1 design. ScopeTuple.normalize_strict(Config.scope_resolver&.call) end |
.normalize_scope(value) ⇒ Object
BC-permissive normalizer for with_scope block input and explicit
tuple inputs. 1-line alias to ScopeTuple.normalize_permissive —
the implementation moved during the 0.3.0 ScopeTuple extraction
(issue #10), but the public method/return-shape stays as a v0.2.x BC
surface. Always returns either nil or a 2-element tuple
[scope, parent_scope] where each element is a String or nil.
NOT a contract chokepoint for resolver returns
current_scope deliberately does NOT route a custom-resolver return
through this helper — it routes through ScopeTuple.normalize_strict
instead — because the bare-scalar passthrough here would silently
coerce a contract violation. This split (strict on the resolver-
callable surface, permissive on the with_scope block surface) is the
Phase-1 asymmetric contract.
211 |
# File 'lib/typed_eav.rb', line 211 def normalize_scope(value) = ScopeTuple.normalize_permissive(value) |
.registry ⇒ Object
57 |
# File 'lib/typed_eav.rb', line 57 def registry = Registry |
.unscoped ⇒ Object
Run the block with scope enforcement disabled. Queries return results across all scopes. Use for admin tools, migrations, and tests.
141 142 143 144 145 146 147 |
# File 'lib/typed_eav.rb', line 141 def unscoped prev = Thread.current[THREAD_UNSCOPED] Thread.current[THREAD_UNSCOPED] = true yield ensure Thread.current[THREAD_UNSCOPED] = prev end |
.unscoped? ⇒ Boolean
True when inside an unscoped { } block.
150 151 152 |
# File 'lib/typed_eav.rb', line 150 def unscoped? !!Thread.current[THREAD_UNSCOPED] end |
.with_context(**kwargs) ⇒ Object
Run the block with kwargs merged into the ambient event context,
restoring the prior stack on exit (exception-safe). Nests cleanly with
shallow per-key merge — outer keys remain visible inside nested blocks
unless overridden by name; deep-merge of nested Hash values is NOT
promised.
The pre-merged hash is FROZEN before being pushed so callbacks invoked
downstream (Config.on_value_change user proc, internal subscribers)
cannot mutate context for the current or outer blocks. Without freeze,
a callback that did ctx[:added] = true would corrupt the stack for
every wrapping block on the same thread.
Why **kwargs and not positional Hash
def with_context(**kwargs) enforces the keyword-syntax call form.
Per Ruby 3.0+ kwargs/Hash separation, TypedEAV.with_context({ foo: 1 })
raises ArgumentError ("wrong number of arguments") — the only accepted
form is TypedEAV.with_context(foo: 1). Without **kwargs, callers
could push arbitrary Hash shapes (including nested Arrays or non-symbol
keys) that wouldn't merge cleanly across nesting and wouldn't match
the documented context shape that hooks read.
See with_scope (above) for the parallel ensure-pop pattern. Mirrors
with_scope's shape exactly except for: (a) **kwargs vs positional
value, (b) merge-into-outer-on-push vs replace-on-push.
179 180 181 182 183 184 185 186 |
# File 'lib/typed_eav.rb', line 179 def with_context(**kwargs) stack = (Thread.current[THREAD_CONTEXT_STACK] ||= []) merged = (stack.last || EMPTY_FROZEN_CONTEXT).merge(kwargs).freeze stack.push(merged) yield ensure stack&.pop end |
.with_scope(value) ⇒ Object
Run the block with value as the ambient scope, restoring the prior
stack on exit (exception-safe). Nests cleanly.
Accepted input shapes (BC-permissive — public block API)
with_scope("t1")— single-arg BC: pushes["t1", nil].with_scope(ar_record)— pushes[ar_record.id.to_s, nil].with_scope(["t1", "ps1"])— Phase 1 tuple form: pushes the tuple.with_scope(nil)— pushes nil (sentinel: no scope).
The single-arg signature with_scope(value) keeps its v0.1.x meaning:
scope = value, parent_scope = nil. Apps that have only ever passed
a scalar do not need to update on upgrade.
The internal stack stores normalized tuples (or nil), NOT raw values,
so current_scope can return stack.last directly without further
coercion.
NOTE: this is the BC-permissive surface. The strict-contract surface
is Config.scope_resolver — see current_scope doc. Two surfaces,
two contracts: with_scope's scalar-OK behavior is BC-preserving;
the resolver-callable contract rejects bare scalars.
131 132 133 134 135 136 137 |
# File 'lib/typed_eav.rb', line 131 def with_scope(value) stack = (Thread.current[THREAD_SCOPE_STACK] ||= []) stack.push(ScopeTuple.normalize_permissive(value)) yield ensure stack&.pop end |