lutaml-model 0.8.9 → 0.8.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/dependent-repos.json +1 -0
  3. data/.rubocop_todo.yml +52 -22
  4. data/README.adoc +43 -0
  5. data/docs/_guides/jsonld-serialization.adoc +3 -1
  6. data/docs/_guides/rdf-serialization.adoc +94 -8
  7. data/docs/_guides/turtle-serialization.adoc +17 -4
  8. data/lib/lutaml/jsonld/transform.rb +70 -24
  9. data/lib/lutaml/key_value/transform.rb +5 -5
  10. data/lib/lutaml/key_value/transformation/collection_serializer.rb +25 -11
  11. data/lib/lutaml/key_value/transformation/value_serializer.rb +7 -7
  12. data/lib/lutaml/key_value/transformation.rb +27 -17
  13. data/lib/lutaml/model/adapter_resolver.rb +4 -6
  14. data/lib/lutaml/model/attribute.rb +26 -23
  15. data/lib/lutaml/model/cached_type_resolver.rb +10 -9
  16. data/lib/lutaml/model/cli.rb +1 -1
  17. data/lib/lutaml/model/collection.rb +4 -4
  18. data/lib/lutaml/model/comparable_model.rb +11 -11
  19. data/lib/lutaml/model/config.rb +1 -1
  20. data/lib/lutaml/model/consolidation/dispatcher.rb +1 -1
  21. data/lib/lutaml/model/consolidation/pattern_chunker.rb +3 -3
  22. data/lib/lutaml/model/format_registry.rb +6 -4
  23. data/lib/lutaml/model/global_context.rb +2 -2
  24. data/lib/lutaml/model/global_register.rb +1 -1
  25. data/lib/lutaml/model/instrumentation.rb +1 -1
  26. data/lib/lutaml/model/mapping/mapping_rule.rb +3 -3
  27. data/lib/lutaml/model/mapping/model_mapping.rb +1 -1
  28. data/lib/lutaml/model/mapping/model_mapping_rule.rb +1 -1
  29. data/lib/lutaml/model/register.rb +3 -3
  30. data/lib/lutaml/model/render_policy.rb +11 -17
  31. data/lib/lutaml/model/runtime_compatibility.rb +0 -1
  32. data/lib/lutaml/model/schema/xml_compiler/group.rb +1 -1
  33. data/lib/lutaml/model/schema/xml_compiler/registry_generator.rb +1 -1
  34. data/lib/lutaml/model/schema/xml_compiler/sequence.rb +0 -2
  35. data/lib/lutaml/model/schema/xml_compiler.rb +14 -14
  36. data/lib/lutaml/model/serialize/attribute_definition.rb +1 -1
  37. data/lib/lutaml/model/serialize/deserialization_context.rb +50 -0
  38. data/lib/lutaml/model/serialize/format_conversion.rb +2 -2
  39. data/lib/lutaml/model/serialize/initialization.rb +44 -7
  40. data/lib/lutaml/model/serialize/model_import.rb +1 -1
  41. data/lib/lutaml/model/serialize.rb +8 -1
  42. data/lib/lutaml/model/services/rule_value_extractor.rb +2 -1
  43. data/lib/lutaml/model/store.rb +77 -24
  44. data/lib/lutaml/model/transformation_registry.rb +1 -1
  45. data/lib/lutaml/model/type_context.rb +7 -1
  46. data/lib/lutaml/model/type_resolver.rb +1 -6
  47. data/lib/lutaml/model/utils.rb +19 -6
  48. data/lib/lutaml/model/validation_framework.rb +1 -1
  49. data/lib/lutaml/model/value_transformer.rb +2 -2
  50. data/lib/lutaml/model/version.rb +1 -1
  51. data/lib/lutaml/rdf/mapping.rb +19 -13
  52. data/lib/lutaml/rdf/mapping_rule.rb +19 -2
  53. data/lib/lutaml/rdf/member_rule.rb +19 -2
  54. data/lib/lutaml/rdf/transform.rb +20 -11
  55. data/lib/lutaml/turtle/transform.rb +125 -53
  56. data/lib/lutaml/xml/adapter/adapter_helpers.rb +1 -1
  57. data/lib/lutaml/xml/adapter/base_adapter.rb +10 -14
  58. data/lib/lutaml/xml/adapter/namespace_uri_collector.rb +3 -3
  59. data/lib/lutaml/xml/adapter/plan_based_builder.rb +14 -14
  60. data/lib/lutaml/xml/adapter/xml_serializer.rb +3 -3
  61. data/lib/lutaml/xml/configurable.rb +2 -1
  62. data/lib/lutaml/xml/data_model.rb +2 -2
  63. data/lib/lutaml/xml/decisions/decision_context.rb +3 -3
  64. data/lib/lutaml/xml/decisions/rules/element_form_default_unqualified_rule.rb +1 -1
  65. data/lib/lutaml/xml/decisions/rules/element_form_option_rule.rb +1 -1
  66. data/lib/lutaml/xml/decisions/rules/used_prefix_rule.rb +1 -1
  67. data/lib/lutaml/xml/declaration_plan.rb +2 -2
  68. data/lib/lutaml/xml/declaration_planner.rb +12 -13
  69. data/lib/lutaml/xml/document.rb +13 -13
  70. data/lib/lutaml/xml/format_chooser.rb +3 -3
  71. data/lib/lutaml/xml/hoisting_algorithm.rb +1 -1
  72. data/lib/lutaml/xml/mapping.rb +2 -2
  73. data/lib/lutaml/xml/mapping_rule.rb +16 -3
  74. data/lib/lutaml/xml/model_transform.rb +17 -19
  75. data/lib/lutaml/xml/namespace_collector.rb +10 -10
  76. data/lib/lutaml/xml/namespace_declaration.rb +2 -2
  77. data/lib/lutaml/xml/namespace_declaration_data.rb +5 -8
  78. data/lib/lutaml/xml/namespace_scope_config.rb +3 -2
  79. data/lib/lutaml/xml/namespace_type_resolver.rb +4 -4
  80. data/lib/lutaml/xml/nokogiri/element.rb +2 -2
  81. data/lib/lutaml/xml/polymorphic_value_handler.rb +1 -1
  82. data/lib/lutaml/xml/schema/xsd/base.rb +7 -7
  83. data/lib/lutaml/xml/schema/xsd/choice.rb +2 -2
  84. data/lib/lutaml/xml/schema/xsd/complex_type.rb +5 -5
  85. data/lib/lutaml/xml/schema/xsd/errors/message_builder.rb +3 -3
  86. data/lib/lutaml/xml/schema/xsd/group.rb +2 -2
  87. data/lib/lutaml/xml/schema/xsd/sequence.rb +2 -2
  88. data/lib/lutaml/xml/schema/xsd_schema.rb +5 -5
  89. data/lib/lutaml/xml/serialization/format_conversion.rb +4 -3
  90. data/lib/lutaml/xml/transformation/element_builder.rb +4 -2
  91. data/lib/lutaml/xml/transformation/rule_applier.rb +2 -2
  92. data/lib/lutaml/xml/transformation/value_serializer.rb +4 -6
  93. data/lib/lutaml/xml/transformation.rb +4 -4
  94. data/lib/lutaml/xml/type/configurable.rb +0 -4
  95. data/lib/lutaml/xml/xml_element.rb +21 -13
  96. data/lutaml-model.gemspec +1 -1
  97. data/spec/lutaml/jsonld/transform_spec.rb +239 -0
  98. data/spec/lutaml/model/cached_type_resolver_spec.rb +3 -3
  99. data/spec/lutaml/model/optimization_spec.rb +228 -0
  100. data/spec/lutaml/model/store_spec.rb +195 -0
  101. data/spec/lutaml/rdf/mapping_rule_spec.rb +97 -0
  102. data/spec/lutaml/rdf/mapping_spec.rb +74 -4
  103. data/spec/lutaml/rdf/member_rule_spec.rb +41 -0
  104. data/spec/lutaml/rdf/rdf_transform_spec.rb +95 -29
  105. data/spec/lutaml/turtle/mapping_spec.rb +2 -2
  106. data/spec/lutaml/turtle/transform_spec.rb +315 -0
  107. data/spec/lutaml/xml/data_model_spec.rb +10 -28
  108. metadata +7 -4
@@ -167,7 +167,7 @@ module Lutaml
167
167
  return nil if Lutaml::Model::RuntimeCompatibility.opal?
168
168
  return nil unless defined?(GC)
169
169
 
170
- GC.start if GC.respond_to?(:compact)
170
+ GC.start if RUBY_VERSION >= "2.7"
171
171
  IO.popen(["ps", "-o", "rss=", "-p", Process.pid.to_s],
172
172
  &:read).to_i * 1024
173
173
  rescue StandardError
@@ -250,7 +250,7 @@ module Lutaml
250
250
 
251
251
  def serialize_attribute(model, element, doc)
252
252
  if custom_methods[:to]
253
- model.send(custom_methods[:to], model, element, doc)
253
+ model.public_send(custom_methods[:to], model, element, doc)
254
254
  end
255
255
  end
256
256
 
@@ -266,7 +266,7 @@ module Lutaml
266
266
 
267
267
  def serialize(model, parent = nil, doc = nil)
268
268
  if custom_methods[:to]
269
- model.send(custom_methods[:to], model, parent, doc)
269
+ model.public_send(custom_methods[:to], model, parent, doc)
270
270
  else
271
271
  value = to_value_for(model)
272
272
 
@@ -415,7 +415,7 @@ module Lutaml
415
415
  def handle_custom_method(model, value, mapper_class)
416
416
  return if !custom_methods[:from] || value.nil?
417
417
 
418
- mapper_class.new.send(custom_methods[:from], model, value)
418
+ mapper_class.new.public_send(custom_methods[:from], model, value)
419
419
  true
420
420
  end
421
421
 
@@ -63,7 +63,7 @@ module Lutaml
63
63
  from_attr, to_attr = transform_attributes(rule, reverse: reverse)
64
64
  next if from_attr.nil? || to_attr.nil?
65
65
 
66
- value = input.send(from_attr.name)
66
+ value = input.public_send(from_attr.name)
67
67
 
68
68
  value = transformed_value(value, rule, from_attr, to_attr,
69
69
  reverse: reverse)
@@ -47,7 +47,7 @@ module Lutaml
47
47
  when Proc
48
48
  transform.call(value)
49
49
  when String, Symbol
50
- transformer.new.send(transform, value)
50
+ transformer.new.public_send(transform, value)
51
51
  when Class
52
52
  transform.public_send(transform_method, value, mapping: true)
53
53
  end
@@ -95,7 +95,7 @@ module Lutaml
95
95
  end
96
96
 
97
97
  def self._resolve_for_child_uncached(child_class, parent_register)
98
- default_reg = if child_class.respond_to?(:lutaml_default_register)
98
+ default_reg = if child_class.is_a?(Class) && child_class.include?(Lutaml::Model::Serialize)
99
99
  child_class.lutaml_default_register
100
100
  end
101
101
 
@@ -172,7 +172,7 @@ module Lutaml
172
172
 
173
173
  # Set @register on the class so instances know their context
174
174
  # This ensures proper OOP context propagation during serialization
175
- klass.set_register_context(@id) if klass.respond_to?(:set_register_context)
175
+ klass.set_register_context(@id) if klass.is_a?(Class) && klass.include?(Lutaml::Model::Serialize)
176
176
 
177
177
  # Register in GlobalContext
178
178
  ctx = global_context
@@ -226,7 +226,7 @@ module Lutaml
226
226
  # Set register context using proper OOP method
227
227
  expected_class.set_register_context(id) if
228
228
  !(expected_class < Lutaml::Model::Type::Value) &&
229
- expected_class.respond_to?(:set_register_context)
229
+ expected_class.is_a?(Class) && expected_class.include?(Lutaml::Model::Serialize)
230
230
 
231
231
  expected_class
232
232
  end
@@ -13,9 +13,11 @@ module Lutaml
13
13
  # time, so downstream code never needs to re-interpret them.
14
14
  module RenderPolicy
15
15
  def self.derived_attribute_for?(context_obj, attr_name)
16
- return false unless context_obj&.class.respond_to?(:attributes)
16
+ return false unless context_obj.is_a?(Lutaml::Model::Serialize) &&
17
+ context_obj.class.is_a?(Class) &&
18
+ context_obj.class.include?(Lutaml::Model::Serialize)
17
19
 
18
- register = context_obj.respond_to?(:lutaml_register) ? context_obj.lutaml_register : nil
20
+ register = context_obj.lutaml_register
19
21
  context_obj.class.attributes(register)&.[](attr_name)&.derived?
20
22
  end
21
23
 
@@ -92,7 +94,7 @@ module Lutaml
92
94
  def should_skip_default?(value, rule, context_obj, attr_name)
93
95
  # Skip if context object is using default and render_default is false
94
96
  # But for collections, check if they were mutated (non-empty)
95
- if context_obj.respond_to?(:using_default?) &&
97
+ if context_obj.is_a?(Lutaml::Model::Serialize) &&
96
98
  context_obj.using_default?(attr_name) &&
97
99
  !extract_option(rule, :render_default)
98
100
  return false if derived_attribute?(context_obj, attr_name)
@@ -115,31 +117,23 @@ module Lutaml
115
117
  # @param option_name [Symbol] The option name
116
118
  # @return [Object, nil] The option value
117
119
  def extract_option(rule, option_name)
118
- if rule.respond_to?(:option)
120
+ if rule.is_a?(CompiledRule)
119
121
  rule.option(option_name)
120
- elsif rule.respond_to?(option_name)
121
- rule.send(option_name)
122
+ elsif rule.is_a?(MappingRule)
123
+ rule.public_send(option_name)
122
124
  end
123
125
  end
124
126
 
125
- # Extract attribute name from rule
126
- #
127
- # @param rule [CompiledRule, MappingRule] The rule
128
- # @return [Symbol] The attribute name
129
127
  def extract_attribute_name(rule)
130
- if rule.respond_to?(:attribute_name)
128
+ if rule.is_a?(CompiledRule)
131
129
  rule.attribute_name
132
- elsif rule.respond_to?(:to)
130
+ else
133
131
  rule.to
134
132
  end
135
133
  end
136
134
 
137
- # Check if rule defines a collection
138
- #
139
- # @param rule [CompiledRule, MappingRule] The rule
140
- # @return [Boolean] true if collection
141
135
  def collection?(rule)
142
- if rule.respond_to?(:collection?)
136
+ if rule.is_a?(CompiledRule) || rule.is_a?(MappingRule)
143
137
  rule.collection?
144
138
  else
145
139
  false
@@ -11,7 +11,6 @@ module Lutaml
11
11
 
12
12
  def self.windows?
13
13
  defined?(Gem) &&
14
- Gem.respond_to?(:win_platform?) &&
15
14
  Gem.win_platform?
16
15
  end
17
16
 
@@ -101,7 +101,7 @@ module Lutaml
101
101
  # because sequence requires a root element
102
102
  if instance.is_a?(Sequence)
103
103
  # Output sequence content directly without the wrapper
104
- instance.send(:xml_block_content, extended_indent)
104
+ instance.xml_block_content(extended_indent)
105
105
  else
106
106
  instance.to_xml_mapping(extended_indent)
107
107
  end
@@ -106,7 +106,7 @@ module Lutaml
106
106
  "\n#{indent}# Resolve serialization mapping imports (sequence imports)\n" +
107
107
  @classes.keys.filter_map do |name|
108
108
  next if name.to_s.include?("Namespace")
109
- next unless @classes[name].respond_to?(:mappings)
109
+ next unless @classes[name].is_a?(Class) && @classes[name].include?(Lutaml::Model::Serialize)
110
110
 
111
111
  class_name = Utils.camel_case(name)
112
112
  "#{indent}#{class_name}.mappings[:xml].ensure_mappings_imported!(:#{@register_id}) if #{class_name}.mappings[:xml]&.respond_to?(:ensure_mappings_imported!)"
@@ -40,8 +40,6 @@ module Lutaml
40
40
  @instances.map(&:required_files)
41
41
  end
42
42
 
43
- private
44
-
45
43
  def xml_block_content(indent)
46
44
  instances.filter_map do |instance|
47
45
  instance.to_xml_mapping(indent * 2)
@@ -166,7 +166,7 @@ module Lutaml
166
166
  require "#{dir}/registry"
167
167
 
168
168
  # Call register_all to register all classes
169
- GeneratedModels.register_all if GeneratedModels.respond_to?(:register_all)
169
+ GeneratedModels.register_all if defined?(GeneratedModels)
170
170
  end
171
171
  end
172
172
 
@@ -210,7 +210,12 @@ module Lutaml
210
210
  schema_to_models(schema.import) if schema.import&.any?
211
211
  # Use schema's resolved_element_order which returns properly typed XSD objects
212
212
  schema.resolved_element_order.each do |order_item|
213
- item_name = order_item.name if order_item.respond_to?(:name)
213
+ item_name = order_item.name if order_item.is_a?(Lutaml::Xml::Schema::Xsd::SimpleType) ||
214
+ order_item.is_a?(Lutaml::Xml::Schema::Xsd::Group) ||
215
+ order_item.is_a?(Lutaml::Xml::Schema::Xsd::ComplexType) ||
216
+ order_item.is_a?(Lutaml::Xml::Schema::Xsd::Element) ||
217
+ order_item.is_a?(Lutaml::Xml::Schema::Xsd::Attribute) ||
218
+ order_item.is_a?(Lutaml::Xml::Schema::Xsd::AttributeGroup)
214
219
  case order_item
215
220
  when Lutaml::Xml::Schema::Xsd::SimpleType
216
221
  @simple_types[item_name] = setup_simple_type(order_item)
@@ -246,8 +251,6 @@ module Lutaml
246
251
  end
247
252
 
248
253
  def restriction_content(instance, restriction)
249
- return instance unless restriction.respond_to?(:max_length)
250
-
251
254
  restriction_min_max(restriction, instance, field: :max_length,
252
255
  value_method: :min)
253
256
  restriction_min_max(restriction, instance, field: :min_length,
@@ -271,7 +274,7 @@ module Lutaml
271
274
 
272
275
  instance.public_send(
273
276
  :"#{field}=",
274
- field_value.map(&:value).send(value_method).to_s,
277
+ field_value.map(&:value).public_send(value_method).to_s,
275
278
  )
276
279
  end
277
280
 
@@ -447,12 +450,10 @@ module Lutaml
447
450
  def setup_restriction(restriction)
448
451
  Restriction.new.tap do |instance|
449
452
  instance.base_class = restriction.base
450
- if restriction.respond_to?(:pattern)
451
- restriction_patterns(restriction.pattern,
452
- instance)
453
- end
453
+ restriction_patterns(restriction.pattern,
454
+ instance)
454
455
  restriction_content(instance, restriction)
455
- if restriction.respond_to?(:enumeration) && restriction.enumeration&.any?
456
+ if restriction.enumeration&.any?
456
457
  instance.enumerations = restriction.enumeration.map(&:value)
457
458
  end
458
459
  end
@@ -523,10 +524,9 @@ compiler_complex_type)
523
524
  def resolved_element_order(object)
524
525
  return [] if object.element_order.nil?
525
526
 
526
- # If the object has its own resolved_element_order method (like XSD objects),
527
+ # If the object is an XSD type (which has its own resolved_element_order),
527
528
  # use it instead of processing element_order which returns generic XML elements
528
- if object.respond_to?(:resolved_element_order) &&
529
- object.class.name.start_with?("Lutaml::Xml::Schema::Xsd", "Lutaml::Xml::Schema::Xsd")
529
+ if object.is_a?(Lutaml::Xml::Schema::Xsd::Base)
530
530
  return object.resolved_element_order
531
531
  end
532
532
 
@@ -538,7 +538,7 @@ compiler_complex_type)
538
538
  next unless element == builder_instance
539
539
 
540
540
  array[i] =
541
- Array(object.send(Utils.snake_case(builder_instance.name)))[index]
541
+ Array(object.public_send(Utils.snake_case(builder_instance.name)))[index]
542
542
  index += 1
543
543
  end
544
544
  end
@@ -122,7 +122,7 @@ module Lutaml
122
122
  else
123
123
  # Builder-style: g.description(value) sets the value
124
124
  value = args.first
125
- send(:"#{name}=", value)
125
+ public_send(:"#{name}=", value)
126
126
  # Track order for mixed_content serialization
127
127
  track_order(name, value, nil) if @__order_tracking__
128
128
  value
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lutaml
4
+ module Model
5
+ module Serialize
6
+ # Encapsulates the parent-to-child options propagation contract.
7
+ #
8
+ # During deserialization, a parent passes context to its children via an
9
+ # options Hash. Only certain keys are safe to propagate — parent-internal
10
+ # keys (resolved_type, namespace_uri, converted) must be stripped so
11
+ # children derive their own context.
12
+ #
13
+ # This class is the single source of truth for which keys propagate,
14
+ # replacing scattered CHILD_PROPAGATION_KEYS constants and ad-hoc
15
+ # `.slice` calls.
16
+ #
17
+ # Usage:
18
+ # child_options = DeserializationContext.propagate(options).merge(register: register)
19
+ # klass.apply_mappings(value, format, child_options)
20
+ class DeserializationContext
21
+ # Keys that are safe to propagate from parent to child deserialization.
22
+ #
23
+ # Parent-internal keys (namespace_uri, resolved_type, converted, mappings)
24
+ # are intentionally excluded — children must derive their own context.
25
+ PROPAGATION_KEYS = %i[
26
+ lutaml_parent
27
+ lutaml_root
28
+ default_namespace
29
+ import_declaration_plan
30
+ polymorphic
31
+ collection
32
+ render_empty
33
+ render_nil
34
+ cdata
35
+ ].freeze
36
+
37
+ # Extract propagable keys from a parent options hash.
38
+ #
39
+ # Returns a new Hash containing only the keys safe for child
40
+ # deserialization. Parent-internal keys are excluded.
41
+ #
42
+ # @param options [Hash] The parent's options hash
43
+ # @return [Hash] A new hash with only propagable keys
44
+ def self.propagate(options)
45
+ options.slice(*PROPAGATION_KEYS)
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -23,7 +23,7 @@ module Lutaml
23
23
  end
24
24
  mappings[format].instance_eval(&)
25
25
 
26
- if mappings[format].respond_to?(:finalize)
26
+ if mappings[format].is_a?(Lutaml::Xml::Mapping)
27
27
  mappings[format].finalize(self)
28
28
  end
29
29
 
@@ -172,7 +172,7 @@ module Lutaml
172
172
 
173
173
  if format == :yamls
174
174
  mapping = mappings[format]
175
- return true if mapping.respond_to?(:yamls_sequence) && mapping.yamls_sequence
175
+ return true if mapping.is_a?(Lutaml::Yamls::Adapter::Mapping) && mapping.yamls_sequence
176
176
  end
177
177
 
178
178
  false
@@ -90,15 +90,15 @@ module Lutaml
90
90
  #
91
91
  # @param source_class [Class] The source class to copy from
92
92
  def initialize_attrs(source_class)
93
- @mappings = Utils.deep_dup(source_class.instance_variable_get(:@mappings)) || {}
94
- @attributes = Utils.deep_dup(source_class.instance_variable_get(:@attributes)) || {}
93
+ @mappings = Utils.deep_dup(source_class.mappings) || {}
94
+ @attributes = Utils.deep_dup(source_class.class_attributes) || {}
95
95
  @choice_attributes = deep_duplicate_choice_attributes(source_class)
96
96
  @register_records = Utils.deep_dup(
97
- source_class.instance_variable_get(:@register_records),
97
+ source_class.register_records,
98
98
  ) || ::Hash.new do |hash, key|
99
99
  hash[key] = { attributes: {}, choice_attributes: [] }
100
100
  end
101
- instance_variable_set(:@model, self)
101
+ model(self)
102
102
  end
103
103
 
104
104
  # Deep duplicate choice attributes from a source class
@@ -106,7 +106,7 @@ module Lutaml
106
106
  # @param source_class [Class] The source class
107
107
  # @return [Array] The duplicated choice attributes
108
108
  def deep_duplicate_choice_attributes(source_class, register = nil)
109
- choice_attrs = Array(source_class.instance_variable_get(:@choice_attributes))
109
+ choice_attrs = Array(source_class.choice_attributes)
110
110
  choice_attrs.map do |choice_attr|
111
111
  choice_attr.deep_duplicate(self, register)
112
112
  end
@@ -127,6 +127,13 @@ module Lutaml
127
127
  end
128
128
  end
129
129
 
130
+ # Raw class-level attributes without register merging.
131
+ # Used by initialize_attrs during class inheritance.
132
+ # @return [Hash] The raw attributes hash
133
+ def class_attributes
134
+ @attributes
135
+ end
136
+
130
137
  # Get all choice attributes for this model
131
138
  #
132
139
  # Merges class-level choice attributes with register-specific choice attributes.
@@ -176,6 +183,12 @@ module Lutaml
176
183
  GlobalContext.resolver.clear_cache(register_id)
177
184
  end
178
185
 
186
+ # Clear per-Attribute type caches (stale entries from GC'd TypeContext objects)
187
+ class_attributes.each_value(&:clear_type_cache)
188
+ @register_records&.each_value do |record|
189
+ record[:attributes]&.each_value(&:clear_type_cache)
190
+ end
191
+
179
192
  # Clear centralized mapping and transformation caches
180
193
  # (Single Source of Truth - no longer uses instance variables)
181
194
  TransformationRegistry.instance.clear
@@ -220,6 +233,12 @@ module Lutaml
220
233
  @using_default[attribute_name] = false
221
234
  end
222
235
 
236
+ Utils.add_method_if_not_defined(klass,
237
+ :values_set_for) do |attribute_names|
238
+ @using_default ||= {}
239
+ attribute_names.each { |name| @using_default[name] = false }
240
+ end
241
+
223
242
  Utils.add_method_if_not_defined(klass,
224
243
  :using_default?) do |attribute_name|
225
244
  @using_default ||= {}
@@ -246,6 +265,24 @@ module Lutaml
246
265
  value
247
266
  end
248
267
 
268
+ # Whether instances of this class should be registered in the
269
+ # global Store for reference resolution. Defaults to true for
270
+ # backward compatibility. Use `skip_reference_registration` to
271
+ # opt out for classes that never participate in cross-referencing.
272
+ def reference_resolvable?
273
+ return true unless instance_variable_defined?(:@skip_reference_registration)
274
+
275
+ !@skip_reference_registration
276
+ end
277
+
278
+ # Opt out of Store registration for this class.
279
+ # Instances will not be tracked in the global Store, saving
280
+ # memory and registration overhead for classes that are never
281
+ # resolved by reference (no xml_id or similar attributes).
282
+ def skip_reference_registration
283
+ @skip_reference_registration = true
284
+ end
285
+
249
286
  # Define a choice constraint
250
287
  #
251
288
  # @param min [Integer] Minimum number of choices
@@ -307,7 +344,7 @@ module Lutaml
307
344
  reg_record = register_records[register_id]
308
345
  return unless reg_record
309
346
 
310
- default_attrs = instance_variable_get(:@attributes) || {}
347
+ default_attrs = class_attributes || {}
311
348
  reg_record_attrs = reg_record[:attributes] || {}
312
349
 
313
350
  reg_record_attrs.each do |name, attr|
@@ -332,7 +369,7 @@ module Lutaml
332
369
  if args.empty?
333
370
  instance_variable_get(:"@#{name}")
334
371
  else
335
- send(:"#{name}=", args.first)
372
+ public_send(:"#{name}=", args.first)
336
373
  track_order(name, args.first, nil) if @__order_tracking__
337
374
  args.first
338
375
  end
@@ -79,7 +79,7 @@ module Lutaml
79
79
  klass = ::Lutaml::Model::Config.mappings_class_for(format)
80
80
  @mappings[format] ||= klass.new
81
81
  mapping = @mappings[format]
82
- if mapping.respond_to?(:import_model_mappings)
82
+ if mapping.is_a?(Mapping)
83
83
  mapping.import_model_mappings(model,
84
84
  register_id)
85
85
  end
@@ -6,6 +6,8 @@ module Lutaml
6
6
  # Autoload subdirectory modules
7
7
  autoload :Initialization, "#{__dir__}/serialize/initialization"
8
8
  autoload :AttributeDefinition, "#{__dir__}/serialize/attribute_definition"
9
+ autoload :DeserializationContext,
10
+ "#{__dir__}/serialize/deserialization_context"
9
11
  autoload :EnumHandling, "#{__dir__}/serialize/enum_handling"
10
12
  autoload :ModelImport, "#{__dir__}/serialize/model_import"
11
13
  autoload :FormatConversion, "#{__dir__}/serialize/format_conversion"
@@ -161,6 +163,11 @@ module Lutaml
161
163
  @using_default[attribute_name] = false
162
164
  end
163
165
 
166
+ def values_set_for(attribute_names)
167
+ @using_default ||= ::Hash.new(true)
168
+ attribute_names.each { |name| @using_default[name] = false }
169
+ end
170
+
164
171
  def using_default?(attribute_name)
165
172
  # nil means "all attributes using default" — return true without allocation
166
173
  return true if @using_default.nil?
@@ -259,7 +266,7 @@ module Lutaml
259
266
  end
260
267
 
261
268
  def register_in_reference_store
262
- Lutaml::Model::Store.register(self)
269
+ Lutaml::Model::Store.register(self) if self.class.reference_resolvable?
263
270
  end
264
271
 
265
272
  private
@@ -36,7 +36,8 @@ instance_object)
36
36
  # When name is nil but document is a hash-like object with a single key matching
37
37
  # the attribute name, extract that value. This handles the case where
38
38
  # map to: :content is used and the document is {"content": "value"}
39
- # Note: doc may be JSON::Ext::Generator::GeneratorMethods::Hash which is_a?(Hash) returns false
39
+ # Note: doc may be an Oj-generated Hash which is_a?(Hash) returns false for,
40
+ # so we use respond_to? for the hash-like protocol check.
40
41
  if name.nil? && doc.respond_to?(:key?) && doc.respond_to?(:values) && doc.size == 1
41
42
  attr_name = rule.to
42
43
  if doc.key?(attr_name.to_s) || doc.key?(attr_name.to_sym)