1 Scope
This document specifies the model register capabilities in LutaML Model, which enable:
Dynamic model attribute modification
Model hierarchy manipulation
Cross-model attribute type substitution
Versioned model trees with consistent class relationships
Mixed-version model construction for flexible upgrade paths
2 Normative references
There are no normative references in this document.
3 Terms and definitions
For the purposes of this document, the following terms and definitions apply.
3.1 model register
registry that maintains and manages dynamic model configurations
3.2 global register
registry that maintains references to all model registers in the system, allowing lookups by register ID
3.3 model path
path expression that identifies a specific model or attribute within a model hierarchy
3.4 model hierarchy
tree structure that represents the relationships between different models and their attributes
3.5 attribute substitution
process of replacing one attribute type with another within a model
3.6 model tree
collection of interconnected models that form a hierarchical structure
3.7 registrable class
class that can be registered in a model register, allowing dynamic instantiation and type resolution
3.8 mixed-version model tree
model tree that incorporates models from multiple version registers, allowing selective version upgrades
4 Principles of model registers
4.1 General
A LutaML model register provides a way to dynamically modify and reconfigure model hierarchies without changing the original model definitions.
Common use cases include:
Replacing attribute types in models
Swapping model subtrees with alternative implementations
Adding or removing attributes from models
Converting between different model namespaces
Managing versioned model trees with consistent class relationships
Creating mixed-version model configurations for targeted upgrades
4.2 Architecture
4.2.1 Basic model register architecture
A model register acts as a dynamic configuration layer that sits above the static model definitions:
╔═══════════════════════╗ ╔══════════════════════╗
║ Model Register ║ ║ Model Definitions ║
╚═══════════════════════╝ ╚══════════════════════╝
│ │
│ ┌───────┴───────┐
│ │ │
│ Model Tree A Model Tree B
│ │ │
│ ┌────┴────┐ ┌────┴────┐
│ │ │ │ │
└────────────────► ModelA ModelB ModelC ModelD
Before: After:
ModelA ModelA
├─ModelB ├─ModelB
│ └─ModelC │ └─NewSubtree
└─ModelD └─ModelD
Before: After:
ModelA ModelA
├─ModelB ├─NewModel
└─ModelC └─ModelC
Before: After:
Document Document
├─NameString ├─StructuredName
└─Author └─Author
└─NameString └─StructuredName
4.2.2 Versioned model register architecture
The complete model register system consists of three components:
A Global Register that maintains references to all Model Registers
Multiple Model Registers, each containing a consistent version of model classes
A Registrable class for each Lutaml::Model instance that can be registered in a model register
┌─────────────────────────────────────────┐
│ Global Register │
│ │
│ ┌───────────┐ ┌───────────┐ ┌───┐ │
│ │Register A │ │Register B │ │...│ │
│ │ (ID: A) │ │ (ID: B) │ │ │ │
│ └───────────┘ └───────────┘ └───┘ │
└─────────────────────────────────────────┘
│ │
▼ ▼
┌───────────┐ ┌───────────┐
│ Models A │ │ Models B │
│ - Top A │ │ - Top B │
│ - L2 A │ │ - L2 B │
│ - L3 A │ │ - L3 B │
└───────────┘ └───────────┘
When a model is instantiated, it is associated with the Model Register that created it. To avoid circular references that can impact garbage collection, models do not maintain direct references to their Model Register. Instead, they maintain the register’s ID, which is used to look up the appropriate register when needed.
┌───────────────────┐ uses ID to lookup
│ TopLevelClass │─────────────────┐
│ (Register ID: A) │◄──────────────┐ │
└───────────────────┘ │ │
│ │ │
│ │ │
▼ │ ▼
┌───────────────────┐ ┌───────────────────┐
│ Model Register A │◄─────│ Global Register │
└───────────────────┘ └───────────────────┘
│
│ provides
▼
┌───────────────────┐ uses ID to lookup
│ L2Class │─────────────────┐
│ (Register ID: A) │◄──────────────┐ │
└───────────────────┘ │ │
│ │ │
│ │ │
▼ │ ▼
┌───────────────────┐ ┌───────────────────┐
│ Model Register A │◄─────│ Global Register │
└───────────────────┘ └───────────────────┘
│
│ provides
▼
┌───────────────────┐
│ L3Class │
│ (Register ID: A) │
└───────────────────┘
4.3 Basic usage
A model register is created and populated with model definitions.
Syntax:
class ModelName < Lutaml::Model::Serializable
attribute :attribute_name, :attribute_type
end
register = Lutaml::ModelRegister.new(ModelName)
register.resolve("ModelName")
# => ModelName
class StorageRecordCollection < Lutaml::ModelCollection
instances :items, StorageRecord
end
class StorageRecord
attribute :ceramic_info, GeneralCeramicInfo
end
class GeneralCeramicInfo < Lutaml::Model::Serializable
attribute :material, :string
attribute :production_date, :date
end
register = Lutaml::ModelRegister.new
register.register_model_tree(StorageRecordCollection)
4.4 Model paths
Model paths identify specific locations within a model hierarchy using the LutaML Path syntax.
Common path patterns:
Single model: ModelName
Nested model: ParentModel > ChildModel
Model attribute: Model.attribute
Nested attribute: ParentModel > ChildModel.attribute
EXAMPLE
Given this model hierarchy:
class Publication
attribute :metadata, Metadata
end
class Book < Publication
attribute :chapters, Chapter, collection: true
end
class Metadata
attribute :title, :string
attribute :date, :date
end
Valid model paths include:
Publication — References the Publication model
Publication > Book — References Book as a child of Publication
Publication.metadata — References the metadata attribute
Publication > Book.chapters — References the chapters collection
5 Global register
5.1 General
The Global Register maintains a mapping of register IDs to Model Register instances. It serves as the central repository for all model registers in the system, allowing models to locate the appropriate register when they need to instantiate dependent model classes.
5.2 Key capabilities
The Global Register provides methods to:
Register a new Model Register with a unique ID
Look up a Model Register by ID
Remove a Model Register when it’s no longer needed
# Create model registers
register_v1 = ModelRegister.new(:v1)
register_v2 = ModelRegister.new(:v2)
# Register with global registry
GlobalRegister.register(register_v1)
GlobalRegister.register(register_v2)
# Look up by ID
same_register = GlobalRegister.lookup(:v1)
# => register_v1
6 Model registration
6.1 General
Models must be registered before they can be dynamically modified. Each model is registered with a specific Model Register instance, which may contain a version-consistent set of model classes.
This is a manual step.
6.2 Registration methods
6.2.1 Register individual model
Registers a single model class.
Syntax:
register.register_model(ModelClass) <1>
# or
register.register_model(:model_name, ModelClass) <2>
Key
- 1
Registers the model class with a snake-case of its name
- 2
Registers the model class with a custom name
EXAMPLE
register.register_model(StorageRecord)
# or
register.register_model(:storage_record, StorageRecord)
6.2.2 Register model tree
Registers a model and all its dependent models.
Syntax:
register.register_model_tree(RootModelClass)
EXAMPLE
register.register_model_tree(StorageRecordCollection)
# Automatically registers:
# - StorageRecordCollection
# - StorageRecord
# - GeneralCeramicInfo
7 Mixed-version model trees
7.1 General
A key advantage of the Model Register system is the ability to create custom registers that mix and match classes from different versions. This enables greater flexibility when specific versions of different model classes need to work together.
┌───────────────────────────────────────────────────────────────┐
│ Global Register │
│ │
│ ┌────────────┐ ┌────────────┐ ┌────────────────────┐ │
│ │Register v1 │ │Register v2 │ │Register MixedVer │ │
│ │ (ID: v1) │ │ (ID: v2) │ │ (ID: mixed) │ │
│ └────────────┘ └────────────┘ └────────────────────┘ │
└───────────────────────────────────────────────────────────────┘
│ │ │
▼ ▼ ▼
┌────────────┐ ┌────────────┐ ┌────────────────────┐
│ v1 Models │ │ v2 Models │ │ Mixed Ver Models │
├────────────┤ ├────────────┤ ├────────────────────┤
│TopLevel v1 │ │TopLevel v2 │ │TopLevel from v1 │
│L2Class v1 │ │L2Class v2 │ │L2Class from v2 │
│L3Class v1 │ │L3Class v2 │ │L3Class from v1 │
└────────────┘ └────────────┘ └────────────────────┘
Different Model Registers can contain different versions of the same model classes:
┌────────────────────────────────────────────────────┐
│ Global Register │
│ │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ Register v1 │ │ Register v2 │ │
│ │ (ID: v1) │ │ (ID: v2) │ │
│ └─────────────────┘ └─────────────────┘ │
└────────────────────────────────────────────────────┘
│ │
▼ ▼
┌─────────────────┐ ┌─────────────────┐
│ TopLevel (v1) │ │ TopLevel (v2) │
├─────────────────┤ ├─────────────────┤
│ - prop1: String │ │ - prop1: String │
│ - prop2: Int │ │ - prop2: Int │
│ │ │ - prop3: Bool │ <- New in v2
└─────────────────┘ └─────────────────┘
│ │
▼ ▼
┌─────────────────┐ ┌─────────────────┐
│ L2Class (v1) │ │ L2Class (v2) │
├─────────────────┤ ├─────────────────┤
│ - oldProp │ │ - newProp │ <- Changed
└─────────────────┘ └─────────────────┘
│ │
▼ ▼
┌─────────────────┐ ┌─────────────────┐
│ L3Class (v1) │ │ L3Class (v2) │
└─────────────────┘ └─────────────────┘
7.2 Creating a mixed-version register
A mixed-version register allows you to selectively choose which version of each model class to use in a given context. This enables targeted upgrades and special compatibility scenarios.
Syntax:
# Create a mixed register
mixed_register = ModelRegister.new(:mixed)
# Configure with models from different versions
mixed_register.register_model(:class_name, register_v1.get_class(:class_name))
mixed_register.register_model(:other_class, register_v2.get_class(:other_class))
# Register with global registry
GlobalRegister.register(mixed_register)
EXAMPLE
# First, create standard version registers
register_v1 = ModelRegister.new(:v1)
register_v2 = ModelRegister.new(:v2)
GlobalRegister.register(register_v1)
GlobalRegister.register(register_v2)
# Register classes into the registers
register_v1.register_model(:top_level_class, V1::TopLevelClass)
register_v1.register_model(:l2_class, V1::L2Class)
register_v1.register_model(:l3_class, V1::L3Class)
register_v2.register_model(:top_level_class, V2::TopLevelClass)
register_v2.register_model(:l2_class, V2::L2Class)
register_v2.register_model(:l3_class, V2::L3Class)
# Create a mixed version register
mixed_register = ModelRegister.new(:mixed)
# Configure mixed register with specific versions
mixed_register.register_model(:top_level_class, register_v1.get_class(:top_level_class))
mixed_register.register_model(:l2_class, register_v2.get_class(:l2_class))
mixed_register.register_model(:l3_class, register_v1.get_class(:l3_class))
GlobalRegister.register(mixed_register)
# Use the mixed register
top_level = mixed_register.get_class(:top_level_class).from_json(data)
top_level.l2_data.class
# => V2::L2Class
top_level.l2_data.l3_data.class
# => V1::L3Class
8 Dynamic modifications
8.1 Attribute type substitution
8.1.1 General
Replace an attribute’s type with another model type.
8.1.2 Single attribute substitution
Replaces a specific attribute instance’s type.
Syntax:
register.register_dynamic_attribute(
model_path: "Model > SubModel.attribute",
attribute: :attribute_name,
type: NewAttributeType
)
EXAMPLE
class VaseCeramicInfo < GeneralCeramicInfo
attribute :height, :float
attribute :diameter, :float
end
register.register_dynamic_attribute(
model_path: "StorageRecordCollection > StorageRecord",
attribute: :ceramic_info,
type: VaseCeramicInfo
)
8.1.3 Global type substitution
Replaces all instances of a type throughout the model hierarchy.
Syntax:
register.register_global_type_substitution(
from_type: OldType,
to_type: NewType
)
EXAMPLE
# Replace all Mml::Mi instances with Plurimath equivalents
register.register_global_type_substitution(
from_type: Mml::Mi,
to_type: Plurimath::Math::Symbols::Symbol
)
8.2 Model tree operations
8.2.1 Subtree replacement
Replaces an entire subtree in the model hierarchy.
Syntax:
register.replace_subtree(
model_path: "Path > To > Subtree",
new_subtree: NewRootModel
)
EXAMPLE
class NewMetadataTree < Lutaml::Model::Serializable
attribute :title, :string
attribute :description, :string
end
register.replace_subtree(
model_path: "Document > Metadata",
new_subtree: NewMetadataTree
)
8.2.2 Attribute modification
Add or remove attributes from a model.
Syntax:
# Add attribute
register.add_attribute(
model_path: "Model",
attribute: :new_attribute,
type: AttributeType
)
# Remove attribute
register.remove_attribute(
model_path: "Model",
attribute: :old_attribute
)
EXAMPLE
register.add_attribute(
model_path: "StorageRecord",
attribute: :last_modified,
type: :datetime
)
register.remove_attribute(
model_path: "StorageRecord",
attribute: :deprecated_field
)
8.3 Model resolution
8.3.1 General
After configuration, models are retrieved from the register using the resolve method.
8.3.2 Basic resolution
Retrieves a configured model class by name.
Syntax:
ModelClass = register.resolve("ModelName")
# or
ModelClass = register.get_class(:model_name)
EXAMPLE
StorageRecordClass = register.resolve("StorageRecord")
record = StorageRecordClass.new(
ceramic_info: VaseCeramicInfo.new(
material: "clay",
height: 10.0
)
)
8.3.3 Path-based resolution
Retrieves a model class using a model path.
Syntax:
ModelClass = register.resolve_path("Path > To > Model")
EXAMPLE
VaseRecord = register.resolve_path(
"StorageRecordCollection > StorageRecord"
)
8.4 Register-aware attribute definitions
The model register system provides a new syntax for defining attributes that are aware of the model register context:
Syntax:
attribute :name, register(...symbol for type...)
This syntax indicates that the attribute type should be resolved through the model register at runtime. This approach allows models to instantiate dependent models using the same register that created them, ensuring version consistency.
EXAMPLE
class TopLevelClass < Lutaml::Model::Serializable
attribute :l2_data, register(:l2_class)
end
class L2Class < Lutaml::Model::Serializable
attribute :l3_data, register(:l3_class)
end
class L3Class < Lutaml::Model::Serializable
end
9 Implementation details
9.1 Model class implementation
Model classes are designed to work with the Model Register system by:
Storing the ID of the Model Register that created them
Using this ID to look up the appropriate register when they need to instantiate dependent models
Passing the register ID to any dependent models they create
9.2 Registrable class
Each model class that can be registered in a model register includes the Registrable module. By default, all Lutaml::Model::Serializable classes include this module.
The Registrable module provides:
Methods to store and retrieve the register ID
Functionality to look up the appropriate register when instantiating dependent models
Serialization support that preserves register associations
9.3 Value register implementation
While model instances store the register ID to maintain proper model relationships, value instances (simple types) do not need to maintain this association since they don’t instantiate other models.
The distinction between model types and value types affects how they interact with the register system:
Model types: Store the register ID to maintain version consistency
Value types: Do not store register information to minimize overhead
10 Example scenarios
10.1 Namespace conversion
This example demonstrates converting models between different namespaces.
# Original MathML models
module Mml
class Expression < Lutaml::Model::Serializable
attribute :operator, Mi
end
class Mi < Lutaml::Model::Serializable
attribute :value, :string
end
end
# Target Plurimath models
module Plurimath
module Math
module Symbols
class Symbol < Lutaml::Model::Serializable
attribute :value, :string
end
end
end
end
# Register and configure conversion
register = Lutaml::ModelRegister.new
register.register_model_tree(Mml::Expression)
register.register_global_type_substitution(
from_type: Mml::Mi,
to_type: Plurimath::Math::Symbols::Symbol
)
# Use converted models
ExpressionClass = register.resolve("Mml::Expression")
expression = ExpressionClass.new(
operator: Plurimath::Math::Symbols::Symbol.new(
value: "+"
)
)
10.2 Dynamic model extension
This example shows extending models with new attributes.
class BaseDocument < Lutaml::Model::Serializable
attribute :title, :string
end
class Chapter < Lutaml::Model::Serializable
attribute :content, :string
end
register = Lutaml::ModelRegister.new
register.register_model_tree(BaseDocument)
# Add versioning attributes
register.add_attribute(
model_path: "BaseDocument",
attribute: :version,
type: :string
)
register.add_attribute(
model_path: "Chapter",
attribute: :last_modified,
type: :datetime
)
# Use extended models
DocumentClass = register.resolve("BaseDocument")
doc = DocumentClass.new(
title: "Example",
version: "1.0"
)
10.3 Versioned model instantiation
This example demonstrates using different model register versions.
# Create and register model registers for different versions
register_v1 = ModelRegister.new(:v1)
register_v2 = ModelRegister.new(:v2)
GlobalRegister.register(register_v1)
GlobalRegister.register(register_v2)
# Register models in each version
register_v1.register_model(:document, V1::Document)
register_v2.register_model(:document, V2::Document)
# Use v1 register to create a Document instance
data = { title: "Example", content: "Text" }
doc_v1 = register_v1.get_class(:document).from_json(data)
# Use v2 register to create a Document instance
doc_v2 = register_v2.get_class(:document).from_json(data)
# The instances will use their respective register versions
# for instantiating any dependent models
Annex A
(normative)
Tutorial: Building an adaptive document model
This tutorial demonstrates using model registers to create an adaptive document model system that can be customized for different use cases.
A.1 Step 1: Base document model
Learning outcomes
Create initial model hierarchy
Register models
Understand basic model relationships
# Define base models
class Document < Lutaml::Model::Serializable
attribute :metadata, Metadata
attribute :content, Content
end
class Metadata < Lutaml::Model::Serializable
attribute :title, :string
attribute :author, :string
attribute :date, :date
end
class Content < Lutaml::Model::Serializable
attribute :sections, Section, collection: true
end
class Section < Lutaml::Model::Serializable
attribute :title, :string
attribute :body, :string
end
# Create and populate register
register = Lutaml::ModelRegister.new
register.register_model_tree(Document)
A.2 Step 2: Technical documentation extension
Learning outcomes
Extend models with new attributes
Replace attribute types
Use path-based modifications
# Define technical documentation models
class TechnicalMetadata < Metadata
attribute :version, :string
attribute :status, :string
end
class CodeSection < Section
attribute :language, :string
attribute :code, :string
end
# Configure register
register.register_dynamic_attribute(
model_path: "Document",
attribute: :metadata,
type: TechnicalMetadata
)
register.add_attribute(
model_path: "Document > Content > Section",
attribute: :type,
type: :string
)
# Allow code sections
register.register_dynamic_attribute(
model_path: "Document > Content",
attribute: :sections,
type: CodeSection
)
A.3 Step 3: Academic publication extension
Learning outcomes
Replace model subtrees
Add nested attributes
Handle collections
# Define academic models
class AcademicMetadata < Metadata
attribute :abstract, :string
attribute :keywords, :string, collection: true
attribute :references, Reference, collection: true
end
class Reference < Lutaml::Model::Serializable
attribute :authors, :string, collection: true
attribute :title, :string
attribute :journal, :string
attribute :year, :integer
end
# Configure register
register.replace_subtree(
model_path: "Document > Metadata",
new_subtree: AcademicMetadata
)
# Add citation support
register.add_attribute(
model_path: "Document > Content > Section",
attribute: :citations,
type: Reference,
collection: true
)
A.4 Step 4: Global modifications
Learning outcomes
Apply global type substitutions
Manage cross-cutting concerns
Handle model relationships
# Define enhanced types
class EnhancedString < Lutaml::Model::Serializable
attribute :value, :string
attribute :language, :string
attribute :format, :string
end
# Replace all string attributes with enhanced strings
register.register_global_type_substitution(
from_type: :string,
to_type: EnhancedString
)
# Add tracking to all models
register.add_attribute(
model_path: "*",
attribute: :created_at,
type: :datetime
)
register.add_attribute(
model_path: "*",
attribute: :updated_at,
type: :datetime
)
A.5 Step 5: Working with mixed model versions
Learning outcomes
Create and manage multiple model versions
Configure mixed-version model trees
Ensure version consistency in complex hierarchies
# Create version registers
register_v1 = ModelRegister.new(:v1)
register_v2 = ModelRegister.new(:v2)
GlobalRegister.register(register_v1)
GlobalRegister.register(register_v2)
# Register our document models in both registers
register_v1.register_model_tree(Document)
# ... v2 has updated implementations
register_v2.register_model_tree(V2::Document)
# Create a mixed register for specific compatibility needs
mixed_register = ModelRegister.new(:mixed)
# Use Document from v2 but Content from v1
mixed_register.register_model(:document, register_v2.get_class(:document))
mixed_register.register_model(:content, register_v1.get_class(:content))
mixed_register.register_model(:section, register_v1.get_class(:section))
GlobalRegister.register(mixed_register)
# Create a document using our mixed register
doc = mixed_register.get_class(:document).new(
metadata: mixed_register.get_class(:metadata).new(
title: "Mixed Version Example"
),
content: mixed_register.get_class(:content).new
)
A.6 Summary
This tutorial demonstrated:
Basic model registration and configuration
Dynamic attribute type substitution
Model subtree replacement
Global type modifications
Cross-cutting attribute addition
Working with versioned model registers
Creating mixed-version model trees
The progression shows how model registers enable flexible and maintainable model configurations that can adapt to different requirements while maintaining model consistency.