Introduction
There are three types of registers in Lutaml::Model:
-
TypeRegister
-
ModelRegister
-
GlobalRegister
TypeRegister
The TypeRegister is a registry class that registers and looks up the Lutaml::Model::Type::Value classes only.
Register a Type::Value class
The following syntax registers a Type::Value class:
# assuming we have a `CustomString` class that inherits from Lutaml::Model::Type::Value
Lutaml::Model::Type.register(:custom_string, Lutaml::Model::Type::CustomString) TypeError is raised if the class does not inherit from Lutaml::Model::Type::Value. |
Lookup a Type::Value class
Lookup a Type::Value class using the assigned name:
Lutaml::Model::Type.lookup(:custom_string) # returns Lutaml::Model::Type::CustomString Lutaml::Model::Type::UnknownTypeError is raised if the name is not found in the registry. |
When looking up a class, the class is returned without looking up in the registry.
Lutaml::Model::Type.lookup(Lutaml::Model::Type::CustomString) # returns Lutaml::Model::Type::CustomString even if it's not registered in the registryModelRegister
The ModelRegister is a registry class that registers and looks up the Lutaml::Model::Registrable classes (by default, Lutaml::Model::Serializable classes are Registrable classes). For consistency, the Lutaml::Model::Type::Value classes are registered similarly, but within the TypeRegister registry, as referenced in the previous section.
| Make sure to register the ModelRegister in GlobalRegister before using it. |
Register a Class
Register a Model class using the following syntax:
# assuming we have a `CustomModel` class that inherits from Lutaml::Model::Serializable
Lutaml::Model::Register.register_model(Lutaml::Model::CustomModel, id: :custom_model)This method register_model registers the class and assigns it the passed ID, which is :custom_model in this case. But if a model is registered without an ID, the class name is used as the ID. For example:
Lutaml::Model::Register.register_model(Lutaml::Model::AnotherCustomModel)This will register the class AnotherCustomModel with the ID :another_custom_model.
Register model tree
The register_model_tree method registers all the classes in the provided Model’s hierarchy. For example:
register = Lutaml::Model::Register.new(:v1)
module Mathml
class Mrow < Lutaml::Model::Serializable
attribute :mstyle, Mstyle
end
class Mstyle < Lutaml::Model::Serializable
attribute :mi, :string
end
class Math < Lutaml::Model::Serializable
attribute :mrow, Mrow
attribute :mstyle, Mstyle
end
end
register.register_model_tree(Mathml::Math) # registers all the classes in the Mathml::Math model tree, in this case Mathml::Mstyle and Mathml::MrowLookup a Class
Lookup a Model class using the assigned name:
register = Lutaml::Model::Register.new(:v1)
register.get_class(:custom_model) # returns Lutaml::Model::CustomModelThe class returned from the get_class method is also aware of the ModelRegister it was registered in. This is useful when you want to use the class directly. For example:
register = Lutaml::Model::Register.new(:v1)
json_hash = {
"mstyle": {
"mrow": {
"mi": "x",
"mo": "+"
}
},
"mrow": {
"mi": "z",
}
}
module Mathml
class Mrow < Lutaml::Model::Serializable
attribute :mi, :string
attribute :mo, :string
end
class Mstyle < Lutaml::Model::Serializable
attribute :mrow, Mrow
attribute :mi, :string
attribute :mo, :string
end
class Math < Lutaml::Model::Serializable
attribute :mrow, Mrow
attribute :mstyle, Mstyle
end
end
register.register_model_tree(Mathml::Math) # registers all the classes in the Mathml::Math model tree, in this case Mstyle and Mrow
# lookup the class and call the desired method, in current case from_json
register.get_class(:math).from_json(json_hash.to_json)
> #<Testing::Math:0x00000002ccd5a678
@mrow=#<Testing::Mrow:0x00000002cc50a1f8 @mi="z", @mo=nil>,
@mstyle=#<Testing::Mstyle:0x00000002cc50a108 @mi=nil, @mo=nil, @mrow=#<Testing::Mrow:0x00000002cc509fc8 @mi="x", @mo="+">>> If the class is not found in either the ModelRegister or the TypeRegister, a Lutaml::Model::UnknownTypeError is raised. |
Global Type substitution
The Lutaml::Model::Register class also provides a method to substitute a type globally. This is useful when you want to replace a type with another type in the entire model tree. For example:
register = Lutaml::Model::Register.new(:v1)
json_hash = {
"mstyle": {
"mrow": {
"mi": "x",
"mo": "+"
}
},
"mrow": {
"mi": "z",
"mstyle": {
"mrow": {
"mi": "x",
"mo": "+"
}
}
}
}
module Mathml
class String < Lutaml::Model::Type::Value
def to_json(*args)
"custom-string: #{super(*args).to_json}"
end
end
class Mrow < Lutaml::Model::Serializable
attribute :mi, :string
attribute :mo, :string
end
class Mstyle < Lutaml::Model::Serializable
attribute :mrow, Mrow
attribute :mi, :string
attribute :mo, :string
end
class Math < Lutaml::Model::Serializable
attribute :mrow, Mrow
attribute :mstyle, Mstyle
end
class ExtendedMrow < Mrow
attribute :mstyle, :mstyle
end
end
register.register_model_tree(Mathml::Math) # registers all the classes in the Mathml::Math model tree, in this case Mstyle and Mrow
# Substitute the Mrow class with the ExtendedMrow class globally
register.register_global_type_substitution(
from_type: Mathml::Mrow,
to_type: Mathml::ExtendedMrow
) # this will replace all instances of Mrow with ExtendedMrow in the entire model tree for this register
register.register_global_type_substitution(
from_type: Lutaml::Model::Type::String,
to_type: Mathml::String
)
# lookup the class and call the desired method, in current case from_json
models = register.get_class(:math).from_json(json_hash.to_json)
models.to_json
> "{\"mrow\":{\"mi\":\"custom-string: \\\"z\\\"\",\"mstyle\":{\"mrow\":{\"mi\":\"custom-string: \\\"x\\\"\",\"mo\":\"custom-string: \\\"+\\\"\"}}},\"mstyle\":{\"mrow\":{\"mi\":\"custom-string: \\\"x\\\"\",\"mo\":\"custom-string: \\\"+\\\"\"}}}"GlobalRegister
The GlobalRegister is a singleton that registers all the ModelRegisters. Model registers can be registered using the following syntax:
v1_register = Lutaml::Model::Register.new(:v1)
global_register = Lutaml::Model::GlobalRegister
global_register.register(v1_register) # register a Model register
# OR
global_register.instance.register(v1_register) # register a Model registerThe register method registers the ModelRegister based on its ID. The ID is used to look up the ModelRegister using the following syntax:
global_register.lookup(:v2) # fetch a Model register
# OR
global_register.instance.lookup(:v2) # fetch a Model registerIf a register is not needed anymore, it can be removed using the following syntax:
global_register.remove(:v1) # remove a ModelRegister using the its ID
# OR
global_register.instance.remove(:v1) # remove a ModelRegister using the it's IDRegister resolution fallback
General
Model registers support hierarchical fallback register resolution, which allows registers to depend on other registers for type resolution.
The following use cases benefit from this feature:
-
Shared types: Common types like
xs:annotation,xs:documentationcan resolved from parent registers without duplicating them in every register -
Isolation: Registers can disable fallback for strict schema isolation
-
Inheritance: Specialized schemas can extend base schemas by falling back to core registers
Default behavior
By default, all custom registers (except :default) automatically fall back to the :default (global) register.
This means if a type is not found in a custom register, it will be searched in the :default register.
register = Lutaml::Model::Register.new(:my_schema)
register.fallback # => [:default]The :default register itself has no fallback chain:
default_register = Lutaml::Model::GlobalRegister.lookup(:default)
default_register.fallback # => []Isolated registers
To create a register that does not fall back to any other register (complete isolation), pass an empty array as the fallback parameter:
isolated = Lutaml::Model::Register.new(:pristine, fallback: [])
isolated.fallback # => []Isolated registers will only resolve models explicitly registered within them.
This is useful for:
-
Testing schema isolation
-
Ensuring no dependency on external types
-
Strict schema validation
Explicit fallback chains
For complex schema hierarchies, you can specify an explicit fallback chain:
# Multi-level fallback: :profile → :core → :default
profile = Lutaml::Model::Register.new(
:gml_profile,
fallback: [:gml_core, :iso_types, :default]
)
profile.fallback # => [:gml_core, :iso_types, :default]Resolution order is by the order of the fallback array.
For example, when resolving a type in the :gml_profile register:
-
Search in local register (
:gml_profile) -
If not found, search in first fallback (
:gml_core) -
If not found, search in second fallback (
:iso_types) -
If not found, search in third fallback (
:default) -
If still not found, raise
UnknownTypeError
Use cases
Vertical separation (schema profiles)
Use fallback chains when specialized schemas extend base schemas:
# Base schema with core types
core_register = Lutaml::Model::Register.new(:gml_core)
Lutaml::Model::GlobalRegister.instance.register(core_register)
core_register.register_model(GeometryType, id: :geometry_type)
core_register.register_model(CoordinateType, id: :coordinate_type)
# Specialized profile extends core
profile_register = Lutaml::Model::Register.new(
:gml_profile,
fallback: [:gml_core, :default]
)
Lutaml::Model::GlobalRegister.instance.register(profile_register)
profile_register.register_model(ProfileGeometryType, id: :profile_geometry)
# Profile models can use both profile-specific and core types
class ProfileDocument < Lutaml::Model::Serializable
@register = profile_register
attribute :geometry, :profile_geometry # From :gml_profile
attribute :coordinate, :coordinate_type # From :gml_core (fallback)
endMulti-version schema support
Use fallback when newer schema versions build on older versions:
# Generate UnitsML schema with module namespace
Lutaml::Model::Schema::XmlCompiler.to_models(
xsd_content,
module_namespace: "UnitsMLV0919",
register_id: :unitsmlv0919, # Implicitly falls back to :default
output_dir: "lib/unitsml",
create_files: true
)
# Load models
require "unitsml/unitsmlv0919_registry"
UnitsMLV0919.register_all
# UnitsMLV0919 models resolve:
# 1. Domain-specific types from :unitsmlv0919 register
# 2. Common types (annotation, documentation) from :default register (fallback)
doc = UnitsMLV0919::UnitsMLType.from_xml(xml)