Class: Lutaml::KeyValue::Transformation

Inherits:
Model::Transformation
  • Object
show all
Includes:
Model::RenderPolicy
Defined in:
lib/lutaml/key_value/transformation.rb,
lib/lutaml/key_value/transformation/rule_compiler.rb,
lib/lutaml/key_value/transformation/value_serializer.rb,
lib/lutaml/key_value/transformation/collection_serializer.rb

Overview

KeyValue-specific transformation implementation.

Transforms model instances into KeyValueElement trees without serialization concerns. This provides the same architectural pattern as Xml::Transformation but for key-value formats (JSON, YAML, TOML).

Architecture:

  • Content Layer: KeyValueElement defines WHAT to serialize
  • Presentation Layer: Adapters define HOW to serialize

This is a critical step toward symmetric OOP architecture across all serialization formats.

Defined Under Namespace

Classes: CollectionSerializer, RuleCompiler, ValueSerializer

Constant Summary collapse

NATIVE_VALUE_CLASS =

Builtin Value types whose cast of an already-native value is the identity — used by the serialize_value fast path.

{
  ::Lutaml::Model::Type::String => ::String,
  ::Lutaml::Model::Type::Integer => ::Integer,
  ::Lutaml::Model::Type::Float => ::Float,
  ::Lutaml::Model::Type::Date => ::Date,
  ::Lutaml::Model::Type::Time => ::Time,
  ::Lutaml::Model::Type::DateTime => ::DateTime,
  ::Lutaml::Model::Type::Symbol => ::Symbol,
}.freeze

Instance Method Summary collapse

Methods included from Model::RenderPolicy

derived_attribute_for?, #should_skip_delegated_value?

Constructor Details

#initialize(model_class, mapping_dsl, format, register) ⇒ Transformation

Initialize serializers before calling super This must happen before super calls freeze

Parameters:

  • The model class to transform

  • The mapping DSL to compile

  • The format (:json, :yaml, :toml)

  • The register for type resolution



45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
# File 'lib/lutaml/key_value/transformation.rb', line 45

def initialize(model_class, mapping_dsl, format, register)
  # Extract register_id before super
  register_id = extract_register_id(register)
  transformation_factory = ->(type_class) {
    build_child_transformation(type_class)
  }

  # Set up serializers before calling super
  # The super will call compile_rules and freeze
  @rule_compiler = RuleCompiler.new(
    model_class: model_class,
    register_id: register_id,
    format: format,
    transformation_factory: transformation_factory,
  )

  @value_serializer = ValueSerializer.new(
    format: format,
    register_id: register_id,
    model_class: model_class,
    transformation_factory: transformation_factory,
  )

  @collection_serializer = CollectionSerializer.new(
    format: format,
    register_id: register_id,
    value_serializer: @value_serializer,
    transformation_factory: transformation_factory,
  )

  super
end

Instance Method Details

#build_child_transformation(type_class, format = self.format, register = self.register) ⇒ Transformation?

Build child transformation for nested model

Parameters:

  • The nested model class

  • (defaults to: self.format)

    The format

  • (defaults to: self.register)

    The register

Returns:

  • Child transformation or nil



99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
# File 'lib/lutaml/key_value/transformation.rb', line 99

def build_child_transformation(type_class, format = self.format,
register = self.register)
  return nil unless type_class.is_a?(Class) &&
    type_class.include?(Lutaml::Model::Serialize)

  # Prevent infinite recursion for self-referential models
  compilation_stack = Thread.current[:lutaml_compilation_stack] ||= []
  return nil if compilation_stack.include?(type_class)

  compilation_stack.push(type_class)
  begin
    # Get the mapping for the current format
    mapping = type_class.mappings_for(format,
                                      extract_register_id(register))

    # Create a new Transformation instance with the mapping
    self.class.new(type_class, mapping, format, register)
  ensure
    compilation_stack.pop
  end
end

#extract_register_id(register) ⇒ Symbol?

Extract register_id from register parameter

Parameters:

  • The register

Returns:

  • The register ID



82
83
84
85
86
87
88
89
90
91
# File 'lib/lutaml/key_value/transformation.rb', line 82

def extract_register_id(register)
  case register
  when Lutaml::Model::Register
    register.id
  when Symbol
    register
  else
    Lutaml::Model::Config.default_register
  end
end

#transform(model_instance, options = {}) ⇒ Lutaml::KeyValue::DataModel::Element

Transform a model instance into KeyValueElement tree

Parameters:

  • The model instance to transform

  • (defaults to: {})

    Transformation options (supports :only, :except for filtering)

Returns:

  • The root element



178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
# File 'lib/lutaml/key_value/transformation.rb', line 178

def transform(model_instance, options = {})
  # For key-value formats, we typically don't have a named root
  # Instead, we create an anonymous root that holds all attributes
  root = Lutaml::KeyValue::DataModel::Element.new("__root__")

  # Apply each compiled rule (with filtering support)
  compiled_rules.each do |rule|
    # Check if this rule should be applied based on only/except options
    next unless valid_mapping?(rule, options)

    apply_rule(root, rule, model_instance, options)
  end

  if ENV["DEBUG_KEYED_COLLECTION"]
    puts "=== TRANSFORM COMPLETE ==="
    puts "root.children.count: #{root.children.count}"
    root.children.each do |child|
      puts "  child key=#{child.key.inspect}, value=#{child.value.inspect}, to_hash=#{child.to_hash.inspect}"
    end
    puts "root.to_hash: #{root.to_hash.inspect}"
  end

  root
end