module Lutaml::Model::Serialize::ClassMethods
def add_custom_handling_methods_to_model(klass)
def add_custom_handling_methods_to_model(klass) Utils.add_boolean_accessor_if_not_defined(klass, :ordered) Utils.add_boolean_accessor_if_not_defined(klass, :mixed) Utils.add_accessor_if_not_defined(klass, :element_order) Utils.add_accessor_if_not_defined(klass, :encoding) 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, :using_default?) do |attribute_name| @using_default ||= {} !!@using_default[attribute_name] end end
def add_enum_getter_if_not_defined(klass, enum_name, collection)
def add_enum_getter_if_not_defined(klass, enum_name, collection) Utils.add_method_if_not_defined(klass, enum_name) do i = instance_variable_get(:"@#{enum_name}") || [] if !collection && i.is_a?(Array) i.first else i.uniq end end end
def add_enum_methods_to_model(klass, enum_name, values, collection: false)
def add_enum_methods_to_model(klass, enum_name, values, collection: false) add_enum_getter_if_not_defined(klass, enum_name, collection) add_enum_setter_if_not_defined(klass, enum_name, values, collection) return unless values.all?(::String) values.each do |value| Utils.add_method_if_not_defined(klass, "#{value}?") do curr_value = public_send(:"#{enum_name}") if collection curr_value.include?(value) else curr_value == value end end Utils.add_method_if_not_defined(klass, value.to_s) do public_send(:"#{value}?") end Utils.add_method_if_not_defined(klass, "#{value}=") do |val| value_set_for(enum_name) enum_vals = public_send(:"#{enum_name}") enum_vals = if !!val if collection enum_vals << value else [value] end elsif collection enum_vals.delete(value) enum_vals else instance_variable_get(:"@#{enum_name}") - [value] end instance_variable_set(:"@#{enum_name}", enum_vals) end Utils.add_method_if_not_defined(klass, "#{value}!") do public_send(:"#{value}=", true) end end end
def add_enum_setter_if_not_defined(klass, enum_name, _values, collection)
def add_enum_setter_if_not_defined(klass, enum_name, _values, collection) Utils.add_method_if_not_defined(klass, "#{enum_name}=") do |value| value = [] if value.nil? value = [value] if !value.is_a?(Array) value_set_for(enum_name) if collection curr_value = public_send(:"#{enum_name}") instance_variable_set(:"@#{enum_name}", curr_value + value) else instance_variable_set(:"@#{enum_name}", value) end end end
def apply_mappings(doc, format, options = {})
def apply_mappings(doc, format, options = {}) register = options[:register] || Lutaml::Model::Config.default_register instance = if options.key?(:instance) options[:instance] elsif model.include?(Lutaml::Model::Serialize) model.new({ __register: register }) else object = model.new register_accessor_methods_for(object, register) object end return instance if Utils.blank?(doc) mappings = mappings_for(format) if mappings.polymorphic_mapping return resolve_polymorphic(doc, format, mappings, instance, options) end transformer = Lutaml::Model::Config.transformer_for(format) transformer.data_to_model(self, doc, format, options) end
def apply_value_map(value, value_map, attr)
def apply_value_map(value, value_map, attr) if value.nil? value_for_option(value_map[:nil], attr) elsif Utils.empty?(value) value_for_option(value_map[:empty], attr, value) elsif Utils.uninitialized?(value) value_for_option(value_map[:omitted], attr) else value end end
def as(format, instance, options = {})
def as(format, instance, options = {}) if instance.is_a?(Array) return instance.map { |item| public_send(:"as_#{format}", item) } end unless instance.is_a?(model) msg = "argument is a '#{instance.class}' but should be a '#{model}'" raise Lutaml::Model::IncorrectModelError, msg end transformer = Lutaml::Model::Config.transformer_for(format) transformer.model_to_data(self, instance, format, options) end
def attribute(name, type, options = {})
def attribute(name, type, options = {}) if type.is_a?(Hash) options[:method_name] = type[:method] type = nil end attr = Attribute.new(name, type, options) attributes[name] = attr define_attribute_methods(attr) attr end
def attributes(register = nil)
def attributes(register = nil) ensure_imports!(register) if finalized? @attributes end
def cast(value)
def cast(value) value end
def choice(min: 1, max: 1, &block)
def choice(min: 1, max: 1, &block) @choice_attributes << Choice.new(self, min, max).tap do |c| c.instance_eval(&block) end end
def default_mappings(format)
def default_mappings(format) klass = ::Lutaml::Model::Config.mappings_class_for(format) mappings = klass.new mappings.tap do |mapping| attributes&.each_key do |name| mapping.map_element( name.to_s, to: name, ) end mapping.root(Utils.base_class_name(self)) if format == :xml end end
def define_attribute_methods(attr)
def define_attribute_methods(attr) name = attr.name if attr.enum? add_enum_methods_to_model( model, name, attr.options[:values], collection: attr.options[:collection], ) elsif attr.derived? && name != attr.method_name define_method(name) do public_send(attr.method_name) end else define_method(name) do instance_variable_get(:"@#{name}") end define_method(:"#{name}=") do |value| value_set_for(name) instance_variable_set(:"@#{name}", attr.cast_value(value, __register)) end end end
def empty_object(attr)
def empty_object(attr) return attr.build_collection if attr.collection? "" end
def ensure_choice_imports!(register_id = nil)
def ensure_choice_imports!(register_id = nil) return if @choices_imported register_id ||= Lutaml::Model::Config.default_register register = Lutaml::Model::GlobalRegister.lookup(register_id) importable_choices.each do |choice, choice_imports| choice_imports.each do |method, models| models.uniq! choice.public_send(method, register.get_class_without_register(models.shift)) until models.empty? end end @choices_imported = true end
def ensure_imports!(register = nil)
def ensure_imports!(register = nil) ensure_model_imports!(register) ensure_choice_imports!(register) end
def ensure_model_imports!(register_id = nil)
def ensure_model_imports!(register_id = nil) return if @models_imported register_id ||= Lutaml::Model::Config.default_register register = Lutaml::Model::GlobalRegister.lookup(register_id) importable_models.each do |method, models| models.uniq.each do |model| model_class = register.get_class_without_register(model) import_model_with_root_error(model_class) @model.public_send(method, model_class) end end @models_imported = true end
def ensure_utf8(value)
def ensure_utf8(value) case value when String value.encode("UTF-8", invalid: :replace, undef: :replace, replace: "") when Array value.map { |v| ensure_utf8(v) } when Hash value.transform_keys do |k| ensure_utf8(k) end.transform_values do |v| ensure_utf8(v) end else value end end
def enums
def enums attributes.select { |_, attr| attr.enum? } end
def extract_register_id(register)
def extract_register_id(register) if register register.is_a?(Lutaml::Model::Register) ? register.id : register elsif class_variable_defined?(:@@__register) class_variable_get(:@@__register) else Lutaml::Model::Config.default_register end end
def finalized?
def finalized? @finalized end
def from(format, data, options = {})
def from(format, data, options = {}) adapter = Lutaml::Model::Config.adapter_for(format) doc = adapter.parse(data, options) send("of_#{format}", doc, options) end
def handle_key_value_mappings(mapping, format)
def handle_key_value_mappings(mapping, format) @mappings[format] ||= KeyValueMapping.new @mappings[format].mappings_hash.merge!(mapping.mappings_hash) end
def import_model(model)
def import_model(model) if model.is_a?(Symbol) || model.is_a?(String) importable_models[:import_model] << model.to_sym @models_imported = false @choices_imported = false setup_trace_point return end import_model_with_root_error(model) import_model_attributes(model) import_model_mappings(model) end
def import_model_attributes(model)
def import_model_attributes(model) if model.is_a?(Symbol) || model.is_a?(String) importable_models[:import_model_attributes] << model.to_sym @models_imported = false @choices_imported = false setup_trace_point return end model.attributes.each_value do |attr| define_attribute_methods(attr) end @choice_attributes.concat(Utils.deep_dup(model.choice_attributes)) @attributes.merge!(Utils.deep_dup(model.attributes)) end
def import_model_mappings(model)
def import_model_mappings(model) if model.is_a?(Symbol) || model.is_a?(String) importable_models[:import_model_mappings] << model.to_sym @models_imported = false setup_trace_point return end import_model_with_root_error(model) Lutaml::Model::Config::AVAILABLE_FORMATS.each do |format| next unless model.mappings.key?(format) mapping = model.mappings_for(format) mapping = Utils.deep_dup(mapping) klass = ::Lutaml::Model::Config.mappings_class_for(format) @mappings[format] ||= klass.new if format == :xml @mappings[format].merge_mapping_attributes(mapping) @mappings[format].merge_mapping_elements(mapping) @mappings[format].merge_elements_sequence(mapping) else @mappings[format].mappings_hash.merge!(mapping.mappings_hash) end end end
def import_model_with_root_error(model)
def import_model_with_root_error(model) return unless model.mappings.key?(:xml) && model.root? raise Lutaml::Model::ImportModelWithRootError.new(model) end
def importable_choices
def importable_choices @importable_choices ||= MappingHash.new { |h, k| h[k] = MappingHash.new { |h1, k1| h1[k1] = [] } } end
def importable_models
def importable_models @importable_models ||= MappingHash.new { |h, k| h[k] = [] } end
def included(base)
def included(base) base.extend(ClassMethods) base.initialize_attrs(self) end
def inherited(subclass)
def inherited(subclass) super subclass.initialize_attrs(self) end
def initialize_attrs(source_class)
def initialize_attrs(source_class) @mappings = Utils.deep_dup(source_class.instance_variable_get(:@mappings)) || {} @attributes = Utils.deep_dup(source_class.instance_variable_get(:@attributes)) || {} @choice_attributes = Utils.deep_dup(source_class.instance_variable_get(:@choice_attributes)) || [] instance_variable_set(:@model, self) end
def key_value(&block)
def key_value(&block) Lutaml::Model::Config::KEY_VALUE_FORMATS.each do |format| mappings[format] ||= KeyValueMapping.new(format) mappings[format].instance_eval(&block) end end
def mappings_for(format)
def mappings_for(format) @mappings[:xml]&.ensure_mappings_imported! if @mappings&.dig(:xml)&.finalized? mappings[format] || default_mappings(format) end
def model(klass = nil)
def model(klass = nil) if klass @model = klass add_custom_handling_methods_to_model(klass) else @model end end
def of(format, doc, options = {})
def of(format, doc, options = {}) if doc.is_a?(Array) && format != :jsonl return doc.map { |item| send(:"of_#{format}", item) } end if format == :xml valid = root? || options[:from_collection] raise Lutaml::Model::NoRootMappingError.new(self) unless valid options[:encoding] = doc.encoding end options[:register] = extract_register_id(options[:register]) transformer = Lutaml::Model::Config.transformer_for(format) transformer.data_to_model(self, doc, format, options) end
def process_mapping(format, &block)
def process_mapping(format, &block) klass = ::Lutaml::Model::Config.mappings_class_for(format) mappings[format] ||= klass.new mappings[format].instance_eval(&block) if mappings[format].respond_to?(:finalize) mappings[format].finalize(self) end end
def register(name)
def register(name) name&.to_sym end
def register_accessor_methods_for(object, register)
def register_accessor_methods_for(object, register) Utils.add_singleton_method_if_not_defined(object, :__register) do @__register end Utils.add_singleton_method_if_not_defined(object, :__register=) do |value| @__register = value end object.__register = register end
def resolve_polymorphic(doc, format, mappings, instance, options = {})
def resolve_polymorphic(doc, format, mappings, instance, options = {}) polymorphic_mapping = mappings.polymorphic_mapping return instance if polymorphic_mapping.polymorphic_map.empty? klass_key = doc[polymorphic_mapping.name] klass_name = polymorphic_mapping.polymorphic_map[klass_key] klass = Object.const_get(klass_name) klass.apply_mappings(doc, format, options.merge(register: instance.__register)) end
def restrict(name, options = {})
def restrict(name, options = {}) validate_attribute_options!(name, options) attr = attributes[name] attr.options.merge!(options) attr.process_options! name end
def root?
def root? mappings_for(:xml)&.root? end
def setup_trace_point
def setup_trace_point @trace ||= TracePoint.new(:end) do |_tp| if include?(Lutaml::Model::Serialize) @finalized = true @trace.disable end end @trace.enable unless @trace.enabled? end
def to(format, instance, options = {})
def to(format, instance, options = {}) value = public_send(:"as_#{format}", instance, options) adapter = Lutaml::Model::Config.adapter_for(format) options[:mapper_class] = self if format == :xml adapter.new(value).public_send(:"to_#{format}", options) end
def validate_attribute_options!(name, options)
def validate_attribute_options!(name, options) invalid_opts = options.keys - Attribute::ALLOWED_OPTIONS return if invalid_opts.empty? raise Lutaml::Model::InvalidAttributeOptionsError.new(name, invalid_opts) end
def value_for_option(option, attr, empty_value = nil)
def value_for_option(option, attr, empty_value = nil) return nil if option == :nil return empty_value || empty_object(attr) if option == :empty Lutaml::Model::UninitializedClass.instance end