Published

Ribose Inc. Standard

•
RS 3004
LutaML — Model Register
LutaML
Ribose Inc. Standard

Published 2025-02-20

Classification: unrestricted




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
Figure 1
Before:                        After:
  ModelA                        ModelA
  ├─ModelB                      ├─ModelB
  │ └─ModelC                    │ └─NewSubtree
  └─ModelD                      └─ModelD
Figure 2 — Subtree Swap
Before:                        After:
  ModelA                        ModelA
  ├─ModelB                      ├─NewModel
  └─ModelC                      └─ModelC
Figure 3 — Single Model Swap
Before:                        After:
  Document                      Document
  ├─NameString                  ├─StructuredName
  └─Author                      └─Author
    └─NameString                  └─StructuredName
Figure 4 — Global Type Substitution

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    │
└───────────┘     └───────────┘
Figure 5

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) │
└───────────────────┘
Figure 6

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
Figure 7
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)
Figure 8

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
Figure 9

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>
Figure 10

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)
Figure 11

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     │
└────────────┘    └────────────┘    └────────────────────┘
Figure 12

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)    │
└─────────────────┘      └─────────────────┘
Figure 13

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)
Figure 14

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
)
Figure 15

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
)
Figure 16

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
)
Figure 17

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
)
Figure 18

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)
Figure 19

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")
Figure 20

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...)
Figure 21

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: "+"
  )
)
Figure 22

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"
)
Figure 23

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
Figure 24

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)
Figure A.1

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
)
Figure A.2

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
)
Figure A.3

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
)
Figure A.4

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
)
Figure A.5

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.