lutaml-model 0.7.5 → 0.7.7

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: 99264c5157019d0cddfc59098e9603152db0a2d84caa8c9710a9c6a165f39cc4
4
- data.tar.gz: 34886a6307aa6b6bb2211925477272c7a1412b67c7ef6695ea495a211bc0e540
3
+ metadata.gz: 518cf7f42227fbdd75f25ed97d2107f0d0c575290ae2a21d5be323b70546cae4
4
+ data.tar.gz: 19f355abc4da8bff1ce95b06eba36aca987888325175aba9ab153998bcc8d68f
5
5
  SHA512:
6
- metadata.gz: f1f358820c85b0626fade2aaac1ff1c3a408c0e267e22b25165d133b92adccbf31c2ca969099dbd441c1fbfc8d9833e5b1ff43fcdf079f6523e769f28bef91c5
7
- data.tar.gz: c2799042e4d91fedd894c7f86c34f79ecc93509e4422b5f204aef1f5eb5150133e120fa442477c39185c210fe65c6fcb844bb6764e3833a6828235a4e9551054
6
+ metadata.gz: 9fb8b93ce570cc804f4f9d2f1562ac58dab5b76aa55b5d3b79d82a95eef6edc4c65752beeb7d6176878d4f765dbf40a3f63331b8b2b726363b7a5a506046d243
7
+ data.tar.gz: 10a50c4c30cf2034d3e394fd8526d00c220df5dcf216d8dd5fc19395858766254d8e8f835232404be2661d5821ebb05e82afad130b238d27aa224d5fc38855cd
@@ -4,7 +4,11 @@ on:
4
4
  push:
5
5
  branches: [ main ]
6
6
  tags: [ v* ]
7
+ paths-ignore:
8
+ - '**.adoc'
7
9
  pull_request:
10
+ paths-ignore:
11
+ - '**.adoc'
8
12
  workflow_dispatch:
9
13
  repository_dispatch:
10
14
  types: [ release-passed ]
@@ -6,10 +6,22 @@ on:
6
6
  push:
7
7
  branches: [ master, main ]
8
8
  tags: [ v* ]
9
+ paths-ignore:
10
+ - '**.adoc'
9
11
  pull_request:
12
+ paths-ignore:
13
+ - '**.adoc'
10
14
 
11
15
  jobs:
12
16
  rake:
13
17
  uses: metanorma/ci/.github/workflows/generic-rake.yml@main
18
+ with:
19
+ before-setup-ruby: |
20
+ mkdir -p .bundle
21
+ touch .bundle/config
22
+ cat .bundle/config
23
+ echo "---" >> .bundle/config
24
+ echo 'BUNDLE_BUILD__RUBY___LL: "--with-cflags=-std=gnu17"' >> .bundle/config
25
+ cat .bundle/config
14
26
  secrets:
15
27
  pat_token: ${{ secrets.LUTAML_CI_PAT_TOKEN }}
data/README.adoc CHANGED
@@ -1813,7 +1813,7 @@ invalid_xml = <<~XML
1813
1813
  <zip>12345</zip>
1814
1814
  </Person>
1815
1815
  XML
1816
- Person.from_xml(valid_xml) # #<Person:0x00000002d56b3988 @city="Metropolis", @name="John Doe", @street="123 Main St", @zip="12345">
1816
+ Person.from_xml(valid_xml) # #<Person:0x00000002d56b3988 @city="Metropolis", @name="John Doe", @street="123 Main St", @zip="12345">
1817
1817
  Person.from_xml(invalid_xml) # raises `Element `zip` does not match the expected sequence order element `city` (Lutaml::Model::IncorrectSequenceError)`
1818
1818
  ----
1819
1819
  ====
@@ -1835,7 +1835,7 @@ class ContactEmail < Lutaml::Model::Serializable
1835
1835
  attribute :email, :string
1836
1836
 
1837
1837
  xml do
1838
- no_root
1838
+ no_root
1839
1839
 
1840
1840
  map_element :email, to: :email
1841
1841
  end
@@ -3151,6 +3151,123 @@ authors.first.name
3151
3151
  ====
3152
3152
 
3153
3153
 
3154
+ ==== Nested keyed object collection
3155
+
3156
+ A nested keyed object collection is a keyed collection that contain other keyed
3157
+ collections. This case is simply a more complex arrangement of the principles
3158
+ applied to keyed object collections.
3159
+
3160
+ This pattern can extend to multiple levels of nesting, where each level contains
3161
+ a keyed object collection that can have its own key and value mappings.
3162
+
3163
+ Depends on whether a custom collection class is needed, the following
3164
+ mechanisms are available:
3165
+
3166
+ * When using a Lutaml::Model::Serializable class for a keyed collection,
3167
+ use the `child_mappings` option to map attributes.
3168
+
3169
+ * When using a Lutaml::Model::Collection class for a keyed collection,
3170
+ there are two options:
3171
+
3172
+ * use the `map_key`, `map_value`, and `map_instances` methods to map attributes;
3173
+ or
3174
+
3175
+ * use the `root_mappings` option to map attributes.
3176
+
3177
+
3178
+ .Nested 2-layer keyed object collection
3179
+ [example]
3180
+ ====
3181
+ This example provides a two-layer nested structure where:
3182
+
3183
+ * The first layer keys pieces by type (`bowls`, `vases`).
3184
+ * The second layer keys glazes by finish name within each piece type.
3185
+ * Each glaze finish contains detailed attributes like temperature.
3186
+
3187
+ [source,ruby]
3188
+ ----
3189
+ # Third layer represents glaze finishes.
3190
+ class GlazeFinish < Lutaml::Model::Serializable
3191
+ attribute :name, :string
3192
+ attribute :temperature, :integer
3193
+
3194
+ key_value do
3195
+ map "name", to: :name
3196
+ map "temperature", to: :temperature
3197
+ end
3198
+ end
3199
+
3200
+ # Second layer represents ceramic pieces each with multiple finishes.
3201
+ class CeramicPiece < Lutaml::Model::Serializable
3202
+ attribute :piece_type, :string
3203
+ attribute :glazes, GlazeFinish, collection: true
3204
+
3205
+ key_value do
3206
+ map "piece_type", to: :piece_type
3207
+ map "glazes", to: :glazes, child_mappings: {
3208
+ name: :key,
3209
+ temperature: :temperature
3210
+ }
3211
+ end
3212
+ end
3213
+
3214
+ # Uppermost layer represents the collection of ceramic pieces.
3215
+ class StudioInventory < Lutaml::Model::Collection
3216
+ instances :pieces, CeramicPiece
3217
+
3218
+ key_value do
3219
+ map to: :pieces, root_mappings: {
3220
+ piece_type: :key,
3221
+ glazes: :value,
3222
+ }
3223
+ end
3224
+ end
3225
+ ----
3226
+
3227
+ [source,yaml]
3228
+ ----
3229
+ ---
3230
+ bowls:
3231
+ matte_finish:
3232
+ name: Earth Matte
3233
+ temperature: 1240
3234
+ glossy_finish:
3235
+ name: Ocean Blue
3236
+ temperature: 1260
3237
+ crackle_finish:
3238
+ name: Antique Crackle
3239
+ temperature: 1220
3240
+ vases:
3241
+ metallic_finish:
3242
+ name: Bronze Metallic
3243
+ temperature: 1280
3244
+ crystalline_finish:
3245
+ name: Ice Crystal
3246
+ temperature: 1300
3247
+ ----
3248
+
3249
+ [source,ruby]
3250
+ ----
3251
+ inventory = StudioInventory.from_yaml(yaml_data)
3252
+
3253
+ # Access nested data through the hierarchy
3254
+ puts inventory.pieces.bowls.matte_finish.name
3255
+ # => "Earth Matte"
3256
+
3257
+ puts inventory.pieces.bowls.matte_finish.temperature
3258
+ # => 1240
3259
+
3260
+ # Iterate through all pieces and their glazes
3261
+ inventory.pieces.each do |piece_type, piece|
3262
+ puts "#{piece_type.capitalize}:"
3263
+ piece.glazes.each do |glaze_name, glaze|
3264
+ puts " #{glaze_name}: #{glaze.name} (#{glaze.temperature}°C)"
3265
+ end
3266
+ end
3267
+ ----
3268
+ ====
3269
+
3270
+
3154
3271
 
3155
3272
 
3156
3273
  === Behavior
@@ -4,6 +4,24 @@ module Lutaml
4
4
  include Enumerable
5
5
 
6
6
  class << self
7
+ INHERITED_ATTRIBUTES = %i[
8
+ instance_type
9
+ instance_name
10
+ order_by_field
11
+ order_direction
12
+ ].freeze
13
+
14
+ def inherited(subclass)
15
+ super
16
+
17
+ INHERITED_ATTRIBUTES.each do |var|
18
+ subclass.instance_variable_set(
19
+ :"@#{var}",
20
+ instance_variable_get(:"@#{var}"),
21
+ )
22
+ end
23
+ end
24
+
7
25
  attr_reader :instance_type,
8
26
  :instance_name,
9
27
  :order_by_field,
@@ -87,23 +105,23 @@ module Lutaml
87
105
  end
88
106
  end
89
107
 
90
- attr_reader :register
108
+ attr_reader :__register
91
109
 
92
- def initialize(items = [], register: Lutaml::Model::Config.default_register)
110
+ def initialize(items = [], __register: Lutaml::Model::Config.default_register)
93
111
  super()
94
112
 
95
- @register = register
113
+ @__register = __register
96
114
  items = [items].compact unless items.is_a?(Array)
97
115
 
98
- register_object = Lutaml::Model::GlobalRegister.lookup(register)
99
- type = register_object.get_class_without_register(self.class.instance_type)
116
+ register_object = Lutaml::Model::GlobalRegister.lookup(@__register)
117
+ type = register_object.get_class(self.class.instance_type)
100
118
  self.collection = items.map do |item|
101
119
  if item.is_a?(type)
102
120
  item
103
121
  elsif type <= Lutaml::Model::Type::Value
104
122
  type.cast(item)
105
123
  else
106
- type.new(item, register: register)
124
+ type.new(item)
107
125
  end
108
126
  end
109
127
 
@@ -4,11 +4,11 @@ require_relative "key_value_mapping_rule"
4
4
  module Lutaml
5
5
  module Model
6
6
  class KeyValueMapping < Mapping
7
- attr_reader :mappings, :format, :key_mappings, :value_mappings
7
+ attr_reader :format, :key_mappings, :value_mappings
8
8
 
9
9
  def initialize(format = nil)
10
10
  super()
11
-
11
+ @mappings = {}
12
12
  @format = format
13
13
  end
14
14
 
@@ -49,7 +49,7 @@ module Lutaml
49
49
  mapping_name = name_for_mapping(root_mappings, name)
50
50
  validate!(mapping_name, to, with, render_nil, render_empty)
51
51
 
52
- @mappings << KeyValueMappingRule.new(
52
+ @mappings[mapping_name] = KeyValueMappingRule.new(
53
53
  mapping_name,
54
54
  to: to,
55
55
  render_nil: render_nil,
@@ -80,7 +80,7 @@ module Lutaml
80
80
  )
81
81
  @raw_mapping = true
82
82
  validate!(Constants::RAW_MAPPING_KEY, to, with, render_nil, nil)
83
- @mappings << KeyValueMappingRule.new(
83
+ @mappings[Constants::RAW_MAPPING_KEY] = KeyValueMappingRule.new(
84
84
  Constants::RAW_MAPPING_KEY,
85
85
  to: to,
86
86
  render_nil: render_nil,
@@ -131,6 +131,14 @@ module Lutaml
131
131
  name
132
132
  end
133
133
 
134
+ def mappings
135
+ @mappings.values
136
+ end
137
+
138
+ def mappings_hash
139
+ @mappings
140
+ end
141
+
134
142
  def validate!(key, to, with, render_nil, render_empty)
135
143
  validate_mappings!(key)
136
144
  validate_to_and_with_arguments!(key, to, with)
@@ -178,7 +186,7 @@ module Lutaml
178
186
 
179
187
  def validate_mappings!(_type)
180
188
  if (@raw_mapping && Utils.present?(@mappings)) ||
181
- (!@raw_mapping && @mappings.any?(&:raw_mapping?))
189
+ (!@raw_mapping && mappings.any?(&:raw_mapping?))
182
190
  raise StandardError, "map_all is not allowed with other mappings"
183
191
  end
184
192
  end
@@ -190,11 +198,11 @@ module Lutaml
190
198
  end
191
199
 
192
200
  def duplicate_mappings
193
- @mappings.map(&:deep_dup)
201
+ Utils.deep_dup(@mappings)
194
202
  end
195
203
 
196
204
  def find_by_to(to)
197
- @mappings.find { |m| m.to.to_s == to.to_s }
205
+ mappings.find { |m| m.to.to_s == to.to_s }
198
206
  end
199
207
 
200
208
  def find_by_name(name)
@@ -202,11 +210,11 @@ module Lutaml
202
210
  end
203
211
 
204
212
  def polymorphic_mapping
205
- @mappings.find(&:polymorphic_mapping?)
213
+ mappings.find(&:polymorphic_mapping?)
206
214
  end
207
215
 
208
216
  def root_mapping
209
- @mappings.find(&:root_mapping?)
217
+ mappings.find(&:root_mapping?)
210
218
  end
211
219
 
212
220
  Lutaml::Model::Config::KEY_VALUE_FORMATS.each do |format|
@@ -64,6 +64,7 @@ module Lutaml
64
64
  with: Utils.deep_dup(custom_methods),
65
65
  delegate: delegate,
66
66
  child_mappings: Utils.deep_dup(child_mappings),
67
+ root_mappings: Utils.deep_dup(root_mappings),
67
68
  value_map: Utils.deep_dup(@value_map),
68
69
  )
69
70
  end
@@ -289,7 +289,7 @@ module Lutaml
289
289
  delegate_value = model.public_send(delegate)
290
290
  return if Utils.initialized?(delegate_value) && !delegate_value.nil?
291
291
 
292
- model.public_send(:"#{delegate}=", attributes[delegate].type(model.register).new)
292
+ model.public_send(:"#{delegate}=", attributes[delegate].type(model.__register).new)
293
293
  end
294
294
 
295
295
  def handle_transform_method(model, value, attributes)
@@ -28,7 +28,7 @@ module Lutaml
28
28
  def get_class(klass_name)
29
29
  expected_class = get_class_without_register(klass_name)
30
30
  if !(expected_class < Lutaml::Model::Type::Value)
31
- expected_class.class_variable_set(:@@register, id)
31
+ expected_class.class_variable_set(:@@__register, id)
32
32
  end
33
33
  expected_class
34
34
  end
@@ -5,8 +5,8 @@ module Lutaml
5
5
  module Schema
6
6
  module SharedMethods
7
7
  def extract_register_from(klass)
8
- register = if klass.class_variable_defined?(:@@register)
9
- klass.class_variable_get(:@@register)
8
+ register = if klass.class_variable_defined?(:@@__register)
9
+ klass.class_variable_get(:@@__register)
10
10
  end
11
11
 
12
12
  case register
@@ -123,7 +123,7 @@ module Lutaml
123
123
  end
124
124
  define_method(:"#{name}=") do |value|
125
125
  value_set_for(name)
126
- instance_variable_set(:"@#{name}", attr.cast_value(value, register))
126
+ instance_variable_set(:"@#{name}", attr.cast_value(value, __register))
127
127
  end
128
128
  end
129
129
  end
@@ -211,14 +211,14 @@ module Lutaml
211
211
  @mappings[format].merge_mapping_elements(mapping)
212
212
  @mappings[format].merge_elements_sequence(mapping)
213
213
  else
214
- @mappings[format].mappings.concat(mapping.mappings)
214
+ @mappings[format].mappings_hash.merge!(mapping.mappings_hash)
215
215
  end
216
216
  end
217
217
  end
218
218
 
219
219
  def handle_key_value_mappings(mapping, format)
220
220
  @mappings[format] ||= KeyValueMapping.new
221
- @mappings[format].mappings.concat(mapping.mappings)
221
+ @mappings[format].mappings_hash.merge!(mapping.mappings_hash)
222
222
  end
223
223
 
224
224
  def import_model(model)
@@ -412,7 +412,7 @@ module Lutaml
412
412
  instance = if options.key?(:instance)
413
413
  options[:instance]
414
414
  elsif model.include?(Lutaml::Model::Serialize)
415
- model.new({}, register: register)
415
+ model.new({ __register: register })
416
416
  else
417
417
  object = model.new
418
418
  register_accessor_methods_for(object, register)
@@ -438,7 +438,7 @@ module Lutaml
438
438
  klass_name = polymorphic_mapping.polymorphic_map[klass_key]
439
439
  klass = Object.const_get(klass_name)
440
440
 
441
- klass.apply_mappings(doc, format, options.merge(register: instance.register))
441
+ klass.apply_mappings(doc, format, options.merge(register: instance.__register))
442
442
  end
443
443
 
444
444
  def apply_value_map(value, value_map, attr)
@@ -485,20 +485,20 @@ module Lutaml
485
485
  end
486
486
 
487
487
  def register_accessor_methods_for(object, register)
488
- Utils.add_singleton_method_if_not_defined(object, :register) do
489
- @register
488
+ Utils.add_singleton_method_if_not_defined(object, :__register) do
489
+ @__register
490
490
  end
491
- Utils.add_singleton_method_if_not_defined(object, :register=) do |value|
492
- @register = value
491
+ Utils.add_singleton_method_if_not_defined(object, :__register=) do |value|
492
+ @__register = value
493
493
  end
494
- object.register = register
494
+ object.__register = register
495
495
  end
496
496
 
497
497
  def extract_register_id(register)
498
498
  if register
499
499
  register.is_a?(Lutaml::Model::Register) ? register.id : register
500
- elsif class_variable_defined?(:@@register)
501
- class_variable_get(:@@register)
500
+ elsif class_variable_defined?(:@@__register)
501
+ class_variable_get(:@@__register)
502
502
  else
503
503
  Lutaml::Model::Config.default_register
504
504
  end
@@ -583,20 +583,21 @@ module Lutaml
583
583
  end
584
584
  end
585
585
 
586
- attr_accessor :element_order, :schema_location, :encoding, :register
586
+ attr_accessor :element_order, :schema_location, :encoding, :__register
587
587
  attr_writer :ordered, :mixed
588
588
 
589
589
  def initialize(attrs = {}, options = {})
590
590
  @using_default = {}
591
591
  return unless self.class.attributes
592
592
 
593
- @register = extract_register_id(options[:register])
593
+ @__register = extract_register_id(attrs, options)
594
594
  set_ordering(attrs)
595
595
  set_schema_location(attrs)
596
596
  initialize_attributes(attrs, options)
597
597
  end
598
598
 
599
- def extract_register_id(register)
599
+ def extract_register_id(attrs, options)
600
+ register = attrs&.dig(:__register) || options&.dig(:register)
600
601
  self.class.extract_register_id(register)
601
602
  end
602
603
 
@@ -609,8 +610,8 @@ module Lutaml
609
610
  end
610
611
 
611
612
  def attr_value(attrs, name, attribute)
612
- value = Utils.fetch_str_or_sym(attrs, name, attribute.default(register))
613
- attribute.cast_value(value, register)
613
+ value = Utils.fetch_str_or_sym(attrs, name, attribute.default(__register))
614
+ attribute.cast_value(value, __register)
614
615
  end
615
616
 
616
617
  def using_default_for(attribute_name)
@@ -670,7 +671,7 @@ module Lutaml
670
671
  end
671
672
 
672
673
  def pretty_print_instance_variables
673
- (instance_variables - %i[@using_default]).sort
674
+ (instance_variables - %i[@using_default @__register]).sort
674
675
  end
675
676
 
676
677
  def to_yaml_hash
@@ -722,9 +723,9 @@ module Lutaml
722
723
  def determine_value(attrs, name, attr)
723
724
  if attrs.key?(name) || attrs.key?(name.to_s)
724
725
  attr_value(attrs, name, attr)
725
- elsif attr.default_set?(register)
726
+ elsif attr.default_set?(__register)
726
727
  using_default_for(name)
727
- attr.default(register)
728
+ attr.default(__register)
728
729
  else
729
730
  Lutaml::Model::UninitializedClass.instance
730
731
  end
@@ -2,11 +2,12 @@ module Lutaml
2
2
  module Model
3
3
  class KeyValueTransform < Lutaml::Model::Transform
4
4
  def data_to_model(data, format, options = {})
5
- instance = if model_class.include?(Lutaml::Model::Serialize)
6
- model_class.new({}, register: register)
7
- else
8
- model_class.new(register)
9
- end
5
+ if model_class.include?(Lutaml::Model::Serialize)
6
+ instance = model_class.new(__register: __register)
7
+ else
8
+ instance = model_class.new
9
+ register_accessor_methods_for(instance, __register)
10
+ end
10
11
  mappings = extract_mappings(options, format)
11
12
 
12
13
  Utils.add_if_present(options, :key_mappings, mappings.key_mappings)
@@ -93,7 +94,7 @@ module Lutaml
93
94
  end
94
95
 
95
96
  def serialize_value(value, rule, attr, format, options)
96
- return attr.serialize(value, format, register, options) unless rule.child_mappings
97
+ return attr.serialize(value, format, __register, options) unless rule.child_mappings
97
98
 
98
99
  generate_hash_from_child_mappings(
99
100
  attr, value, format, rule.child_mappings
@@ -111,12 +112,12 @@ module Lutaml
111
112
 
112
113
  generate_remaining_mappings_for_value(child_mappings, value, format)
113
114
 
114
- attr_type = attr.type(register)
115
+ attr_type = attr.type(__register)
115
116
  value.each do |child_obj|
116
117
  rules = attr_type.mappings_for(format)
117
118
 
118
119
  hash.merge!(
119
- extract_hash_for_child_mapping(child_mappings, child_obj, rules),
120
+ extract_hash_for_child_mapping(child_mappings, child_obj, rules, format),
120
121
  )
121
122
  end
122
123
 
@@ -144,14 +145,13 @@ module Lutaml
144
145
  mappings.find_by_to(name)&.name.to_s
145
146
  end
146
147
 
147
- def extract_hash_for_child_mapping(child_mappings, child_obj, rules)
148
+ def extract_hash_for_child_mapping(child_mappings, child_obj, rules, format)
148
149
  key = nil
149
150
  value = {}
150
151
 
151
152
  child_mappings.each do |attr_name, path|
152
153
  rule = rules.find_by_to(attr_name)
153
-
154
- attr_value = normalize_attribute_value(child_obj.send(attr_name))
154
+ attr_value = normalize_attribute_value(child_obj, rule.from, format)
155
155
 
156
156
  next unless rule&.render?(attr_value, nil)
157
157
  next key = attr_value if path == :key
@@ -163,14 +163,10 @@ module Lutaml
163
163
  { key => value }
164
164
  end
165
165
 
166
- def normalize_attribute_value(value)
167
- if value.is_a?(Lutaml::Model::Serialize)
168
- value.to_hash
169
- elsif value.is_a?(Array) && value.first.is_a?(Lutaml::Model::Serialize)
170
- value.map(&:to_hash)
171
- else
172
- value
173
- end
166
+ def normalize_attribute_value(value, attr_name, format)
167
+ Lutaml::Model::Config.adapter_for(format).parse(
168
+ value.public_send(:"to_#{format}"),
169
+ )[attr_name.to_s]
174
170
  end
175
171
 
176
172
  def extract_hash_value_for_child_mapping(path, value, map_value)
@@ -189,7 +185,7 @@ module Lutaml
189
185
  return if value.nil? && !rule.render_nil
190
186
 
191
187
  attribute = instance.send(rule.delegate).class.attributes[rule.to]
192
- hash[rule_from_name(rule)] = attribute.serialize(value, format, register)
188
+ hash[rule_from_name(rule)] = attribute.serialize(value, format, __register)
193
189
  end
194
190
 
195
191
  def extract_value_for_delegate(instance, rule)
@@ -206,7 +202,7 @@ module Lutaml
206
202
 
207
203
  raise "Attribute '#{rule.to}' not found in #{self}" unless valid_rule?(rule, attr)
208
204
 
209
- value = rule_value_extractor_class.call(rule, doc, format, attr, register, options)
205
+ value = rule_value_extractor_class.call(rule, doc, format, attr, __register, options)
210
206
  value = apply_value_map(value, rule.value_map(:from, options), attr)
211
207
 
212
208
  return process_custom_method(rule, instance, value) if rule.has_custom_method_for_deserialization?
@@ -226,7 +222,7 @@ module Lutaml
226
222
 
227
223
  def cast_value(value, attr, format, rule)
228
224
  cast_options = rule.polymorphic ? { polymorphic: rule.polymorphic } : {}
229
- attr.cast(value, format, register, cast_options)
225
+ attr.cast(value, format, __register, cast_options)
230
226
  end
231
227
 
232
228
  def translate_mappings(hash, child_mappings, attr, format)
@@ -248,7 +244,7 @@ module Lutaml
248
244
  end
249
245
 
250
246
  def build_child_hash(key, value, child_mappings, attr, format)
251
- attr_type = attr.type(register)
247
+ attr_type = attr.type(__register)
252
248
  child_mappings.to_h do |attr_name, path|
253
249
  attr_value = extract_attr_value(path, key, value)
254
250
  attr_rule = attr_type.mappings_for(format).find_by_to(attr_name)
@@ -271,7 +267,7 @@ module Lutaml
271
267
  end
272
268
 
273
269
  def map_child_data(child_hash, attr, format)
274
- attr_type = attr.type(register)
270
+ attr_type = attr.type(__register)
275
271
  self.class.data_to_model(
276
272
  attr_type,
277
273
  child_hash,
@@ -3,10 +3,10 @@ module Lutaml
3
3
  class XmlTransform < Lutaml::Model::Transform
4
4
  def data_to_model(data, _format, options = {})
5
5
  if model_class.include?(Lutaml::Model::Serialize)
6
- instance = model_class.new({}, register: register)
6
+ instance = model_class.new({ __register: __register })
7
7
  else
8
8
  instance = model_class.new
9
- register_accessor_methods_for(instance, register)
9
+ register_accessor_methods_for(instance, __register)
10
10
  end
11
11
  apply_xml_mapping(data, instance, options)
12
12
  end
@@ -53,7 +53,7 @@ module Lutaml
53
53
 
54
54
  if (val.nil? || Utils.uninitialized?(val)) && (instance.using_default?(rule.to) || rule.render_default)
55
55
  defaults_used << rule.to
56
- attr&.default(register) || rule.to_value_for(instance)
56
+ attr&.default(__register) || rule.to_value_for(instance)
57
57
  else
58
58
  val
59
59
  end
@@ -114,7 +114,7 @@ module Lutaml
114
114
  return doc.root.find_attribute_value(rule_names) if rule.attribute?
115
115
 
116
116
  attr = attribute_for_rule(rule)
117
- attr_type = attr&.type(register)
117
+ attr_type = attr&.type(__register)
118
118
 
119
119
  children = doc.children.select do |child|
120
120
  rule_names.include?(child.namespaced_name) && !child.text?
@@ -136,9 +136,9 @@ module Lutaml
136
136
  if !rule.has_custom_method_for_deserialization? && attr_type <= Serialize
137
137
  cast_options = options.except(:mappings)
138
138
  cast_options[:polymorphic] = rule.polymorphic if rule.polymorphic
139
- cast_options[:register] = register
139
+ cast_options[:register] = __register
140
140
 
141
- values << attr.cast(child, :xml, register, cast_options)
141
+ values << attr.cast(child, :xml, __register, cast_options)
142
142
  elsif attr.raw?
143
143
  values << inner_xml_of(child)
144
144
  else
@@ -179,7 +179,7 @@ module Lutaml
179
179
 
180
180
  return value unless cast_value?(attr, rule)
181
181
 
182
- attr.cast(value, :xml, register, options)
182
+ attr.cast(value, :xml, __register, options)
183
183
  end
184
184
 
185
185
  def cast_value?(attr, rule)
@@ -6,15 +6,16 @@ module Lutaml
6
6
  end
7
7
 
8
8
  def self.model_to_data(context, model, format, options = {})
9
- new(context, model.register).model_to_data(model, format, options)
9
+ register = model.__register if model.respond_to?(:__register)
10
+ new(context, register).model_to_data(model, format, options)
10
11
  end
11
12
 
12
- attr_reader :context, :attributes, :register
13
+ attr_reader :context, :attributes, :__register
13
14
 
14
15
  def initialize(context, register = nil)
15
16
  @context = context
16
17
  @attributes = context.attributes
17
- @register = register || Lutaml::Model::Config.default_register
18
+ @__register = register || Lutaml::Model::Config.default_register
18
19
  end
19
20
 
20
21
  def model_class
@@ -71,18 +72,18 @@ module Lutaml
71
72
  def attribute_for_rule(rule)
72
73
  return attributes[rule.to] unless rule.delegate
73
74
 
74
- attributes[rule.delegate].type(register).attributes[rule.to]
75
+ attributes[rule.delegate].type(__register).attributes[rule.to]
75
76
  end
76
77
 
77
78
  def register_accessor_methods_for(object, register)
78
79
  klass = object.class
79
- Utils.add_method_if_not_defined(klass, :register) do
80
- @register
80
+ Utils.add_method_if_not_defined(klass, :__register) do
81
+ @__register
81
82
  end
82
- Utils.add_method_if_not_defined(klass, :register=) do |value|
83
- @register = value
83
+ Utils.add_method_if_not_defined(klass, :__register=) do |value|
84
+ @__register = value
84
85
  end
85
- object.register = register
86
+ object.__register = register
86
87
  end
87
88
  end
88
89
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Lutaml
4
4
  module Model
5
- VERSION = "0.7.5"
5
+ VERSION = "0.7.7"
6
6
  end
7
7
  end
@@ -443,10 +443,10 @@ module Lutaml
443
443
 
444
444
  return_register = if register.is_a?(Lutaml::Model::Register)
445
445
  register.id
446
- elsif @root.respond_to?(:register)
447
- @root.register
448
- elsif @root.instance_variable_defined?(:@register)
449
- @root.instance_variable_get(:@register)
446
+ elsif @root.respond_to?(:__register)
447
+ @root.__register
448
+ elsif @root.instance_variable_defined?(:@__register)
449
+ @root.instance_variable_get(:@__register)
450
450
  end
451
451
  return_register || Lutaml::Model::Config.default_register
452
452
  end
@@ -1,19 +1,11 @@
1
1
  require "spec_helper"
2
2
 
3
3
  class CustomModelChild
4
- attr_accessor :street, :city, :register
5
-
6
- def initialize(register = nil)
7
- @register = register
8
- end
4
+ attr_accessor :street, :city
9
5
  end
10
6
 
11
7
  class CustomModelParent
12
- attr_accessor :first_name, :middle_name, :last_name, :child_mapper, :math, :register
13
-
14
- def initialize(register = nil)
15
- @register = register
16
- end
8
+ attr_accessor :first_name, :middle_name, :last_name, :child_mapper, :math
17
9
 
18
10
  def name
19
11
  "#{first_name} #{last_name}"
@@ -21,11 +13,7 @@ class CustomModelParent
21
13
  end
22
14
 
23
15
  class GenericFormulaClass
24
- attr_accessor :value, :register
25
-
26
- def initialize(register = nil)
27
- @register = register
28
- end
16
+ attr_accessor :value
29
17
  end
30
18
 
31
19
  class Mi < Lutaml::Model::Serializable
@@ -108,11 +108,7 @@ module DefaultsSpec
108
108
 
109
109
  # Class for testing render_default: true with custom model
110
110
  class Lang
111
- attr_accessor :lang, :content, :register
112
-
113
- def initialize(register = nil)
114
- @register = register
115
- end
111
+ attr_accessor :lang, :content
116
112
  end
117
113
 
118
114
  class CustomModelWithDefaultValue < Lutaml::Model::Serializable
@@ -111,9 +111,7 @@ RSpec.describe "RegisterXmlSpec" do
111
111
  {
112
112
  numerator: RegisterXmlSpec::Mo.new(value: "a"),
113
113
  denominator: RegisterXmlSpec::Mi.new(value: "b"),
114
- },
115
- {
116
- register: register,
114
+ __register: register,
117
115
  },
118
116
  ),
119
117
  )
@@ -0,0 +1,217 @@
1
+ require "spec_helper"
2
+
3
+ module NestedChildMappingsSpec
4
+ class ManifestFont < Lutaml::Model::Serializable
5
+ attribute :name, :string
6
+ attribute :styles, :string, collection: true
7
+
8
+ key_value do
9
+ map "name", to: :name
10
+ map "styles", to: :styles
11
+ end
12
+ end
13
+
14
+ class ManifestResponseFontStyle < Lutaml::Model::Serializable
15
+ attribute :full_name, :string
16
+ attribute :type, :string
17
+ attribute :paths, :string, collection: true
18
+
19
+ key_value do
20
+ map "full_name", to: :full_name
21
+ map "type", to: :type
22
+ map "paths", to: :paths
23
+ end
24
+ end
25
+
26
+ class ManifestResponseFont < ManifestFont
27
+ attribute :name, :string
28
+ attribute :styles, ManifestResponseFontStyle, collection: true
29
+
30
+ key_value do
31
+ map "name", to: :name
32
+ map "styles", to: :styles, child_mappings: {
33
+ type: :key,
34
+ full_name: :full_name,
35
+ paths: :paths,
36
+ }
37
+ end
38
+ end
39
+
40
+ class ManifestResponse < Lutaml::Model::Collection
41
+ instances :fonts, ManifestResponseFont
42
+
43
+ key_value do
44
+ map to: :fonts, root_mappings: {
45
+ name: :key,
46
+ styles: :value,
47
+ }
48
+ end
49
+ end
50
+
51
+ class ManifestResponseWrap < ManifestResponse
52
+ # This class confirms the successful inhertiance of the `Collection` attribute.
53
+ end
54
+ end
55
+
56
+ RSpec.describe "NestedChildMappingsSpec" do
57
+ let(:yaml) do
58
+ <<~YAML
59
+ ---
60
+ Yu Gothic:
61
+ Bold:
62
+ full_name: Yu Gothic Bold
63
+ paths:
64
+ - "/Applications/Microsoft Excel.app/Contents/Resources/DFonts/YuGothB.ttc"
65
+ - "/Applications/Microsoft OneNote.app/Contents/Resources/DFonts/YuGothB.ttc"
66
+ - "/Applications/Microsoft Outlook.app/Contents/Resources/DFonts/YuGothB.ttc"
67
+ - "/Applications/Microsoft PowerPoint.app/Contents/Resources/DFonts/YuGothB.ttc"
68
+ - "/Applications/Microsoft Word.app/Contents/Resources/DFonts/YuGothB.ttc"
69
+ Regular:
70
+ full_name: Yu Gothic Regular
71
+ paths:
72
+ - "/Applications/Microsoft Excel.app/Contents/Resources/DFonts/YuGothR.ttc"
73
+ - "/Applications/Microsoft OneNote.app/Contents/Resources/DFonts/YuGothR.ttc"
74
+ - "/Applications/Microsoft Outlook.app/Contents/Resources/DFonts/YuGothR.ttc"
75
+ - "/Applications/Microsoft PowerPoint.app/Contents/Resources/DFonts/YuGothR.ttc"
76
+ - "/Applications/Microsoft Word.app/Contents/Resources/DFonts/YuGothR.ttc"
77
+ Noto Sans Condensed:
78
+ Regular:
79
+ full_name: Noto Sans Condensed
80
+ paths:
81
+ - "/Users/foo/.fontist/fonts/NotoSans-Condensed.ttf"
82
+ Bold:
83
+ full_name: Noto Sans Condensed Bold
84
+ paths:
85
+ - "/Users/foo/.fontist/fonts/NotoSans-CondensedBold.ttf"
86
+ Bold Italic:
87
+ full_name: Noto Sans Condensed Bold Italic
88
+ paths:
89
+ - "/Users/foo/.fontist/fonts/NotoSans-CondensedBoldItalic.ttf"
90
+ Italic:
91
+ full_name: Noto Sans Condensed Italic
92
+ paths:
93
+ - "/Users/foo/.fontist/fonts/NotoSans-CondensedItalic.ttf"
94
+ YAML
95
+ end
96
+
97
+ let(:wrapped_yaml) do
98
+ <<~YAML
99
+ ---
100
+ Yu Gothic:
101
+ Bold:
102
+ full_name: Yu Gothic Bold
103
+ paths:
104
+ - "/Applications/Microsoft Excel.app/Contents/Resources/DFonts/YuGothB.ttc"
105
+ - "/Applications/Microsoft OneNote.app/Contents/Resources/DFonts/YuGothB.ttc"
106
+ - "/Applications/Microsoft Outlook.app/Contents/Resources/DFonts/YuGothB.ttc"
107
+ - "/Applications/Microsoft PowerPoint.app/Contents/Resources/DFonts/YuGothB.ttc"
108
+ - "/Applications/Microsoft Word.app/Contents/Resources/DFonts/YuGothB.ttc"
109
+ Regular:
110
+ full_name: Yu Gothic Regular
111
+ paths:
112
+ - "/Applications/Microsoft Excel.app/Contents/Resources/DFonts/YuGothR.ttc"
113
+ - "/Applications/Microsoft OneNote.app/Contents/Resources/DFonts/YuGothR.ttc"
114
+ - "/Applications/Microsoft Outlook.app/Contents/Resources/DFonts/YuGothR.ttc"
115
+ - "/Applications/Microsoft PowerPoint.app/Contents/Resources/DFonts/YuGothR.ttc"
116
+ - "/Applications/Microsoft Word.app/Contents/Resources/DFonts/YuGothR.ttc"
117
+ Noto Sans Condensed:
118
+ Regular:
119
+ full_name: Noto Sans Condensed
120
+ paths:
121
+ - "/Users/foo/.fontist/fonts/NotoSans-Condensed.ttf"
122
+ Bold:
123
+ full_name: Noto Sans Condensed Bold
124
+ paths:
125
+ - "/Users/foo/.fontist/fonts/NotoSans-CondensedBold.ttf"
126
+ Bold Italic:
127
+ full_name: Noto Sans Condensed Bold Italic
128
+ paths:
129
+ - "/Users/foo/.fontist/fonts/NotoSans-CondensedBoldItalic.ttf"
130
+ Italic:
131
+ full_name: Noto Sans Condensed Italic
132
+ paths:
133
+ - "/Users/foo/.fontist/fonts/NotoSans-CondensedItalic.ttf"
134
+ YAML
135
+ end
136
+
137
+ let(:parsed_yaml) do
138
+ NestedChildMappingsSpec::ManifestResponse.from_yaml(yaml)
139
+ end
140
+
141
+ let(:wrap_parsed_yaml) do
142
+ NestedChildMappingsSpec::ManifestResponseWrap.from_yaml(wrapped_yaml)
143
+ end
144
+
145
+ let(:expected_fonts) do
146
+ NestedChildMappingsSpec::ManifestResponse.new(
147
+ [
148
+ NestedChildMappingsSpec::ManifestResponseFont.new(
149
+ name: "Yu Gothic",
150
+ styles: [
151
+ NestedChildMappingsSpec::ManifestResponseFontStyle.new(
152
+ full_name: "Yu Gothic Bold",
153
+ type: "Bold",
154
+ paths: [
155
+ "/Applications/Microsoft Excel.app/Contents/Resources/DFonts/YuGothB.ttc",
156
+ "/Applications/Microsoft OneNote.app/Contents/Resources/DFonts/YuGothB.ttc",
157
+ "/Applications/Microsoft Outlook.app/Contents/Resources/DFonts/YuGothB.ttc",
158
+ "/Applications/Microsoft PowerPoint.app/Contents/Resources/DFonts/YuGothB.ttc",
159
+ "/Applications/Microsoft Word.app/Contents/Resources/DFonts/YuGothB.ttc",
160
+ ],
161
+ ),
162
+ NestedChildMappingsSpec::ManifestResponseFontStyle.new(
163
+ full_name: "Yu Gothic Regular",
164
+ type: "Regular",
165
+ paths: [
166
+ "/Applications/Microsoft Excel.app/Contents/Resources/DFonts/YuGothR.ttc",
167
+ "/Applications/Microsoft OneNote.app/Contents/Resources/DFonts/YuGothR.ttc",
168
+ "/Applications/Microsoft Outlook.app/Contents/Resources/DFonts/YuGothR.ttc",
169
+ "/Applications/Microsoft PowerPoint.app/Contents/Resources/DFonts/YuGothR.ttc",
170
+ "/Applications/Microsoft Word.app/Contents/Resources/DFonts/YuGothR.ttc",
171
+ ],
172
+ ),
173
+ ],
174
+ ),
175
+ NestedChildMappingsSpec::ManifestResponseFont.new(
176
+ name: "Noto Sans Condensed",
177
+ styles: [
178
+ NestedChildMappingsSpec::ManifestResponseFontStyle.new(
179
+ full_name: "Noto Sans Condensed",
180
+ type: "Regular",
181
+ paths: ["/Users/foo/.fontist/fonts/NotoSans-Condensed.ttf"],
182
+ ),
183
+ NestedChildMappingsSpec::ManifestResponseFontStyle.new(
184
+ full_name: "Noto Sans Condensed Bold",
185
+ type: "Bold",
186
+ paths: ["/Users/foo/.fontist/fonts/NotoSans-CondensedBold.ttf"],
187
+ ),
188
+ NestedChildMappingsSpec::ManifestResponseFontStyle.new(
189
+ full_name: "Noto Sans Condensed Bold Italic",
190
+ type: "Bold Italic",
191
+ paths: ["/Users/foo/.fontist/fonts/NotoSans-CondensedBoldItalic.ttf"],
192
+ ),
193
+ NestedChildMappingsSpec::ManifestResponseFontStyle.new(
194
+ full_name: "Noto Sans Condensed Italic",
195
+ type: "Italic",
196
+ paths: ["/Users/foo/.fontist/fonts/NotoSans-CondensedItalic.ttf"],
197
+ ),
198
+ ],
199
+ ),
200
+ ],
201
+ )
202
+ end
203
+
204
+ it "parses nested child mappings correctly" do
205
+ expect(parsed_yaml).to eq(expected_fonts)
206
+ end
207
+
208
+ it "rounds trip correctly" do
209
+ expected_yaml = parsed_yaml.to_yaml
210
+ expect(expected_yaml).to eq(yaml)
211
+ end
212
+
213
+ it "round trips nested child mappings correctly with Wrap class" do
214
+ expected_yaml = wrap_parsed_yaml.to_yaml
215
+ expect(expected_yaml).to eq(yaml)
216
+ end
217
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lutaml-model
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.5
4
+ version: 0.7.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-07-25 00:00:00.000000000 Z
11
+ date: 2025-07-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: moxml
@@ -367,6 +367,7 @@ files:
367
367
  - spec/lutaml/model/xml/derived_attributes_spec.rb
368
368
  - spec/lutaml/model/xml/namespace/nested_with_explicit_namespace_spec.rb
369
369
  - spec/lutaml/model/xml/namespace_spec.rb
370
+ - spec/lutaml/model/xml/root_mappings/nested_child_mappings_spec.rb
370
371
  - spec/lutaml/model/xml/xml_element_spec.rb
371
372
  - spec/lutaml/model/xml_adapter/nokogiri_adapter_spec.rb
372
373
  - spec/lutaml/model/xml_adapter/oga_adapter_spec.rb