lutaml-model 0.8.66 → 0.8.68

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: f1a4f0f42471c24f887cb31b50be356825d1bb538ca568b30d15a703928c7d0b
4
- data.tar.gz: b2b5227d2f505a10921d41123caf0f04b2cafec68ab0318db49690d394d1309a
3
+ metadata.gz: '08259545535ddbd9c57aad9adfcb1fa66ebf9179fe00cbfa6be408285d1cdca5'
4
+ data.tar.gz: 84c75404c371bb433b18d9cc87fab24b252b15fe8c2555544fc7b12478ff37c6
5
5
  SHA512:
6
- metadata.gz: 191a88d3dc8eacc1e94c8ea2b033500dbb4ce77345feefb6e7328527c4648c67575d10db7f9cb9e4d9611fada0902210077ae5c6ec0fcc4ff457b8637f35e2ef
7
- data.tar.gz: fe6e34251e26d5593ec07873e5f0320a0802e0cd3c86ec89b8492c9add49c11f919dedd4aef225f6b8b63ca6564c0d3841f88d16b614fe265c8ebf3fb27fdb00
6
+ metadata.gz: 2b730dcc7e7f555270929f4a42eb9b7a2f7e2d26a4a73fb1c75cbaa6415d2d54c0c5674ba2fad1bdba1cda106c3f625f19eed4db5d58d59334c79311c5dc46ad
7
+ data.tar.gz: 4a8fa22b33898a088d8ba3e9a2b00c8bed316f73a0d0e2dcca776aa290f484bf9519952c7e2076161779faaff4aa5d16fb3be52b10e963366369bf663d75d6f4
@@ -83,6 +83,13 @@ module Lutaml
83
83
  def root_mapping?
84
84
  name == "root_mapping"
85
85
  end
86
+
87
+ # The writer symbol for this rule's target, interned once per
88
+ # rule: the interpolation allocated a String and interned it per
89
+ # row per document in the transform's hot loop.
90
+ def kv_setter
91
+ @kv_setter ||= :"#{to}="
92
+ end
86
93
  end
87
94
  end
88
95
  end
@@ -1,5 +1,6 @@
1
1
  module Lutaml
2
2
  module KeyValue
3
+ MISSING = Object.new.freeze
3
4
  class Transform < Lutaml::Model::Transform
4
5
  def data_to_model(data, format, options = {})
5
6
  # Use child's own default register if it has one
@@ -208,14 +209,17 @@ module Lutaml
208
209
  children = []
209
210
  absent = []
210
211
  rows.each do |rule, attr, kind, type, child_rows|
211
- unless Lutaml::Model::Utils.string_or_symbol_key?(doc, rule.name)
212
+ # One hash pass per row: fetch with a sentinel distinguishes
213
+ # absent keys from present-nil values (both read as nil), so
214
+ # the separate string_or_symbol_key? probe goes away.
215
+ v = Lutaml::Model::Utils.fetch_str_or_sym(doc, rule.name, MISSING)
216
+ if v.equal?(MISSING)
212
217
  absent << rule
213
218
  next
214
219
  end
215
- v = Lutaml::Model::Utils.fetch_str_or_sym(doc, rule.name)
216
220
 
217
221
  if kind == :scalar
218
- setters << [:"#{rule.to}=", v]
222
+ setters << [rule.kv_setter, v]
219
223
  next
220
224
  end
221
225
 
@@ -239,7 +243,7 @@ module Lutaml
239
243
 
240
244
  built << child
241
245
  end
242
- setters << [:"#{rule.to}=", built]
246
+ setters << [rule.kv_setter, built]
243
247
  children.concat(built)
244
248
  else
245
249
  return nil unless v.is_a?(::Hash)
@@ -249,7 +253,7 @@ module Lutaml
249
253
  options)
250
254
  return nil if child.nil?
251
255
 
252
- setters << [:"#{rule.to}=", child]
256
+ setters << [rule.kv_setter, child]
253
257
  children << child
254
258
  end
255
259
  end
@@ -316,6 +316,16 @@ module Lutaml
316
316
  @unresolved_type || @type
317
317
  end
318
318
 
319
+ def reference_type?
320
+ (@reference_type ||= unresolved_type == Lutaml::Model::Type::Reference)
321
+ end
322
+
323
+ def value_map_boolean?
324
+ (@value_map_boolean ||=
325
+ type == Lutaml::Model::Type::Boolean ||
326
+ unresolved_type == Lutaml::Model::Type::Boolean)
327
+ end
328
+
319
329
  def polymorphic?
320
330
  @options[:polymorphic_class]
321
331
  end
@@ -438,8 +448,7 @@ module Lutaml
438
448
 
439
449
  option = value_map[key]
440
450
  if (option.is_a?(TrueClass) || option.is_a?(FalseClass)) &&
441
- (type == Lutaml::Model::Type::Boolean ||
442
- unresolved_type == Lutaml::Model::Type::Boolean)
451
+ value_map_boolean?
443
452
  return option
444
453
  end
445
454
 
@@ -507,7 +516,8 @@ module Lutaml
507
516
  return resolved_type.new(value) if value.is_a?(::Hash) && !hash_type?
508
517
 
509
518
  # Special handling for Reference types - pass the metadata
510
- if unresolved_type == Lutaml::Model::Type::Reference
519
+ # (the comparison is attribute-constant; memoize the verdict)
520
+ if reference_type?
511
521
  return resolved_type.cast_with_metadata(value,
512
522
  @options[:ref_model_class], @options[:ref_key_attribute])
513
523
  end
@@ -566,10 +576,15 @@ instance_object = nil)
566
576
  end
567
577
 
568
578
  def default_value(register, instance_object = nil)
569
- if delegate
570
- type(register).attributes(register)[to].default(register,
571
- instance_object)
572
- elsif options[:default].is_a?(Proc)
579
+ # The kind is fixed at definition; the common case (no default
580
+ # at all) paid three option checks per absent attribute per
581
+ # instantiation before the memo.
582
+ case (@default_kind ||= _default_kind)
583
+ when :none
584
+ Lutaml::Model::UninitializedClass.instance
585
+ when :static
586
+ options[:default]
587
+ when :proc
573
588
  if instance_object
574
589
  ::Lutaml::Model::Attribute.evaluating_default do
575
590
  instance_object.instance_exec(&options[:default])
@@ -577,13 +592,20 @@ instance_object = nil)
577
592
  else
578
593
  options[:default].call
579
594
  end
580
- elsif options.key?(:default)
581
- options[:default]
582
- else
583
- Lutaml::Model::UninitializedClass.instance
595
+ else # :delegate
596
+ type(register).attributes(register)[to].default(register,
597
+ instance_object)
584
598
  end
585
599
  end
586
600
 
601
+ def _default_kind
602
+ return :delegate if delegate
603
+ return :proc if options[:default].is_a?(Proc)
604
+ return :static if options.key?(:default)
605
+
606
+ :none
607
+ end
608
+
587
609
  # Marks the dynamic extent in which a default proc runs against a
588
610
  # model instance, so Serialize#method_missing can fall back to the
589
611
  # model class for methods the instance does not define (0.7.x
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Lutaml
4
4
  module Model
5
- VERSION = "0.8.66"
5
+ VERSION = "0.8.68"
6
6
  end
7
7
  end
@@ -66,14 +66,14 @@ module Lutaml
66
66
  def attributes_kwargs(plan, value, node = nil)
67
67
  kwargs = {}
68
68
  sole_claimants = sole_claimant_names(plan)
69
- plan[:attr_rows].each do |rule, attr|
70
- v = value.attribute(rule.name.to_s)
71
- if v.nil? && node && sole_claimants.include?(rule.name.to_s)
69
+ plan_attr_rows(plan).each do |rule, attr, name|
70
+ v = value.attribute(name)
71
+ if v.nil? && node && sole_claimants.include?(name)
72
72
  # #754/#790 parity: a sole-claimant attribute rule binds
73
73
  # any qualification (the interpretive matcher's lenient
74
74
  # recovery). The walk captures exact (URI, local) matches
75
75
  # only, so the qualified spelling is read off the node.
76
- v = lenient_node_attribute(node, rule.name.to_s)
76
+ v = lenient_node_attribute(node, name)
77
77
  end
78
78
  next if v.nil?
79
79
 
@@ -86,6 +86,15 @@ module Lutaml
86
86
  kwargs
87
87
  end
88
88
 
89
+ # Wire-name strings are plan-frozen; materializing them per
90
+ # hydration allocated two or three strings per attribute row
91
+ # per instance.
92
+ def plan_attr_rows(plan)
93
+ plan[:attr_row_names] ||= plan[:attr_rows].map do |rule, attr|
94
+ [rule, attr, rule.name.to_s]
95
+ end
96
+ end
97
+
89
98
  # Returns [kwargs, hydrated_child_instances, delegate_values]
90
99
  # — child instances come back so the caller can decorate
91
100
  # parent/root links once the parent exists; delegate values
@@ -364,10 +373,11 @@ module Lutaml
364
373
  # names eligible for #754/#790 lenient binding (multi-claimant
365
374
  # names stay exact; per #841 leniency is sole-claimant-only).
366
375
  def sole_claimant_names(plan)
367
- plan[:attr_rows].map { |rule, _attr| rule.name.to_s }
368
- .tally
369
- .select { |_n, c| c == 1 }
370
- .keys
376
+ # Frozen with the plan: recomputing the tally per hydration
377
+ # showed in the #876-era profile as Enumerable#find/tally churn.
378
+ plan[:sole_claimants] ||= plan[:attr_rows]
379
+ .map { |rule, _attr| rule.name.to_s }.tally
380
+ .select { |_n, c| c == 1 }.keys
371
381
  end
372
382
 
373
383
  # Local-name attribute lookup on the source node, qualified
@@ -391,9 +401,12 @@ module Lutaml
391
401
  want_tags = !row_tags.nil?
392
402
  value.count.times do |i|
393
403
  child = value.at(i)
394
- next if child.name.nil? # content runs, read separately
404
+ # One FFI crossing per child: the name was read twice here
405
+ # (nil check + bucket key), doubling the hottest accessor.
406
+ name = child.name
407
+ next if name.nil? # content runs, read separately
395
408
 
396
- (grouped[child.name] ||= []) << child
409
+ (grouped[name] ||= []) << child
397
410
  if want_tags && child.type_tag != 0
398
411
  (tagged[child.type_tag] ||= []) << child
399
412
  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.8.66
4
+ version: 0.8.68
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-09-26 00:00:00.000000000 Z
11
+ date: 2026-09-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: base64