Skip to content

Introduce context-first configuration and register Unitsdb model classes - #25

Merged
ronaldtse merged 10 commits into
mainfrom
feat/context-register-models
Apr 22, 2026
Merged

Introduce context-first configuration and register Unitsdb model classes#25
ronaldtse merged 10 commits into
mainfrom
feat/context-register-models

Conversation

@suleman-uzair

Copy link
Copy Markdown
Member

Summary

This change introduces a context-first Unitsdb::Configuration flow and wires model registration through Configuration.register_model.

What changed

  • Added Unitsdb::Configuration with:
    • register_model
    • context / populate_context
    • register / populate_register
    • substitution resolution for symbol/class substitution input
  • Updated Unitsdb.database to be context-aware and cache by context ID.
  • Updated Unitsdb::Database.from_db to:
    • accept context:
    • load within GlobalContext.with_context(context_id)
    • pass register: only when an explicit register exists
  • Added Configuration.register_model(...) entries across Unitsdb model files, including:
    • core serializable models
    • identifier/reference/root-derived models
    • QUDT and UCUM model classes
  • Added focused context support spec:
    • validates custom context substitution path with explicit register population

Current behavior/design intent

  • Context is first-class.
  • Register remains explicit (not auto-created).
  • No self-healing/stale auto-refresh logic.

@suleman-uzair
suleman-uzair requested a review from Copilot April 20, 2026 13:20

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Introduces a context-first configuration system for Unitsdb and registers model classes through Unitsdb::Configuration, while updating database loading to be context-aware and more strictly validated.

Changes:

  • Added Unitsdb::Configuration for model registration, context creation, and substitution resolution.
  • Refactored Unitsdb::Database.from_db to validate/compose YAML inputs and to load within a GlobalContext context.
  • Updated Unitsdb model files to self-register via Configuration.register_model, and added specs for context usage and improved load behavior.

Reviewed changes

Copilot reviewed 35 out of 35 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
spec/unitsdb/database_spec.rb Adds specs for stdout silence and clearer load-time failure cases (invalid YAML structure, missing collection key).
spec/unitsdb/configuration_spec.rb Adds a focused spec validating custom context + substitution + explicit registration behavior.
lib/unitsdb/units.rb Registers Units model in the configuration registry.
lib/unitsdb/unit_systems.rb Registers UnitSystems model in the configuration registry.
lib/unitsdb/unit_system_reference.rb Registers UnitSystemReference model in the configuration registry.
lib/unitsdb/unit_system.rb Registers UnitSystem model in the configuration registry.
lib/unitsdb/unit_reference.rb Registers UnitReference model in the configuration registry.
lib/unitsdb/unit.rb Registers Unit model in the configuration registry.
lib/unitsdb/ucum.rb Registers UCUM-related models in the configuration registry.
lib/unitsdb/symbol_presentations.rb Registers SymbolPresentations model in the configuration registry.
lib/unitsdb/si_derived_base.rb Registers SiDerivedBase model in the configuration registry.
lib/unitsdb/scales.rb Registers Scales model in the configuration registry.
lib/unitsdb/scale_reference.rb Registers ScaleReference model in the configuration registry.
lib/unitsdb/scale_properties.rb Registers ScaleProperties model in the configuration registry.
lib/unitsdb/scale.rb Registers Scale model in the configuration registry.
lib/unitsdb/root_unit_reference.rb Registers RootUnitReference model in the configuration registry.
lib/unitsdb/qudt.rb Registers QUDT-related models in the configuration registry.
lib/unitsdb/quantity_reference.rb Registers QuantityReference model in the configuration registry.
lib/unitsdb/quantity.rb Registers Quantity model in the configuration registry.
lib/unitsdb/quantities.rb Registers Quantities model in the configuration registry.
lib/unitsdb/prefixes.rb Registers Prefixes model in the configuration registry.
lib/unitsdb/prefix_reference.rb Registers PrefixReference model in the configuration registry.
lib/unitsdb/prefix.rb Registers Prefix model in the configuration registry.
lib/unitsdb/localized_string.rb Registers LocalizedString model in the configuration registry.
lib/unitsdb/identifier.rb Registers Identifier model in the configuration registry.
lib/unitsdb/external_reference.rb Registers ExternalReference model in the configuration registry.
lib/unitsdb/dimensions.rb Registers Dimensions model in the configuration registry.
lib/unitsdb/dimension_reference.rb Registers DimensionReference model in the configuration registry.
lib/unitsdb/dimension_details.rb Registers DimensionDetails model in the configuration registry.
lib/unitsdb/dimension.rb Registers Dimension model in the configuration registry.
lib/unitsdb/database.rb Refactors DB loading: centralizes file list, validates schema versions/collections, improves error handling, adds context-based load wrapper, registers Database.
lib/unitsdb/configuration.rb Adds the new configuration/context system including registry building and substitution resolution.
lib/unitsdb/config.rb Removes legacy Unitsdb::Config model lookup mechanism.
lib/unitsdb.rb Requires configuration, adjusts autoloads for Opal, makes Unitsdb.database context-aware with per-context caching.
Gemfile Points lutaml-model to a specific GitHub branch for global-context behavior changes.
Comments suppressed due to low confidence (1)

spec/unitsdb/database_spec.rb:1

  • ENV["DEBUG"] = original_debug will raise a TypeError when original_debug is nil (common when DEBUG was not set). Restore the env var by deleting it when original_debug is nil, otherwise reassign it.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread lib/unitsdb/database.rb
Comment on lines +325 to 327
Lutaml::Model::GlobalContext.with_context(context_id) do
from_hash(combined_hash, register: context_id)
end

Copilot AI Apr 20, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR description says register: should be passed “only when an explicit register exists”, but this call passes register: context_id unconditionally. If the intent is to avoid implicitly creating/using a register, update this to only provide register: when a register has been explicitly populated/declared for that context (and omit the keyword entirely otherwise).

Copilot uses AI. Check for mistakes.
Comment thread lib/unitsdb/database.rb
Comment on lines +361 to +382
def self.validate_schema_versions!(documents)
versions = DATABASE_FILES.each_with_object({}) do |(collection_key, filename), result|
document = documents.fetch(collection_key)
result[filename] = document.fetch("schema_version")
rescue KeyError
raise Errors::DatabaseFileInvalidError,
"Missing schema_version in files: #{missing_schema.join(', ')}"
"Missing schema_version in #{filename}"
end

# Extract versions from each file
prefixes_version = prefixes_hash["schema_version"]
dimensions_version = dimensions_hash["schema_version"]
units_version = units_hash["schema_version"]
quantities_version = quantities_hash["schema_version"]
unit_systems_version = unit_systems_hash["schema_version"]

# Check if all versions match
versions = [
prefixes_version,
dimensions_version,
units_version,
quantities_version,
unit_systems_version,
]

unless versions.uniq.size == 1
version_info = {
"prefixes.yaml" => prefixes_version,
"dimensions.yaml" => dimensions_version,
"units.yaml" => units_version,
"quantities.yaml" => quantities_version,
"unit_systems.yaml" => unit_systems_version,
}
unless versions.values.uniq.size == 1
raise Errors::VersionMismatchError,
"Version mismatch in database files: #{version_info.inspect}"
"Version mismatch in database files: #{versions.inspect}"
end

# Check if the version is supported
version = versions.first
unless version == "2.0.0"
version = versions.values.first
unless version == SUPPORTED_SCHEMA_VERSION
raise Errors::UnsupportedVersionError,
"Unsupported database version: #{version}. Only version 2.0.0 is supported."
"Unsupported database version: #{version}. Only version #{SUPPORTED_SCHEMA_VERSION} is supported."
end

combined_yaml = {
"schema_version" => prefixes_version,
"prefixes" => prefixes_hash["prefixes"],
"dimensions" => dimensions_hash["dimensions"],
"units" => units_hash["units"],
"quantities" => quantities_hash["quantities"],
"unit_systems" => unit_systems_hash["unit_systems"],
}.to_yaml
version
end

Copilot AI Apr 20, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This introduces new, user-facing validation paths (missing schema_version, version mismatch across files, and unsupported schema version) without corresponding specs. Add tests in spec/unitsdb/database_spec.rb that exercise each raised error and verify the error class/message match expectations (similar to the newly added “not a mapping” and “missing collection key” specs).

Copilot uses AI. Check for mistakes.

@ronaldtse ronaldtse Apr 22, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@suleman-uzair this seems to require a revert? Or is this because of Opal? The point is to check all of the schema files to ensure they have the same version.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ronaldtse I do not think this needs a revert. I refactored the structure here, but I did not intend to change the version validation or the other existing behavior in this method. If you had a specific case in mind that looks different after the refactor, let me know and I'll look into it.

Comment thread lib/unitsdb/configuration.rb Outdated
end

def context(id = context_id, force_populate: false)
return find_context(id) if find_context(id) && !force_populate && populated_for(id)

Copilot AI Apr 20, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

find_context(id) is called twice, which is unnecessary and makes the early-return condition harder to read. Store it in a local variable (e.g., existing = find_context(id)) and use that for the conditional/return to reduce repeated lookups and improve clarity.

Suggested change
return find_context(id) if find_context(id) && !force_populate && populated_for(id)
existing = find_context(id)
return existing if existing && !force_populate && populated_for(id)

Copilot uses AI. Check for mistakes.
@suleman-uzair suleman-uzair changed the title [WIP] Introduce context-first configuration and register Unitsdb model classes Introduce context-first configuration and register Unitsdb model classes Apr 22, 2026
@suleman-uzair
suleman-uzair marked this pull request as ready for review April 22, 2026 08:20
@ronaldtse
ronaldtse merged commit 21b5bf7 into main Apr 22, 2026
15 of 18 checks passed
@ronaldtse
ronaldtse deleted the feat/context-register-models branch April 22, 2026 10:25
ronaldtse added a commit that referenced this pull request Jun 29, 2026
Architectural cleanup of the state left by PR #25 (context-first
configuration). Database goes from 700+ lines to 240; Config state
collapses to one ivar; CLI plumbing centralized; respond_to-for-typing
and send-for-dispatch removed everywhere in lib/.

Services extracted from Database
- lib/unitsdb/database/loader.rb — YAML file IO + schema version
  validation. Knows nothing about contexts/registers.
- lib/unitsdb/database/reference_validator.rb — orchestrator with
  IdRegistry, ReferenceChecker, Reference.destructure, and a
  LookupStrategies module (COMPOSITE_KEY / BARE_ID /
  UNIT_SYSTEM_ALTERNATE). Replaces the 150-line duplicate of
  validate_reference that lived in both Database and the CLI
  command.
- lib/unitsdb/database/uniqueness_validator.rb — policy encoded via
  SHORT_COLLECTIONS and IDENTIFIER_COLLECTIONS constants.

Database query API
- New COLLECTIONS and SYMBOL_COLLECTIONS constants replace four
  hardcoded lists.
- New collection(name) validates against COLLECTIONS and dispatches
  via public_send. find_by_type / get_by_id / search /
  find_by_symbol / match_entities now reject unknown collection
  names with ArgumentError instead of silently returning wrong-type
  data.
- Removed every respond_to duck-typing check; iterations are typed.
  Dead Prefix.name branch gone. Dead check_dimension_references
  gone (Dimension has no dimension_reference attr — the prior
  respond_to hid the bug).

Config consolidation
- Single registered_models hash; the vestigial @models ivar is
  gone. models= is a thin enumerator over register_model.
- @populated_for bool-hash replaced by simpler semantics:
  context is non-destructive (returns existing, populates only
  when missing); populate_context force-rebuilds.
- Renamed register to register_id_for (was ambiguous with
  register_model). force_populate: keyword dropped.
- New ensure_default_context! as the single bootstrap site.

CLI consolidation
- New lib/unitsdb/commands/thor.rb shared base owning --trace,
  exit_on_failure?, run_command, and handle_error. Five Thor
  classes inherit; ten byte-identical handle_*error methods
  deleted.
- New lib/unitsdb/commands/entity_presenter.rb. Get/Search no
  longer duplicate a 60-line print_entity_details; dead
  respond_to branches for non-existent attrs (definition,
  dimensions, value, symbol, organization) gone.
- commands/validate/{qudt,ucum,si}_references.rb rewritten to a
  common COLLECTIONS-driven shape; fixed bug where the UCUM
  validator looked at external_references (no such attr).

Subcommand cleanup (QUDT / UCUM / CheckSi)
- Removed all remaining respond_to duck-typing in matcher /
  formatter / updater files (~60 occurrences). Dead branches
  for attrs that don't exist in the 2.0 schema (Prefix#factor,
  ExternalReference#verified, top-level entity#id) deleted.
- Replaced qudt_dimension.respond_to?(:dimension_exponent_for_length)
  with is_a?(Unitsdb::QudtDimensionVector); replaced
  db_dimension.send(dimension_type) with public_send.
- commands/check_si/si_updater.rb: replaced the
  send(:yaml_file, true) Lutaml-private-API reach-through with
  an explicit resolve_yaml_file helper.
- commands/check_si/si_matcher.rb rewritten with a DetailedMatcher
  strategy class and a MATCHERS table so per-entity-type dispatch
  is OCP — adding a new type is one entry.

Load + test infrastructure
- lib/unitsdb.rb: 30+ internal require calls replaced by an
  autoload tree. MODELS list + eager_load_models keeps the Lutaml
  type registry populated across Config snapshot/restore cycles.
- Public test-reset APIs: Unitsdb.reset_database_cache! and
  Config.with_isolated_config replace every spec IVAR poke.
- SiMatcher.match_details is a public accessor on class << self;
  si_updater / si_formatter no longer reach into matcher ivars.

Specs
- Every double() replaced with real Unitsdb::* instances.
- database_search_spec covers find_by_symbol, match_entities,
  validate_uniqueness, validate_references with synthetic
  duplicate / broken-reference cases built from real models.
- cli_spec covers subcommand dispatch, search/get happy paths,
  JSON output, validate references/identifiers, --trace re-raise.
- New unitsdb_bundled_entities_spec asserts typed attribute
  access and round-trip fidelity for known bundled entities plus
  Identifier / LocalizedString / SymbolPresentations /
  ExternalReference leaves.

Suite: 852 examples, 0 failures (was 815). rubocop clean.
ronaldtse added a commit that referenced this pull request Jun 29, 2026
Architectural cleanup of the state left by PR #25 (context-first
configuration). Database goes from 700+ lines to 240; Config state
collapses to one ivar; CLI plumbing centralized; respond_to-for-typing
and send-for-dispatch removed everywhere in lib/.

Services extracted from Database
- lib/unitsdb/database/loader.rb — YAML file IO + schema version
  validation. Knows nothing about contexts/registers.
- lib/unitsdb/database/reference_validator.rb — orchestrator with
  IdRegistry, ReferenceChecker, Reference.destructure, and a
  LookupStrategies module (COMPOSITE_KEY / BARE_ID /
  UNIT_SYSTEM_ALTERNATE). Replaces the 150-line duplicate of
  validate_reference that lived in both Database and the CLI
  command.
- lib/unitsdb/database/uniqueness_validator.rb — policy encoded via
  SHORT_COLLECTIONS and IDENTIFIER_COLLECTIONS constants.

Database query API
- New COLLECTIONS and SYMBOL_COLLECTIONS constants replace four
  hardcoded lists.
- New collection(name) validates against COLLECTIONS and dispatches
  via public_send. find_by_type / get_by_id / search /
  find_by_symbol / match_entities now reject unknown collection
  names with ArgumentError instead of silently returning wrong-type
  data.
- Removed every respond_to duck-typing check; iterations are typed.
  Dead Prefix.name branch gone. Dead check_dimension_references
  gone (Dimension has no dimension_reference attr — the prior
  respond_to hid the bug).

Config consolidation
- Single registered_models hash; the vestigial @models ivar is
  gone. models= is a thin enumerator over register_model.
- @populated_for bool-hash replaced by simpler semantics:
  context is non-destructive (returns existing, populates only
  when missing); populate_context force-rebuilds.
- Renamed register to register_id_for (was ambiguous with
  register_model). force_populate: keyword dropped.
- New ensure_default_context! as the single bootstrap site.

CLI consolidation
- New lib/unitsdb/commands/thor.rb shared base owning --trace,
  exit_on_failure?, run_command, and handle_error. Five Thor
  classes inherit; ten byte-identical handle_*error methods
  deleted.
- New lib/unitsdb/commands/entity_presenter.rb. Get/Search no
  longer duplicate a 60-line print_entity_details; dead
  respond_to branches for non-existent attrs (definition,
  dimensions, value, symbol, organization) gone.
- commands/validate/{qudt,ucum,si}_references.rb rewritten to a
  common COLLECTIONS-driven shape; fixed bug where the UCUM
  validator looked at external_references (no such attr).

Subcommand cleanup (QUDT / UCUM / CheckSi)
- Removed all remaining respond_to duck-typing in matcher /
  formatter / updater files (~60 occurrences). Dead branches
  for attrs that don't exist in the 2.0 schema (Prefix#factor,
  ExternalReference#verified, top-level entity#id) deleted.
- Replaced qudt_dimension.respond_to?(:dimension_exponent_for_length)
  with is_a?(Unitsdb::QudtDimensionVector); replaced
  db_dimension.send(dimension_type) with public_send.
- commands/check_si/si_updater.rb: replaced the
  send(:yaml_file, true) Lutaml-private-API reach-through with
  an explicit resolve_yaml_file helper.
- commands/check_si/si_matcher.rb rewritten with a DetailedMatcher
  strategy class and a MATCHERS table so per-entity-type dispatch
  is OCP — adding a new type is one entry.

Load + test infrastructure
- lib/unitsdb.rb: 30+ internal require calls replaced by an
  autoload tree. MODELS list + eager_load_models keeps the Lutaml
  type registry populated across Config snapshot/restore cycles.
- Public test-reset APIs: Unitsdb.reset_database_cache! and
  Config.with_isolated_config replace every spec IVAR poke.
- SiMatcher.match_details is a public accessor on class << self;
  si_updater / si_formatter no longer reach into matcher ivars.

Specs
- Every double() replaced with real Unitsdb::* instances.
- database_search_spec covers find_by_symbol, match_entities,
  validate_uniqueness, validate_references with synthetic
  duplicate / broken-reference cases built from real models.
- cli_spec covers subcommand dispatch, search/get happy paths,
  JSON output, validate references/identifiers, --trace re-raise.
- New unitsdb_bundled_entities_spec asserts typed attribute
  access and round-trip fidelity for known bundled entities plus
  Identifier / LocalizedString / SymbolPresentations /
  ExternalReference leaves.

Suite: 852 examples, 0 failures (was 815). rubocop clean.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants