From 59b8b027f9432fe75af44e166409fa5a40363174 Mon Sep 17 00:00:00 2001 From: suleman-uzair Date: Tue, 31 Mar 2026 21:41:21 +0500 Subject: [PATCH 01/15] feat: use lutaml register for MathML v4 model resolution --- Gemfile | 2 +- lib/unitsml/fenced.rb | 2 +- lib/unitsml/formula.rb | 19 +++++++------------ lib/unitsml/unit.rb | 4 ++-- 4 files changed, 11 insertions(+), 16 deletions(-) diff --git a/Gemfile b/Gemfile index 7c1cff3..bf80b69 100644 --- a/Gemfile +++ b/Gemfile @@ -7,7 +7,7 @@ gemspec gem "canon" gem "lutaml-model", "~> 0.8.0", github: "lutaml/lutaml-model", branch: "main" -gem "mml" +gem "mml", github: "plurimath/mml", branch: "main" gem "oga" gem "ox" gem "plurimath", github: "plurimath/plurimath", branch: "rt-lutaml-080" diff --git a/lib/unitsml/fenced.rb b/lib/unitsml/fenced.rb index de10929..0ebef7f 100644 --- a/lib/unitsml/fenced.rb +++ b/lib/unitsml/fenced.rb @@ -31,7 +31,7 @@ def to_mathml(options = {}) mathml = value.to_mathml(options) return mathml unless options[:explicit_parenthesis] - fenced = ::Mml::V4::Mrow.new(mo_value: [::Mml::V4::Mo.new(value: open_paren)]) + fenced = ::Mml::V4::Mrow.new(mo_value: [::Mml::V4::Mo.new(value: open_paren, lutaml_register: :mml_v4)], lutaml_register: :mml_v4) fenced.ordered = true fenced.element_order ||= [xml_order_element("mo")] [mathml].flatten.each { |record| add_math_element(fenced, record) } diff --git a/lib/unitsml/formula.rb b/lib/unitsml/formula.rb index 1cce999..12dead5 100644 --- a/lib/unitsml/formula.rb +++ b/lib/unitsml/formula.rb @@ -29,15 +29,13 @@ def ==(other) def to_mathml(options = {}) if root options = update_options(options) - nullify_mml_models if plurimath_available? - math = ::Mml::V4::Math.new(display: "block") + math = ::Mml::V4::Math.new(display: "block", lutaml_register: :mml_v4) math.ordered = true math.element_order ||= [] value.each do |instance| process_value(math, instance.to_mathml(options)) end generated_math = math.to_xml.gsub(%r{&(.*?)(?=\s+<").strip + end end end diff --git a/lib/unitsml/unit.rb b/lib/unitsml/unit.rb index b32ad62..09982ce 100644 --- a/lib/unitsml/unit.rb +++ b/lib/unitsml/unit.rb @@ -37,7 +37,7 @@ def unit_symbols def to_mathml(options) value = unit_symbols&.mathml tag_name = value.match(/^<(?\w+)/)[:tag] - value = ::Mml::V4.const_get(tag_name.capitalize).from_xml(value) + value = ::Mml::V4.const_get(tag_name.capitalize).from_xml(value, register: :mml_v4) value.value = "#{prefix.to_mathml(options)}#{value.value}" if prefix if power_numerator value = msup_tag( @@ -127,7 +127,7 @@ def system_reference end def msup_tag(value, options) - msup = ::Mml::V4::Msup.new + msup = ::Mml::V4::Msup.new(lutaml_register: :mml_v4) msup.ordered = true msup.element_order = [] [value, power_numerator.to_mathml(options)].flatten.each do |record| From e29bd4deec3cfb5bb9fe2767982f84b8eb5dae21 Mon Sep 17 00:00:00 2001 From: suleman-uzair Date: Mon, 6 Apr 2026 16:50:18 +0500 Subject: [PATCH 02/15] Update UnitsML to support latest MML, Plurimath, and UnitsDB changes --- docs/README.adoc | 18 ++++++++-- lib/unitsml.rb | 1 + lib/unitsml/dimension.rb | 9 +++-- lib/unitsml/extender.rb | 4 ++- lib/unitsml/fenced.rb | 10 ++++-- lib/unitsml/formula.rb | 11 +++++-- lib/unitsml/mathml_helper.rb | 20 ++++++++++++ lib/unitsml/number.rb | 12 ++++--- lib/unitsml/prefix.rb | 4 ++- lib/unitsml/unit.rb | 6 ++-- lib/unitsml/unitsdb.rb | 55 ++++++++++++++++++++++++++++--- lib/unitsml/unitsdb/dimension.rb | 21 +++++++++--- lib/unitsml/utility.rb | 56 ++++++++++++++++++++++++++++---- unitsml.gemspec | 2 +- 14 files changed, 192 insertions(+), 37 deletions(-) create mode 100644 lib/unitsml/mathml_helper.rb diff --git a/docs/README.adoc b/docs/README.adoc index b55c7e9..8be7ffe 100644 --- a/docs/README.adoc +++ b/docs/README.adoc @@ -40,8 +40,22 @@ The UnitsML Ruby library consists of several key components: * Prefix: Represents unit prefixes (k, m, μ, etc.) * Dimension: Represents physical dimensions -The library also includes the full https://github.com/unitsml/unitsdb[UnitsDB] -units database, which contains standard units, prefixes, and dimensions. +The library uses the https://github.com/unitsml/unitsdb-ruby[unitsdb-ruby] +gem as the primary runtime source for standard units, prefixes, and dimensions. +A checked-in `unitsdb/` directory is retained for static/Opal fallback loading. + +== Development setup + +During local development, this repository expects `lutaml-model`, `mml`, +`plurimath`, and `unitsdb-ruby` to be available as sibling checkouts at +`../lutaml-model`, `../mml`, `../plurimath`, and `../unitsdb-ruby`. The +development `Gemfile` uses Bundler `path` dependencies for those repositories. + +The standard Ruby runtime path goes through `::Unitsdb.database`. When a local +`unitsdb-ruby` checkout does not have its `data/` submodule populated, UnitsML +falls back to the packaged YAML files under `vendor/unitsdb`. The checked-in +`unitsdb/` YAML files are reserved for the Opal/static fallback in +`lib/unitsml/opal_unitsdb.rb`. == Usage diff --git a/lib/unitsml.rb b/lib/unitsml.rb index 47d40c4..8221d8e 100644 --- a/lib/unitsml.rb +++ b/lib/unitsml.rb @@ -13,6 +13,7 @@ module Unitsml autoload :FencedNumeric, "unitsml/fenced_numeric" autoload :Formula, "unitsml/formula" autoload :IntermediateExpRules, "unitsml/intermediate_exp_rules" + autoload :MathmlHelper, "unitsml/mathml_helper" autoload :Model, "unitsml/model" autoload :Namespace, "unitsml/namespace" autoload :Number, "unitsml/number" diff --git a/lib/unitsml/dimension.rb b/lib/unitsml/dimension.rb index 50fc686..f371779 100644 --- a/lib/unitsml/dimension.rb +++ b/lib/unitsml/dimension.rb @@ -2,6 +2,8 @@ module Unitsml class Dimension + include MathmlHelper + attr_accessor :dimension_name, :power_numerator def initialize(dimension_name, power_numerator = nil) @@ -26,7 +28,7 @@ def dim_symbols def to_mathml(options) # MathML key's value in unitsdb/dimensions.yaml # file includes mi tags only. - value = ::Mml::V4::Mi.from_xml(dim_symbols.mathml) + value = mml_v4_from_xml(::Mml::V4::Mi, dim_symbols.mathml) method_name = if power_numerator value = msup_tag(value, options) :msup @@ -94,8 +96,9 @@ def element_name def msup_tag(value, options) mathml = power_numerator.to_mathml(options) - msup = ::Mml::V4::Msup.new( - mrow_value: [::Mml::V4::Mrow.new(mi_value: [value])], + msup = mml_v4_new( + ::Mml::V4::Msup, + mrow_value: [mml_v4_new(::Mml::V4::Mrow, mi_value: [value])], ) [mathml].flatten.each do |record| record_values = msup.public_send("#{record[:method_name]}_value") || [] diff --git a/lib/unitsml/extender.rb b/lib/unitsml/extender.rb index 897f24e..979bcbb 100644 --- a/lib/unitsml/extender.rb +++ b/lib/unitsml/extender.rb @@ -2,6 +2,8 @@ module Unitsml class Extender + include MathmlHelper + attr_accessor :symbol def initialize(symbol) @@ -18,7 +20,7 @@ def to_mathml(options) extender = multiplier(options[:multiplier] || "⋅", unicode: true) { method_name: :mo, - value: ::Mml::V4::Mo.new(value: extender, rspace: rspace), + value: mml_v4_new(::Mml::V4::Mo, value: extender, rspace: rspace) } end diff --git a/lib/unitsml/fenced.rb b/lib/unitsml/fenced.rb index 0ebef7f..7d70e04 100644 --- a/lib/unitsml/fenced.rb +++ b/lib/unitsml/fenced.rb @@ -3,6 +3,7 @@ module Unitsml class Fenced include FencedNumeric + include MathmlHelper attr_reader :open_paren, :value, :close_paren @@ -31,12 +32,15 @@ def to_mathml(options = {}) mathml = value.to_mathml(options) return mathml unless options[:explicit_parenthesis] - fenced = ::Mml::V4::Mrow.new(mo_value: [::Mml::V4::Mo.new(value: open_paren, lutaml_register: :mml_v4)], lutaml_register: :mml_v4) + fenced = mml_v4_new( + ::Mml::V4::Mrow, + mo_value: [mml_v4_new(::Mml::V4::Mo, value: open_paren)], + ) fenced.ordered = true fenced.element_order ||= [xml_order_element("mo")] [mathml].flatten.each { |record| add_math_element(fenced, record) } - fenced.mo_value << ::Mml::V4::Mo.new(value: close_paren) - fenced.element_order << xml_order_element("mo") + fenced.mo_value << mml_v4_new(::Mml::V4::Mo, value: close_paren) + fenced.element_order << xml_order_element('mo') { method_name: :mrow, value: fenced } end diff --git a/lib/unitsml/formula.rb b/lib/unitsml/formula.rb index 12dead5..3428294 100644 --- a/lib/unitsml/formula.rb +++ b/lib/unitsml/formula.rb @@ -5,6 +5,8 @@ module Unitsml class Formula + include MathmlHelper + attr_accessor :value, :explicit_value, :root def initialize(value = [], @@ -29,7 +31,7 @@ def ==(other) def to_mathml(options = {}) if root options = update_options(options) - math = ::Mml::V4::Math.new(display: "block", lutaml_register: :mml_v4) + math = mml_v4_new(::Mml::V4::Math, display: 'block') math.ordered = true math.element_order ||= [] value.each do |instance| @@ -78,8 +80,7 @@ def to_plurimath(options = {}) :asciimath) end - Plurimath::Math.parse(compact_mathml_for_plurimath(to_mathml(options)), - :mathml) + Plurimath::Math.parse(compact_mathml_for_plurimath(to_mathml(options)), :mathml) end def dimensions_extraction @@ -215,5 +216,9 @@ def update_options(options) def compact_mathml_for_plurimath(mathml) mathml.gsub(/>\s+<").strip end + + def compact_mathml_for_plurimath(mathml) + mathml.gsub(/>\s+<").strip + end end end diff --git a/lib/unitsml/mathml_helper.rb b/lib/unitsml/mathml_helper.rb new file mode 100644 index 0000000..e5e9132 --- /dev/null +++ b/lib/unitsml/mathml_helper.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +module Unitsml + module MathmlHelper + module_function + + def mml_v4_context_id + ::Mml::V4::Configuration.context + ::Mml::V4::Configuration.context_id + end + + def mml_v4_from_xml(klass, xml) + klass.from_xml(xml, register: mml_v4_context_id) + end + + def mml_v4_new(klass, **attributes) + klass.new(**attributes, lutaml_register: mml_v4_context_id) + end + end +end diff --git a/lib/unitsml/number.rb b/lib/unitsml/number.rb index a1fa826..851a922 100644 --- a/lib/unitsml/number.rb +++ b/lib/unitsml/number.rb @@ -3,6 +3,7 @@ module Unitsml class Number include FencedNumeric + include MathmlHelper attr_accessor :value alias raw_value value @@ -19,8 +20,8 @@ def ==(other) def to_mathml(_options) matched_value = value&.match(/-?(.+)/) mn_value = matched_value ? matched_value[1] : value - mn_tag = ::Mml::V4::Mn.new(value: mn_value) - value.start_with?("-") ? mrow_hash(mn_tag) : mn_hash(mn_tag) + mn_tag = mml_v4_new(::Mml::V4::Mn, value: mn_value) + value.start_with?('-') ? mrow_hash(mn_tag) : mn_hash(mn_tag) end def to_html(_options) @@ -62,10 +63,11 @@ def float_to_display def mrow_hash(mn_tag) { method_name: :mrow, - value: ::Mml::V4::Mrow.new( - mo_value: [::Mml::V4::Mo.new(value: "−")], + value: mml_v4_new( + ::Mml::V4::Mrow, + mo_value: [mml_v4_new(::Mml::V4::Mo, value: '−')], mn_value: [mn_tag], - ), + ) } end diff --git a/lib/unitsml/prefix.rb b/lib/unitsml/prefix.rb index fd17e47..63568d0 100644 --- a/lib/unitsml/prefix.rb +++ b/lib/unitsml/prefix.rb @@ -2,6 +2,8 @@ module Unitsml class Prefix + include MathmlHelper + attr_accessor :prefix_name, :only_instance def initialize(prefix_name, only_instance = false) @@ -41,7 +43,7 @@ def to_mathml(_) ) return symbol unless only_instance - { method_name: :mi, value: ::Mml::V4::Mi.new(value: symbol) } + { method_name: :mi, value: mml_v4_new(::Mml::V4::Mi, value: symbol) } end def to_latex(_) diff --git a/lib/unitsml/unit.rb b/lib/unitsml/unit.rb index 09982ce..d2b290e 100644 --- a/lib/unitsml/unit.rb +++ b/lib/unitsml/unit.rb @@ -2,6 +2,8 @@ module Unitsml class Unit + include MathmlHelper + attr_accessor :unit_name, :power_numerator, :prefix SI_UNIT_SYSTEM = %w[si_base si_derived_special @@ -37,7 +39,7 @@ def unit_symbols def to_mathml(options) value = unit_symbols&.mathml tag_name = value.match(/^<(?\w+)/)[:tag] - value = ::Mml::V4.const_get(tag_name.capitalize).from_xml(value, register: :mml_v4) + value = mml_v4_from_xml(::Mml::V4.const_get(tag_name.capitalize), value) value.value = "#{prefix.to_mathml(options)}#{value.value}" if prefix if power_numerator value = msup_tag( @@ -127,7 +129,7 @@ def system_reference end def msup_tag(value, options) - msup = ::Mml::V4::Msup.new(lutaml_register: :mml_v4) + msup = mml_v4_new(::Mml::V4::Msup) msup.ordered = true msup.element_order = [] [value, power_numerator.to_mathml(options)].flatten.each do |record| diff --git a/lib/unitsml/unitsdb.rb b/lib/unitsml/unitsdb.rb index 2fa60a0..8f7ca7c 100644 --- a/lib/unitsml/unitsdb.rb +++ b/lib/unitsml/unitsdb.rb @@ -13,20 +13,28 @@ module Unitsdb autoload :SiDerivedBase, "#{__dir__}/unitsdb/si_derived_base" class << self + REQUIRED_DATABASE_FILES = %w[ + prefixes.yaml + dimensions.yaml + units.yaml + quantities.yaml + unit_systems.yaml + ].freeze + def units - Units.new(units: ::Unitsdb.database.units) + Units.new(units: database.units) end def prefixes - Prefixes.new(prefixes: ::Unitsdb.database.prefixes) + Prefixes.new(prefixes: database.prefixes) end def dimensions - Dimensions.new(dimensions: ::Unitsdb.database.dimensions) + Dimensions.new(dimensions: database.dimensions) end def quantities - Quantities.new(quantities: ::Unitsdb.database.quantities) + Quantities.new(quantities: database.quantities) end def prefixes_array @@ -39,6 +47,45 @@ def prefixes_by_size(size) @sized_prefixes[size] = prefixes_array.select { |p| p.size == size } end + + def database + @database ||= load_database + end + + private + + def load_database + if ::Unitsdb.respond_to?(:database) + return ::Unitsdb.database + end + + ::Unitsdb::Database.from_db(database_path) + rescue ::Unitsdb::Errors::DatabaseNotFoundError, + ::Unitsdb::Errors::DatabaseFileNotFoundError + ::Unitsdb::Database.from_db(database_path) + end + + def database_path + candidate_database_paths.find { |path| database_files_present?(path) } || + File.join(unitsdb_gem_path, "vendor", "unitsdb") + end + + def candidate_database_paths + [ + File.join(unitsdb_gem_path, "data"), + File.join(unitsdb_gem_path, "vendor", "unitsdb") + ] + end + + def database_files_present?(dir_path) + REQUIRED_DATABASE_FILES.all? do |file_name| + File.exist?(File.join(dir_path, file_name)) + end + end + + def unitsdb_gem_path + Gem.loaded_specs.fetch("unitsdb").full_gem_path + end end end end diff --git a/lib/unitsml/unitsdb/dimension.rb b/lib/unitsml/unitsdb/dimension.rb index 223613f..0bfa2d5 100644 --- a/lib/unitsml/unitsdb/dimension.rb +++ b/lib/unitsml/unitsdb/dimension.rb @@ -50,9 +50,9 @@ def electric_current=(value) end def dim_symbols - processed_keys.map do |vec| - public_send(vec)&.dim_symbols&.map(&:id) - end.flatten.compact + processed_keys.flat_map do |vec| + dimension_symbols_for(public_send(vec)).map(&:id) + end.compact end def processed_symbol @@ -76,10 +76,21 @@ def quantities_common_code(instance_var, value) instance_variable_set(:"@#{instance_var}", value) @processed_keys << instance_var.to_s - return if Lutaml::Model::Utils.empty?(value.symbols) + dim_symbols = dimension_symbols_for(value) + return if Lutaml::Model::Utils.empty?(dim_symbols) @parsable = true - value.dim_symbols_ids(@parsables, id) + dim_symbols.each { |dim_sym| @parsables[dim_sym.id] = id } + end + + def dimension_symbols_for(value) + return [] if value.nil? + + if value.respond_to?(:dim_symbols) + Array(value.dim_symbols) + else + Array(value.symbols) + end end def wrap_dimension_value(value) diff --git a/lib/unitsml/utility.rb b/lib/unitsml/utility.rb index a840b04..d5c069b 100644 --- a/lib/unitsml/utility.rb +++ b/lib/unitsml/utility.rb @@ -139,26 +139,68 @@ def unit_numerator_float(object_hash) end def prefix_object(prefix) - return prefix unless prefix.is_a?(String) - return nil unless Unitsdb.prefixes_array.any?(prefix) + return nil if prefix.nil? + return prefix if prefix_like?(prefix) - Prefix.new(prefix) + if prefix.is_a?(String) + return nil unless Unitsdb.prefixes_array.any?(prefix) + + return Prefix.new(prefix) + end + + return Unitsdb.prefixes.find_by_id(prefix.id) if prefix.respond_to?(:id) + + prefix end def combine_prefixes(p1, p2) + p1 = prefix_object(p1) + p2 = prefix_object(p2) return nil if p1.nil? && p2.nil? - return p1.symbolid if p2.nil? - return p2.symbolid if p1.nil? - return UNKNOWN if p1.base != p2.base + return prefix_symbolid(p1) if p2.nil? + return prefix_symbolid(p2) if p1.nil? + return UNKNOWN if prefix_base(p1) != prefix_base(p2) Unitsdb.prefixes_array.each do |prefix_name| p = prefix_object(prefix_name) - return p if p.base == p1.base && p.power == p1.power + p2.power + return p if prefix_base(p) == prefix_base(p1) && + prefix_power(p) == prefix_power(p1) + prefix_power(p2) end UNKNOWN end + def prefix_like?(prefix) + prefix.respond_to?(:base) && prefix.respond_to?(:power) && + (prefix.respond_to?(:symbolid) || prefix.respond_to?(:symbols)) + end + + def prefix_symbolid(prefix) + return prefix.symbolid if prefix.respond_to?(:symbolid) + + resolved_prefix(prefix)&.symbols&.first&.ascii + end + + def prefix_base(prefix) + return prefix.base if prefix.respond_to?(:base) + + resolved_prefix(prefix)&.base + end + + def prefix_power(prefix) + return prefix.power if prefix.respond_to?(:power) + + resolved_prefix(prefix)&.power + end + + def resolved_prefix(prefix) + return prefix if prefix.nil? + return prefix if prefix.respond_to?(:symbols) && prefix.respond_to?(:base) && prefix.respond_to?(:power) + return Unitsdb.prefixes.find_by_id(prefix.id) if prefix.respond_to?(:id) + + prefix + end + def unit(units, formula, dims, norm_text, name, options) attributes = { id: unit_id(norm_text), diff --git a/unitsml.gemspec b/unitsml.gemspec index e2e95c2..f1ffe95 100644 --- a/unitsml.gemspec +++ b/unitsml.gemspec @@ -30,7 +30,7 @@ Gem::Specification.new do |spec| spec.bindir = "exe" spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } - spec.require_paths = ["lib", "unitsdb/**/*.yaml"] + spec.require_paths = ['lib'] spec.add_dependency "htmlentities" spec.add_dependency "lutaml-model", "~> 0.8.0" From 06238d03e86cde3be1ef64c8ca5756989f8a6962 Mon Sep 17 00:00:00 2001 From: suleman-uzair Date: Tue, 7 Apr 2026 22:50:39 +0500 Subject: [PATCH 03/15] feat: use Unitsdb::Configuration for context and type substitution management --- Gemfile | 4 +- docs/README.adoc | 5 +- lib/unitsml.rb | 44 +------------- lib/unitsml/configuration.rb | 45 ++++++++++++++ lib/unitsml/dimension.rb | 11 ++-- lib/unitsml/extender.rb | 2 +- lib/unitsml/fenced.rb | 6 +- lib/unitsml/formula.rb | 7 ++- lib/unitsml/mathml_helper.rb | 19 ++++-- lib/unitsml/number.rb | 6 +- lib/unitsml/prefix.rb | 2 +- lib/unitsml/unit.rb | 4 +- lib/unitsml/unitsdb.rb | 29 +++++----- lib/unitsml/unitsdb/database.rb | 16 +++++ lib/unitsml/unitsdb/dimension.rb | 28 ++++----- ...nsion_quantity.rb => dimension_details.rb} | 4 +- lib/unitsml/unitsdb/dimensions.rb | 2 + lib/unitsml/unitsdb/prefix_reference.rb | 2 + lib/unitsml/unitsdb/prefixes.rb | 2 + lib/unitsml/unitsdb/quantities.rb | 2 + lib/unitsml/unitsdb/si_derived_base.rb | 14 ----- lib/unitsml/unitsdb/unit.rb | 10 +--- lib/unitsml/unitsdb/units.rb | 6 +- lib/unitsml/utility.rb | 58 +++++++++++++++---- spec/unitsml_spec.rb | 6 ++ 25 files changed, 191 insertions(+), 143 deletions(-) create mode 100644 lib/unitsml/configuration.rb create mode 100644 lib/unitsml/unitsdb/database.rb rename lib/unitsml/unitsdb/{dimension_quantity.rb => dimension_details.rb} (59%) delete mode 100644 lib/unitsml/unitsdb/si_derived_base.rb diff --git a/Gemfile b/Gemfile index bf80b69..30f3021 100644 --- a/Gemfile +++ b/Gemfile @@ -6,11 +6,11 @@ source "https://rubygems.org" gemspec gem "canon" -gem "lutaml-model", "~> 0.8.0", github: "lutaml/lutaml-model", branch: "main" +gem "lutaml-model", "~> 0.8.0", github: "lutaml/lutaml-model", branch: "fix/global-context-register-lookup-fallback" gem "mml", github: "plurimath/mml", branch: "main" gem "oga" gem "ox" -gem "plurimath", github: "plurimath/plurimath", branch: "rt-lutaml-080" +gem "plurimath", github: "plurimath/plurimath", branch: "feat/autoload-and-mml-update" gem "pry" gem "rake" gem "rspec" diff --git a/docs/README.adoc b/docs/README.adoc index 8be7ffe..dca3f35 100644 --- a/docs/README.adoc +++ b/docs/README.adoc @@ -42,7 +42,6 @@ The UnitsML Ruby library consists of several key components: The library uses the https://github.com/unitsml/unitsdb-ruby[unitsdb-ruby] gem as the primary runtime source for standard units, prefixes, and dimensions. -A checked-in `unitsdb/` directory is retained for static/Opal fallback loading. == Development setup @@ -53,9 +52,7 @@ development `Gemfile` uses Bundler `path` dependencies for those repositories. The standard Ruby runtime path goes through `::Unitsdb.database`. When a local `unitsdb-ruby` checkout does not have its `data/` submodule populated, UnitsML -falls back to the packaged YAML files under `vendor/unitsdb`. The checked-in -`unitsdb/` YAML files are reserved for the Opal/static fallback in -`lib/unitsml/opal_unitsdb.rb`. +falls back to the packaged YAML files under `vendor/unitsdb`. == Usage diff --git a/lib/unitsml.rb b/lib/unitsml.rb index 8221d8e..609341e 100644 --- a/lib/unitsml.rb +++ b/lib/unitsml.rb @@ -7,6 +7,7 @@ module Unitsml module_function autoload :Dimension, "unitsml/dimension" + autoload :Configuration, "unitsml/configuration" autoload :Errors, "unitsml/errors" autoload :Extender, "unitsml/extender" autoload :Fenced, "unitsml/fenced" @@ -27,54 +28,11 @@ module Unitsml autoload :Utility, "unitsml/utility" autoload :VERSION, "unitsml/version" - REGISTER_ID = :unitsml_ruby - def parse(string) Unitsml::Parser.new(string).parse end - - def register - @register ||= Lutaml::Model::GlobalRegister.lookup(REGISTER_ID) - end - - def register_model(klass, id:) - register.register_model(klass, id: id) - end - - def get_class_from_register(class_name) - register.get_class(class_name) - end - - def register_type_substitution(from:, to:) - register.register_global_type_substitution( - from_type: from, - to_type: to, - ) - end -end - -Lutaml::Model::GlobalRegister.register( - Lutaml::Model::Register.new(Unitsml::REGISTER_ID), -) - -{ - Unitsdb::Unit => Unitsml::Unitsdb::Unit, - Unitsdb::Units => Unitsml::Unitsdb::Units, - Unitsdb::Prefixes => Unitsml::Unitsdb::Prefixes, - Unitsdb::Dimension => Unitsml::Unitsdb::Dimension, - Unitsdb::PrefixReference => Unitsml::Unitsdb::PrefixReference, - Unitsdb::DimensionDetails => Unitsml::Unitsdb::DimensionQuantity, -}.each do |key, value| - Unitsml.register_type_substitution(from: key, to: value) end -[ - [Unitsml::Unitsdb::Dimensions, :unitsdb_dimensions], - [Unitsml::Unitsdb::Prefixes, :unitsdb_prefixes], - [Unitsml::Unitsdb::Quantities, :unitsdb_quantities], - [Unitsml::Unitsdb::Units, :unitsdb_units], -].each { |klass, id| Unitsml.register_model(klass, id: id) } - Lutaml::Model::Config.configure do |config| config.xml_adapter_type = RUBY_ENGINE == "opal" ? :oga : :ox end diff --git a/lib/unitsml/configuration.rb b/lib/unitsml/configuration.rb new file mode 100644 index 0000000..47b3141 --- /dev/null +++ b/lib/unitsml/configuration.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +module Unitsml + module Configuration + CONTEXT_ID = :unitsml_ruby + + module_function + + def context_id + CONTEXT_ID + end + + def context(force_populate: false) + existing = ::Unitsdb::Configuration.find_context(context_id) + return existing if existing && !force_populate + + build_context + end + + def register_model(klass, id:) + registered_models[id.to_sym] = klass + end + + def registered_models + @registered_models ||= {} + end + + def build_context + ::Unitsdb::Configuration.context # ensure unitsdb context exists + + substitutions = registered_models.each_value.filter_map do |klass| + parent = klass.superclass + next if parent == Object + + { from_type: parent, to_type: klass } + end + + ::Unitsdb::Configuration.populate_context( + id: context_id, + fallback_to: [::Unitsdb::Configuration.context_id], + substitutions: substitutions, + ) + end + end +end diff --git a/lib/unitsml/dimension.rb b/lib/unitsml/dimension.rb index f371779..029144a 100644 --- a/lib/unitsml/dimension.rb +++ b/lib/unitsml/dimension.rb @@ -28,7 +28,7 @@ def dim_symbols def to_mathml(options) # MathML key's value in unitsdb/dimensions.yaml # file includes mi tags only. - value = mml_v4_from_xml(::Mml::V4::Mi, dim_symbols.mathml) + value = mml_v4_from_xml(:mi, dim_symbols.mathml) method_name = if power_numerator value = msup_tag(value, options) :msup @@ -65,7 +65,10 @@ def to_xml(_) symbol: dim_instance.processed_symbol, power_numerator: power_numerator&.raw_value || 1, } - Model::DimensionQuantities.const_get(modelize(element_name)).new(attributes) + Model::DimensionQuantities.const_get(modelize(element_name)).new( + **attributes, + lutaml_register: Configuration.context.id, + ) end def xml_instances_hash(options) @@ -97,8 +100,8 @@ def element_name def msup_tag(value, options) mathml = power_numerator.to_mathml(options) msup = mml_v4_new( - ::Mml::V4::Msup, - mrow_value: [mml_v4_new(::Mml::V4::Mrow, mi_value: [value])], + :msup, + mrow_value: [mml_v4_new(:mrow, mi_value: [value])], ) [mathml].flatten.each do |record| record_values = msup.public_send("#{record[:method_name]}_value") || [] diff --git a/lib/unitsml/extender.rb b/lib/unitsml/extender.rb index 979bcbb..3a052e7 100644 --- a/lib/unitsml/extender.rb +++ b/lib/unitsml/extender.rb @@ -20,7 +20,7 @@ def to_mathml(options) extender = multiplier(options[:multiplier] || "⋅", unicode: true) { method_name: :mo, - value: mml_v4_new(::Mml::V4::Mo, value: extender, rspace: rspace) + value: mml_v4_new(:mo, value: extender, rspace: rspace) } end diff --git a/lib/unitsml/fenced.rb b/lib/unitsml/fenced.rb index 7d70e04..e587c0f 100644 --- a/lib/unitsml/fenced.rb +++ b/lib/unitsml/fenced.rb @@ -33,13 +33,13 @@ def to_mathml(options = {}) return mathml unless options[:explicit_parenthesis] fenced = mml_v4_new( - ::Mml::V4::Mrow, - mo_value: [mml_v4_new(::Mml::V4::Mo, value: open_paren)], + :mrow, + mo_value: [mml_v4_new(:mo, value: open_paren)], ) fenced.ordered = true fenced.element_order ||= [xml_order_element("mo")] [mathml].flatten.each { |record| add_math_element(fenced, record) } - fenced.mo_value << mml_v4_new(::Mml::V4::Mo, value: close_paren) + fenced.mo_value << mml_v4_new(:mo, value: close_paren) fenced.element_order << xml_order_element('mo') { method_name: :mrow, value: fenced } end diff --git a/lib/unitsml/formula.rb b/lib/unitsml/formula.rb index 3428294..6a06200 100644 --- a/lib/unitsml/formula.rb +++ b/lib/unitsml/formula.rb @@ -31,7 +31,7 @@ def ==(other) def to_mathml(options = {}) if root options = update_options(options) - math = mml_v4_new(::Mml::V4::Math, display: 'block') + math = mml_v4_new(:math, display: 'block') math.ordered = true math.element_order ||= [] value.each do |instance| @@ -151,7 +151,10 @@ def dimensions(dims, options) dim_id = dims.map(&:generate_id).join attributes = { id: "D_#{dim_id}" } dims.each { |dim| attributes.merge!(dim.xml_instances_hash(options)) } - Model::Dimension.new(attributes).to_xml.force_encoding("UTF-8") + Model::Dimension.new( + **attributes, + lutaml_register: Configuration.context.id, + ).to_xml.force_encoding("UTF-8") end def sort_dims(values) diff --git a/lib/unitsml/mathml_helper.rb b/lib/unitsml/mathml_helper.rb index e5e9132..633776c 100644 --- a/lib/unitsml/mathml_helper.rb +++ b/lib/unitsml/mathml_helper.rb @@ -4,17 +4,24 @@ module Unitsml module MathmlHelper module_function - def mml_v4_context_id + def mml_v4_context ::Mml::V4::Configuration.context - ::Mml::V4::Configuration.context_id end - def mml_v4_from_xml(klass, xml) - klass.from_xml(xml, register: mml_v4_context_id) + def mml_v4_from_xml(klass_ref, xml) + mml_v4_class_for(klass_ref).from_xml(xml, context: mml_v4_context.id) end - def mml_v4_new(klass, **attributes) - klass.new(**attributes, lutaml_register: mml_v4_context_id) + def mml_v4_new(klass_ref, **attributes) + klass = mml_v4_class_for(klass_ref) + # lutaml-model still resolves nested symbolic child types via `lutaml_register`. + klass.new(**attributes, lutaml_register: mml_v4_context.id) + end + + def mml_v4_class_for(klass_ref) + return klass_ref if klass_ref.is_a?(Class) + + mml_v4_context.lookup_local(klass_ref.to_sym) end end end diff --git a/lib/unitsml/number.rb b/lib/unitsml/number.rb index 851a922..0c2f2f3 100644 --- a/lib/unitsml/number.rb +++ b/lib/unitsml/number.rb @@ -20,7 +20,7 @@ def ==(other) def to_mathml(_options) matched_value = value&.match(/-?(.+)/) mn_value = matched_value ? matched_value[1] : value - mn_tag = mml_v4_new(::Mml::V4::Mn, value: mn_value) + mn_tag = mml_v4_new(:mn, value: mn_value) value.start_with?('-') ? mrow_hash(mn_tag) : mn_hash(mn_tag) end @@ -64,8 +64,8 @@ def mrow_hash(mn_tag) { method_name: :mrow, value: mml_v4_new( - ::Mml::V4::Mrow, - mo_value: [mml_v4_new(::Mml::V4::Mo, value: '−')], + :mrow, + mo_value: [mml_v4_new(:mo, value: '−')], mn_value: [mn_tag], ) } diff --git a/lib/unitsml/prefix.rb b/lib/unitsml/prefix.rb index 63568d0..e441817 100644 --- a/lib/unitsml/prefix.rb +++ b/lib/unitsml/prefix.rb @@ -43,7 +43,7 @@ def to_mathml(_) ) return symbol unless only_instance - { method_name: :mi, value: mml_v4_new(::Mml::V4::Mi, value: symbol) } + { method_name: :mi, value: mml_v4_new(:mi, value: symbol) } end def to_latex(_) diff --git a/lib/unitsml/unit.rb b/lib/unitsml/unit.rb index d2b290e..26040d6 100644 --- a/lib/unitsml/unit.rb +++ b/lib/unitsml/unit.rb @@ -39,7 +39,7 @@ def unit_symbols def to_mathml(options) value = unit_symbols&.mathml tag_name = value.match(/^<(?\w+)/)[:tag] - value = mml_v4_from_xml(::Mml::V4.const_get(tag_name.capitalize), value) + value = mml_v4_from_xml(tag_name, value) value.value = "#{prefix.to_mathml(options)}#{value.value}" if prefix if power_numerator value = msup_tag( @@ -129,7 +129,7 @@ def system_reference end def msup_tag(value, options) - msup = mml_v4_new(::Mml::V4::Msup) + msup = mml_v4_new(:msup) msup.ordered = true msup.element_order = [] [value, power_numerator.to_mathml(options)].flatten.each do |record| diff --git a/lib/unitsml/unitsdb.rb b/lib/unitsml/unitsdb.rb index 8f7ca7c..3fcb8d5 100644 --- a/lib/unitsml/unitsdb.rb +++ b/lib/unitsml/unitsdb.rb @@ -1,17 +1,16 @@ # frozen_string_literal: true +require_relative "unitsdb/database" +require_relative "unitsdb/dimension_details" +require_relative "unitsdb/prefix_reference" +require_relative "unitsdb/dimension" +require_relative "unitsdb/dimensions" +require_relative "unitsdb/unit" +require_relative "unitsdb/units" +require_relative "unitsdb/prefixes" +require_relative "unitsdb/quantities" module Unitsml module Unitsdb - autoload :Unit, "#{__dir__}/unitsdb/unit" - autoload :Units, "#{__dir__}/unitsdb/units" - autoload :Prefixes, "#{__dir__}/unitsdb/prefixes" - autoload :Dimension, "#{__dir__}/unitsdb/dimension" - autoload :Dimensions, "#{__dir__}/unitsdb/dimensions" - autoload :Quantities, "#{__dir__}/unitsdb/quantities" - autoload :PrefixReference, "#{__dir__}/unitsdb/prefix_reference" - autoload :DimensionQuantity, "#{__dir__}/unitsdb/dimension_quantity" - autoload :SiDerivedBase, "#{__dir__}/unitsdb/si_derived_base" - class << self REQUIRED_DATABASE_FILES = %w[ prefixes.yaml @@ -22,19 +21,19 @@ class << self ].freeze def units - Units.new(units: database.units) + @units ||= Units.new(units: database.units, lutaml_register: Configuration.context.id) end def prefixes - Prefixes.new(prefixes: database.prefixes) + @prefixes ||= Prefixes.new(prefixes: database.prefixes, lutaml_register: Configuration.context.id) end def dimensions - Dimensions.new(dimensions: database.dimensions) + @dimensions ||= Dimensions.new(dimensions: database.dimensions, lutaml_register: Configuration.context.id) end def quantities - Quantities.new(quantities: database.quantities) + @quantities ||= Quantities.new(quantities: database.quantities, lutaml_register: Configuration.context.id) end def prefixes_array @@ -56,7 +55,7 @@ def database def load_database if ::Unitsdb.respond_to?(:database) - return ::Unitsdb.database + return ::Unitsdb.database(context: Configuration.context.id) end ::Unitsdb::Database.from_db(database_path) diff --git a/lib/unitsml/unitsdb/database.rb b/lib/unitsml/unitsdb/database.rb new file mode 100644 index 0000000..fbbfec2 --- /dev/null +++ b/lib/unitsml/unitsdb/database.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +module Unitsml + module Unitsdb + class Database < ::Unitsdb::Database + def self.from_db(dir_path, context: Unitsml::Configuration.context.id) + if RUBY_ENGINE == "opal" + else + super + end + end + + Configuration.register_model(self, id: :database) + end + end +end diff --git a/lib/unitsml/unitsdb/dimension.rb b/lib/unitsml/unitsdb/dimension.rb index 0bfa2d5..df5a975 100644 --- a/lib/unitsml/unitsdb/dimension.rb +++ b/lib/unitsml/unitsdb/dimension.rb @@ -16,37 +16,35 @@ def initialize(attrs) end def length=(value) - quantities_common_code(:length, wrap_dimension_value(value)) + quantities_common_code(:length, value) end def mass=(value) - quantities_common_code(:mass, wrap_dimension_value(value)) + quantities_common_code(:mass, value) end def time=(value) - quantities_common_code(:time, wrap_dimension_value(value)) + quantities_common_code(:time, value) end def thermodynamic_temperature=(value) - quantities_common_code(:thermodynamic_temperature, - wrap_dimension_value(value)) + quantities_common_code(:thermodynamic_temperature, value) end def amount_of_substance=(value) - quantities_common_code(:amount_of_substance, - wrap_dimension_value(value)) + quantities_common_code(:amount_of_substance, value) end def luminous_intensity=(value) - quantities_common_code(:luminous_intensity, wrap_dimension_value(value)) + quantities_common_code(:luminous_intensity, value) end def plane_angle=(value) - quantities_common_code(:plane_angle, wrap_dimension_value(value)) + quantities_common_code(:plane_angle, value) end def electric_current=(value) - quantities_common_code(:electric_current, wrap_dimension_value(value)) + quantities_common_code(:electric_current, value) end def dim_symbols @@ -92,14 +90,8 @@ def dimension_symbols_for(value) Array(value.symbols) end end - - def wrap_dimension_value(value) - return value if value.is_a?(DimensionQuantity) - return DimensionQuantity.new(value.to_hash) if value.is_a?(::Unitsdb::DimensionDetails) - return DimensionQuantity.new(value) if value.is_a?(Hash) - - value - end end + + Configuration.register_model(Dimension, id: :dimension) end end diff --git a/lib/unitsml/unitsdb/dimension_quantity.rb b/lib/unitsml/unitsdb/dimension_details.rb similarity index 59% rename from lib/unitsml/unitsdb/dimension_quantity.rb rename to lib/unitsml/unitsdb/dimension_details.rb index 8d8c420..00dbe2c 100644 --- a/lib/unitsml/unitsdb/dimension_quantity.rb +++ b/lib/unitsml/unitsdb/dimension_details.rb @@ -2,10 +2,12 @@ module Unitsml module Unitsdb - class DimensionQuantity < ::Unitsdb::DimensionDetails + class DimensionDetails < ::Unitsdb::DimensionDetails def dim_symbols_ids(hash, dim_id) symbols&.each { |dim_sym| hash[dim_sym.id] = dim_id } end end + + Configuration.register_model(DimensionDetails, id: :dimension_details) end end diff --git a/lib/unitsml/unitsdb/dimensions.rb b/lib/unitsml/unitsdb/dimensions.rb index f606ec6..373bd34 100644 --- a/lib/unitsml/unitsdb/dimensions.rb +++ b/lib/unitsml/unitsdb/dimensions.rb @@ -32,5 +32,7 @@ def find(field, matching_data) dimensions.find { |dim| dim.send(field) == matching_data } end end + + Configuration.register_model(Dimensions, id: :dimensions) end end diff --git a/lib/unitsml/unitsdb/prefix_reference.rb b/lib/unitsml/unitsdb/prefix_reference.rb index 6187094..3c5e56b 100644 --- a/lib/unitsml/unitsdb/prefix_reference.rb +++ b/lib/unitsml/unitsdb/prefix_reference.rb @@ -21,5 +21,7 @@ def prefix end end end + + Configuration.register_model(PrefixReference, id: :prefix_reference) end end diff --git a/lib/unitsml/unitsdb/prefixes.rb b/lib/unitsml/unitsdb/prefixes.rb index 47ce5a2..80256b3 100644 --- a/lib/unitsml/unitsdb/prefixes.rb +++ b/lib/unitsml/unitsdb/prefixes.rb @@ -30,5 +30,7 @@ def find(matching_data, field, prefix_method) end end end + + Configuration.register_model(Prefixes, id: :prefixes) end end diff --git a/lib/unitsml/unitsdb/quantities.rb b/lib/unitsml/unitsdb/quantities.rb index 1c521f5..11383fa 100644 --- a/lib/unitsml/unitsdb/quantities.rb +++ b/lib/unitsml/unitsdb/quantities.rb @@ -9,5 +9,7 @@ def find_by_id(q_id) end end end + + Configuration.register_model(Quantities, id: :quantities) end end diff --git a/lib/unitsml/unitsdb/si_derived_base.rb b/lib/unitsml/unitsdb/si_derived_base.rb deleted file mode 100644 index 6b2f9b4..0000000 --- a/lib/unitsml/unitsdb/si_derived_base.rb +++ /dev/null @@ -1,14 +0,0 @@ -# frozen_string_literal: true - -module Unitsml - module Unitsdb - class SiDerivedBase < ::Unitsdb::SiDerivedBase - def prefix_reference=(value) - return super if value.nil? - return super if value.is_a?(PrefixReference) - - super(PrefixReference.new(value.to_hash)) - end - end - end -end diff --git a/lib/unitsml/unitsdb/unit.rb b/lib/unitsml/unitsdb/unit.rb index aa0964d..b2eaecd 100644 --- a/lib/unitsml/unitsdb/unit.rb +++ b/lib/unitsml/unitsdb/unit.rb @@ -3,14 +3,6 @@ module Unitsml module Unitsdb class Unit < ::Unitsdb::Unit - def si_derived_bases=(value) - return super if value.nil? - - super(value.map do |s| - SiDerivedBase.new(s.to_hash) - end) - end - def dimension_url quantity_id = quantity_references[0].id quantity = Unitsml::Unitsdb.quantities.find_by_id(quantity_id) @@ -24,6 +16,8 @@ def en_name def nist_id identifiers.find { |id| id.type == "nist" }&.id end + + Configuration.register_model(self, id: :unit) end end end diff --git a/lib/unitsml/unitsdb/units.rb b/lib/unitsml/unitsdb/units.rb index 7bbea64..8716839 100644 --- a/lib/unitsml/unitsdb/units.rb +++ b/lib/unitsml/unitsdb/units.rb @@ -3,10 +3,6 @@ module Unitsml module Unitsdb class Units < ::Unitsdb::Units - def units=(value) - super(value.map { |u| Unit.new(u.to_hash) }) - end - def find_by_id(u_id) find(u_id, :id, :identifiers) end @@ -45,5 +41,7 @@ def find(matching_data, field, unit_method) end end end + + Configuration.register_model(Units, id: :units) end end diff --git a/lib/unitsml/utility.rb b/lib/unitsml/utility.rb index d5c069b..c535337 100644 --- a/lib/unitsml/utility.rb +++ b/lib/unitsml/utility.rb @@ -210,7 +210,7 @@ def unit(units, formula, dims, norm_text, name, options) root_units: rootunits(units), } attributes[:dimension_url] = "##{dim_id(dims)}" if dims - Model::Unit.new(attributes).to_xml + Model::Unit.new(**attributes, lutaml_register: Configuration.context.id).to_xml .force_encoding("UTF-8") .gsub("<", "<") .gsub(">", ">") @@ -221,7 +221,10 @@ def unit(units, formula, dims, norm_text, name, options) def unitname(text, name) name ||= unit_instance(text)&.en_name || text - Model::Units::Name.new(name: name) + Model::Units::Name.new( + name: name, + lutaml_register: Configuration.context.id, + ) end def unitsymbols(formula, options) @@ -229,20 +232,30 @@ def unitsymbols(formula, options) Model::Units::Symbol.new( type: lang, content: formula.public_send(:"to_#{lang.downcase}", options), + lutaml_register: Configuration.context.id, ) end end def unitsystem(units) ret = [] - ret << Model::Units::System.new(name: "not_SI", type: "not_SI") if units.any? { |u| !u.si_system_type? } + if units.any? { |u| !u.si_system_type? } + ret << Model::Units::System.new( + name: "not_SI", + type: "not_SI", + lutaml_register: Configuration.context.id, + ) + end if units.any?(&:si_system_type?) if units.size == 1 base = units[0].downcase_system_type == "si_base" base = true if units[0].unit_name == "g" && units[0]&.prefix_name == "k" end - ret << Model::Units::System.new(name: "SI", - type: (base ? "SI_base" : "SI_derived")) + ret << Model::Units::System.new( + name: "SI", + type: (base ? "SI_base" : "SI_derived"), + lutaml_register: Configuration.context.id, + ) end ret end @@ -253,7 +266,10 @@ def dimension(norm_text) dim_attrs = { id: dim_id } dimid2dimensions(dim_id)&.compact&.each { |u| dimension1(u, dim_attrs) } - Model::Dimension.new(dim_attrs).to_xml.force_encoding("UTF-8") + Model::Dimension.new( + dim_attrs, + lutaml_register: Configuration.context.id, + ).to_xml.force_encoding("UTF-8") end def dimension1(dim, dims_hash) @@ -262,6 +278,7 @@ def dimension1(dim, dims_hash) dims_hash[underscore(dim_name).to_sym] = dim_klass.new( symbol: dim[:symbol], power_numerator: float_to_display(dim[:exponent]), + lutaml_register: Configuration.context.id, ) end @@ -296,14 +313,21 @@ def prefixes(units, options) prefix_power: prefix&.power, id: prefix&.id } type_and_methods = { ASCII: :to_asciimath, unicode: :to_unicode, LaTeX: :to_latex, HTML: :to_html } - prefix_attrs[:name] = Model::Prefixes::Name.new(content: prefix&.name) + prefix_attrs[:name] = Model::Prefixes::Name.new( + content: prefix&.name, + lutaml_register: Configuration.context.id, + ) prefix_attrs[:symbol] = type_and_methods.map do |type, method_name| Model::Prefixes::Symbol.new( type: type, content: prefix&.public_send(method_name, options), + lutaml_register: Configuration.context.id, ) end - Model::Prefix.new(prefix_attrs).to_xml.force_encoding("UTF-8").gsub( + Model::Prefix.new( + prefix_attrs, + lutaml_register: Configuration.context.id, + ).to_xml.force_encoding("UTF-8").gsub( "&", "&" ) end.join("\n") @@ -317,9 +341,15 @@ def rootunits(units) attributes[:prefix] = unit.prefix_name if unit.prefix unit.power_numerator && unit.power_numerator != "1" and attributes[:power_numerator] = unit.power_numerator.raw_value - Model::Units::EnumeratedRootUnit.new(attributes) + Model::Units::EnumeratedRootUnit.new( + **attributes, + lutaml_register: Configuration.context.id, + ) end - Model::Units::RootUnits.new(enumerated_root_unit: enum_root_units) + Model::Units::RootUnits.new( + enumerated_root_unit: enum_root_units, + lutaml_register: Configuration.context.id, + ) end def unit_id(text) @@ -340,7 +370,10 @@ def dimension_components(dims) dim_attrs = { id: dim_id(dims) } dims.map { |u| dimension1(u, dim_attrs) } - Model::Dimension.new(dim_attrs).to_xml.force_encoding("UTF-8") + Model::Dimension.new( + **dim_attrs, + lutaml_register: Configuration.context.id, + ).to_xml.force_encoding("UTF-8") end def quantity(normtext, instance) @@ -363,6 +396,7 @@ def model_quantity_xml(id, url) id: id, name: quantity_name(id), dimension_url: url, + lutaml_register: Configuration.context.id, ).to_xml.force_encoding("UTF-8") end @@ -370,7 +404,7 @@ def quantity_name(id) quantity_instance(id)&.names&.filter_map do |name| next unless name.lang == "en" - Model::Quantities::Name.new(content: name.value) + Model::Quantities::Name.new(content: name.value, lutaml_register: Configuration.context.id) end end diff --git a/spec/unitsml_spec.rb b/spec/unitsml_spec.rb index 733d854..054263e 100644 --- a/spec/unitsml_spec.rb +++ b/spec/unitsml_spec.rb @@ -4,4 +4,10 @@ it "has a version number" do expect(Unitsml::VERSION).not_to be_nil end + + it "parses a basic unit expression" do + formula = described_class.parse("mm") + expect(formula).to be_a(Unitsml::Formula) + expect(formula.to_latex).to eq("m\\ensuremath{\\mathrm{m}}") + end end From 5f5979244305a4d209c486eb806588948a60a966 Mon Sep 17 00:00:00 2001 From: suleman-uzair Date: Tue, 7 Apr 2026 22:54:56 +0500 Subject: [PATCH 04/15] added unitsdb-ruby branch name --- Gemfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Gemfile b/Gemfile index 30f3021..54ecfa4 100644 --- a/Gemfile +++ b/Gemfile @@ -19,3 +19,4 @@ gem "rubocop-performance" gem "rubocop-rake" gem "rubocop-rspec" gem "simplecov" +gem "unitsdb", github: "unitsml/unitsdb-ruby", branch: "feat/context-register-models" From 5aa5c95336e10f594da3e78a16f7b5820ce4456b Mon Sep 17 00:00:00 2001 From: suleman-uzair Date: Wed, 8 Apr 2026 20:47:02 +0500 Subject: [PATCH 05/15] feat: improve UnitsDB loading and add Opal payload error coverage --- lib/unitsml/errors.rb | 1 + .../errors/opal_payload_not_bundled_error.rb | 11 +++++ lib/unitsml/unitsdb/database.rb | 13 ++++-- spec/unitsml/unitsdb/database_spec.rb | 41 +++++++++++++++++++ 4 files changed, 62 insertions(+), 4 deletions(-) create mode 100644 lib/unitsml/errors/opal_payload_not_bundled_error.rb create mode 100644 spec/unitsml/unitsdb/database_spec.rb diff --git a/lib/unitsml/errors.rb b/lib/unitsml/errors.rb index 1731e5c..f2a5df4 100644 --- a/lib/unitsml/errors.rb +++ b/lib/unitsml/errors.rb @@ -3,6 +3,7 @@ module Unitsml module Errors autoload :BaseError, "unitsml/errors/base_error" + autoload :OpalPayloadNotBundledError, "unitsml/errors/opal_payload_not_bundled_error" autoload :PlurimathLoadError, "unitsml/errors/plurimath_load_error" end end diff --git a/lib/unitsml/errors/opal_payload_not_bundled_error.rb b/lib/unitsml/errors/opal_payload_not_bundled_error.rb new file mode 100644 index 0000000..02c1083 --- /dev/null +++ b/lib/unitsml/errors/opal_payload_not_bundled_error.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module Unitsml + module Errors + class OpalPayloadNotBundledError < Unitsml::Errors::BaseError + def to_s + "[unitsml] Error: Opal database payload is not bundled." + end + end + end +end diff --git a/lib/unitsml/unitsdb/database.rb b/lib/unitsml/unitsdb/database.rb index fbbfec2..c089f51 100644 --- a/lib/unitsml/unitsdb/database.rb +++ b/lib/unitsml/unitsdb/database.rb @@ -3,11 +3,16 @@ module Unitsml module Unitsdb class Database < ::Unitsdb::Database + DATABASE = nil + def self.from_db(dir_path, context: Unitsml::Configuration.context.id) - if RUBY_ENGINE == "opal" - else - super - end + return super unless RUBY_ENGINE == "opal" + + context_id = context.to_sym + raise Unitsml::Errors::OpalPayloadNotBundledError unless DATABASE + Unitsml::Configuration.context + + from_hash(DATABASE, register: context_id) end Configuration.register_model(self, id: :database) diff --git a/spec/unitsml/unitsdb/database_spec.rb b/spec/unitsml/unitsdb/database_spec.rb new file mode 100644 index 0000000..7c7080a --- /dev/null +++ b/spec/unitsml/unitsdb/database_spec.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +RSpec.describe Unitsml::Unitsdb::Database do + def with_replaced_constant(owner, constant_name, value) + had_constant = owner.const_defined?(constant_name, false) + original_value = owner.const_get(constant_name) if had_constant + + owner.send(:remove_const, constant_name) if had_constant + owner.const_set(constant_name, value) + + yield + ensure + owner.send(:remove_const, constant_name) if owner.const_defined?(constant_name, false) + owner.const_set(constant_name, original_value) if had_constant + end + + describe ".from_db" do + context "when not running on opal" do + it "delegates to the parent database loader" do + with_replaced_constant(Object, :RUBY_ENGINE, "ruby") do + allow(::Unitsdb::Database).to receive(:from_db).and_return(:loaded_database) + + result = described_class.from_db("/tmp/unitsdb", context: :unitsml_ruby) + + expect(result).to eq(:loaded_database) + expect(::Unitsdb::Database).to have_received(:from_db).with("/tmp/unitsdb", context: :unitsml_ruby) + end + end + end + + context "when running on opal" do + it "raises a clear error when the bundled payload is missing" do + with_replaced_constant(Object, :RUBY_ENGINE, "opal") do + expect do + described_class.from_db("/does/not/matter", context: :unitsml_ruby) + end.to raise_error(Unitsml::Errors::OpalPayloadNotBundledError, /not bundled/) + end + end + end + end +end From 60cb9565db163bb120e0035e34652381d0e50cbe Mon Sep 17 00:00:00 2001 From: suleman-uzair Date: Mon, 13 Apr 2026 22:18:45 +0500 Subject: [PATCH 06/15] refactor: fixed failing specs and Gemfile --- Gemfile | 2 +- lib/unitsml/formula.rb | 9 +++------ lib/unitsml/mathml_helper.rb | 8 ++++++-- lib/unitsml/unit.rb | 7 ++++++- 4 files changed, 16 insertions(+), 10 deletions(-) diff --git a/Gemfile b/Gemfile index 54ecfa4..2148322 100644 --- a/Gemfile +++ b/Gemfile @@ -19,4 +19,4 @@ gem "rubocop-performance" gem "rubocop-rake" gem "rubocop-rspec" gem "simplecov" -gem "unitsdb", github: "unitsml/unitsdb-ruby", branch: "feat/context-register-models" +gem "unitsdb", github: "unitsml/unitsdb-ruby", branch: "feat/context-register-models", submodules: true diff --git a/lib/unitsml/formula.rb b/lib/unitsml/formula.rb index 6a06200..e273ae6 100644 --- a/lib/unitsml/formula.rb +++ b/lib/unitsml/formula.rb @@ -31,13 +31,14 @@ def ==(other) def to_mathml(options = {}) if root options = update_options(options) - math = mml_v4_new(:math, display: 'block') + math = mml_v4_new(:math, display: "block") math.ordered = true math.element_order ||= [] value.each do |instance| process_value(math, instance.to_mathml(options)) end - generated_math = math.to_xml.gsub(%r{&(.*?)(?=\s+<").strip end - - def compact_mathml_for_plurimath(mathml) - mathml.gsub(/>\s+<").strip - end end end diff --git a/lib/unitsml/mathml_helper.rb b/lib/unitsml/mathml_helper.rb index 633776c..5c17c85 100644 --- a/lib/unitsml/mathml_helper.rb +++ b/lib/unitsml/mathml_helper.rb @@ -9,15 +9,19 @@ def mml_v4_context end def mml_v4_from_xml(klass_ref, xml) - mml_v4_class_for(klass_ref).from_xml(xml, context: mml_v4_context.id) + mml_v4_class_for(klass_ref).from_xml(xml, register: mml_v4_context.id) end def mml_v4_new(klass_ref, **attributes) klass = mml_v4_class_for(klass_ref) - # lutaml-model still resolves nested symbolic child types via `lutaml_register`. klass.new(**attributes, lutaml_register: mml_v4_context.id) end + def mml_v4_with_content(instance, content) + attributes = instance.to_hash.transform_keys(&:to_sym) + mml_v4_new(instance.class, **attributes, value: content) + end + def mml_v4_class_for(klass_ref) return klass_ref if klass_ref.is_a?(Class) diff --git a/lib/unitsml/unit.rb b/lib/unitsml/unit.rb index 26040d6..af63564 100644 --- a/lib/unitsml/unit.rb +++ b/lib/unitsml/unit.rb @@ -40,7 +40,12 @@ def to_mathml(options) value = unit_symbols&.mathml tag_name = value.match(/^<(?\w+)/)[:tag] value = mml_v4_from_xml(tag_name, value) - value.value = "#{prefix.to_mathml(options)}#{value.value}" if prefix + if prefix + value = mml_v4_with_content( + value, + "#{prefix.to_mathml(**options, parent: value)}#{value.value}", + ) + end if power_numerator value = msup_tag( { method_name: tag_name, value: value }, From b160141c04c82b695e24acefbde132ca2b603ada Mon Sep 17 00:00:00 2001 From: suleman-uzair Date: Mon, 13 Apr 2026 22:28:35 +0500 Subject: [PATCH 07/15] refactor: rubocop fixes --- lib/unitsml/errors.rb | 3 +- lib/unitsml/extender.rb | 2 +- lib/unitsml/fenced.rb | 2 +- lib/unitsml/number.rb | 6 +-- lib/unitsml/unitsdb.rb | 26 ++++++++--- lib/unitsml/unitsdb/database.rb | 1 + lib/unitsml/utility.rb | 67 ++++++++++++++++++--------- spec/unitsml/unitsdb/database_spec.rb | 43 +++++++---------- unitsml.gemspec | 2 +- 9 files changed, 92 insertions(+), 60 deletions(-) diff --git a/lib/unitsml/errors.rb b/lib/unitsml/errors.rb index f2a5df4..42d1441 100644 --- a/lib/unitsml/errors.rb +++ b/lib/unitsml/errors.rb @@ -3,7 +3,8 @@ module Unitsml module Errors autoload :BaseError, "unitsml/errors/base_error" - autoload :OpalPayloadNotBundledError, "unitsml/errors/opal_payload_not_bundled_error" + autoload :OpalPayloadNotBundledError, + "unitsml/errors/opal_payload_not_bundled_error" autoload :PlurimathLoadError, "unitsml/errors/plurimath_load_error" end end diff --git a/lib/unitsml/extender.rb b/lib/unitsml/extender.rb index 3a052e7..f17652d 100644 --- a/lib/unitsml/extender.rb +++ b/lib/unitsml/extender.rb @@ -20,7 +20,7 @@ def to_mathml(options) extender = multiplier(options[:multiplier] || "⋅", unicode: true) { method_name: :mo, - value: mml_v4_new(:mo, value: extender, rspace: rspace) + value: mml_v4_new(:mo, value: extender, rspace: rspace), } end diff --git a/lib/unitsml/fenced.rb b/lib/unitsml/fenced.rb index e587c0f..5ec806f 100644 --- a/lib/unitsml/fenced.rb +++ b/lib/unitsml/fenced.rb @@ -40,7 +40,7 @@ def to_mathml(options = {}) fenced.element_order ||= [xml_order_element("mo")] [mathml].flatten.each { |record| add_math_element(fenced, record) } fenced.mo_value << mml_v4_new(:mo, value: close_paren) - fenced.element_order << xml_order_element('mo') + fenced.element_order << xml_order_element("mo") { method_name: :mrow, value: fenced } end diff --git a/lib/unitsml/number.rb b/lib/unitsml/number.rb index 0c2f2f3..608abed 100644 --- a/lib/unitsml/number.rb +++ b/lib/unitsml/number.rb @@ -21,7 +21,7 @@ def to_mathml(_options) matched_value = value&.match(/-?(.+)/) mn_value = matched_value ? matched_value[1] : value mn_tag = mml_v4_new(:mn, value: mn_value) - value.start_with?('-') ? mrow_hash(mn_tag) : mn_hash(mn_tag) + value.start_with?("-") ? mrow_hash(mn_tag) : mn_hash(mn_tag) end def to_html(_options) @@ -65,9 +65,9 @@ def mrow_hash(mn_tag) method_name: :mrow, value: mml_v4_new( :mrow, - mo_value: [mml_v4_new(:mo, value: '−')], + mo_value: [mml_v4_new(:mo, value: "−")], mn_value: [mn_tag], - ) + ), } end diff --git a/lib/unitsml/unitsdb.rb b/lib/unitsml/unitsdb.rb index 3fcb8d5..78e91e6 100644 --- a/lib/unitsml/unitsdb.rb +++ b/lib/unitsml/unitsdb.rb @@ -21,19 +21,31 @@ class << self ].freeze def units - @units ||= Units.new(units: database.units, lutaml_register: Configuration.context.id) + @units ||= Units.new( + units: database.units, + lutaml_register: Configuration.context.id, + ) end def prefixes - @prefixes ||= Prefixes.new(prefixes: database.prefixes, lutaml_register: Configuration.context.id) + @prefixes ||= Prefixes.new( + prefixes: database.prefixes, + lutaml_register: Configuration.context.id, + ) end def dimensions - @dimensions ||= Dimensions.new(dimensions: database.dimensions, lutaml_register: Configuration.context.id) + @dimensions ||= Dimensions.new( + dimensions: database.dimensions, + lutaml_register: Configuration.context.id, + ) end def quantities - @quantities ||= Quantities.new(quantities: database.quantities, lutaml_register: Configuration.context.id) + @quantities ||= Quantities.new( + quantities: database.quantities, + lutaml_register: Configuration.context.id, + ) end def prefixes_array @@ -65,14 +77,16 @@ def load_database end def database_path - candidate_database_paths.find { |path| database_files_present?(path) } || + candidate_database_paths.find do |path| + database_files_present?(path) + end || File.join(unitsdb_gem_path, "vendor", "unitsdb") end def candidate_database_paths [ File.join(unitsdb_gem_path, "data"), - File.join(unitsdb_gem_path, "vendor", "unitsdb") + File.join(unitsdb_gem_path, "vendor", "unitsdb"), ] end diff --git a/lib/unitsml/unitsdb/database.rb b/lib/unitsml/unitsdb/database.rb index c089f51..7a5be53 100644 --- a/lib/unitsml/unitsdb/database.rb +++ b/lib/unitsml/unitsdb/database.rb @@ -10,6 +10,7 @@ def self.from_db(dir_path, context: Unitsml::Configuration.context.id) context_id = context.to_sym raise Unitsml::Errors::OpalPayloadNotBundledError unless DATABASE + Unitsml::Configuration.context from_hash(DATABASE, register: context_id) diff --git a/lib/unitsml/utility.rb b/lib/unitsml/utility.rb index c535337..4329c69 100644 --- a/lib/unitsml/utility.rb +++ b/lib/unitsml/utility.rb @@ -40,6 +40,12 @@ module Utility ].freeze UNKNOWN = "unknown" + PREFIX_SYMBOL_METHODS = { + ASCII: :to_asciimath, + unicode: :to_unicode, + LaTeX: :to_latex, + HTML: :to_html, + }.freeze class << self def unit_instance(unit) @@ -164,7 +170,7 @@ def combine_prefixes(p1, p2) Unitsdb.prefixes_array.each do |prefix_name| p = prefix_object(prefix_name) return p if prefix_base(p) == prefix_base(p1) && - prefix_power(p) == prefix_power(p1) + prefix_power(p2) + prefix_power(p) == prefix_power(p1) + prefix_power(p2) end UNKNOWN @@ -178,7 +184,10 @@ def prefix_like?(prefix) def prefix_symbolid(prefix) return prefix.symbolid if prefix.respond_to?(:symbolid) - resolved_prefix(prefix)&.symbols&.first&.ascii + resolved_prefix = resolved_prefix(prefix) + return unless resolved_prefix + + resolved_prefix.symbols&.first&.ascii end def prefix_base(prefix) @@ -309,28 +318,42 @@ def dimid2dimensions(normtext) def prefixes(units, options) uniq_prefixes = units.filter_map(&:prefix).uniq(&:prefix_name) uniq_prefixes.map do |prefix| - prefix_attrs = { prefix_base: prefix&.base, - prefix_power: prefix&.power, id: prefix&.id } - type_and_methods = { ASCII: :to_asciimath, unicode: :to_unicode, - LaTeX: :to_latex, HTML: :to_html } - prefix_attrs[:name] = Model::Prefixes::Name.new( - content: prefix&.name, - lutaml_register: Configuration.context.id, - ) - prefix_attrs[:symbol] = type_and_methods.map do |type, method_name| - Model::Prefixes::Symbol.new( - type: type, - content: prefix&.public_send(method_name, options), - lutaml_register: Configuration.context.id, - ) - end - Model::Prefix.new( - prefix_attrs, + prefix_xml(prefix, options) + end.join("\n") + end + + def prefix_xml(prefix, options) + Model::Prefix.new( + prefix_attributes(prefix, options), + lutaml_register: Configuration.context.id, + ).to_xml.force_encoding("UTF-8").gsub("&", "&") + end + + def prefix_attributes(prefix, options) + { + prefix_base: prefix&.base, + prefix_power: prefix&.power, + id: prefix&.id, + name: prefix_name(prefix), + symbol: prefix_symbols(prefix, options), + } + end + + def prefix_name(prefix) + Model::Prefixes::Name.new( + content: prefix&.name, + lutaml_register: Configuration.context.id, + ) + end + + def prefix_symbols(prefix, options) + PREFIX_SYMBOL_METHODS.map do |type, method_name| + Model::Prefixes::Symbol.new( + type: type, + content: prefix&.public_send(method_name, options), lutaml_register: Configuration.context.id, - ).to_xml.force_encoding("UTF-8").gsub( - "&", "&" ) - end.join("\n") + end end def rootunits(units) diff --git a/spec/unitsml/unitsdb/database_spec.rb b/spec/unitsml/unitsdb/database_spec.rb index 7c7080a..eb8b4ef 100644 --- a/spec/unitsml/unitsdb/database_spec.rb +++ b/spec/unitsml/unitsdb/database_spec.rb @@ -1,40 +1,33 @@ # frozen_string_literal: true RSpec.describe Unitsml::Unitsdb::Database do - def with_replaced_constant(owner, constant_name, value) - had_constant = owner.const_defined?(constant_name, false) - original_value = owner.const_get(constant_name) if had_constant - - owner.send(:remove_const, constant_name) if had_constant - owner.const_set(constant_name, value) - - yield - ensure - owner.send(:remove_const, constant_name) if owner.const_defined?(constant_name, false) - owner.const_set(constant_name, original_value) if had_constant - end - describe ".from_db" do context "when not running on opal" do - it "delegates to the parent database loader" do - with_replaced_constant(Object, :RUBY_ENGINE, "ruby") do - allow(::Unitsdb::Database).to receive(:from_db).and_return(:loaded_database) + before do + stub_const("RUBY_ENGINE", "ruby") + allow(Unitsdb::Database) + .to receive(:from_db).and_return(:loaded_database) + end - result = described_class.from_db("/tmp/unitsdb", context: :unitsml_ruby) + it "delegates to the parent database loader" do + result = described_class.from_db("/tmp/unitsdb", + context: :unitsml_ruby) - expect(result).to eq(:loaded_database) - expect(::Unitsdb::Database).to have_received(:from_db).with("/tmp/unitsdb", context: :unitsml_ruby) - end + expect(result).to eq(:loaded_database) + expect(Unitsdb::Database).to have_received(:from_db).with( + "/tmp/unitsdb", context: :unitsml_ruby + ) end end context "when running on opal" do + before { stub_const("RUBY_ENGINE", "opal") } + it "raises a clear error when the bundled payload is missing" do - with_replaced_constant(Object, :RUBY_ENGINE, "opal") do - expect do - described_class.from_db("/does/not/matter", context: :unitsml_ruby) - end.to raise_error(Unitsml::Errors::OpalPayloadNotBundledError, /not bundled/) - end + expect do + described_class.from_db("/does/not/matter", context: :unitsml_ruby) + end.to raise_error(Unitsml::Errors::OpalPayloadNotBundledError, + /not bundled/) end end end diff --git a/unitsml.gemspec b/unitsml.gemspec index f1ffe95..4e61cc3 100644 --- a/unitsml.gemspec +++ b/unitsml.gemspec @@ -30,7 +30,7 @@ Gem::Specification.new do |spec| spec.bindir = "exe" spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } - spec.require_paths = ['lib'] + spec.require_paths = ["lib"] spec.add_dependency "htmlentities" spec.add_dependency "lutaml-model", "~> 0.8.0" From 5260d435c2049379871f8b1c2843b95e83ac8509 Mon Sep 17 00:00:00 2001 From: suleman-uzair Date: Wed, 15 Apr 2026 22:25:45 +0500 Subject: [PATCH 08/15] feat: lazy load unitsdb and handle opal payloads --- Gemfile | 15 ++++++++++--- docs/README.adoc | 18 +++++++++------- lib/unitsml.rb | 1 - lib/unitsml/configuration.rb | 2 ++ lib/unitsml/unitsdb.rb | 6 ++++++ spec/unitsml/unitsdb/database_spec.rb | 31 +++++++++++++++++++++++++++ 6 files changed, 61 insertions(+), 12 deletions(-) diff --git a/Gemfile b/Gemfile index 2148322..b5fca7b 100644 --- a/Gemfile +++ b/Gemfile @@ -6,11 +6,15 @@ source "https://rubygems.org" gemspec gem "canon" -gem "lutaml-model", "~> 0.8.0", github: "lutaml/lutaml-model", branch: "fix/global-context-register-lookup-fallback" +gem "lutaml-model", + "~> 0.8.0", + github: "lutaml/lutaml-model", + branch: "fix/global-context-register-lookup-fallback" gem "mml", github: "plurimath/mml", branch: "main" gem "oga" gem "ox" -gem "plurimath", github: "plurimath/plurimath", branch: "feat/autoload-and-mml-update" +gem "plurimath", github: "plurimath/plurimath", + branch: "feat/autoload-and-mml-update" gem "pry" gem "rake" gem "rspec" @@ -19,4 +23,9 @@ gem "rubocop-performance" gem "rubocop-rake" gem "rubocop-rspec" gem "simplecov" -gem "unitsdb", github: "unitsml/unitsdb-ruby", branch: "feat/context-register-models", submodules: true +gem "unitsdb", + github: "unitsml/unitsdb-ruby", + branch: "feat/context-register-models", + submodules: true +# gem "unitsdb", path: "../unitsdb-ruby" +gem "irb" diff --git a/docs/README.adoc b/docs/README.adoc index dca3f35..0f2ae34 100644 --- a/docs/README.adoc +++ b/docs/README.adoc @@ -45,14 +45,16 @@ gem as the primary runtime source for standard units, prefixes, and dimensions. == Development setup -During local development, this repository expects `lutaml-model`, `mml`, -`plurimath`, and `unitsdb-ruby` to be available as sibling checkouts at -`../lutaml-model`, `../mml`, `../plurimath`, and `../unitsdb-ruby`. The -development `Gemfile` uses Bundler `path` dependencies for those repositories. - -The standard Ruby runtime path goes through `::Unitsdb.database`. When a local -`unitsdb-ruby` checkout does not have its `data/` submodule populated, UnitsML -falls back to the packaged YAML files under `vendor/unitsdb`. +The development `Gemfile` pins the current integration branches for +`lutaml-model`, `mml`, `plurimath`, and `unitsdb-ruby`. If you need to test +coordinated local changes across those repositories, temporarily switch those +dependencies to Bundler `path` entries that point at sibling checkouts, for +example `../lutaml-model` or `../unitsdb-ruby`. Keep those local path changes +out of commits unless the branch intentionally requires them. + +The standard Ruby runtime path goes through `::Unitsdb.database`. When +`unitsdb-ruby` cannot load its bundled `data/` directory, UnitsML falls back to +the packaged YAML files under `vendor/unitsdb`. == Usage diff --git a/lib/unitsml.rb b/lib/unitsml.rb index 609341e..b938e7e 100644 --- a/lib/unitsml.rb +++ b/lib/unitsml.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true require "lutaml/model" -require "unitsdb" module Unitsml module_function diff --git a/lib/unitsml/configuration.rb b/lib/unitsml/configuration.rb index 47b3141..19ba45c 100644 --- a/lib/unitsml/configuration.rb +++ b/lib/unitsml/configuration.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +require "unitsdb" + module Unitsml module Configuration CONTEXT_ID = :unitsml_ruby diff --git a/lib/unitsml/unitsdb.rb b/lib/unitsml/unitsdb.rb index 78e91e6..17287d0 100644 --- a/lib/unitsml/unitsdb.rb +++ b/lib/unitsml/unitsdb.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +require "unitsdb" + require_relative "unitsdb/database" require_relative "unitsdb/dimension_details" require_relative "unitsdb/prefix_reference" @@ -66,6 +68,10 @@ def database private def load_database + if RUBY_ENGINE == "opal" + return Database.from_db(nil, context: Configuration.context.id) + end + if ::Unitsdb.respond_to?(:database) return ::Unitsdb.database(context: Configuration.context.id) end diff --git a/spec/unitsml/unitsdb/database_spec.rb b/spec/unitsml/unitsdb/database_spec.rb index eb8b4ef..ce23865 100644 --- a/spec/unitsml/unitsdb/database_spec.rb +++ b/spec/unitsml/unitsdb/database_spec.rb @@ -32,3 +32,34 @@ end end end + +RSpec.describe Unitsml::Unitsdb do + describe ".database" do + let(:context) { instance_double("Unitsdb context", id: :unitsml_ruby) } + + before do + described_class.instance_variable_set(:@database, nil) + allow(Unitsml::Configuration).to receive(:context).and_return(context) + end + + after do + described_class.instance_variable_set(:@database, nil) + end + + context "when running on opal" do + before do + stub_const("RUBY_ENGINE", "opal") + allow(Unitsml::Unitsdb::Database) + .to receive(:from_db).and_return(:opal_database) + end + + it "loads the packaged opal payload without a filesystem path" do + expect(described_class.database).to eq(:opal_database) + expect(Unitsml::Unitsdb::Database).to have_received(:from_db).with( + nil, + context: :unitsml_ruby, + ) + end + end + end +end From ba623e13e15fb65831cf20663b84937410cc64b6 Mon Sep 17 00:00:00 2001 From: suleman-uzair Date: Mon, 20 Apr 2026 21:23:30 +0500 Subject: [PATCH 09/15] fix: load UnitsDB with context fallback and clean up MathML handling --- Gemfile | 2 +- docs/README.adoc | 3 +- lib/unitsml/formula.rb | 11 ------- lib/unitsml/unit.rb | 2 +- lib/unitsml/unitsdb.rb | 14 +++++--- lib/unitsml/unitsdb/prefix_reference.rb | 2 +- lib/unitsml/utility.rb | 6 ++-- spec/unitsml/unitsdb/database_spec.rb | 43 ++++++++++++++++++++++--- 8 files changed, 57 insertions(+), 26 deletions(-) diff --git a/Gemfile b/Gemfile index b5fca7b..10bd118 100644 --- a/Gemfile +++ b/Gemfile @@ -10,7 +10,7 @@ gem "lutaml-model", "~> 0.8.0", github: "lutaml/lutaml-model", branch: "fix/global-context-register-lookup-fallback" -gem "mml", github: "plurimath/mml", branch: "main" +gem "mml", github: "plurimath/mml", branch: "fix/mixed-content-collection" gem "oga" gem "ox" gem "plurimath", github: "plurimath/plurimath", diff --git a/docs/README.adoc b/docs/README.adoc index 0f2ae34..d69b7e4 100644 --- a/docs/README.adoc +++ b/docs/README.adoc @@ -54,7 +54,8 @@ out of commits unless the branch intentionally requires them. The standard Ruby runtime path goes through `::Unitsdb.database`. When `unitsdb-ruby` cannot load its bundled `data/` directory, UnitsML falls back to -the packaged YAML files under `vendor/unitsdb`. +the packaged YAML files in the `unitsdb-ruby` gem's `vendor/unitsdb` +directory, rather than a `vendor/unitsdb` directory in this repository. == Usage diff --git a/lib/unitsml/formula.rb b/lib/unitsml/formula.rb index 6d6589c..e273ae6 100644 --- a/lib/unitsml/formula.rb +++ b/lib/unitsml/formula.rb @@ -199,17 +199,6 @@ def plurimath_available? Plurimath.const_defined?(:Mathml) end - def nullify_mml_models - return unless defined?(Plurimath::Mathml::Parser::CONFIGURATION) - - Plurimath::Mathml::Parser::CONFIGURATION.each_key { |klass| klass.model(klass) } - end - - def reset_mml_models - return unless defined?(Plurimath::Mathml::Parser::CONFIGURATION) - - ::Mml::V4::Configuration.custom_models = Plurimath::Mathml::Parser::CONFIGURATION - end def process_value(math, mathml_instances) case mathml_instances when Array diff --git a/lib/unitsml/unit.rb b/lib/unitsml/unit.rb index af63564..af3c64c 100644 --- a/lib/unitsml/unit.rb +++ b/lib/unitsml/unit.rb @@ -43,7 +43,7 @@ def to_mathml(options) if prefix value = mml_v4_with_content( value, - "#{prefix.to_mathml(**options, parent: value)}#{value.value}", + "#{prefix.to_mathml(options.merge(parent: value))}#{value.value}", ) end if power_numerator diff --git a/lib/unitsml/unitsdb.rb b/lib/unitsml/unitsdb.rb index 17287d0..bb864a2 100644 --- a/lib/unitsml/unitsdb.rb +++ b/lib/unitsml/unitsdb.rb @@ -68,18 +68,24 @@ def database private def load_database + context_id = Configuration.context.id + if RUBY_ENGINE == "opal" - return Database.from_db(nil, context: Configuration.context.id) + return Database.from_db(nil, context: context_id) end if ::Unitsdb.respond_to?(:database) - return ::Unitsdb.database(context: Configuration.context.id) + return load_unitsdb_database(context_id) end - ::Unitsdb::Database.from_db(database_path) + Database.from_db(database_path, context: context_id) + end + + def load_unitsdb_database(context_id) + ::Unitsdb.database(context: context_id) rescue ::Unitsdb::Errors::DatabaseNotFoundError, ::Unitsdb::Errors::DatabaseFileNotFoundError - ::Unitsdb::Database.from_db(database_path) + Database.from_db(database_path, context: context_id) end def database_path diff --git a/lib/unitsml/unitsdb/prefix_reference.rb b/lib/unitsml/unitsdb/prefix_reference.rb index 3c5e56b..7b919c2 100644 --- a/lib/unitsml/unitsdb/prefix_reference.rb +++ b/lib/unitsml/unitsdb/prefix_reference.rb @@ -16,7 +16,7 @@ def power end def prefix - @prefix ||= ::Unitsdb.database.prefixes.find do |p| + @prefix ||= Unitsml::Unitsdb.database.prefixes.find do |p| p.identifiers.any? { |i| i.id == id } end end diff --git a/lib/unitsml/utility.rb b/lib/unitsml/utility.rb index 4329c69..512f6fe 100644 --- a/lib/unitsml/utility.rb +++ b/lib/unitsml/utility.rb @@ -184,10 +184,10 @@ def prefix_like?(prefix) def prefix_symbolid(prefix) return prefix.symbolid if prefix.respond_to?(:symbolid) - resolved_prefix = resolved_prefix(prefix) - return unless resolved_prefix + prefix_record = resolved_prefix(prefix) + return unless prefix_record - resolved_prefix.symbols&.first&.ascii + prefix_record.symbols&.first&.ascii end def prefix_base(prefix) diff --git a/spec/unitsml/unitsdb/database_spec.rb b/spec/unitsml/unitsdb/database_spec.rb index ce23865..9effa09 100644 --- a/spec/unitsml/unitsdb/database_spec.rb +++ b/spec/unitsml/unitsdb/database_spec.rb @@ -31,11 +31,9 @@ end end end -end -RSpec.describe Unitsml::Unitsdb do - describe ".database" do - let(:context) { instance_double("Unitsdb context", id: :unitsml_ruby) } + describe Unitsml::Unitsdb, ".database" do + let(:context) { Struct.new(:id).new(:unitsml_ruby) } before do described_class.instance_variable_set(:@database, nil) @@ -61,5 +59,42 @@ ) end end + + context "when unitsdb-ruby exposes a database loader" do + before do + stub_const("RUBY_ENGINE", "ruby") + allow(Unitsdb).to receive(:database).and_return(:unitsdb_database) + end + + it "uses the unitsdb-ruby loader with the UnitsML context" do + expect(described_class.database).to eq(:unitsdb_database) + expect(Unitsdb).to have_received(:database).with( + context: :unitsml_ruby, + ) + end + end + + context "when unitsdb-ruby cannot load its packaged data directory" do + before do + stub_const("RUBY_ENGINE", "ruby") + allow(Unitsdb).to receive(:database).and_raise( + Unitsdb::Errors::DatabaseNotFoundError, + ) + allow(described_class) + .to receive(:database_path) + .and_return("/tmp/fallback-unitsdb") + allow(Unitsml::Unitsdb::Database) + .to receive(:from_db) + .and_return(:fallback_database) + end + + it "falls back to the UnitsML database wrapper with the same context" do + expect(described_class.database).to eq(:fallback_database) + expect(Unitsml::Unitsdb::Database).to have_received(:from_db).with( + "/tmp/fallback-unitsdb", + context: :unitsml_ruby, + ) + end + end end end From 12cee6c901095e3b943a703f1cab19f4c3c802a6 Mon Sep 17 00:00:00 2001 From: suleman-uzair Date: Tue, 21 Apr 2026 17:59:59 +0500 Subject: [PATCH 10/15] Fix MathML v4 content handling with released mml --- Gemfile | 3 +-- lib/unitsml.rb | 1 + lib/unitsml/mathml_helper.rb | 31 +++++++++++++++++++++++++++++- lib/unitsml/unit.rb | 2 +- spec/unitsml/mathml_helper_spec.rb | 17 ++++++++++++++++ spec/unitsml/unit_spec.rb | 23 ++++++++++++++++++++++ 6 files changed, 73 insertions(+), 4 deletions(-) create mode 100644 spec/unitsml/mathml_helper_spec.rb create mode 100644 spec/unitsml/unit_spec.rb diff --git a/Gemfile b/Gemfile index 10bd118..d033604 100644 --- a/Gemfile +++ b/Gemfile @@ -10,7 +10,7 @@ gem "lutaml-model", "~> 0.8.0", github: "lutaml/lutaml-model", branch: "fix/global-context-register-lookup-fallback" -gem "mml", github: "plurimath/mml", branch: "fix/mixed-content-collection" +gem "mml" gem "oga" gem "ox" gem "plurimath", github: "plurimath/plurimath", @@ -27,5 +27,4 @@ gem "unitsdb", github: "unitsml/unitsdb-ruby", branch: "feat/context-register-models", submodules: true -# gem "unitsdb", path: "../unitsdb-ruby" gem "irb" diff --git a/lib/unitsml.rb b/lib/unitsml.rb index b938e7e..a1622ca 100644 --- a/lib/unitsml.rb +++ b/lib/unitsml.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require "lutaml/model" +require "mml" module Unitsml module_function diff --git a/lib/unitsml/mathml_helper.rb b/lib/unitsml/mathml_helper.rb index 5c17c85..3f61454 100644 --- a/lib/unitsml/mathml_helper.rb +++ b/lib/unitsml/mathml_helper.rb @@ -14,12 +14,17 @@ def mml_v4_from_xml(klass_ref, xml) def mml_v4_new(klass_ref, **attributes) klass = mml_v4_class_for(klass_ref) + coerce_mml_v4_collection_attributes!(klass, attributes) klass.new(**attributes, lutaml_register: mml_v4_context.id) end def mml_v4_with_content(instance, content) attributes = instance.to_hash.transform_keys(&:to_sym) - mml_v4_new(instance.class, **attributes, value: content) + mml_v4_new( + instance.class, + **attributes, + mml_v4_content_attribute(instance) => content + ) end def mml_v4_class_for(klass_ref) @@ -27,5 +32,29 @@ def mml_v4_class_for(klass_ref) mml_v4_context.lookup_local(klass_ref.to_sym) end + + private + + def coerce_mml_v4_collection_attributes!(klass, attributes) + return unless klass.respond_to?(:attributes) + + attributes.each do |name, value| + attribute = klass.attributes[name] + attributes[name] = coerce_mml_v4_collection_value(attribute, value) + end + end + + def coerce_mml_v4_collection_value(attribute, value) + return value unless attribute&.collection? + return value if value.nil? + return value if attribute.collection_instance?(value) + + attribute.build_collection(value) + end + + def mml_v4_content_attribute(instance) + register = instance.lutaml_register || mml_v4_context.id + instance.class.mappings_for(:xml, register).content_mapping.to + end end end diff --git a/lib/unitsml/unit.rb b/lib/unitsml/unit.rb index af3c64c..fd65b22 100644 --- a/lib/unitsml/unit.rb +++ b/lib/unitsml/unit.rb @@ -43,7 +43,7 @@ def to_mathml(options) if prefix value = mml_v4_with_content( value, - "#{prefix.to_mathml(options.merge(parent: value))}#{value.value}", + "#{prefix.to_mathml(options.merge(parent: value))}#{Array(value.value).join}", ) end if power_numerator diff --git a/spec/unitsml/mathml_helper_spec.rb b/spec/unitsml/mathml_helper_spec.rb new file mode 100644 index 0000000..7cbda32 --- /dev/null +++ b/spec/unitsml/mathml_helper_spec.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe Unitsml::MathmlHelper do + let(:helper_host) { Class.new { extend Unitsml::MathmlHelper } } + + describe ".mml_v4_with_content" do + it "replaces the mapped content attribute with the provided content" do + instance = helper_host.send(:mml_v4_new, :mi, value: []) + + updated = helper_host.send(:mml_v4_with_content, instance, "m") + + expect(updated.value).to eq("m") + end + end +end diff --git a/spec/unitsml/unit_spec.rb b/spec/unitsml/unit_spec.rb new file mode 100644 index 0000000..9e32407 --- /dev/null +++ b/spec/unitsml/unit_spec.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe Unitsml::Unit do + describe "#to_mathml" do + it "joins parsed token collections before prefix concatenation" do + unit = described_class.new("m", nil, prefix: instance_double(Unitsml::Prefix)) + parsed_unit = instance_double("ParsedUnitSymbol", value: ["m"]) + updated_value = instance_double("UpdatedMathmlValue") + + allow(unit).to receive(:unit_symbols).and_return( + instance_double("UnitSymbol", mathml: 'm'), + ) + allow(unit).to receive(:mml_v4_from_xml).with("mi", 'm') + .and_return(parsed_unit) + allow(unit.prefix).to receive(:to_mathml).with(hash_including(parent: parsed_unit)).and_return("m") + allow(unit).to receive(:mml_v4_with_content).with(parsed_unit, "mm").and_return(updated_value) + + expect(unit.to_mathml({})).to eq(method_name: :mi, value: updated_value) + end + end +end From 43a4890e67e43a16294734383529ec147fae6853 Mon Sep 17 00:00:00 2001 From: suleman-uzair Date: Tue, 21 Apr 2026 18:24:34 +0500 Subject: [PATCH 11/15] Fix UnitsDB compatibility and MathML helper regressions --- Gemfile | 1 - lib/unitsml/configuration.rb | 8 ++++---- lib/unitsml/mathml_helper.rb | 2 +- lib/unitsml/utility.rb | 34 ++++++++++++++++++++++++++++++---- spec/unitsml/unit_spec.rb | 34 ++++++++++++++++++++++++---------- 5 files changed, 59 insertions(+), 20 deletions(-) diff --git a/Gemfile b/Gemfile index d033604..5f8c484 100644 --- a/Gemfile +++ b/Gemfile @@ -7,7 +7,6 @@ gemspec gem "canon" gem "lutaml-model", - "~> 0.8.0", github: "lutaml/lutaml-model", branch: "fix/global-context-register-lookup-fallback" gem "mml" diff --git a/lib/unitsml/configuration.rb b/lib/unitsml/configuration.rb index 19ba45c..882876a 100644 --- a/lib/unitsml/configuration.rb +++ b/lib/unitsml/configuration.rb @@ -13,7 +13,7 @@ def context_id end def context(force_populate: false) - existing = ::Unitsdb::Configuration.find_context(context_id) + existing = ::Unitsdb::Config.find_context(context_id) return existing if existing && !force_populate build_context @@ -28,7 +28,7 @@ def registered_models end def build_context - ::Unitsdb::Configuration.context # ensure unitsdb context exists + ::Unitsdb::Config.context # ensure unitsdb context exists substitutions = registered_models.each_value.filter_map do |klass| parent = klass.superclass @@ -37,9 +37,9 @@ def build_context { from_type: parent, to_type: klass } end - ::Unitsdb::Configuration.populate_context( + ::Unitsdb::Config.populate_context( id: context_id, - fallback_to: [::Unitsdb::Configuration.context_id], + fallback_to: [::Unitsdb::Config.context_id], substitutions: substitutions, ) end diff --git a/lib/unitsml/mathml_helper.rb b/lib/unitsml/mathml_helper.rb index 3f61454..c7f8375 100644 --- a/lib/unitsml/mathml_helper.rb +++ b/lib/unitsml/mathml_helper.rb @@ -23,7 +23,7 @@ def mml_v4_with_content(instance, content) mml_v4_new( instance.class, **attributes, - mml_v4_content_attribute(instance) => content + mml_v4_content_attribute(instance) => content, ) end diff --git a/lib/unitsml/utility.rb b/lib/unitsml/utility.rb index 512f6fe..2033fae 100644 --- a/lib/unitsml/utility.rb +++ b/lib/unitsml/utility.rb @@ -229,7 +229,7 @@ def unit(units, formula, dims, norm_text, name, options) end def unitname(text, name) - name ||= unit_instance(text)&.en_name || text + name ||= unit_en_name(unit_instance(text)) || text Model::Units::Name.new( name: name, lutaml_register: Configuration.context.id, @@ -270,7 +270,7 @@ def unitsystem(units) end def dimension(norm_text) - dim_id = unit_instance(norm_text)&.dimension_url + dim_id = unit_dimension_id(unit_instance(norm_text)) return unless dim_id dim_attrs = { id: dim_id } @@ -383,7 +383,7 @@ def unit_id(text) end def format_unit_id(unit, text) - return unit.nist_id&.gsub("'", "_") if unit + return unit_nist_id(unit)&.gsub("'", "_") if unit text&.gsub("*", ".")&.gsub("^", "") end @@ -405,10 +405,36 @@ def quantity(normtext, instance) model_quantity_xml( instance || unit.quantity_references&.first&.id, - "##{unit.dimension_url}", + "##{unit_dimension_id(unit)}", ) end + def unit_nist_id(unit) + return unless unit + return unit.nist_id if unit.respond_to?(:nist_id) + + unit.identifiers&.find { |identifier| identifier.type == "nist" }&.id + end + + def unit_en_name(unit) + return unless unit + return unit.en_name if unit.respond_to?(:en_name) + + unit.names&.find { |name| name.lang == "en" }&.value + end + + def unit_dimension_id(unit) + return unless unit + return unit.dimension_url if unit.respond_to?(:dimension_url) + + unit.dimension_reference&.id || + quantity_dimension_id(quantity_instance(unit.quantity_references&.first&.id)) + end + + def quantity_dimension_id(quantity) + quantity&.dimension_reference&.id + end + def unit_or_quantity(unit, quantity) (unit && unit.quantity_references.size == 1) || quantity_instance(quantity) diff --git a/spec/unitsml/unit_spec.rb b/spec/unitsml/unit_spec.rb index 9e32407..2b317c2 100644 --- a/spec/unitsml/unit_spec.rb +++ b/spec/unitsml/unit_spec.rb @@ -4,19 +4,33 @@ RSpec.describe Unitsml::Unit do describe "#to_mathml" do - it "joins parsed token collections before prefix concatenation" do - unit = described_class.new("m", nil, prefix: instance_double(Unitsml::Prefix)) - parsed_unit = instance_double("ParsedUnitSymbol", value: ["m"]) - updated_value = instance_double("UpdatedMathmlValue") + let(:mathml) { 'm' } + let(:prefix) { instance_double(Unitsml::Prefix) } + let(:unit) { described_class.new("m", nil, prefix: prefix) } + let(:parsed_unit) { instance_double(ParsedUnitSymbol, value: ["m"]) } + let(:updated_value) { instance_double(UpdatedMathmlValue) } + + before do + stub_const("ParsedUnitSymbol", Class.new) + stub_const("UpdatedMathmlValue", Class.new) + stub_const("UnitSymbol", Class.new) - allow(unit).to receive(:unit_symbols).and_return( - instance_double("UnitSymbol", mathml: 'm'), - ) - allow(unit).to receive(:mml_v4_from_xml).with("mi", 'm') + allow(unit).to receive(:unit_symbols).and_return(unit_symbol) + allow(unit).to receive(:mml_v4_from_xml).with("mi", mathml) .and_return(parsed_unit) - allow(unit.prefix).to receive(:to_mathml).with(hash_including(parent: parsed_unit)).and_return("m") - allow(unit).to receive(:mml_v4_with_content).with(parsed_unit, "mm").and_return(updated_value) + allow(prefix).to receive(:to_mathml) + .with(hash_including(parent: parsed_unit)) + .and_return("m") + allow(unit).to receive(:mml_v4_with_content) + .with(parsed_unit, "mm") + .and_return(updated_value) + end + def unit_symbol + instance_double(UnitSymbol, mathml: mathml) + end + + it "joins parsed token collections before prefix concatenation" do expect(unit.to_mathml({})).to eq(method_name: :mi, value: updated_value) end end From 91ee2c02d4ad86b4d077413c4339d485891e2077 Mon Sep 17 00:00:00 2001 From: suleman-uzair Date: Tue, 21 Apr 2026 18:37:50 +0500 Subject: [PATCH 12/15] Make MathML helper spec tolerate collection-backed content --- spec/unitsml/mathml_helper_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/unitsml/mathml_helper_spec.rb b/spec/unitsml/mathml_helper_spec.rb index 7cbda32..67c7128 100644 --- a/spec/unitsml/mathml_helper_spec.rb +++ b/spec/unitsml/mathml_helper_spec.rb @@ -11,7 +11,7 @@ updated = helper_host.send(:mml_v4_with_content, instance, "m") - expect(updated.value).to eq("m") + expect(updated.value).to eq(["m"]) end end end From 03953e12b61180421587dda5689fd75d92bdf38d Mon Sep 17 00:00:00 2001 From: suleman-uzair Date: Tue, 21 Apr 2026 18:38:24 +0500 Subject: [PATCH 13/15] fixed rubocop warning --- Gemfile | 1 - 1 file changed, 1 deletion(-) diff --git a/Gemfile b/Gemfile index 5f8c484..4cc366a 100644 --- a/Gemfile +++ b/Gemfile @@ -26,4 +26,3 @@ gem "unitsdb", github: "unitsml/unitsdb-ruby", branch: "feat/context-register-models", submodules: true -gem "irb" From 98ab447de0dbbae707362e5f86a3104254b55cbe Mon Sep 17 00:00:00 2001 From: suleman-uzair Date: Wed, 22 Apr 2026 12:35:11 +0500 Subject: [PATCH 14/15] Remove redundant Opal branch from UnitsML UnitsDB loader --- lib/unitsml/unitsdb.rb | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/unitsml/unitsdb.rb b/lib/unitsml/unitsdb.rb index bb864a2..7efecc2 100644 --- a/lib/unitsml/unitsdb.rb +++ b/lib/unitsml/unitsdb.rb @@ -70,10 +70,6 @@ def database def load_database context_id = Configuration.context.id - if RUBY_ENGINE == "opal" - return Database.from_db(nil, context: context_id) - end - if ::Unitsdb.respond_to?(:database) return load_unitsdb_database(context_id) end From 00d8da74f9bc8992b9f04a583a00b84bb9993f87 Mon Sep 17 00:00:00 2001 From: suleman-uzair Date: Wed, 22 Apr 2026 12:41:22 +0500 Subject: [PATCH 15/15] Update UnitsDB spec for Opal loader delegation --- spec/unitsml/unitsdb/database_spec.rb | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/spec/unitsml/unitsdb/database_spec.rb b/spec/unitsml/unitsdb/database_spec.rb index 9effa09..cfe495f 100644 --- a/spec/unitsml/unitsdb/database_spec.rb +++ b/spec/unitsml/unitsdb/database_spec.rb @@ -44,17 +44,15 @@ described_class.instance_variable_set(:@database, nil) end - context "when running on opal" do + context "when running on opal and unitsdb-ruby exposes a database loader" do before do stub_const("RUBY_ENGINE", "opal") - allow(Unitsml::Unitsdb::Database) - .to receive(:from_db).and_return(:opal_database) + allow(Unitsdb).to receive(:database).and_return(:unitsdb_database) end - it "loads the packaged opal payload without a filesystem path" do - expect(described_class.database).to eq(:opal_database) - expect(Unitsml::Unitsdb::Database).to have_received(:from_db).with( - nil, + it "uses the unitsdb-ruby loader with the UnitsML context" do + expect(described_class.database).to eq(:unitsdb_database) + expect(Unitsdb).to have_received(:database).with( context: :unitsml_ruby, ) end