Module: Lutaml::Model::Serialize::Initialization
- Included in:
- ClassMethods
- Defined in:
- lib/lutaml/model/serialize/initialization.rb
Overview
Handles initialization and namespace methods for Serialize::ClassMethods
Extracted from serialize.rb to improve code organization. Provides methods for class initialization and namespace handling.
Instance Method Summary collapse
-
#add_custom_handling_methods_to_model(klass) ⇒ Object
Add custom handling methods to a model class.
-
#add_format_specific_model_methods(_klass) ⇒ Object
Hook for format-specific model methods.
-
#allocate_for_deserialization(register = nil) ⇒ Object
Allocate an instance for deserialization without calling initialize.
-
#attributes(register = nil) ⇒ Hash
Get all attributes for this model.
-
#cast(value) ⇒ Object
Cast a value (pass-through implementation).
-
#choice(min: 1, max: 1, format: nil, &block) ⇒ Object
Define a choice constraint.
-
#choice_attributes(register = nil) ⇒ Array
Get all choice attributes for this model.
-
#class_attributes ⇒ Hash
Raw class-level attributes without register merging.
-
#clear_cache(register_id = nil) ⇒ Object
Clear all cached data for this model class.
-
#deep_duplicate_choice_attributes(source_class, register = nil) ⇒ Array
Deep duplicate choice attributes from a source class.
-
#ensure_format_mapping_imports!(_register = nil) ⇒ Object
Hook for format-specific mapping import resolution.
-
#ensure_imports!(register = nil) ⇒ Object
Ensure all imports are resolved.
-
#ensure_register_methods_defined(register_id) ⇒ Object
Define register-specific attribute methods on the class itself.
-
#included(base) ⇒ Object
Handle inclusion by extending with ClassMethods.
-
#inherited(subclass) ⇒ Object
Handle inheritance by copying parent's configuration.
-
#initialize_attrs(source_class) ⇒ Object
Initialize class attributes from a source class.
-
#instantiate(attrs = {}, register = nil) ⇒ Object
Fast bulk constructor for deserialization-heavy callers (native extensions building object trees bottom-up).
-
#instantiate_writers(register_id = nil) ⇒ Object
Writer method symbols for instantiate, memoized per register alongside the attribute merge (cleared with it).
-
#lutaml_default_register ⇒ Symbol?
Get or set the default register for this Model class.
-
#model(klass = nil) ⇒ Class
Get or set the model class.
-
#namespace(_ns_class = nil) ⇒ Class?
Class-level namespace getter/setter.
-
#namespace_prefix ⇒ String?
Get the default namespace prefix for this Model.
-
#namespace_uri ⇒ String?
Get the namespace URI for this Model.
-
#reference_resolvable? ⇒ Boolean
Whether instances of this class should be registered in the global Store for reference resolution.
-
#register(name) ⇒ Symbol?
Convert register parameter to symbol.
-
#register_record(register_id = nil) ⇒ Hash?
Get the register record for a specific register ID.
-
#set_register_context(register_id) ⇒ void
Set the register context for this Model class.
-
#skip_reference_registration ⇒ Object
Opt out of Store registration for this class.
Instance Method Details
#add_custom_handling_methods_to_model(klass) ⇒ Object
Add custom handling methods to a model class
246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 |
# File 'lib/lutaml/model/serialize/initialization.rb', line 246 def add_custom_handling_methods_to_model(klass) Utils.add_method_if_not_defined(klass, :using_default_for) do |attribute_name| @using_default ||= {} @using_default[attribute_name] = true end Utils.add_method_if_not_defined(klass, :value_set_for) do |attribute_name| @using_default ||= {} @using_default[attribute_name] = false end Utils.add_method_if_not_defined(klass, :values_set_for) do |attribute_names| @using_default ||= {} attribute_names.each { |name| @using_default[name] = false } end Utils.add_method_if_not_defined(klass, :using_default?) do |attribute_name| @using_default ||= {} !!@using_default[attribute_name] end # Hook for format-specific model methods (e.g., XML adds ordered, mixed, element_order) add_format_specific_model_methods(klass) end |
#add_format_specific_model_methods(_klass) ⇒ Object
Hook for format-specific model methods. XML overrides via FormatConversion prepend to add XML accessors.
279 280 281 |
# File 'lib/lutaml/model/serialize/initialization.rb', line 279 def add_format_specific_model_methods(_klass) # No-op by default end |
#allocate_for_deserialization(register = nil) ⇒ Object
Allocate an instance for deserialization without calling initialize.
Skips the expensive initialize_attributes pass (which iterates all attributes to set defaults). The XML mapping pipeline sets values directly via rule.deserialize instead. Uses Hash.new(true) as the default for @using_default so that using_default? returns true for all attributes until value_set_for is called.
347 348 349 350 351 352 |
# File 'lib/lutaml/model/serialize/initialization.rb', line 347 def allocate_for_deserialization(register = nil) instance = allocate register_id = extract_register_id(register) instance.finalize_deserialization(register_id) instance end |
#attributes(register = nil) ⇒ Hash
Get all attributes for this model
Merges class-level attributes with register-specific attributes. Memoized per register: this is on the per-element deserialization hot path, and the merged hash is stable until an attribute, import or register record mutates (those clear @merged_attributes_cache).
124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 |
# File 'lib/lutaml/model/serialize/initialization.rb', line 124 def attributes(register = nil) # Cache hit first: a filled entry can only exist after imports # were ensured for that register (the fill path below runs # ensure_imports! first), and mutations that invalidate the # merge clear @merged_attributes_cache. The lookup was walked # per rule application (751k per ISO-13849 parse). if @register_records&.any? register_id = extract_register_id(register) cached = (@merged_attributes_cache ||= {})[register_id] return cached if cached ensure_imports!(register) if finalized? # ensure_imports! may re-entrantly run clear_cache (restrict / # import resolution), which nils @merged_attributes_cache — # compute the merge first and re-materialize the memo store # at write time (#815: standalone to_xml on register-bound # models crashed with nil[] here). merged = @attributes.merge(@register_records[register_id][:attributes]) (@merged_attributes_cache ||= {})[register_id] = merged else ensure_imports!(register) if finalized? @attributes end end |
#cast(value) ⇒ Object
Cast a value (pass-through implementation)
287 288 289 |
# File 'lib/lutaml/model/serialize/initialization.rb', line 287 def cast(value) value end |
#choice(min: 1, max: 1, format: nil, &block) ⇒ Object
Define a choice constraint
314 315 316 317 318 319 |
# File 'lib/lutaml/model/serialize/initialization.rb', line 314 def choice(min: 1, max: 1, format: nil, &block) @choice_attributes << Choice.new(self, min, max, format: format).tap do |c| c.instance_eval(&block) end end |
#choice_attributes(register = nil) ⇒ Array
Get all choice attributes for this model
Merges class-level choice attributes with register-specific choice attributes.
162 163 164 165 166 167 168 169 |
# File 'lib/lutaml/model/serialize/initialization.rb', line 162 def choice_attributes(register = nil) ensure_imports!(register) if finalized? if @register_records&.any? @choice_attributes + @register_records[extract_register_id(register)][:choice_attributes] else @choice_attributes end end |
#class_attributes ⇒ Hash
Raw class-level attributes without register merging. Used by initialize_attrs during class inheritance.
152 153 154 |
# File 'lib/lutaml/model/serialize/initialization.rb', line 152 def class_attributes @attributes end |
#clear_cache(register_id = nil) ⇒ Object
Clear all cached data for this model class
Centralized caching (Phase 11.5):
- Type caches: GlobalContext.resolver
- Mapping caches: TransformationRegistry
- Transformation caches: TransformationRegistry
199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 |
# File 'lib/lutaml/model/serialize/initialization.rb', line 199 def clear_cache(register_id = nil) # Clear centralized type cache in GlobalContext.resolver if defined?(Lutaml::Model::GlobalContext) GlobalContext.resolver.clear_cache(register_id) end # Clear memoized attribute merge (see .attributes) @merged_attributes_cache = nil @instantiate_writers = nil # Clear per-Attribute type caches (stale entries from GC'd TypeContext objects) class_attributes.each_value(&:clear_type_cache) @register_records&.each_value do |record| record[:attributes]&.each_value(&:clear_type_cache) end # Clear centralized mapping and transformation caches # (Single Source of Truth - no longer uses instance variables) TransformationRegistry.instance.clear # Clear Transform cache (uses class identity as key) Transform.invalidate_for(self, register_id) # Clear import resolution guard flags so imports can be re-resolved instance_variables.each do |ivar| ivar_s = ivar.to_s remove_instance_variable(ivar) if ivar_s.start_with?("@_imports_resolved_") || ivar_s == "@_register_methods_defined" end end |
#deep_duplicate_choice_attributes(source_class, register = nil) ⇒ Array
Deep duplicate choice attributes from a source class
108 109 110 111 112 113 |
# File 'lib/lutaml/model/serialize/initialization.rb', line 108 def deep_duplicate_choice_attributes(source_class, register = nil) choice_attrs = Array(source_class.choice_attributes) choice_attrs.map do |choice_attr| choice_attr.deep_duplicate(self, register) end end |
#ensure_format_mapping_imports!(_register = nil) ⇒ Object
Hook for format-specific mapping import resolution. Override in format modules (e.g., XML prepends to resolve XML mapping imports).
187 188 189 |
# File 'lib/lutaml/model/serialize/initialization.rb', line 187 def ensure_format_mapping_imports!(_register = nil) # No-op by default; XML overrides via prepend end |
#ensure_imports!(register = nil) ⇒ Object
Ensure all imports are resolved
174 175 176 177 178 179 180 181 |
# File 'lib/lutaml/model/serialize/initialization.rb', line 174 def ensure_imports!(register = nil) ensure_model_imports!(register) ensure_choice_imports!(register) ensure_restrict_attributes!(register) # Hook for format-specific mapping import resolution. # XML overrides this to call mappings[:xml]&.ensure_mappings_imported!(register) ensure_format_mapping_imports!(register) end |
#ensure_register_methods_defined(register_id) ⇒ Object
Define register-specific attribute methods on the class itself.
Called once per (class, register) combination. Replaces per-instance singleton class allocation with class-level method definitions, preserving Ruby's inline method cache optimization.
431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 |
# File 'lib/lutaml/model/serialize/initialization.rb', line 431 def ensure_register_methods_defined(register_id) return if register_id == :default @_register_methods_defined ||= {} return if @_register_methods_defined[register_id] reg_record = register_records[register_id] return unless reg_record default_attrs = class_attributes || {} reg_record_attrs = reg_record[:attributes] || {} reg_record_attrs.each do |name, attr| next if default_attrs.key?(name) next if method_defined?(name, false) if attr.collection? define_collection_register_methods(name) else define_scalar_register_methods(name) end end @_register_methods_defined[register_id] = true end |
#included(base) ⇒ Object
Handle inclusion by extending with ClassMethods
84 85 86 87 |
# File 'lib/lutaml/model/serialize/initialization.rb', line 84 def included(base) base.extend(ClassMethods) base.initialize_attrs(self) end |
#inherited(subclass) ⇒ Object
Handle inheritance by copying parent's configuration
76 77 78 79 |
# File 'lib/lutaml/model/serialize/initialization.rb', line 76 def inherited(subclass) super subclass.initialize_attrs(self) end |
#initialize_attrs(source_class) ⇒ Object
Initialize class attributes from a source class
92 93 94 95 96 97 98 99 100 101 102 |
# File 'lib/lutaml/model/serialize/initialization.rb', line 92 def initialize_attrs(source_class) @mappings = Utils.deep_dup(source_class.mappings) || {} @attributes = Utils.deep_dup(source_class.class_attributes) || {} @choice_attributes = deep_duplicate_choice_attributes(source_class) @register_records = Utils.deep_dup( source_class.register_records, ) || ::Hash.new do |hash, key| hash[key] = { attributes: {}, choice_attributes: [] } end model(self) end |
#instantiate(attrs = {}, register = nil) ⇒ Object
Fast bulk constructor for deserialization-heavy callers (native extensions building object trees bottom-up).
Allocates without running #initialize and applies each present attribute through its compiled writer, which casts the value and marks it as explicitly set — so defaults, to_hash output, and using_default? behave exactly as if the instance had been produced by from_hash. Values may be primitives or already built instances (instances pass through casting unchanged); absent keys keep their defaults. Unknown keys raise.
369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 |
# File 'lib/lutaml/model/serialize/initialization.rb', line 369 def instantiate(attrs = {}, register = nil) instance = allocate_for_deserialization(register) register_id = instance.lutaml_register attrs_by = attributes(register_id) writers = instantiate_writers(register_id) given = 0 attrs_by.each do |name, attr| next if attr.derived? # Symbol keys hit directly (native-extension callers); # string keys fall back with one allocated name. key = if attrs.key?(name) given += 1 name elsif (str_name = name.to_s) && attrs.key?(str_name) given += 1 str_name end if key instance.public_send(writers[name], attrs[key]) else # Absent keys seed their default explicitly, mirroring # initialize_attributes: every mapped attribute ends up # set (nil when no default), never left as the # uninitialized sentinel — readers and formatters touch # absent attributes freely. default = attr.default_value(register_id, instance) value = if Lutaml::Model::Utils.uninitialized?(default) nil else attr.cast_value(default, register_id) end instance.public_send(writers[name], value) instance.using_default_for(name) end end reserved = (attrs.key?(:lutaml_register) ? 1 : 0) + (attrs.key?("lutaml_register") ? 1 : 0) if given + reserved < attrs.size known = attrs_by.keys unknown = attrs.keys.find { |k| !known.include?(k.to_sym) } raise Error, "unknown attribute '#{unknown}' for #{self}" end instance end |
#instantiate_writers(register_id = nil) ⇒ Object
Writer method symbols for instantiate, memoized per register alongside the attribute merge (cleared with it).
417 418 419 420 421 422 |
# File 'lib/lutaml/model/serialize/initialization.rb', line 417 def instantiate_writers(register_id = nil) (@instantiate_writers ||= {})[register_id] ||= attributes(register_id) .each_with_object({}) do |(name, _attr), memo| memo[name] = :"#{name}=" end end |
#lutaml_default_register ⇒ Symbol?
Get or set the default register for this Model class.
Override in subclasses to specify a preferred default register context. This allows versioned schemas (e.g., MML v2, v3) to use their own register by default when instances are created without explicit register.
69 70 71 |
# File 'lib/lutaml/model/serialize/initialization.rb', line 69 def lutaml_default_register nil end |
#model(klass = nil) ⇒ Class
Get or set the model class
234 235 236 237 238 239 240 241 |
# File 'lib/lutaml/model/serialize/initialization.rb', line 234 def model(klass = nil) if klass @model = klass add_custom_handling_methods_to_model(klass) else @model end end |
#namespace(_ns_class = nil) ⇒ Class?
Class-level namespace getter/setter.
No-op by default. When XML format is loaded, this is overridden via prepend to provide XML namespace handling.
18 19 20 |
# File 'lib/lutaml/model/serialize/initialization.rb', line 18 def namespace(_ns_class = nil) @namespace_class end |
#namespace_prefix ⇒ String?
Get the default namespace prefix for this Model
32 33 34 |
# File 'lib/lutaml/model/serialize/initialization.rb', line 32 def namespace_prefix nil end |
#namespace_uri ⇒ String?
Get the namespace URI for this Model
25 26 27 |
# File 'lib/lutaml/model/serialize/initialization.rb', line 25 def namespace_uri nil end |
#reference_resolvable? ⇒ Boolean
Whether instances of this class should be registered in the
global Store for reference resolution. Defaults to true for
backward compatibility. Use skip_reference_registration to
opt out for classes that never participate in cross-referencing.
295 296 297 298 299 |
# File 'lib/lutaml/model/serialize/initialization.rb', line 295 def reference_resolvable? return true unless instance_variable_defined?(:@skip_reference_registration) !@skip_reference_registration end |
#register(name) ⇒ Symbol?
Convert register parameter to symbol
333 334 335 |
# File 'lib/lutaml/model/serialize/initialization.rb', line 333 def register(name) name&.to_sym end |
#register_record(register_id = nil) ⇒ Hash?
Get the register record for a specific register ID
325 326 327 |
# File 'lib/lutaml/model/serialize/initialization.rb', line 325 def register_record(register_id = nil) register_records[extract_register_id(register_id)] end |
#set_register_context(register_id) ⇒ void
This method returns an undefined value.
Set the register context for this Model class.
This is called by Register#register_model to ensure the class knows its register context for proper instance initialization.
43 44 45 46 47 |
# File 'lib/lutaml/model/serialize/initialization.rb', line 43 def set_register_context(register_id) return if instance_variable_defined?(:@register) @register = register_id end |
#skip_reference_registration ⇒ Object
Opt out of Store registration for this class. Instances will not be tracked in the global Store, saving memory and registration overhead for classes that are never resolved by reference (no xml_id or similar attributes).
305 306 307 |
# File 'lib/lutaml/model/serialize/initialization.rb', line 305 def skip_reference_registration @skip_reference_registration = true end |