lutaml-model 0.8.9 → 0.8.10

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 (90) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +12 -32
  3. data/lib/lutaml/key_value/transform.rb +5 -5
  4. data/lib/lutaml/key_value/transformation/collection_serializer.rb +25 -11
  5. data/lib/lutaml/key_value/transformation/value_serializer.rb +7 -7
  6. data/lib/lutaml/key_value/transformation.rb +27 -17
  7. data/lib/lutaml/model/adapter_resolver.rb +4 -6
  8. data/lib/lutaml/model/attribute.rb +26 -23
  9. data/lib/lutaml/model/cached_type_resolver.rb +10 -9
  10. data/lib/lutaml/model/cli.rb +1 -1
  11. data/lib/lutaml/model/collection.rb +4 -4
  12. data/lib/lutaml/model/comparable_model.rb +11 -11
  13. data/lib/lutaml/model/config.rb +1 -1
  14. data/lib/lutaml/model/consolidation/dispatcher.rb +1 -1
  15. data/lib/lutaml/model/consolidation/pattern_chunker.rb +3 -3
  16. data/lib/lutaml/model/format_registry.rb +6 -4
  17. data/lib/lutaml/model/global_context.rb +2 -2
  18. data/lib/lutaml/model/global_register.rb +1 -1
  19. data/lib/lutaml/model/instrumentation.rb +1 -1
  20. data/lib/lutaml/model/mapping/mapping_rule.rb +3 -3
  21. data/lib/lutaml/model/mapping/model_mapping.rb +1 -1
  22. data/lib/lutaml/model/mapping/model_mapping_rule.rb +1 -1
  23. data/lib/lutaml/model/register.rb +3 -3
  24. data/lib/lutaml/model/render_policy.rb +11 -17
  25. data/lib/lutaml/model/runtime_compatibility.rb +0 -1
  26. data/lib/lutaml/model/schema/xml_compiler/group.rb +1 -1
  27. data/lib/lutaml/model/schema/xml_compiler/registry_generator.rb +1 -1
  28. data/lib/lutaml/model/schema/xml_compiler/sequence.rb +0 -2
  29. data/lib/lutaml/model/schema/xml_compiler.rb +14 -14
  30. data/lib/lutaml/model/serialize/attribute_definition.rb +1 -1
  31. data/lib/lutaml/model/serialize/deserialization_context.rb +50 -0
  32. data/lib/lutaml/model/serialize/format_conversion.rb +2 -2
  33. data/lib/lutaml/model/serialize/initialization.rb +44 -7
  34. data/lib/lutaml/model/serialize/model_import.rb +1 -1
  35. data/lib/lutaml/model/serialize.rb +8 -1
  36. data/lib/lutaml/model/services/rule_value_extractor.rb +2 -1
  37. data/lib/lutaml/model/store.rb +27 -21
  38. data/lib/lutaml/model/transformation_registry.rb +1 -1
  39. data/lib/lutaml/model/type_context.rb +7 -1
  40. data/lib/lutaml/model/type_resolver.rb +1 -6
  41. data/lib/lutaml/model/utils.rb +19 -6
  42. data/lib/lutaml/model/validation_framework.rb +1 -1
  43. data/lib/lutaml/model/value_transformer.rb +2 -2
  44. data/lib/lutaml/model/version.rb +1 -1
  45. data/lib/lutaml/xml/adapter/adapter_helpers.rb +1 -1
  46. data/lib/lutaml/xml/adapter/base_adapter.rb +10 -14
  47. data/lib/lutaml/xml/adapter/namespace_uri_collector.rb +3 -3
  48. data/lib/lutaml/xml/adapter/plan_based_builder.rb +14 -14
  49. data/lib/lutaml/xml/adapter/xml_serializer.rb +3 -3
  50. data/lib/lutaml/xml/configurable.rb +2 -1
  51. data/lib/lutaml/xml/data_model.rb +2 -2
  52. data/lib/lutaml/xml/decisions/decision_context.rb +3 -3
  53. data/lib/lutaml/xml/decisions/rules/element_form_default_unqualified_rule.rb +1 -1
  54. data/lib/lutaml/xml/decisions/rules/element_form_option_rule.rb +1 -1
  55. data/lib/lutaml/xml/decisions/rules/used_prefix_rule.rb +1 -1
  56. data/lib/lutaml/xml/declaration_plan.rb +2 -2
  57. data/lib/lutaml/xml/declaration_planner.rb +12 -13
  58. data/lib/lutaml/xml/document.rb +13 -13
  59. data/lib/lutaml/xml/format_chooser.rb +3 -3
  60. data/lib/lutaml/xml/hoisting_algorithm.rb +1 -1
  61. data/lib/lutaml/xml/mapping.rb +2 -2
  62. data/lib/lutaml/xml/mapping_rule.rb +16 -3
  63. data/lib/lutaml/xml/model_transform.rb +17 -19
  64. data/lib/lutaml/xml/namespace_collector.rb +10 -10
  65. data/lib/lutaml/xml/namespace_declaration.rb +2 -2
  66. data/lib/lutaml/xml/namespace_declaration_data.rb +5 -8
  67. data/lib/lutaml/xml/namespace_scope_config.rb +3 -2
  68. data/lib/lutaml/xml/namespace_type_resolver.rb +4 -4
  69. data/lib/lutaml/xml/nokogiri/element.rb +2 -2
  70. data/lib/lutaml/xml/polymorphic_value_handler.rb +1 -1
  71. data/lib/lutaml/xml/schema/xsd/base.rb +7 -7
  72. data/lib/lutaml/xml/schema/xsd/choice.rb +2 -2
  73. data/lib/lutaml/xml/schema/xsd/complex_type.rb +5 -5
  74. data/lib/lutaml/xml/schema/xsd/errors/message_builder.rb +3 -3
  75. data/lib/lutaml/xml/schema/xsd/group.rb +2 -2
  76. data/lib/lutaml/xml/schema/xsd/sequence.rb +2 -2
  77. data/lib/lutaml/xml/schema/xsd_schema.rb +5 -5
  78. data/lib/lutaml/xml/serialization/format_conversion.rb +4 -3
  79. data/lib/lutaml/xml/transformation/element_builder.rb +4 -2
  80. data/lib/lutaml/xml/transformation/rule_applier.rb +2 -2
  81. data/lib/lutaml/xml/transformation/value_serializer.rb +4 -6
  82. data/lib/lutaml/xml/transformation.rb +4 -4
  83. data/lib/lutaml/xml/type/configurable.rb +0 -4
  84. data/lib/lutaml/xml/xml_element.rb +21 -13
  85. data/lutaml-model.gemspec +1 -1
  86. data/spec/lutaml/model/cached_type_resolver_spec.rb +3 -3
  87. data/spec/lutaml/model/optimization_spec.rb +228 -0
  88. data/spec/lutaml/model/store_spec.rb +41 -0
  89. data/spec/lutaml/xml/data_model_spec.rb +10 -28
  90. metadata +6 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 002a96224c1367b179f30fe6c2f984b7ae80fa7a6c222d8866b10e26e8ee8c4c
4
- data.tar.gz: 2677ff0aca481ae66bf7be4f47cb132417b8d59ddbbf5d266129e9e6c6017e57
3
+ metadata.gz: b1dea5a70c106b45dafcf2950dda9a8a1c2d8f2d37a4d32ec9a610e461b97a91
4
+ data.tar.gz: ca6332ea17ae83c4473a5143d454f155006ad369759dab98c48a1ba825aa6d74
5
5
  SHA512:
6
- metadata.gz: c28f9a50a50f742dc106fb34af4e620b8f12f036b5256e6e5b36bec7ed3ae0634294e078edeb48cef3983e82b266abdb214baa7dceab9e7bd865ad8858162bb5
7
- data.tar.gz: 6ef9ffdf02e9e7497a717d082c77c4fce9de3350f1039c3073135fc5d1107ebd50c0024419c33839ff895c70f97aa5907c67455a19f29d4a56e3ca9815c08c79
6
+ metadata.gz: 01c6623cc80b904875abe68601be5f4b76f441a0b6d0a599325db9454152072c4548985b29e12656b79cffeb28a965ddbf2d3191c6dc617503d914324cee3d35
7
+ data.tar.gz: 38c58ea41a62c035f56ba5a6ae758ed45e861417d7b41dba4003418600f36a88b6ab1a6ab753bd3ff54c3c45a2ed25ac13a68c774c9f488e9785d3d5c44894c6
data/.rubocop_todo.yml CHANGED
@@ -1,6 +1,6 @@
1
1
  # This configuration was generated by
2
2
  # `rubocop --auto-gen-config`
3
- # on 2026-05-14 09:40:04 UTC using RuboCop version 1.86.0.
3
+ # on 2026-05-18 04:34:59 UTC using RuboCop version 1.86.0.
4
4
  # The point is for the user to remove these configuration records
5
5
  # one by one as the offenses are removed from the code base.
6
6
  # Note that changes in the inspected code, or installation of new
@@ -11,28 +11,13 @@ Gemspec/RequiredRubyVersion:
11
11
  Exclude:
12
12
  - 'lutaml-model.gemspec'
13
13
 
14
- # Offense count: 1
15
- # This cop supports safe autocorrection (--autocorrect).
16
- # Configuration parameters: EnforcedStyle, IndentationWidth.
17
- # SupportedStyles: with_first_argument, with_fixed_indentation
18
- Layout/ArgumentAlignment:
19
- Exclude:
20
- - 'lib/lutaml/model/store.rb'
21
-
22
- # Offense count: 2982
14
+ # Offense count: 3012
23
15
  # This cop supports safe autocorrection (--autocorrect).
24
16
  # Configuration parameters: Max, AllowHeredoc, AllowURI, AllowQualifiedName, URISchemes, AllowRBSInlineAnnotation, AllowCopDirectives, AllowedPatterns, SplitStrings.
25
17
  # URISchemes: http, https
26
18
  Layout/LineLength:
27
19
  Enabled: false
28
20
 
29
- # Offense count: 1
30
- # This cop supports safe autocorrection (--autocorrect).
31
- # Configuration parameters: AllowInHeredoc.
32
- Layout/TrailingWhitespace:
33
- Exclude:
34
- - 'lib/lutaml/model/store.rb'
35
-
36
21
  # Offense count: 21
37
22
  # Configuration parameters: AllowedMethods.
38
23
  # AllowedMethods: enums
@@ -129,23 +114,23 @@ Lint/UselessConstantScoping:
129
114
  Exclude:
130
115
  - 'lib/lutaml/xml/mapping_rule.rb'
131
116
 
132
- # Offense count: 342
117
+ # Offense count: 345
133
118
  # Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes, Max.
134
119
  Metrics/AbcSize:
135
120
  Enabled: false
136
121
 
137
- # Offense count: 39
122
+ # Offense count: 40
138
123
  # Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns, inherit_mode.
139
124
  # AllowedMethods: refine
140
125
  Metrics/BlockLength:
141
- Max: 127
126
+ Max: 133
142
127
 
143
128
  # Offense count: 16
144
129
  # Configuration parameters: CountBlocks, CountModifierForms.
145
130
  Metrics/BlockNesting:
146
131
  Max: 6
147
132
 
148
- # Offense count: 302
133
+ # Offense count: 310
149
134
  # Configuration parameters: AllowedMethods, AllowedPatterns, Max.
150
135
  Metrics/CyclomaticComplexity:
151
136
  Enabled: false
@@ -161,7 +146,7 @@ Metrics/ParameterLists:
161
146
  Max: 24
162
147
  MaxOptionalParameters: 5
163
148
 
164
- # Offense count: 253
149
+ # Offense count: 262
165
150
  # Configuration parameters: AllowedMethods, AllowedPatterns, Max.
166
151
  Metrics/PerceivedComplexity:
167
152
  Enabled: false
@@ -253,7 +238,7 @@ RSpec/BeforeAfterAll:
253
238
  RSpec/ContextWording:
254
239
  Enabled: false
255
240
 
256
- # Offense count: 95
241
+ # Offense count: 96
257
242
  # Configuration parameters: IgnoredMetadata.
258
243
  RSpec/DescribeClass:
259
244
  Enabled: false
@@ -264,7 +249,7 @@ RSpec/DescribeMethod:
264
249
  - 'spec/lutaml/xml/schema/xsd/schema_helper_methods_spec.rb'
265
250
  - 'spec/lutaml/xml/serializable_namespace_spec.rb'
266
251
 
267
- # Offense count: 1246
252
+ # Offense count: 1257
268
253
  # Configuration parameters: CountAsOne.
269
254
  RSpec/ExampleLength:
270
255
  Max: 68
@@ -339,7 +324,7 @@ RSpec/MultipleDescribes:
339
324
  - 'spec/lutaml/xml/namespace_resolution_strategy_spec.rb'
340
325
  - 'spec/lutaml/xml/xml_space_type_spec.rb'
341
326
 
342
- # Offense count: 1482
327
+ # Offense count: 1490
343
328
  RSpec/MultipleExpectations:
344
329
  Max: 21
345
330
 
@@ -393,7 +378,7 @@ RSpec/RepeatedExampleGroupDescription:
393
378
  RSpec/SpecFilePathFormat:
394
379
  Enabled: false
395
380
 
396
- # Offense count: 32
381
+ # Offense count: 33
397
382
  # Configuration parameters: IgnoreNameless, IgnoreSymbolicNames.
398
383
  RSpec/VerifiedDoubles:
399
384
  Exclude:
@@ -401,6 +386,7 @@ RSpec/VerifiedDoubles:
401
386
  - 'spec/lutaml/key_value/transformation/rule_compiler_spec.rb'
402
387
  - 'spec/lutaml/key_value/transformation/value_serializer_spec.rb'
403
388
  - 'spec/lutaml/model/compiled_rule_spec.rb'
389
+ - 'spec/lutaml/model/optimization_spec.rb'
404
390
  - 'spec/lutaml/model/schema/renderer_spec.rb'
405
391
  - 'spec/lutaml/model/transformation_builder_spec.rb'
406
392
  - 'spec/lutaml/model/transformation_spec.rb'
@@ -457,12 +443,6 @@ Style/MixinUsage:
457
443
  - 'bench/bench_unitsml.rb'
458
444
  - 'bench/bench_xmi.rb'
459
445
 
460
- # Offense count: 1
461
- # This cop supports safe autocorrection (--autocorrect).
462
- Style/MultilineIfModifier:
463
- Exclude:
464
- - 'lib/lutaml/model/store.rb'
465
-
466
446
  # Offense count: 12
467
447
  # Configuration parameters: AllowedClasses.
468
448
  Style/OneClassPerFile:
@@ -33,7 +33,7 @@ module Lutaml
33
33
 
34
34
  # transformation_for returns nil for cyclic dependencies or :building sentinel
35
35
  # Fall back to legacy approach in these cases
36
- if transformation.respond_to?(:transform)
36
+ if transformation.is_a?(Lutaml::KeyValue::Transformation)
37
37
  # Use new Transformation to get KeyValueElement
38
38
  kv_element = transformation.transform(instance, options)
39
39
  # Convert KeyValueElement to hash for backward compatibility with adapters
@@ -68,7 +68,7 @@ module Lutaml
68
68
 
69
69
  def process_mapping_for_instance(instance, hash, format, rule, options)
70
70
  if rule.custom_methods[:to]
71
- return instance.send(rule.custom_methods[:to], instance, hash)
71
+ return instance.public_send(rule.custom_methods[:to], instance, hash)
72
72
  end
73
73
 
74
74
  attribute = attributes[rule.to]
@@ -223,13 +223,13 @@ format)
223
223
  value = extract_value_for_delegate(instance, rule)
224
224
  return if value.nil? && rule.value_map(:to)[:nil] == :omitted
225
225
 
226
- attribute = instance.send(rule.delegate).class.attributes(lutaml_register)[rule.to]
226
+ attribute = instance.public_send(rule.delegate).class.attributes(lutaml_register)[rule.to]
227
227
  hash[rule_from_name(rule)] =
228
228
  attribute.serialize(value, format, lutaml_register)
229
229
  end
230
230
 
231
231
  def extract_value_for_delegate(instance, rule)
232
- instance.send(rule.delegate).send(rule.to)
232
+ instance.public_send(rule.delegate).public_send(rule.to)
233
233
  end
234
234
 
235
235
  def extract_mappings(options, format)
@@ -268,7 +268,7 @@ format)
268
268
  def process_custom_method(rule, instance, value)
269
269
  return unless Lutaml::Model::Utils.present?(value)
270
270
 
271
- model_class.new.send(rule.custom_methods[:from], instance, value)
271
+ model_class.new.public_send(rule.custom_methods[:from], instance, value)
272
272
  end
273
273
 
274
274
  def cast_value(value, attr, format, rule, instance)
@@ -175,10 +175,14 @@ options = {})
175
175
  items.each do |item|
176
176
  next if item.nil?
177
177
 
178
- key_value = item.respond_to?(key_attribute) ? item.public_send(key_attribute) : nil
178
+ key_value = if item.is_a?(Lutaml::Model::Serialize) && item.class.attributes.key?(key_attribute)
179
+ item.public_send(key_attribute)
180
+ end
179
181
  next if key_value.nil?
180
182
 
181
- attr_value = item.respond_to?(value_attribute) ? item.public_send(value_attribute) : nil
183
+ attr_value = if item.is_a?(Lutaml::Model::Serialize) && item.class.attributes.key?(value_attribute)
184
+ item.public_send(value_attribute)
185
+ end
182
186
  next if attr_value.nil? || Lutaml::Model::Utils.uninitialized?(attr_value)
183
187
 
184
188
  # Get the attribute definition for the value attribute
@@ -193,7 +197,7 @@ options = {})
193
197
  item_mapping = item_class.mappings_for(format, register_id)
194
198
  item_mapping&.mappings&.each do |mapping_rule|
195
199
  if mapping_rule.to == value_attribute
196
- child_mappings = mapping_rule.child_mappings if mapping_rule.respond_to?(:child_mappings)
200
+ child_mappings = mapping_rule.child_mappings
197
201
  break
198
202
  end
199
203
  end
@@ -226,7 +230,9 @@ options)
226
230
 
227
231
  keyed_hash = {}
228
232
  items.each do |item|
229
- key_value = item.respond_to?(key_attribute) ? item.public_send(key_attribute) : nil
233
+ key_value = if item.is_a?(Lutaml::Model::Serialize) && item.class.attributes.key?(key_attribute)
234
+ item.public_send(key_attribute)
235
+ end
230
236
  next if key_value.nil?
231
237
 
232
238
  item_hash = {}
@@ -281,7 +287,9 @@ options)
281
287
  items.each do |item|
282
288
  next if item.nil?
283
289
 
284
- key_value = item.respond_to?(key_attribute) ? item.public_send(key_attribute) : nil
290
+ key_value = if item.is_a?(Lutaml::Model::Serialize) && item.class.attributes.key?(key_attribute)
291
+ item.public_send(key_attribute)
292
+ end
285
293
  next if key_value.nil?
286
294
 
287
295
  item_hash = {}
@@ -340,7 +348,9 @@ options)
340
348
  value_attribute = find_value_attribute(child_mappings)
341
349
 
342
350
  items.each do |item|
343
- key_value = item.respond_to?(key_attribute) ? item.public_send(key_attribute) : nil
351
+ key_value = if item.is_a?(Lutaml::Model::Serialize) && item.class.attributes.key?(key_attribute)
352
+ item.public_send(key_attribute)
353
+ end
344
354
  next if key_value.nil?
345
355
 
346
356
  if value_attribute
@@ -371,7 +381,9 @@ options)
371
381
  # Serialize a keyed collection item with value mapping.
372
382
  def serialize_keyed_value_item(keyed_hash, item, key_value,
373
383
  value_attribute, options)
374
- attr_value = item.respond_to?(value_attribute) ? item.public_send(value_attribute) : nil
384
+ attr_value = if item.is_a?(Lutaml::Model::Serialize) && item.class.attributes.key?(value_attribute)
385
+ item.public_send(value_attribute)
386
+ end
375
387
  return if attr_value.nil? || Lutaml::Model::Utils.uninitialized?(attr_value)
376
388
 
377
389
  item_class = item.class
@@ -419,7 +431,9 @@ options)
419
431
  child_mappings.each do |attr_name, path_spec|
420
432
  next if %i[key value].include?(path_spec)
421
433
 
422
- attr_value = item.respond_to?(attr_name) ? item.public_send(attr_name) : nil
434
+ attr_value = if item.is_a?(Lutaml::Model::Serialize) && item.class.attributes.key?(attr_name)
435
+ item.public_send(attr_name)
436
+ end
423
437
  next if attr_value.nil? || Lutaml::Model::Utils.uninitialized?(attr_value)
424
438
 
425
439
  attr_def = nil
@@ -508,7 +522,7 @@ options)
508
522
  serialize_collection_value(value, attr_type, options)
509
523
  elsif attr_type.is_a?(Class) && attr_type < Lutaml::Model::Serialize
510
524
  serialize_nested_model(value, attr_type, options)
511
- elsif attr_type.respond_to?(:new)
525
+ elsif attr_type.is_a?(Class) && attr_type < Lutaml::Model::Type::Value
512
526
  serialize_primitive(value, attr_type)
513
527
  else
514
528
  value
@@ -523,7 +537,7 @@ options)
523
537
  # Handle Reference type collections - serialize each as a key
524
538
  if attr_type == Lutaml::Model::Type::Reference
525
539
  return items.map do |item|
526
- item.respond_to?(:"to_#{format}") ? item.send(:"to_#{format}") : item
540
+ item.is_a?(Lutaml::Model::Type::Value) ? item.public_send(:"to_#{format}") : item
527
541
  end
528
542
  end
529
543
 
@@ -547,7 +561,7 @@ options)
547
561
  # Serialize a primitive value.
548
562
  def serialize_primitive(value, attr_type)
549
563
  wrapped_value = attr_type.new(value)
550
- wrapped_value.send(:"to_#{format}")
564
+ wrapped_value.public_send(:"to_#{format}")
551
565
  end
552
566
 
553
567
  # Create a transformation for a type class.
@@ -128,13 +128,13 @@ model_class: nil)
128
128
  if rule.attribute_type.is_a?(Class) &&
129
129
  rule.attribute_type < Lutaml::Model::Serialize
130
130
  validate_serializable_type!(value, rule)
131
- return value.send(:"to_#{format}")
131
+ return value.public_send(:"to_#{format}")
132
132
  end
133
133
 
134
134
  # Wrap value in type and call to_#{format}
135
- if rule.attribute_type.respond_to?(:new)
135
+ if rule.attribute_type.is_a?(Class) && rule.attribute_type < Lutaml::Model::Type::Value
136
136
  wrapped_value = rule.attribute_type.new(value)
137
- wrapped_value.send(:"to_#{format}")
137
+ wrapped_value.public_send(:"to_#{format}")
138
138
  else
139
139
  value
140
140
  end
@@ -152,7 +152,7 @@ model_class: nil)
152
152
  subs = context.substitution_for(rule.attribute_type)
153
153
  uses_type_substitution = subs.any? { |s| s.to_type == value.class }
154
154
 
155
- if rule.attribute_type.respond_to?(:model) && rule.attribute_type.model
155
+ if rule.attribute_type.is_a?(Class) && rule.attribute_type.include?(Lutaml::Model::Serialize) && rule.attribute_type.model
156
156
  # Mapper class: value should be an instance of the mapped model
157
157
  unless value.is_a?(rule.attribute_type.model) || uses_type_substitution
158
158
  msg = "attribute '#{rule.attribute_name}' value is a '#{value.class}' " \
@@ -227,7 +227,7 @@ model_class: nil)
227
227
  # @param rule [CompiledRule] The compiled rule
228
228
  # @return [Boolean] true if Reference type
229
229
  def reference_type?(rule)
230
- return false unless rule.respond_to?(:attribute_name)
230
+ return false unless rule.is_a?(Lutaml::Model::CompiledRule)
231
231
  return false unless model_class
232
232
 
233
233
  # Get the attribute from the model class to check unresolved_type
@@ -256,8 +256,8 @@ model_class: nil)
256
256
  end
257
257
 
258
258
  # Fallback: just call to_format on the value
259
- if value.respond_to?(:"to_#{format}")
260
- value.send(:"to_#{format}")
259
+ if value.is_a?(Lutaml::Model::Type::Value) || value.is_a?(Lutaml::Model::Serialize)
260
+ value.public_send(:"to_#{format}")
261
261
  else
262
262
  value
263
263
  end
@@ -209,8 +209,8 @@ register = self.register)
209
209
  # Handle custom serialization methods (e.g., with: { to: ... })
210
210
  if rule.has_custom_methods? && rule.custom_methods[:to]
211
211
  # Call custom method which directly modifies the parent element
212
- return model_instance.send(rule.custom_methods[:to],
213
- model_instance, parent)
212
+ return model_instance.public_send(rule.custom_methods[:to],
213
+ model_instance, parent)
214
214
  end
215
215
 
216
216
  # Handle delegation - extract value from delegated object
@@ -357,11 +357,15 @@ converted_from_empty_to_nil: false, converted_from_nil_to_empty: false)
357
357
  next if item.nil?
358
358
 
359
359
  # Get the key value from the item
360
- key_value = item.respond_to?(key_attribute) ? item.public_send(key_attribute) : nil
360
+ key_value = if item.is_a?(Lutaml::Model::Serialize) && item.class.attributes.key?(key_attribute)
361
+ item.public_send(key_attribute)
362
+ end
361
363
  next if key_value.nil?
362
364
 
363
365
  # Get the value from the item
364
- attr_value = item.respond_to?(value_attribute) ? item.public_send(value_attribute) : nil
366
+ attr_value = if item.is_a?(Lutaml::Model::Serialize) && item.class.attributes.key?(value_attribute)
367
+ item.public_send(value_attribute)
368
+ end
365
369
  next if attr_value.nil? || Lutaml::Model::Utils.uninitialized?(attr_value)
366
370
 
367
371
  # For the value, we need to transform it appropriately
@@ -419,7 +423,7 @@ converted_from_empty_to_nil: false, converted_from_nil_to_empty: false)
419
423
  item_class_mapping, format, register)
420
424
 
421
425
  # Find the rule for the value_attribute in the item's class mapping
422
- item_rule = item_class_transformation.send(:compiled_rules).find do |r|
426
+ item_rule = item_class_transformation.compiled_rules.find do |r|
423
427
  r.attribute_name == value_attribute
424
428
  end
425
429
 
@@ -460,8 +464,9 @@ converted_from_empty_to_nil: false, converted_from_nil_to_empty: false)
460
464
  items.each do |item|
461
465
  next if item.nil?
462
466
 
463
- # Get the key value from the item
464
- key_value = item.respond_to?(key_attribute) ? item.public_send(key_attribute) : nil
467
+ key_value = if item.is_a?(Lutaml::Model::Serialize) && item.class.attributes.key?(key_attribute)
468
+ item.public_send(key_attribute)
469
+ end
465
470
  next if key_value.nil?
466
471
 
467
472
  # Serialize all attributes except the key
@@ -573,7 +578,9 @@ child_mappings, options)
573
578
 
574
579
  items.each do |item|
575
580
  # Get the key value from the item
576
- key_value = item.respond_to?(key_attribute) ? item.public_send(key_attribute) : nil
581
+ key_value = if item.is_a?(Lutaml::Model::Serialize) && item.class.attributes.key?(key_attribute)
582
+ item.public_send(key_attribute)
583
+ end
577
584
 
578
585
  if ENV["DEBUG_KEYED_COLLECTION"]
579
586
  puts " item: #{item.inspect}, key_value=#{key_value.inspect}"
@@ -583,7 +590,9 @@ child_mappings, options)
583
590
 
584
591
  # If there's a value mapping, serialize just that attribute value
585
592
  if value_attribute
586
- attr_value = item.respond_to?(value_attribute) ? item.public_send(value_attribute) : nil
593
+ attr_value = if item.is_a?(Lutaml::Model::Serialize) && item.class.attributes.key?(value_attribute)
594
+ item.public_send(value_attribute)
595
+ end
587
596
  next if attr_value.nil? || Lutaml::Model::Utils.uninitialized?(attr_value)
588
597
 
589
598
  # Get the attribute definition for proper serialization
@@ -637,7 +646,9 @@ child_mappings, options)
637
646
  next if path_spec == :value
638
647
 
639
648
  # Get the attribute value
640
- attr_value = item.respond_to?(attr_name) ? item.public_send(attr_name) : nil
649
+ attr_value = if item.is_a?(Lutaml::Model::Serialize) && item.class.attributes.key?(attr_name)
650
+ item.public_send(attr_name)
651
+ end
641
652
 
642
653
  if ENV["DEBUG_KEYED_COLLECTION"]
643
654
  puts " #{attr_name}: path_spec=#{path_spec.inspect}, attr_value=#{attr_value.inspect}"
@@ -833,10 +844,9 @@ child_mappings, options)
833
844
  child_hash = child_root.to_hash
834
845
  child_hash["__root__"]
835
846
  # Use the transformation to serialize the nested model
836
- elsif attr_type.respond_to?(:new)
837
- # Serialize primitive value
847
+ elsif attr_type.is_a?(Class) && attr_type < Lutaml::Model::Type::Value
838
848
  wrapped_value = attr_type.new(value)
839
- wrapped_value.send(:"to_#{format}")
849
+ wrapped_value.public_send(:"to_#{format}")
840
850
  else
841
851
  value
842
852
  end
@@ -940,7 +950,7 @@ child_mappings, options)
940
950
  subs = context.substitution_for(rule.attribute_type)
941
951
  uses_type_substitution = subs.any? { |s| s.to_type == value.class }
942
952
 
943
- if rule.attribute_type.respond_to?(:model) && rule.attribute_type.model
953
+ if rule.attribute_type.is_a?(Class) && rule.attribute_type.include?(Lutaml::Model::Serialize) && rule.attribute_type.model
944
954
  # Mapper class: value should be an instance of the mapped model
945
955
  # or a valid substituted type
946
956
  unless value.is_a?(rule.attribute_type.model) || uses_type_substitution
@@ -1054,14 +1064,14 @@ child_mappings, options)
1054
1064
  end
1055
1065
 
1056
1066
  # Value is a valid Serialize instance - serialize it using its own to_#{format} method
1057
- return value.send(:"to_#{format}")
1067
+ return value.public_send(:"to_#{format}")
1058
1068
  end
1059
1069
 
1060
1070
  # Wrap value in type and call to_#{format} instance method (like legacy Attribute#serialize_value)
1061
1071
  # This allows custom type subclasses to override to_json, to_yaml, etc.
1062
- if rule.attribute_type.respond_to?(:new)
1072
+ if rule.attribute_type.is_a?(Class) && rule.attribute_type < Lutaml::Model::Type::Value
1063
1073
  wrapped_value = rule.attribute_type.new(value)
1064
- wrapped_value.send(:"to_#{format}")
1074
+ wrapped_value.public_send(:"to_#{format}")
1065
1075
  else
1066
1076
  value
1067
1077
  end
@@ -225,7 +225,7 @@ module Lutaml
225
225
  # @param type [String] normalized type name
226
226
  def load_adapter_file(adapter, type)
227
227
  loader = FormatRegistry.adapter_loader_for(adapter.to_sym)
228
- if loader.respond_to?(:load_adapter_file)
228
+ if loader
229
229
  loader.load_adapter_file(adapter, type)
230
230
  return
231
231
  end
@@ -248,10 +248,8 @@ module Lutaml
248
248
  # @param format [Symbol] format name
249
249
  def load_moxml_adapter(type_name, format)
250
250
  loader = FormatRegistry.adapter_loader_for(format)
251
- if loader.respond_to?(:load_moxml_adapter)
252
- loader.load_moxml_adapter(type_name,
253
- format)
254
- end
251
+ loader&.load_moxml_adapter(type_name,
252
+ format)
255
253
  end
256
254
 
257
255
  # Resolve the adapter class from the type name.
@@ -261,7 +259,7 @@ module Lutaml
261
259
  # @return [Class] the adapter class
262
260
  def class_for(adapter, type)
263
261
  loader = FormatRegistry.adapter_loader_for(adapter.to_sym)
264
- if loader.respond_to?(:class_for)
262
+ if loader
265
263
  return loader.class_for(adapter, type)
266
264
  end
267
265
 
@@ -36,20 +36,8 @@ module Lutaml
36
36
  :string,
37
37
  ].freeze
38
38
 
39
- # Keys that are safe to propagate from parent to child deserialization.
40
- # Parent-internal keys (namespace_uri, resolved_type, converted) are
41
- # intentionally excluded — children must derive their own context.
42
- CHILD_PROPAGATION_KEYS = %i[
43
- lutaml_parent
44
- lutaml_root
45
- default_namespace
46
- import_declaration_plan
47
- polymorphic
48
- collection
49
- render_empty
50
- render_nil
51
- cdata
52
- ].freeze
39
+ # Delegate propagation to DeserializationContext for single source of truth.
40
+ CHILD_PROPAGATION_KEYS = Serialize::DeserializationContext::PROPAGATION_KEYS
53
41
 
54
42
  # Methods where accidental override is likely to cause issues
55
43
  # All names are allowed - this list only controls which ones get a warning
@@ -590,9 +578,10 @@ instance_object = nil)
590
578
  type(register)
591
579
  end
592
580
  if collection_instance?(value) || value.is_a?(Array)
581
+ merged_opts = options.merge(resolved_type: resolved_type,
582
+ converted: true)
593
583
  return build_collection(value.map do |v|
594
- cast(v, format, register,
595
- options.merge(resolved_type: resolved_type, converted: true))
584
+ cast(v, format, register, merged_opts)
596
585
  end)
597
586
  end
598
587
 
@@ -616,11 +605,11 @@ instance_object = nil)
616
605
 
617
606
  klass = resolve_polymorphic_class(resolved_type, value, options)
618
607
  if can_serialize?(klass, value, format, options)
619
- propagated = options.slice(*CHILD_PROPAGATION_KEYS)
608
+ propagated = Serialize::DeserializationContext.propagate(options)
620
609
  klass.apply_mappings(value, format,
621
610
  propagated.merge(register: register))
622
611
  elsif needs_conversion?(klass, value)
623
- klass.send(:"from_#{format}", value)
612
+ klass.public_send(:"from_#{format}", value)
624
613
  else
625
614
  # No need to use register#get_class,
626
615
  # can_serialize? method already checks if type is Serializable or not.
@@ -711,9 +700,7 @@ instance_object = nil)
711
700
  # Skip validation during deep_dup - options are already validated in original
712
701
  # This prevents infinite recursion when process_options! tries to access collection
713
702
  self.class.new(name, unresolved_type, duped_options).tap do |dup_attr|
714
- # Copy already-processed instance variables directly
715
- dup_attr.send(:raw=, @raw)
716
- dup_attr.send(:validations=, @validations)
703
+ dup_attr.copy_internal_state_from(self)
717
704
  end
718
705
  end
719
706
 
@@ -765,6 +752,22 @@ instance_object = nil)
765
752
  type_namespace_class(register)&.prefix_default
766
753
  end
767
754
 
755
+ # Copy internal state from another Attribute instance.
756
+ # Used by `dup` to transfer processed state without calling private writers.
757
+ def copy_internal_state_from(source)
758
+ @raw = source.raw?
759
+ @validations = source.validations
760
+ end
761
+
762
+ # Clear type resolution caches for this attribute.
763
+ # Called by GlobalContext.clear_caches to ensure stale entries
764
+ # from GC'd TypeContext objects don't persist.
765
+ def clear_type_cache
766
+ @type_cache&.clear
767
+ @cached_type_default = nil
768
+ @default_type_context = nil
769
+ end
770
+
768
771
  private
769
772
 
770
773
  attr_writer :raw, :validations
@@ -852,7 +855,7 @@ instance_object = nil)
852
855
  register
853
856
  end
854
857
 
855
- as_options = options.slice(*CHILD_PROPAGATION_KEYS).merge(register: value_register)
858
+ as_options = Serialize::DeserializationContext.propagate(options).merge(register: value_register)
856
859
 
857
860
  # Respect mapping layer policy: render_empty from MappingRule
858
861
  # Allow empty Serializable models when render_empty: true
@@ -874,7 +877,7 @@ instance_object = nil)
874
877
 
875
878
  def serialize_value(value, format, resolved_type)
876
879
  value = wrap_in_type_if_needed(value, resolved_type)
877
- value.send(:"to_#{format}")
880
+ value.public_send(:"to_#{format}")
878
881
  end
879
882
 
880
883
  def wrap_in_type_if_needed(value, resolved_type)
@@ -63,10 +63,11 @@ module Lutaml
63
63
  # @return [Class] The resolved type class
64
64
  # @raise [UnknownTypeError] If type cannot be resolved
65
65
  def resolve(name, context)
66
- # Fast path: Class types are passed through directly (no caching needed)
67
- return @delegate.resolve(name, context) if name.is_a?(Class)
68
-
69
- cache_key = build_cache_key(name, context)
66
+ cache_key = if name.is_a?(Class)
67
+ [context.id, name]
68
+ else
69
+ build_cache_key(name, context)
70
+ end
70
71
 
71
72
  @cache_backend.fetch_or_store(cache_key) do
72
73
  @delegate.resolve(name, context)
@@ -79,14 +80,14 @@ module Lutaml
79
80
  # @param context [TypeContext] The resolution context
80
81
  # @return [Boolean] true if type can be resolved
81
82
  def resolvable?(name, context)
82
- return true if name.is_a?(Class)
83
-
84
- cache_key = build_cache_key(name, context)
83
+ cache_key = if name.is_a?(Class)
84
+ [context.id, name]
85
+ else
86
+ build_cache_key(name, context)
87
+ end
85
88
 
86
- # Check cache first (fast path)
87
89
  return true if @cache_backend.key?(cache_key)
88
90
 
89
- # Not in cache - delegate
90
91
  @delegate.resolvable?(name, context)
91
92
  end
92
93
 
@@ -70,7 +70,7 @@ module Lutaml
70
70
  format = path.split(".").last.downcase
71
71
  format = "yaml" if format == "yml"
72
72
 
73
- model.send("from_#{format}", content)
73
+ model.public_send("from_#{format}", content)
74
74
  rescue StandardError => e
75
75
  raise StandardError, "Error parsing file #{path}: #{e.message}"
76
76
  end