Add Registry#resolve_missing — read-through resolution of unknown types - #26
Open
fractaledmind wants to merge 1 commit into
Open
Add Registry#resolve_missing — read-through resolution of unknown types#26fractaledmind wants to merge 1 commit into
fractaledmind wants to merge 1 commit into
Conversation
The registry is per-process, in-memory state. When types are created at runtime (e.g. from database rows), a process that booted before a type was registered — or that never created the row itself — raises UnknownType on lookup even though the type is derivable. Consumers worked around this by registering defensively at each call site. resolve_missing lets the application register a resolver invoked on a lookup miss BEFORE UnknownType is raised: it receives the missing symbol, registers it as a side effect (from wherever the source of truth lives), and lookup retries once. The registry becomes a read-through cache. - Runs the resolver without the registry lock held (it calls #register, which locks), guarded against per-thread re-entrancy so a resolver that re-triggers the same miss can't recurse forever. - A resolver that raises is swallowed; the original UnknownType is surfaced. - All construction paths (Amount.new, conversions, generated constructors, register_default_rate) funnel through #lookup, so one resolver covers them all. - #clear! resets the resolver too. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
The registry is per-process, in-memory state. When types are registered at runtime from a source of truth (e.g. database rows), a process that booted before a type was created — or that never created the row itself — raises
UnknownTypeon lookup even though the type is derivable. Consumers work around this by registering defensively at each call site, which is easy to forget (a single missed site terminally fails otherwise-complete work).Real-world manifestation in a consumer: an app registers each token's Amount type via a
Tokenafter_commit(per-process) plus a boot sweep. On a token's first-ever use, a different worker holds the DB row without its type and raisesAmount::Registry::UnknownType.What
Registry#resolve_missing { |symbol| ... }registers an optional resolver invoked on a#lookupmiss beforeUnknownTypeis raised. The block receives the missing symbol, registers it as a side effect (from wherever the source of truth lives), and lookup retries once. The registry becomes a read-through cache.#register, which locks), guarded against per-thread re-entrancy so a resolver that re-triggers the same miss can't recurse forever.UnknownTypeis surfaced (a resolver failure never masks the real error).Amount.new, conversions, generated constructors,register_default_rate) funnel through#lookup, so one resolver covers them all.#clear!resets the resolver too.Opt-in and fully backward-compatible: with no resolver set,
#lookupbehaves exactly as before.Tests
Six new minitest cases in
test/test_amount.rb: resolve-on-miss, still-raises-when-resolver-registers-nothing, raising-resolver-surfaces-original-error, re-entrancy guard (runs once, no recursion), no-block clears, andclear!resets. Full suite green (test_amount,test_active_record,test_registry_generator, both RSpec matcher specs); RuboCop clean. Version bumped to 0.0.9 with a CHANGELOG entry.Opened from a fork (
fractaledmind:feat/registry-resolve-missing) — feel free to re-push the branch intozarpay/amountsdirectly if you'd prefer it on the canonical repo before merging/releasing 0.0.9.🤖 Generated with Claude Code