lutaml-model 0.8.68 → 0.8.69

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: '08259545535ddbd9c57aad9adfcb1fa66ebf9179fe00cbfa6be408285d1cdca5'
4
- data.tar.gz: 84c75404c371bb433b18d9cc87fab24b252b15fe8c2555544fc7b12478ff37c6
3
+ metadata.gz: f8ea7edf1c5b0a9d5e208fb6a83df7f40eba0a83e9ac78797130269e1a3cba7c
4
+ data.tar.gz: 7f2d97eac672eae74a1a192db0196305d62c45d3bd1f34855fb30a79475d6f88
5
5
  SHA512:
6
- metadata.gz: 2b730dcc7e7f555270929f4a42eb9b7a2f7e2d26a4a73fb1c75cbaa6415d2d54c0c5674ba2fad1bdba1cda106c3f625f19eed4db5d58d59334c79311c5dc46ad
7
- data.tar.gz: 4a8fa22b33898a088d8ba3e9a2b00c8bed316f73a0d0e2dcca776aa290f484bf9519952c7e2076161779faaff4aa5d16fb3be52b10e963366369bf663d75d6f4
6
+ metadata.gz: e202fb8a65018521903d42867ecd434df1f84c8568eab99f0f4e737651b6ecb51e91fc60c9b188aecc8e65f645bb10f9103a73b70617356a05c9d7ba566c8c8a
7
+ data.tar.gz: cabf97f1c9aaf7432a4831d3c5b5fec4d54306a073f9deb4e1e2cb4bf7ad97b608b52775cfef0b73294c51fd794368531c521386bb55a84ac2c1c23ad4004e56
@@ -151,8 +151,19 @@ model_class: nil)
151
151
  return value.public_send(:"to_#{format}")
152
152
  end
153
153
 
154
- # Wrap value in type and call to_#{format}
155
154
  if rule.attribute_type.is_a?(Class) && rule.attribute_type < Lutaml::Model::Type::Value
155
+ plan = serialize_plan(rule)
156
+ # Identity only under identity_fast_type's exact condition
157
+ # (no custom to AND no custom from): a custom from means the
158
+ # wrapper constructor normalizes the value, so the raw value
159
+ # must still pass through it.
160
+ if plan[:identity_type] && value.instance_of?(plan[:identity_type])
161
+ return value
162
+ end
163
+ if (to_fn = plan[:to_fn])
164
+ return to_fn.call(rule.attribute_type.new(value))
165
+ end
166
+
156
167
  wrapped_value = rule.attribute_type.new(value)
157
168
  wrapped_value.public_send(:"to_#{format}")
158
169
  else
@@ -187,9 +198,23 @@ model_class: nil)
187
198
  reference: reference_type?(rule),
188
199
  union: ::Lutaml::Model::Type::Union.rule?(rule),
189
200
  nested: nested_model?(rule),
201
+ # The custom to_<format> serializer, resolved once per rule:
202
+ # the default path is pure identity (to_<format> returns the
203
+ # wrapped .value), so values without a custom serializer skip
204
+ # the wrap-allocate-cast ceremony entirely, and custom ones
205
+ # skip the per-value registry lookup.
206
+ to_fn: custom_to_fn(rule),
190
207
  }
191
208
  end
192
209
 
210
+ def custom_to_fn(rule)
211
+ type = rule.attribute_type
212
+ return nil unless type.is_a?(::Class) && type < ::Lutaml::Model::Type::Value
213
+
214
+ ::Lutaml::Model::Type::Value
215
+ .format_type_serializer_for(@format, type)&.fetch(:to, nil)
216
+ end
217
+
193
218
  # Builtin scalars whose type registers no custom to_<format>
194
219
  # serializer serialize as the value itself — Type::Value#to_<format>'s
195
220
  # default — without the wrap-allocate-cast ceremony per field.
@@ -12,6 +12,14 @@ module Lutaml
12
12
  # options are normalized into value_map entries at MappingRule construction
13
13
  # time, so downstream code never needs to re-interpret them.
14
14
  module RenderPolicy
15
+ # Isolated holder: host transformations freeze after compile; an
16
+ # ivar cache on them raises FrozenError (learned the hard way).
17
+ SKIP_PLANS = ::Class.new do
18
+ def self.cache
19
+ @cache ||= {}.compare_by_identity
20
+ end
21
+ end.cache
22
+
15
23
  def self.derived_attribute_for?(context_obj, attr_name)
16
24
  return false unless context_obj.is_a?(Lutaml::Model::Serialize) &&
17
25
  context_obj.class.is_a?(Class) &&
@@ -52,16 +60,26 @@ module Lutaml
52
60
  # @param context_obj [Object] The context object (model_instance or delegate_obj)
53
61
  # @return [Boolean] true if should skip
54
62
  def check_skip_logic?(value, rule, context_obj)
55
- attr_name = extract_attribute_name(rule)
56
-
57
- to_map = to_value_map(rule)
58
-
59
- case value
60
- when nil
63
+ # Rule-constant parts (attribute name, :to value map) are frozen
64
+ # per rule; recomputing them per value showed in the to_yaml
65
+ # wall profile. The host transformation freezes itself after
66
+ # compile, so the cache lives in an isolated holder (TypeProbeCache
67
+ # precedent): host object -> identity-keyed rule plans.
68
+ per_host = (SKIP_PLANS[self] ||= {}.compare_by_identity)
69
+ skip_plan = (per_host[rule] ||= [extract_attribute_name(rule), to_value_map(rule)])
70
+ attr_name, to_map = skip_plan
71
+
72
+ # Plain conditionals, not `when ->(v)` literals: each lambda
73
+ # literal in a case/when allocates on every call — two per
74
+ # value here, 16% of to_yaml's allocations in the object-mode
75
+ # profile.
76
+ if value.nil?
61
77
  return to_map[:nil] == :omitted
62
- when ->(v) { Lutaml::Model::Utils.empty?(v) }
78
+ end
79
+ if Lutaml::Model::Utils.empty?(value)
63
80
  return to_map[:empty] == :omitted
64
- when ->(v) { Lutaml::Model::Utils.uninitialized?(v) }
81
+ end
82
+ if Lutaml::Model::Utils.uninitialized?(value)
65
83
  return to_map[:omitted] == :omitted || to_map[:omitted].nil?
66
84
  end
67
85
 
@@ -67,12 +67,14 @@ from: nil)
67
67
  # @param type_class [Class] the type class to look up
68
68
  # @return [Hash, nil] { to: Proc, from: Proc } or nil
69
69
  def format_type_serializer_for(format, type_class)
70
- # Hot path: memoize per [format, resolved class] — the hierarchy
71
- # walk below only runs on a miss. Format serializers register at
72
- # load time, so a hit is stable.
73
- cache = SERIALIZER_LOOKUP_CACHE
74
- key = [format, type_class]
75
- return cache[key] if cache.key?(key)
70
+ # Hot path: memoized per format -> class with NO key
71
+ # allocation — the previous [format, class] array key
72
+ # allocated on every lookup (2k arrays per 200 serializes in
73
+ # the object-mode profile). Identity-keyed nested maps; the
74
+ # hierarchy walk below only runs on a miss, and format
75
+ # serializers register at load time, so a hit is stable.
76
+ per_format = (SERIALIZER_LOOKUP_CACHE[format] ||= {}.compare_by_identity)
77
+ return per_format[type_class] if per_format.key?(type_class)
76
78
 
77
79
  klass = type_class
78
80
  found = nil
@@ -85,7 +87,7 @@ from: nil)
85
87
 
86
88
  klass = klass.superclass
87
89
  end
88
- cache[key] = found
90
+ per_format[type_class] = found
89
91
  found
90
92
  end
91
93
 
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Lutaml
4
4
  module Model
5
- VERSION = "0.8.68"
5
+ VERSION = "0.8.69"
6
6
  end
7
7
  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.68
4
+ version: 0.8.69
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.