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
@@ -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
@@ -292,7 +292,7 @@ module Lutaml
292
292
  !ctx.stopped?
293
293
  }) do |collection, errors, ctx|
294
294
  missing_items = collection.select do |instance|
295
- value = instance.respond_to?(field) ? instance.public_send(field) : nil
295
+ value = instance.is_a?(Serialize) ? instance.public_send(field) : nil
296
296
  Utils.blank?(value)
297
297
  end
298
298
 
@@ -420,7 +420,7 @@ module Lutaml
420
420
  end
421
421
 
422
422
  def extract_field_value(instance, field)
423
- instance.respond_to?(field) ? instance.public_send(field) : nil
423
+ instance.is_a?(Serialize) ? instance.public_send(field) : nil
424
424
  end
425
425
 
426
426
  def add_uniqueness_error(errors, field, duplicates, message)
@@ -553,7 +553,7 @@ lutaml_register: Lutaml::Model::Config.default_register)
553
553
  key = if field_or_proc.is_a?(Proc)
554
554
  field_or_proc.call(item)
555
555
  else
556
- item.send(field_or_proc)
556
+ item.public_send(field_or_proc)
557
557
  end
558
558
  @index_caches[name][key] = item
559
559
  end
@@ -591,7 +591,7 @@ lutaml_register: Lutaml::Model::Config.default_register)
591
591
  if field.is_a?(Proc)
592
592
  collection.sort_by!(&field)
593
593
  else
594
- collection.sort_by! { |item| item.send(field) }
594
+ collection.sort_by! { |item| item.public_send(field) }
595
595
  end
596
596
  end
597
597
 
@@ -19,10 +19,10 @@ module Lutaml
19
19
 
20
20
  compared_objects[comparison_key(other)] = true
21
21
  self.class.attributes.all? do |attr, _|
22
- attr_value = send(attr)
23
- other_value = other.send(attr)
22
+ attr_value = public_send(attr)
23
+ other_value = other.public_send(attr)
24
24
 
25
- if attr_value.respond_to?(:eql?) && same_class?(attr_value)
25
+ if attr_value.is_a?(ComparableModel) && same_class?(attr_value)
26
26
  attr_value.eql?(other_value, compared_objects)
27
27
  else
28
28
  attr_value == other_value
@@ -59,9 +59,9 @@ module Lutaml
59
59
 
60
60
  def attributes_hash(processed_objects)
61
61
  self.class.attributes.map do |attr, _|
62
- attr_value = send(attr)
62
+ attr_value = public_send(attr)
63
63
 
64
- if attr_value.respond_to?(:calculate_hash)
64
+ if attr_value.is_a?(ComparableModel)
65
65
  attr_value.calculate_hash(processed_objects)
66
66
  else
67
67
  attr_value.hash
@@ -205,7 +205,7 @@ module Lutaml
205
205
  return yield nil, nil, obj1, obj2, true if obj1.class != obj2.class
206
206
 
207
207
  obj1.class.attributes.each_with_index do |(name, type), index|
208
- yield name, type, obj1.send(name), obj2.send(name), index == obj1.class.attributes.length - 1
208
+ yield name, type, obj1.public_send(name), obj2.public_send(name), index == obj1.class.attributes.length - 1
209
209
  end
210
210
  end
211
211
 
@@ -398,7 +398,7 @@ module Lutaml
398
398
  return format_value(obj) unless obj.is_a?(ComparableModel)
399
399
 
400
400
  obj.class.attributes.map do |attr, _|
401
- "#{attr}: #{format_value(obj.send(attr))}"
401
+ "#{attr}: #{format_value(obj.public_send(attr))}"
402
402
  end.join("\n")
403
403
  end
404
404
 
@@ -426,7 +426,7 @@ module Lutaml
426
426
  def type_name(type)
427
427
  if type.is_a?(Class)
428
428
  type.name
429
- elsif type.respond_to?(:type)
429
+ elsif type.is_a?(Attribute)
430
430
  type.type.name
431
431
  else
432
432
  type.class.name
@@ -439,8 +439,8 @@ module Lutaml
439
439
  # @return [String] Formatted attributes of the objects
440
440
  def format_object_attributes(obj1, obj2, parent_node)
441
441
  obj1.class.attributes.each_key do |attr|
442
- value1 = obj1.send(attr)
443
- value2 = obj2.send(attr) if obj2.respond_to?(attr)
442
+ value1 = obj1.public_send(attr)
443
+ value2 = obj2.public_send(attr)
444
444
 
445
445
  attr_type = obj1.class.attributes[attr].collection? ? "collection" : type_name(obj1.class.attributes[attr])
446
446
 
@@ -525,7 +525,7 @@ type_info = nil)
525
525
  # @return [String] Formatted ComparableModel object
526
526
  def format_comparable_mapper(obj, parent_node, color = nil)
527
527
  obj.class.attributes.each do |attr_name, attr_type|
528
- attr_value = obj.send(attr_name)
528
+ attr_value = obj.public_send(attr_name)
529
529
  attr_node = Tree.new("#{attr_name} (#{type_name(attr_type)}):",
530
530
  color: color)
531
531
  parent_node.add_child(attr_node)
@@ -125,7 +125,7 @@ module Lutaml
125
125
  #
126
126
  # @param format [Symbol] the format name
127
127
  def define_adapter_type_methods(format)
128
- return if respond_to?(:"#{format}_adapter_type=")
128
+ return if method_defined?(:"#{format}_adapter_type=")
129
129
 
130
130
  # Adapter class getter (returns Class)
131
131
  define_method(:"#{format}_adapter") do
@@ -31,7 +31,7 @@ module Lutaml
31
31
 
32
32
  value = if target_type == item.class
33
33
  item
34
- elsif item.respond_to?(:value)
34
+ elsif item.is_a?(Lutaml::Model::Serialize) && item.class.attributes.key?(:value)
35
35
  item.value
36
36
  else
37
37
  item
@@ -53,7 +53,7 @@ module Lutaml
53
53
  # @param token [Object] a mixed content token
54
54
  # @return [Symbol] :element or :text
55
55
  def token_type(token)
56
- if token.respond_to?(:node_type)
56
+ if token.is_a?(Lutaml::Xml::XmlElement)
57
57
  token.node_type == :element ? :element : :text
58
58
  elsif token.is_a?(Hash)
59
59
  token[:type] || (token.key?(:text) ? :text : :element)
@@ -65,7 +65,7 @@ module Lutaml
65
65
  # @param token [Object] a mixed content token
66
66
  # @return [String, nil] the element name
67
67
  def token_name(token)
68
- if token.respond_to?(:name)
68
+ if token.is_a?(Lutaml::Xml::XmlElement)
69
69
  token.name
70
70
  elsif token.is_a?(Hash)
71
71
  token[:name]
@@ -75,7 +75,7 @@ module Lutaml
75
75
  # @param token [Object] a mixed content token
76
76
  # @return [String, nil] the text content
77
77
  def token_text(token)
78
- if token.respond_to?(:text)
78
+ if token.is_a?(Lutaml::Xml::XmlElement)
79
79
  token.text
80
80
  elsif token.is_a?(Hash)
81
81
  token[:text]
@@ -163,10 +163,12 @@ module Lutaml
163
163
  end
164
164
 
165
165
  # Aliased _type methods
166
- cfg.send(:alias_method, :"#{format}_adapter_type=",
167
- :"#{format}_adapter=")
168
- cfg.send(:alias_method, :"#{format}_adapter_type",
169
- :"#{format}_adapter")
166
+ cfg.class_eval do
167
+ alias_method :"#{format}_adapter_type=", :"#{format}_adapter="
168
+ end
169
+ cfg.class_eval do
170
+ alias_method :"#{format}_adapter_type", :"#{format}_adapter"
171
+ end
170
172
  end
171
173
 
172
174
  # Derive a symbol adapter name from an adapter class.
@@ -181,7 +181,7 @@ module Lutaml
181
181
  @resolver.clear_all_caches
182
182
  @imports.reset!
183
183
  @format_registries.each_value do |reg|
184
- reg.clear! if reg.respond_to?(:clear!)
184
+ reg.clear! if reg.is_a?(FormatRegistry)
185
185
  end
186
186
  @namespace_register_map.clear
187
187
  @default_context_id = :default
@@ -227,7 +227,7 @@ module Lutaml
227
227
  # @return [void]
228
228
  def clear_format_registry!(format)
229
229
  reg = @format_registries[format]
230
- reg&.clear! if reg.respond_to?(:clear!)
230
+ reg&.clear! if reg.is_a?(FormatRegistry)
231
231
  end
232
232
 
233
233
  # Backward-compatible accessor for XML namespace registry.
@@ -86,7 +86,7 @@ module Lutaml
86
86
  if ctx
87
87
  ctx.registry.names.each do |name|
88
88
  model_class = ctx.registry.lookup(name)
89
- if model_class.respond_to?(:clear_cache)
89
+ if model_class.is_a?(Class) && model_class.include?(Lutaml::Model::Serialize)
90
90
  model_class.clear_cache(register_id)
91
91
  end
92
92
  end