lutaml-model 0.8.64 → 0.8.65

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8a6392d2dcfd63cc0cb61e585a164c72e09438c25154651cf9ad935e32985429
4
- data.tar.gz: 492ad5dd9799652c1dd6abc9e148edba5937f3a613e143bdf556f625d94851fc
3
+ metadata.gz: a8bbc0a4da8114a582ee834a333a8614ca1051747296a1e967eb36f47ef68b48
4
+ data.tar.gz: ec01b4058ae9ce9d400f229ae4f116bcdde25cdd015e3b96ff3e2b39af62ff10
5
5
  SHA512:
6
- metadata.gz: c14bf78b03a2cce38a13d7a7c09cc07a7cca4b1dbed59a46c0faa9a5c4c275c872dcbff23afae3f8b8518b4fa926ad64597f0f7d67a7567a711ea05d60f8754d
7
- data.tar.gz: c8d8b9723545747d826a7c16bb3d11699963299b2a238b5c04e5a724b1de555463fb839689cef9e46e52137c92a1f6831bd50676097653d888a92c57d7255f29
6
+ metadata.gz: 494b45969f4e8f67e5f79fe09eb6e1630e5b04109fe46b13210cc8dfe455a11e7b7d7955a91ae2132c037c7e53d21fb09c0462a55899cccaafc4d4e5b609bccd
7
+ data.tar.gz: 6694a345fda4b371988fab2af07b9311b7eed87f2e61a696eab7c2a247ba04950bbb93439b1019a27903dbfcf8c5815067f2d3449a41affe2cdb21a6a3deb583
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Lutaml
4
4
  module Model
5
- VERSION = "0.8.64"
5
+ VERSION = "0.8.65"
6
6
  end
7
7
  end
@@ -36,6 +36,17 @@ module Lutaml
36
36
 
37
37
  class << self
38
38
  def compile(model_class, register)
39
+ # A child model's declared lutaml_default_register takes
40
+ # precedence over the ambient (parent) register — the same
41
+ # contract the interpretive path applies through
42
+ # Register.resolve_for_child. Without this, plan compilation
43
+ # resolves the child's symbol attribute types in the parent
44
+ # context and raises UnknownTypeError for ids registered only
45
+ # in the child's own register (#876).
46
+ register = Lutaml::Model::Register.resolve_for_child(
47
+ model_class, register
48
+ )
49
+
39
50
  key = [model_class, register]
40
51
  return PLAN_CACHE[key] if PLAN_CACHE.key?(key)
41
52
 
@@ -20,11 +20,21 @@ module Lutaml
20
20
  # node: the parsed source element (leptris); ordered/mixed
21
21
  # plans rebuild element_order from it and ordered children
22
22
  # hydrate natively against their own nodes
23
- def call(model_class, plan, value, parent: nil, node: nil)
23
+ def call(model_class, plan, value, parent: nil, node: nil,
24
+ register: nil)
25
+ # The child model's declared lutaml_default_register takes
26
+ # precedence over the ambient register — the same
27
+ # resolve_for_child contract the interpretive path applies.
28
+ # Without it, hydration resolves the child's symbol attribute
29
+ # types in the parent context and raises UnknownTypeError
30
+ # for ids registered only in the child's register (#876).
31
+ register = Lutaml::Model::Register.resolve_for_child(
32
+ model_class, register || Lutaml::Model::Config.default_register
33
+ )
24
34
  buckets = node && plan[:needs_nodes] ? element_buckets(node) : nil
25
35
  attr_kwargs = attributes_kwargs(plan, value, node)
26
36
  child_kwargs, children, delegates =
27
- children_kwargs(model_class, plan, value, buckets)
37
+ children_kwargs(model_class, plan, value, buckets, register)
28
38
  plan[:collection_defaults].each do |name|
29
39
  next if attr_kwargs.key?(name) || child_kwargs.key?(name)
30
40
 
@@ -80,7 +90,9 @@ module Lutaml
80
90
  # — child instances come back so the caller can decorate
81
91
  # parent/root links once the parent exists; delegate values
82
92
  # wait for the instance (their target object must exist).
83
- def children_kwargs(_model_class, plan, value, buckets = nil)
93
+ def children_kwargs(_model_class, plan, value, buckets = nil,
94
+ register = nil)
95
+ register ||= Lutaml::Model::Config.default_register
84
96
  grouped, tagged = group_children(value, plan[:row_tags])
85
97
  buckets = nil unless buckets && plan[:needs_nodes]
86
98
  kwargs = {}
@@ -126,7 +138,8 @@ module Lutaml
126
138
  child_plan = PlanCompiler.compile(child_type, register)
127
139
  items = buckets.fetch(rule.name.to_s, []).map do |n|
128
140
  call(child_type, child_plan,
129
- child_plan[:descriptor].walk(n), node: n)
141
+ child_plan[:descriptor].walk(n), node: n,
142
+ register: register)
130
143
  end
131
144
  unless items.empty?
132
145
  children.concat(items)
@@ -162,7 +175,7 @@ module Lutaml
162
175
  items = grouped.fetch(rule.name.to_s, []).each_with_index
163
176
  .map do |v, i|
164
177
  call(child_type, child_plan, v,
165
- node: cursor && cursor[i])
178
+ node: cursor && cursor[i], register: register)
166
179
  end
167
180
  next if items.empty?
168
181
 
@@ -25,16 +25,24 @@ module Lutaml
25
25
 
26
26
  class << self
27
27
  # plan: the compiler's entry for instance's class
28
- def call(instance, plan)
28
+ def call(instance, plan, register = nil)
29
+ # The child model's declared lutaml_default_register takes
30
+ # precedence over the ambient register — the resolve_for_child
31
+ # contract (#876); without it, child plans and attribute
32
+ # serialization resolve in the parent context.
33
+ register = Lutaml::Model::Register.resolve_for_child(
34
+ instance.class,
35
+ register || Lutaml::Model::Config.default_register,
36
+ )
29
37
  return nil if plan[:ordered] && instance.element_order.nil?
30
38
 
31
39
  doc = ::Leptris::XML::Document.create
32
40
  root = doc.create_element(plan[:tree][:name])
33
41
  doc.root = root
34
42
  if plan[:ordered]
35
- build_ordered(root, instance, plan, doc)
43
+ build_ordered(root, instance, plan, doc, register)
36
44
  else
37
- build(root, instance, plan, doc)
45
+ build(root, instance, plan, doc, register)
38
46
  end
39
47
  doc.to_xml(indent: 2, no_decl: true)
40
48
  end
@@ -54,8 +62,9 @@ module Lutaml
54
62
  Lutaml::Model::Config.default_register
55
63
  end
56
64
 
57
- def build(element, instance, plan, doc)
58
- write_attributes(element, instance, plan)
65
+ def build(element, instance, plan, doc, reg = nil)
66
+ reg ||= register
67
+ write_attributes(element, instance, plan, reg)
59
68
  plan[:rows].each do |rule, attr, kind, spelling, delegate|
60
69
  value = value_of(instance, attr, delegate)
61
70
  next unless rule.render?(value, instance)
@@ -67,24 +76,25 @@ module Lutaml
67
76
  # as the interpretive writer does.
68
77
  Array(value).each do |item|
69
78
  add_leaf(element, row_name(rule, spelling),
70
- attr.serialize(item, :xml, register), doc,
79
+ attr.serialize(item, :xml, reg), doc,
71
80
  attrs: rule.when_attribute)
72
81
  end
73
82
  when :collection_native, :collection_cb
74
83
  Array(value).each do |item|
75
84
  add_leaf(element, rule.name.to_s,
76
- attr.serialize(item, :xml, register), doc,
85
+ attr.serialize(item, :xml, reg), doc,
77
86
  attrs: rule.when_attribute)
78
87
  end
79
88
  when :nested, :ordered_deferred
80
89
  Array(value).each do |item|
81
- child_plan = PlanCompiler.compile(item.class, register)
90
+ child_plan = PlanCompiler.compile(item.class, reg)
82
91
  child = element.create_child(child_plan[:tree][:name])
83
92
  if child_plan[:ordered]
84
- return nil unless build_ordered(child, item, child_plan, doc)
93
+ return nil unless build_ordered(child, item, child_plan,
94
+ doc, reg)
85
95
 
86
96
  else
87
- build(child, item, child_plan, doc)
97
+ build(child, item, child_plan, doc, reg)
88
98
  end
89
99
  end
90
100
  when :raw
@@ -103,8 +113,9 @@ module Lutaml
103
113
  # the model are reflected; element_order text is the fallback.
104
114
  # PIs drop (interpretive parity). Returns nil when a nested
105
115
  # ordered child lacks element_order (caller falls back).
106
- def build_ordered(element, instance, plan, doc)
107
- write_attributes(element, instance, plan)
116
+ def build_ordered(element, instance, plan, doc, reg = nil)
117
+ reg ||= register
118
+ write_attributes(element, instance, plan, reg)
108
119
  rows_by_name = {}
109
120
  plan[:rows].each do |rule, attr, kind, spelling, delegate|
110
121
  next unless rule.name
@@ -162,7 +173,7 @@ module Lutaml
162
173
 
163
174
  element_indices[object.name] += 1
164
175
  add_leaf(element, object.name,
165
- attr.serialize(items[index], :xml, register), doc)
176
+ attr.serialize(items[index], :xml, reg), doc)
166
177
  when :nested, :ordered_deferred
167
178
  item = if attr.collection?
168
179
  index = element_indices[object.name]
@@ -173,18 +184,19 @@ module Lutaml
173
184
  end
174
185
  next unless item
175
186
 
176
- child_plan = PlanCompiler.compile(item.class, register)
187
+ child_plan = PlanCompiler.compile(item.class, reg)
177
188
  child = element.create_child(child_plan[:tree][:name])
178
189
  if child_plan[:ordered]
179
- return nil unless build_ordered(child, item, child_plan, doc)
190
+ return nil unless build_ordered(child, item, child_plan,
191
+ doc, reg)
180
192
  else
181
- build(child, item, child_plan, doc)
193
+ build(child, item, child_plan, doc, reg)
182
194
  end
183
195
  when :raw
184
196
  element.add_child(value.to_s) unless value.nil?
185
197
  when :scalar
186
198
  add_leaf(element, object.name,
187
- attr.serialize(value, :xml, register), doc)
199
+ attr.serialize(value, :xml, reg), doc)
188
200
  end
189
201
  end
190
202
  end
@@ -193,7 +205,7 @@ module Lutaml
193
205
 
194
206
  # Attributes in document order when the instance recorded it
195
207
  # (parsed models), else plan row order.
196
- def write_attributes(element, instance, plan)
208
+ def write_attributes(element, instance, plan, _reg = nil)
197
209
  recorded = instance.attribute_order
198
210
  if recorded && !recorded.empty?
199
211
  by_name = {}
@@ -213,7 +225,7 @@ module Lutaml
213
225
  end
214
226
  end
215
227
 
216
- def write_attribute(element, instance, name, entry)
228
+ def write_attribute(element, instance, name, entry, reg = nil)
217
229
  return unless entry
218
230
 
219
231
  _rule, attr, delegate = entry
@@ -221,7 +233,7 @@ module Lutaml
221
233
  return if value.nil?
222
234
 
223
235
  element.set_attribute(name,
224
- attr.serialize(value, :xml, register).to_s)
236
+ attr.serialize(value, :xml, reg || register).to_s)
225
237
  end
226
238
 
227
239
  # Partition rows (#88) re-emit their discriminator: the wire
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ RSpec.describe "Cross-register embedding on the plan paths (#876)" do
6
+ before do
7
+ stub_const("CrossRegister876", Module.new)
8
+ stub_const("CrossRegister876::Part", part_class)
9
+ stub_const("CrossRegister876::Widget", widget_class)
10
+ stub_const("Host876", host_class)
11
+
12
+ ctx = Lutaml::Model::GlobalContext.create_context(
13
+ id: :crossregister876, registry: Lutaml::Model::TypeRegistry.new,
14
+ )
15
+ ctx.registry.register(:part, CrossRegister876::Part)
16
+ ctx.registry.register(:widget, CrossRegister876::Widget)
17
+ end
18
+
19
+ let(:part_class) do
20
+ Class.new(Lutaml::Model::Serializable) do
21
+ def self.lutaml_default_register
22
+ :crossregister876
23
+ end
24
+
25
+ xml do
26
+ element "part"
27
+ end
28
+ end
29
+ end
30
+
31
+ let(:widget_class) do
32
+ klass = Class.new(Lutaml::Model::Serializable) do
33
+ def self.lutaml_default_register
34
+ :crossregister876
35
+ end
36
+
37
+ attribute :part_value, :part, collection: true
38
+
39
+ xml do
40
+ element "widget"
41
+ ordered
42
+ map_element "part", to: :part_value
43
+ end
44
+ end
45
+ klass
46
+ end
47
+
48
+ let(:host_class) do
49
+ Class.new(Lutaml::Model::Serializable) do
50
+ attribute :widget, CrossRegister876::Widget
51
+
52
+ xml do
53
+ element "host"
54
+ map_element "widget", to: :widget
55
+ end
56
+ end
57
+ end
58
+
59
+ let(:xml) { "<host><widget><part/></widget></host>" }
60
+
61
+ it "resolves the child's symbol types through the child register" do
62
+ model = Host876.from_xml(xml)
63
+ expect(model.widget.part_value.first).to be_a(CrossRegister876::Part)
64
+ end
65
+
66
+ it "round-trips through both the plan serializer and back" do
67
+ model = Host876.from_xml(xml)
68
+ xml_out = model.to_xml
69
+ expect(xml_out).to include("<widget>")
70
+ expect(xml_out).to include("<part/>")
71
+
72
+ reparsed = Host876.from_xml(xml_out)
73
+ expect(reparsed.widget.part_value.first).to be_a(CrossRegister876::Part)
74
+ end
75
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lutaml-model
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.64
4
+ version: 0.8.65
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
@@ -1916,6 +1916,7 @@ files:
1916
1916
  - spec/lutaml/xml/conformance/xml_schema_instance_spec.rb
1917
1917
  - spec/lutaml/xml/content_model_validation_spec.rb
1918
1918
  - spec/lutaml/xml/cross_class_dispatch_spec.rb
1919
+ - spec/lutaml/xml/cross_register_plan_spec.rb
1919
1920
  - spec/lutaml/xml/data_model_spec.rb
1920
1921
  - spec/lutaml/xml/decisions/decision_engine_spec.rb
1921
1922
  - spec/lutaml/xml/decisions/decision_spec.rb