Module: Lutaml::Model::Serialize::ClassMethods

Defined in:
lib/lutaml/model/serialize.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#attributesObject

Returns the value of attribute attributes.



28
29
30
# File 'lib/lutaml/model/serialize.rb', line 28

def attributes
  @attributes
end

#mappingsObject

Returns the value of attribute mappings.



28
29
30
# File 'lib/lutaml/model/serialize.rb', line 28

def mappings
  @mappings
end

Instance Method Details

#add_custom_handling_methods_to_model(klass) ⇒ Object



55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/lutaml/model/serialize.rb', line 55

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

#add_enum_getter_if_not_defined(klass, enum_name, collection) ⇒ Object



155
156
157
158
159
160
161
162
163
164
165
# File 'lib/lutaml/model/serialize.rb', line 155

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

#add_enum_methods_to_model(klass, enum_name, values, collection: false) ⇒ Object



108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
# File 'lib/lutaml/model/serialize.rb', line 108

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
                    []
                  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

#add_enum_setter_if_not_defined(klass, enum_name, _values, collection) ⇒ Object



167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
# File 'lib/lutaml/model/serialize.rb', line 167

def add_enum_setter_if_not_defined(klass, enum_name, _values, collection)
  Utils.add_method_if_not_defined(klass, "#{enum_name}=") do |value|
    value = [value] unless 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

#apply_child_mappings(hash, child_mappings) ⇒ Object



316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
# File 'lib/lutaml/model/serialize.rb', line 316

def apply_child_mappings(hash, child_mappings)
  return hash unless child_mappings

  hash.map do |key, value|
    child_mappings.to_h do |attr_name, path|
      attr_value = if path == :key
                     key
                   elsif path == :value
                     value
                   else
                     path = [path] unless path.is_a?(Array)
                     value.dig(*path.map(&:to_s))
                   end

      [attr_name, attr_value]
    end
  end
end

#apply_hash_mapping(doc, instance, format, _options = {}) ⇒ Object



446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
# File 'lib/lutaml/model/serialize.rb', line 446

def apply_hash_mapping(doc, instance, format, _options = {})
  mappings = mappings_for(format).mappings
  mappings.each do |rule|
    raise "Attribute '#{rule.to}' not found in #{self}" unless valid_rule?(rule)

    attr = attribute_for_rule(rule)

    names = rule.multiple_mappings? ? rule.name : [rule.name]

    value = names.collect do |rule_name|
      if doc.key?(rule_name.to_s)
        doc[rule_name.to_s]
      elsif doc.key?(rule_name.to_sym)
        doc[rule_name.to_sym]
      else
        attr&.default
      end
    end.compact.first

    if rule.using_custom_methods?
      if Utils.present?(value)
        value = new.send(rule.custom_methods[:from], instance, value)
      end

      next
    end

    value = apply_child_mappings(value, rule.child_mappings)
    value = attr.cast(value, format)

    rule.deserialize(instance, value, attributes, self)
  end

  instance
end

#apply_mappings(doc, format, options = {}) ⇒ Object



380
381
382
383
384
385
386
# File 'lib/lutaml/model/serialize.rb', line 380

def apply_mappings(doc, format, options = {})
  instance = options[:instance] || model.new
  return instance if Utils.blank?(doc)
  return apply_xml_mapping(doc, instance, options) if format == :xml

  apply_hash_mapping(doc, instance, format, options)
end

#apply_xml_mapping(doc, instance, options = {}) ⇒ Object



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
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
# File 'lib/lutaml/model/serialize.rb', line 388

def apply_xml_mapping(doc, instance, options = {})
  instance.encoding = options[:encoding]
  return instance unless doc

  if options[:default_namespace].nil?
    options[:default_namespace] =
      mappings_for(:xml)&.namespace_uri
  end
  mappings = mappings_for(:xml).mappings

  if doc.is_a?(Array)
    raise "May be `collection: true` is missing for #{self} in #{options[:caller_class]}"
  end

  if instance.respond_to?(:ordered=) && doc.is_a?(Lutaml::Model::MappingHash)
    instance.element_order = doc.item_order
    instance.ordered = mappings_for(:xml).ordered? || options[:ordered]
    instance.mixed = mappings_for(:xml).mixed_content? || options[:mixed_content]
  end

  if doc["__schema_location"]
    instance.schema_location = Lutaml::Model::SchemaLocation.new(
      schema_location: doc["__schema_location"][:schema_location],
      prefix: doc["__schema_location"][:prefix],
      namespace: doc["__schema_location"][:namespace],
    )
  end

  defaults_used = []

  mappings.each do |rule|
    raise "Attribute '#{rule.to}' not found in #{self}" unless valid_rule?(rule)

    attr = attribute_for_rule(rule)

    namespaced_names = rule.namespaced_names(options[:default_namespace])

    value = if rule.raw_mapping?
              doc.node.inner_xml
            elsif rule.content_mapping?
              doc[rule.content_key]
            elsif key = (namespaced_names & doc.keys).first
              doc[key]
            else
              defaults_used << rule.to
              attr&.default || rule.to_value_for(instance)
            end
    value = normalize_xml_value(value, rule, attr, options)
    rule.deserialize(instance, value, attributes, self)
  end

  defaults_used.each do |attribute_name|
    instance.using_default_for(attribute_name)
  end

  instance
end

#attribute(name, type, options = {}) ⇒ Object

Define an attribute for the model



85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
# File 'lib/lutaml/model/serialize.rb', line 85

def attribute(name, type, options = {})
  attr = Attribute.new(name, type, options)
  attributes[name] = attr

  if attr.enum?
    add_enum_methods_to_model(
      model,
      name,
      options[:values],
      collection: options[:collection],
    )
  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))
    end
  end
end

#attribute_for_child(child_name, format) ⇒ Object



374
375
376
377
378
# File 'lib/lutaml/model/serialize.rb', line 374

def attribute_for_child(child_name, format)
  mapping_rule = mappings_for(format).find_by_name(child_name)

  attribute_for_rule(mapping_rule) if mapping_rule
end

#attribute_for_rule(rule) ⇒ Object



368
369
370
371
372
# File 'lib/lutaml/model/serialize.rb', line 368

def attribute_for_rule(rule)
  return attributes[rule.to] unless rule.delegate

  attributes[rule.delegate].type.attributes[rule.to]
end

#cast(value) ⇒ Object



80
81
82
# File 'lib/lutaml/model/serialize.rb', line 80

def cast(value)
  value
end

#cast_value?(attr, rule) ⇒ Boolean

Returns:

  • (Boolean)


507
508
509
510
511
512
# File 'lib/lutaml/model/serialize.rb', line 507

def cast_value?(attr, rule)
  attr &&
    !rule.raw_mapping? &&
    !rule.content_mapping? &&
    !rule.custom_methods[:from]
end

#default_mappings(format) ⇒ Object



301
302
303
304
305
306
307
308
309
310
311
312
313
314
# File 'lib/lutaml/model/serialize.rb', line 301

def default_mappings(format)
  klass = format == :xml ? XmlMapping : KeyValueMapping

  klass.new.tap do |mapping|
    attributes&.each_key do |name|
      mapping.map_element(
        name.to_s,
        to: name,
      )
    end

    mapping.root(to_s.split("::").last) if format == :xml
  end
end

#ensure_utf8(value) ⇒ Object



521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
# File 'lib/lutaml/model/serialize.rb', line 521

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

#enumsObject



183
184
185
# File 'lib/lutaml/model/serialize.rb', line 183

def enums
  attributes.select { |_, attr| attr.enum? }
end

#generate_hash_from_child_mappings(value, child_mappings) ⇒ Object



335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
# File 'lib/lutaml/model/serialize.rb', line 335

def generate_hash_from_child_mappings(value, child_mappings)
  return value unless child_mappings

  hash = {}

  value.each do |child_obj|
    map_key = nil
    map_value = {}
    child_mappings.each do |attr_name, path|
      if path == :key
        map_key = child_obj.send(attr_name)
      elsif path == :value
        map_value = child_obj.send(attr_name)
      else
        path = [path] unless path.is_a?(Array)
        path[0...-1].inject(map_value) do |acc, k|
          acc[k.to_s] ||= {}
        end.public_send(:[]=, path.last.to_s, child_obj.send(attr_name))
      end
    end

    hash[map_key] = map_value
  end

  hash
end

#handle_delegate(instance, rule, hash, format) ⇒ Object



287
288
289
290
291
292
293
294
295
# File 'lib/lutaml/model/serialize.rb', line 287

def handle_delegate(instance, rule, hash, format)
  name = rule.to
  value = instance.send(rule.delegate).send(name)
  return if value.nil? && !rule.render_nil

  attribute = instance.send(rule.delegate).class.attributes[name]
  rule_from_name = rule.multiple_mappings? ? rule.from.first.to_s : rule.from.to_s
  hash[rule_from_name] = attribute.serialize(value, format)
end

#hash_representation(instance, format, options = {}) ⇒ Object



254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
# File 'lib/lutaml/model/serialize.rb', line 254

def hash_representation(instance, format, options = {})
  only = options[:only]
  except = options[:except]
  mappings = mappings_for(format).mappings

  mappings.each_with_object({}) do |rule, hash|
    name = rule.to
    next if except&.include?(name) || (only && !only.include?(name))
    next if !rule.custom_methods[:to] && (!rule.render_default? && instance.using_default?(rule.to))

    next handle_delegate(instance, rule, hash, format) if rule.delegate

    if rule.custom_methods[:to]
      next instance.send(rule.custom_methods[:to], instance, hash)
    end

    value = instance.send(name)

    attribute = attributes[name]

    value = if rule.child_mappings
              generate_hash_from_child_mappings(value, rule.child_mappings)
            else
              attribute.serialize(value, format, options)
            end

    next if Utils.blank?(value) && !rule.render_nil

    rule_from_name = rule.multiple_mappings? ? rule.from.first.to_s : rule.from.to_s
    hash[rule_from_name] = value
  end
end

#included(base) ⇒ Object



35
36
37
38
# File 'lib/lutaml/model/serialize.rb', line 35

def included(base)
  base.extend(ClassMethods)
  base.initialize_attrs(self)
end

#inherited(subclass) ⇒ Object



30
31
32
33
# File 'lib/lutaml/model/serialize.rb', line 30

def inherited(subclass)
  super
  subclass.initialize_attrs(self)
end

#initialize_attrs(source_class) ⇒ Object



40
41
42
43
44
# File 'lib/lutaml/model/serialize.rb', line 40

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)) || {}
  instance_variable_set(:@model, self)
end

#key_value(&block) ⇒ Object



247
248
249
250
251
252
# File 'lib/lutaml/model/serialize.rb', line 247

def key_value(&block)
  Lutaml::Model::Config::KEY_VALUE_FORMATS.each do |format|
    mappings[format] ||= KeyValueMapping.new
    mappings[format].instance_eval(&block)
  end
end

#mappings_for(format) ⇒ Object



297
298
299
# File 'lib/lutaml/model/serialize.rb', line 297

def mappings_for(format)
  mappings[format] || default_mappings(format)
end

#model(klass = nil) ⇒ Object



46
47
48
49
50
51
52
53
# File 'lib/lutaml/model/serialize.rb', line 46

def model(klass = nil)
  if klass
    @model = klass
    add_custom_handling_methods_to_model(klass)
  else
    @model
  end
end

#normalize_xml_value(value, rule, attr, options = {}) ⇒ Object



482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
# File 'lib/lutaml/model/serialize.rb', line 482

def normalize_xml_value(value, rule, attr, options = {})
  value = [value].compact if attr&.collection? && !value.is_a?(Array)

  value = if value.is_a?(Array)
            value.map do |v|
              text_hash?(attr, v) ? v.text : v
            end
          elsif attr&.raw? && value
            value.node.children.map(&:to_xml).join
          elsif text_hash?(attr, value)
            value.text
          else
            value
          end

  return value unless cast_value?(attr, rule)

  options.merge(caller_class: self, mixed_content: rule.mixed_content)
  attr.cast(
    value,
    :xml,
    options,
  )
end

#text_hash?(attr, value) ⇒ Boolean

Returns:

  • (Boolean)


514
515
516
517
518
519
# File 'lib/lutaml/model/serialize.rb', line 514

def text_hash?(attr, value)
  return false unless value.is_a?(Hash)
  return value.one? && value.text? unless attr

  !(attr.type <= Serialize) && attr.type != Lutaml::Model::Type::Hash
end

#valid_rule?(rule) ⇒ Boolean

Returns:

  • (Boolean)


362
363
364
365
366
# File 'lib/lutaml/model/serialize.rb', line 362

def valid_rule?(rule)
  attribute = attribute_for_rule(rule)

  !!attribute || rule.custom_methods[:from]
end