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

Includes:
Liquefiable::ClassMethods
Defined in:
lib/lutaml/model/serialize.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Liquefiable::ClassMethods

#drop_class, #drop_class_name, #register_drop_method, #register_liquid_drop_class, #validate_liquid!

Instance Attribute Details

#choice_attributesObject

Returns the value of attribute choice_attributes.



33
34
35
# File 'lib/lutaml/model/serialize.rb', line 33

def choice_attributes
  @choice_attributes
end

#mappingsObject

Returns the value of attribute mappings.



33
34
35
# File 'lib/lutaml/model/serialize.rb', line 33

def mappings
  @mappings
end

Instance Method Details

#add_custom_handling_methods_to_model(klass) ⇒ Object



71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
# File 'lib/lutaml/model/serialize.rb', line 71

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



293
294
295
296
297
298
299
300
301
302
303
# File 'lib/lutaml/model/serialize.rb', line 293

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



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
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
# File 'lib/lutaml/model/serialize.rb', line 246

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

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



305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
# File 'lib/lutaml/model/serialize.rb', line 305

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

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



410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
# File 'lib/lutaml/model/serialize.rb', line 410

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

#apply_value_map(value, value_map, attr) ⇒ Object



444
445
446
447
448
449
450
451
452
453
454
# File 'lib/lutaml/model/serialize.rb', line 444

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

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



368
369
370
371
372
373
374
375
376
377
378
379
380
# File 'lib/lutaml/model/serialize.rb', line 368

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

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

Define an attribute for the model



132
133
134
135
136
137
138
139
140
141
142
143
# File 'lib/lutaml/model/serialize.rb', line 132

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

#attributes(register = nil) ⇒ Object



52
53
54
55
# File 'lib/lutaml/model/serialize.rb', line 52

def attributes(register = nil)
  ensure_imports!(register) if finalized?
  @attributes
end

#cast(value) ⇒ Object



96
97
98
# File 'lib/lutaml/model/serialize.rb', line 96

def cast(value)
  value
end

#choice(min: 1, max: 1, &block) ⇒ Object



100
101
102
103
104
# File 'lib/lutaml/model/serialize.rb', line 100

def choice(min: 1, max: 1, &block)
  @choice_attributes << Choice.new(self, min, max).tap do |c|
    c.instance_eval(&block)
  end
end

#default_mappings(format) ⇒ Object



394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
# File 'lib/lutaml/model/serialize.rb', line 394

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

#define_attribute_methods(attr) ⇒ Object



106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
# File 'lib/lutaml/model/serialize.rb', line 106

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

#empty_object(attr) ⇒ Object



463
464
465
466
467
# File 'lib/lutaml/model/serialize.rb', line 463

def empty_object(attr)
  return attr.build_collection if attr.collection?

  ""
end

#ensure_choice_imports!(register_id = nil) ⇒ Object



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

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

#ensure_imports!(register = nil) ⇒ Object



57
58
59
60
# File 'lib/lutaml/model/serialize.rb', line 57

def ensure_imports!(register = nil)
  ensure_model_imports!(register)
  ensure_choice_imports!(register)
end

#ensure_model_imports!(register_id = nil) ⇒ Object



507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
# File 'lib/lutaml/model/serialize.rb', line 507

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

#ensure_utf8(value) ⇒ Object



469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
# File 'lib/lutaml/model/serialize.rb', line 469

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



322
323
324
# File 'lib/lutaml/model/serialize.rb', line 322

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

#extract_register_id(register) ⇒ Object



497
498
499
500
501
502
503
504
505
# File 'lib/lutaml/model/serialize.rb', line 497

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

#finalized?Boolean

Returns:

  • (Boolean)


549
550
551
# File 'lib/lutaml/model/serialize.rb', line 549

def finalized?
  @finalized
end

#from(format, data, options = {}) ⇒ Object



336
337
338
339
340
341
# File 'lib/lutaml/model/serialize.rb', line 336

def from(format, data, options = {})
  adapter = Lutaml::Model::Config.adapter_for(format)

  doc = adapter.parse(data, options)
  send("of_#{format}", doc, options)
end

#handle_key_value_mappings(mapping, format) ⇒ Object



219
220
221
222
# File 'lib/lutaml/model/serialize.rb', line 219

def handle_key_value_mappings(mapping, format)
  @mappings[format] ||= KeyValueMapping.new
  @mappings[format].mappings.concat(mapping.mappings)
end

#import_model(model) ⇒ Object



224
225
226
227
228
229
230
231
232
233
234
235
236
# File 'lib/lutaml/model/serialize.rb', line 224

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

#import_model_attributes(model) ⇒ Object



174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
# File 'lib/lutaml/model/serialize.rb', line 174

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

#import_model_mappings(model) ⇒ Object



191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
# File 'lib/lutaml/model/serialize.rb', line 191

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.concat(mapping.mappings)
    end
  end
end

#import_model_with_root_error(model) ⇒ Object



168
169
170
171
172
# File 'lib/lutaml/model/serialize.rb', line 168

def import_model_with_root_error(model)
  return unless model.mappings.key?(:xml) && model.root?

  raise Lutaml::Model::ImportModelWithRootError.new(model)
end

#importable_choicesObject



242
243
244
# File 'lib/lutaml/model/serialize.rb', line 242

def importable_choices
  @importable_choices ||= MappingHash.new { |h, k| h[k] = MappingHash.new { |h1, k1| h1[k1] = [] } }
end

#importable_modelsObject



238
239
240
# File 'lib/lutaml/model/serialize.rb', line 238

def importable_models
  @importable_models ||= MappingHash.new { |h, k| h[k] = [] }
end

#included(base) ⇒ Object



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

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

#inherited(subclass) ⇒ Object



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

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

#initialize_attrs(source_class) ⇒ Object



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

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

#key_value(&block) ⇒ Object



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

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

#mappings_for(format) ⇒ Object



389
390
391
392
# File 'lib/lutaml/model/serialize.rb', line 389

def mappings_for(format)
  @mappings[:xml]&.ensure_mappings_imported! if @mappings&.dig(:xml)&.finalized?
  mappings[format] || default_mappings(format)
end

#model(klass = nil) ⇒ Object



62
63
64
65
66
67
68
69
# File 'lib/lutaml/model/serialize.rb', line 62

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

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



343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
# File 'lib/lutaml/model/serialize.rb', line 343

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

#process_mapping(format, &block) ⇒ Object



326
327
328
329
330
331
332
333
334
# File 'lib/lutaml/model/serialize.rb', line 326

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

#register(name) ⇒ Object



160
161
162
# File 'lib/lutaml/model/serialize.rb', line 160

def register(name)
  name&.to_sym
end

#register_accessor_methods_for(object, register) ⇒ Object



487
488
489
490
491
492
493
494
495
# File 'lib/lutaml/model/serialize.rb', line 487

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

#resolve_polymorphic(doc, format, mappings, instance, options = {}) ⇒ Object



433
434
435
436
437
438
439
440
441
442
# File 'lib/lutaml/model/serialize.rb', line 433

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

#restrict(name, options = {}) ⇒ Object



145
146
147
148
149
150
151
# File 'lib/lutaml/model/serialize.rb', line 145

def restrict(name, options = {})
  validate_attribute_options!(name, options)
  attr = attributes[name]
  attr.options.merge!(options)
  attr.process_options!
  name
end

#root?Boolean

Returns:

  • (Boolean)


164
165
166
# File 'lib/lutaml/model/serialize.rb', line 164

def root?
  mappings_for(:xml)&.root?
end

#setup_trace_pointObject



539
540
541
542
543
544
545
546
547
# File 'lib/lutaml/model/serialize.rb', line 539

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

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



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

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

#validate_attribute_options!(name, options) ⇒ Object



153
154
155
156
157
158
# File 'lib/lutaml/model/serialize.rb', line 153

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

#value_for_option(option, attr, empty_value = nil) ⇒ Object



456
457
458
459
460
461
# File 'lib/lutaml/model/serialize.rb', line 456

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