lutaml-model 0.6.5 → 0.6.7

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: 07d4f2ac62702a8ac27070d4d3246e59c367937cf50b649979ecb3c525f9b60e
4
- data.tar.gz: 31adf8c870b4196ea69a065559ebbb5c2f35719aba3256597d9e24474bcbfa57
3
+ metadata.gz: da09d917512e0b5f82b847627920cb874b9bf4506ef568d95b45dd26aefef054
4
+ data.tar.gz: c31a188213089b94d752ba6e679ba8ac80f33c9f8e4f36f5bffddebf949b4a10
5
5
  SHA512:
6
- metadata.gz: 5ef7e89a66c74f214202d44a5b8a55d7c2aedde5129e2723f36128e8651a019cd578df6d24c375b2f760f81cd9dcd2d85ed6ae5f8972345467c13466da71c527
7
- data.tar.gz: 74aff7dee7711d4c9ec88730332b91f15ba530b92946b50b1cb218157856c1c31e3c6d9bdf292b8d9ab2832586c2ee61ae29d49a8254fecbd44cc92510f6667e
6
+ metadata.gz: 4b3f701841e4ac8d6b76f1afb7cccf1e2476200e9842ab1c5f3c664b4ec064f79831886963b0b7436ea16a8944158d7d50abe7a1e29b786e08486ad0fb2a0e10
7
+ data.tar.gz: c2c4ddb8166ccde0274acc9f38ba36a2058de7b3fc61c88ab82cc8f29cbc046d43e6844da874367720d59b4c35553a9cb5adccbfd17429a1be52a8f623f96992
@@ -11,6 +11,7 @@
11
11
  "glossarist/glossarist-ruby",
12
12
  "lutaml/oasis-etm",
13
13
  "lutaml/ali",
14
- "lutaml/reqif"
14
+ "lutaml/reqif",
15
+ "lutaml/lutaml-xsd"
15
16
  ]
16
17
  }
data/.rubocop_todo.yml CHANGED
@@ -1,6 +1,6 @@
1
1
  # This configuration was generated by
2
2
  # `rubocop --auto-gen-config`
3
- # on 2025-02-21 10:20:24 UTC using RuboCop version 1.71.2.
3
+ # on 2025-02-21 13:26:38 UTC using RuboCop version 1.71.2.
4
4
  # The point is for the user to remove these configuration records
5
5
  # one by one as the offenses are removed from the code base.
6
6
  # Note that changes in the inspected code, or installation of new
@@ -27,7 +27,7 @@ Layout/IndentationWidth:
27
27
  Exclude:
28
28
  - 'lib/lutaml/model/schema/xml_compiler.rb'
29
29
 
30
- # Offense count: 466
30
+ # Offense count: 468
31
31
  # This cop supports safe autocorrection (--autocorrect).
32
32
  # Configuration parameters: Max, AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, AllowedPatterns, SplitStrings.
33
33
  # URISchemes: http, https
data/README.adoc CHANGED
@@ -270,19 +270,17 @@ Or install it yourself as:
270
270
  gem install lutaml-model
271
271
  ----
272
272
 
273
- == Data model class
273
+ == Model
274
274
 
275
- === Definition
276
-
277
- ==== General
275
+ === General
278
276
 
279
- There are two ways to define a data model in Lutaml::Model:
277
+ There are two ways to define an information model in Lutaml::Model:
280
278
 
281
279
  * Inheriting from the `Lutaml::Model::Serializable` class
282
280
  * Including the `Lutaml::Model::Serialize` module
283
281
 
284
282
  [[define-through-inheritance]]
285
- ==== Definition through inheritance
283
+ === Definition through inheritance
286
284
 
287
285
  The simplest way to define a model is to create a class that inherits from
288
286
  `Lutaml::Model::Serializable`.
@@ -301,7 +299,7 @@ end
301
299
  ----
302
300
 
303
301
  [[define-through-inclusion]]
304
- ==== Definition through inclusion
302
+ === Definition through inclusion
305
303
 
306
304
  If the model class already has a super class that it inherits from, the model
307
305
  can be extended using the `Lutaml::Model::Serialize` module.
@@ -344,63 +342,14 @@ same class and all their attributes are equal.
344
342
  ----
345
343
 
346
344
 
345
+ == Value types
347
346
 
348
- == Defining attributes
349
-
350
- === Derived Attributes
351
-
352
- A derived attribute is computed dynamically based on an instance method instead of storing a static value. It is defined using the `method:` option.
353
-
354
- Syntax:
355
-
356
- [source,ruby]
357
- ----
358
- attribute :name_of_attribute, method: :instance_method_name
359
- ----
347
+ === General types
360
348
 
361
- .Defining methods as attributes
362
- [example]
363
- ====
364
- [source,ruby]
365
- ----
366
- class Invoice < Lutaml::Model::Serializable
367
- attribute :subtotal, :float
368
- attribute :tax, :float
369
- attribute :total, method: :total_value
370
-
371
- def total_value
372
- subtotal + tax
373
- end
374
- end
375
-
376
- i = Invoice.new(subtotal: 100.0, tax: 12.0)
377
- i.total
378
- #=> 112.0
379
-
380
- puts i.to_yaml
381
- #=> ---
382
- #=> subtotal: 100.0
383
- #=> tax: 12.0
384
- #=> total: 112.0
385
- ----
386
- ====
387
-
388
- === Supported attribute value types
389
-
390
- ==== General types
391
-
392
- Lutaml::Model supports the following attribute types, they can be
393
- referred by a string, a symbol, or their class constant.
349
+ Lutaml::Model supports the following attribute value types.
394
350
 
395
351
  Every type has a corresponding Ruby class and a serialization format type.
396
352
 
397
- Syntax:
398
-
399
- [source,ruby]
400
- ----
401
- attribute :name_of_attribute, {symbol | string | class}
402
- ----
403
-
404
353
  .Mapping between Lutaml::Model::Type classes, Ruby equivalents and serialization format types
405
354
  |===
406
355
  | Lutaml::Model::Type | Ruby class | XML | JSON | YAML | Example value
@@ -423,33 +372,7 @@ attribute :name_of_attribute, {symbol | string | class}
423
372
  |===
424
373
 
425
374
 
426
- .Defining attributes with supported types via symbol, string and class
427
- [example]
428
- ====
429
- [source,ruby]
430
- ----
431
- class Studio < Lutaml::Model::Serializable
432
- # The following are equivalent
433
- attribute :location, :string
434
- attribute :potter, "String"
435
- attribute :kiln, :string
436
- end
437
- ----
438
-
439
- [source,ruby]
440
- ----
441
- > s = Studio.new(location: 'London', potter: 'John Doe', kiln: 'Kiln 1')
442
- > # <Studio:0x0000000104ac7240 @location="London", @potter="John Doe", @kiln="Kiln 1">
443
- > s.location
444
- > # "London"
445
- > s.potter
446
- > # "John Doe"
447
- > s.kiln
448
- > # "Kiln 1"
449
- ----
450
- ====
451
-
452
- ==== Decimal type
375
+ === Decimal type
453
376
 
454
377
  WARNING: Decimal is an optional feature.
455
378
 
@@ -472,7 +395,7 @@ If the `bigdecimal` library is not loaded, usage of the `Decimal` type will
472
395
  raise a `Lutaml::Model::TypeNotSupportedError`.
473
396
 
474
397
 
475
- ==== Custom type
398
+ === Custom type
476
399
 
477
400
  A custom class can be used as an attribute type. The custom class must inherit
478
401
  from `Lutaml::Model::Type::Value` or a class that inherits from it.
@@ -491,7 +414,6 @@ set as `value`. Casts the value to the custom type.
491
414
  string). Takes the internal `value` and converts it into an output suitable for
492
415
  serialization.
493
416
 
494
-
495
417
  .Using a custom value type to normalize a postcode with minimal methods
496
418
  [example]
497
419
  ====
@@ -520,8 +442,7 @@ end
520
442
  ----
521
443
  ====
522
444
 
523
-
524
- ==== Serialization of custom types
445
+ === Serialization of custom types
525
446
 
526
447
  The serialization of custom types can be made to differ per serialization format
527
448
  by defining methods in the class definitions. This requires additional methods
@@ -608,7 +529,105 @@ part lost due to the inability of JSON to handle high-precision date-time.
608
529
  ====
609
530
 
610
531
 
611
- === Attribute as a collection
532
+ == Attributes
533
+
534
+
535
+ === Basic attributes
536
+
537
+ An attribute is the basic building block of a model. It is a named value that
538
+ stores a single piece of data (which may be one or multiple pieces of data).
539
+
540
+ An attribute only accepts the type of value defined in the attribute definition.
541
+
542
+ The attribute value type can be one of the following:
543
+
544
+ * Value (inherits from Lutaml::Model::Value)
545
+ * Model (inherits from Lutaml::Model::Serializable)
546
+
547
+ Syntax:
548
+
549
+ [source,ruby]
550
+ ----
551
+ attribute :name_of_attribute, Type
552
+ ----
553
+
554
+ Where,
555
+
556
+ `name_of_attribute`:: The defined name of the attribute.
557
+ `Type`:: The type of the attribute.
558
+
559
+ .Using the `attribute` class method to define simple attributes
560
+ [example]
561
+ ====
562
+ [source,ruby]
563
+ ----
564
+ class Studio < Lutaml::Model::Serializable
565
+ attribute :name, :string
566
+ attribute :address, :string
567
+ attribute :established, :date
568
+ end
569
+ ----
570
+
571
+ [source,ruby]
572
+ ----
573
+ s = Studio.new(name: 'Pottery Studio', address: '123 Clay St', established: Date.new(2020, 1, 1))
574
+ puts s.name
575
+ #=> "Pottery Studio"
576
+ puts s.address
577
+ #=> "123 Clay St"
578
+ puts s.established
579
+ #=> <Date: 2020-01-01>
580
+ ----
581
+ ====
582
+
583
+
584
+ An attribute with a defined value type also accepts values that are of a class that
585
+ is a subclass of the defined type.
586
+
587
+ This means that the assigned `Type` accepts polymorphic classes as long as the
588
+ assigned instance is of a class that either inherits from the declared type or
589
+ matches it.
590
+
591
+ .Using a superclass type receive child object
592
+ [example]
593
+ ====
594
+ [source,ruby]
595
+ ----
596
+ class Studio < Lutaml::Model::Serializable
597
+ attribute :name, :string
598
+ end
599
+
600
+ class CeramicStudio < Studio
601
+ attribute :clay_type, :string
602
+ end
603
+
604
+ class PotteryClass < Lutaml::Model::Serializable
605
+ attribute :studio, Studio
606
+ end
607
+ ----
608
+
609
+ [source,ruby]
610
+ ----
611
+ # This works
612
+ > s = Studio.new(name: 'Pottery Studio')
613
+ > p = PotteryClass.new(studio: s)
614
+ > p.studio
615
+ # => <Studio:0x0000000104ac7240 @name="Pottery Studio", @address=nil, @established=nil>
616
+
617
+ # A subclass of Studio is also valid
618
+ > s = CeramicStudio.new(name: 'Ceramic World', clay_type: 'Red')
619
+ > p = PotteryClass.new(studio: s)
620
+ > p.studio
621
+ # => <CeramicStudio:0x0000000104ac7240 @name="Ceramic World", @address=nil, @established=nil, @clay_type="Red">
622
+ > p.studio.name
623
+ # => "Ceramic World"
624
+ > p.studio.clay_type
625
+ # => "Red"
626
+ ----
627
+ ====
628
+
629
+
630
+ === Collection attributes
612
631
 
613
632
  Define attributes as collections (arrays or hashes) to store multiple values
614
633
  using the `collection` option.
@@ -682,61 +701,122 @@ end
682
701
  ----
683
702
  ====
684
703
 
704
+ === Derived attributes
685
705
 
686
- === Sequence within XmlMapping
706
+ A derived attribute is computed dynamically based on an instance method instead of storing a static value. It is defined using the `method:` option.
687
707
 
688
- The sequence option enforces that the defined components must appear in a specified order.
708
+ Syntax:
689
709
 
690
- NOTE: `sequence` only works within XML and only supports `map_element` mappings.
710
+ [source,ruby]
711
+ ----
712
+ attribute :name_of_attribute, method: :instance_method_name
713
+ ----
691
714
 
692
- .Using the `sequence` keyword to define a set of elements in desired order.
715
+ .Defining methods as attributes
693
716
  [example]
694
717
  ====
695
718
  [source,ruby]
696
719
  ----
697
- class Kiln < Lutaml::Model::Serializable
698
- attribute :id, :string
699
- attribute :name, :string
700
- attribute :type, :string
701
- attribute :color, :string
720
+ class Invoice < Lutaml::Model::Serializable
721
+ attribute :subtotal, :float
722
+ attribute :tax, :float
723
+ attribute :total, method: :total_value
702
724
 
703
- xml do
704
- sequence do
705
- map_element :id, to: :id
706
- map_element :name, to: :name
707
- map_element :type, to: :type
708
- map_element :color, to: :color
709
- end
725
+ def total_value
726
+ subtotal + tax
710
727
  end
711
728
  end
712
729
 
713
- class KilnCollection < Lutaml::Model::Serializable
714
- attribute :kiln, Kiln, collection: 1..2
730
+ i = Invoice.new(subtotal: 100.0, tax: 12.0)
731
+ i.total
732
+ #=> 112.0
715
733
 
716
- xml do
717
- root "collection"
718
- map_element "kiln", to: :kiln
719
- end
734
+ puts i.to_yaml
735
+ #=> ---
736
+ #=> subtotal: 100.0
737
+ #=> tax: 12.0
738
+ #=> total: 112.0
739
+ ----
740
+ ====
741
+
742
+
743
+ === Choice attributes
744
+
745
+ The `choice` directive allows specifying that elements from the specified range are included.
746
+
747
+ NOTE: Attribute-level definitions are supported. This can be used with both
748
+ `key_value` and `xml` mappings.
749
+
750
+ Syntax:
751
+
752
+ [source,ruby]
753
+ ----
754
+ choice(min: {min}, max: {max}) do
755
+ {block}
720
756
  end
721
757
  ----
722
758
 
759
+ Where,
760
+
761
+ `min`:: The minimum number of elements that must be included.
762
+ `max`:: The maximum number of elements that can be included.
763
+ `block`:: The block of elements that must be included. The block can contain
764
+ multiple `attribute` and `choice` directives.
765
+
766
+ .Using the `choice` directive to define a set of attributes with a range
767
+ [example]
768
+ ====
723
769
  [source,ruby]
724
770
  ----
725
- > parsed = Kiln.from_xml("<Kiln> <id>1</id> <name>Nick</name> <type>Hard</type> <color>Black</color> </Kiln>")
726
- > # parsed.not_to raise_error
771
+ class Studio < Lutaml::Model::Serializable
772
+ choice(min: 1, max: 3) do
773
+ choice(min: 1, max: 2) do
774
+ attribute :prefix, :string
775
+ attribute :forename, :string
776
+ end
777
+
778
+ attribute :completeName, :string
779
+ end
780
+ end
727
781
  ----
782
+
783
+ This means that the `Studio` class must have at least one and at most three
784
+ attributes.
785
+
786
+ * The first choice must have at least one and at most two attributes.
787
+ * The second attribute is the `completeName`.
788
+ * The first choice can have either the `prefix` and `forename` attributes or just the `forename` attribute.
789
+ * The last attribute `completeName` is optional.
728
790
  ====
729
791
 
730
792
 
731
- === Reusable Classes with Import
732
793
 
733
- Lutaml lets you create reusable element and attribute collections using `no_root`. These can be imported into other models using:
794
+ === Importable models for reuse
795
+
796
+ An importable model is a model that can be imported into another model using the
797
+ `import_*` directive.
798
+
799
+ Such a model is specified by setting the model's XML serialization configuration
800
+ with the `no_root` directive.
801
+
802
+ As a result, the model can be imported into another model using the following
803
+ directives:
804
+
805
+ `import_model`:: imports both attributes and mappings.
806
+
807
+ `import_model_attributes`:: imports only attributes.
808
+
809
+ `import_model_mappings`:: imports only mappings.
810
+
811
+ NOTE: This feature only works with XML for now. The import order determines how
812
+ elements and attributes are overwritten.
813
+
814
+ Models with `no_root` can only be parsed through **parent models**.
815
+ Direct calling `NoRootModel.from_xml` will raise a `NoRootMappingError`.
734
816
 
735
- - `import_model`: imports both attributes and mappings
736
- - `import_model_attributes`: imports only attributes
737
- - `import_model_mappings`: imports only mappings
817
+ Namespaces are not supported in importable models. If `namespace` is defined with
818
+ `no_root`, `NoRootNamespaceError` will raise.
738
819
 
739
- NOTE: This feature works with XML. Import order determines how elements and attributes are overwritten.
740
820
 
741
821
  [example]
742
822
  ====
@@ -794,39 +874,11 @@ end
794
874
  > parsed = GroupOfItems.from_xml(xml)
795
875
  > # Lutaml::Model::NoRootMappingError: "GroupOfItems has `no_root`, it allowed only for reusable models"
796
876
  ----
797
-
798
- NOTE: Models with `no_root` can only be parsed through **Parent Models**. Direct calling `from_xml` will raise `NoRootMappingError`.
799
- And if `namespace` is defined with `no_root`, `NoRootNamespaceError` will raise.
800
-
801
877
  ====
802
878
 
803
879
 
804
- === Choice
805
-
806
- The `choice` option ensures that elements from the specified range are included.
807
-
808
- NOTE: Attribute-level definitions are supported. This can be used with both key_value and xml mappings.
809
-
810
- .Using the `choice` option to define a set of attributes with a range.
811
- [example]
812
- ====
813
- [source,ruby]
814
- ----
815
- class Studio < Lutaml::Model::Serializable
816
- choice(min: 1, max: 3) do
817
- choice(min: 1, max: 2) do
818
- attribute :prefix, :string
819
- attribute :forename, :string
820
- end
821
-
822
- attribute :completeName, :string
823
- end
824
- end
825
- ----
826
- ====
827
-
828
880
 
829
- === Attribute value validation
881
+ === Value validation
830
882
 
831
883
  ==== General
832
884
 
@@ -1138,6 +1190,56 @@ end
1138
1190
  ----
1139
1191
  ====
1140
1192
 
1193
+
1194
+ ==== Ommiting root element
1195
+
1196
+ The root element can be omitted by using the `no_root` method.
1197
+
1198
+ When `no_root` is used, only `map_element` can be used because without a root
1199
+ element there cannot be attributes.
1200
+
1201
+ Syntax:
1202
+
1203
+ [source,ruby]
1204
+ ----
1205
+ xml do
1206
+ no_root
1207
+ end
1208
+ ----
1209
+
1210
+ [example]
1211
+ ====
1212
+ [source,ruby]
1213
+ ----
1214
+ class NameAndCode < Lutaml::Model::Serializable
1215
+ attribute :name, :string
1216
+ attribute :code, :string
1217
+
1218
+ xml do
1219
+ no_root
1220
+ map_element "code", to: :code
1221
+ map_element "name", to: :name
1222
+ end
1223
+ end
1224
+ ----
1225
+
1226
+ [source,xml]
1227
+ ----
1228
+ <name>Name</name>
1229
+ <code>ID-001</code>
1230
+ ----
1231
+
1232
+ [source,ruby]
1233
+ ----
1234
+ > parsed = NameAndCode.from_xml(xml)
1235
+ > # <NameAndCode:0x0000000107a3ca70 @code="ID-001", @name="Name">
1236
+ > parsed.to_xml
1237
+ > # <code>ID-001</code><name>Name</name>
1238
+ ----
1239
+ ====
1240
+
1241
+
1242
+
1141
1243
  [[xml-map-all]]
1142
1244
  ==== Mapping all XML content
1143
1245
 
@@ -1920,6 +2022,104 @@ end
1920
2022
  ====
1921
2023
 
1922
2024
 
2025
+
2026
+ ==== Sequence
2027
+
2028
+ The `sequence` directive specifies that the defined attributes must appear in a
2029
+ specified order in XML.
2030
+
2031
+ NOTE: Sequence only supports `map_element` mappings.
2032
+
2033
+ Syntax:
2034
+
2035
+ [source,ruby]
2036
+ ----
2037
+ xml do
2038
+ sequence do
2039
+ map_element 'xml_element_name_1', to: :name_of_attribute_1
2040
+ map_element 'xml_element_name_2', to: :name_of_attribute_2
2041
+ # Add more map_element lines as needed to establish a complete sequence
2042
+ end
2043
+ end
2044
+ ----
2045
+
2046
+ The appearance of the elements in the XML document must match the order defined
2047
+ in the `sequence` block. In this case, the `<xml_element_name_1>` element
2048
+ should appear before the `<xml_element_name_2>` element.
2049
+
2050
+ .Using the `sequence` keyword to define a set of elements in desired order.
2051
+ [example]
2052
+ ====
2053
+ [source,ruby]
2054
+ ----
2055
+ class Kiln < Lutaml::Model::Serializable
2056
+ attribute :id, :string
2057
+ attribute :name, :string
2058
+ attribute :type, :string
2059
+ attribute :color, :string
2060
+
2061
+ xml do
2062
+ sequence do
2063
+ map_element :id, to: :id
2064
+ map_element :name, to: :name
2065
+ map_element :type, to: :type
2066
+ map_element :color, to: :color
2067
+ end
2068
+ end
2069
+ end
2070
+
2071
+ class KilnCollection < Lutaml::Model::Serializable
2072
+ attribute :kiln, Kiln, collection: 1..2
2073
+
2074
+ xml do
2075
+ root "collection"
2076
+ map_element "kiln", to: :kiln
2077
+ end
2078
+ end
2079
+ ----
2080
+
2081
+ [source,xml]
2082
+ ----
2083
+ <collection>
2084
+ <kiln>
2085
+ <id>1</id>
2086
+ <name>Nick</name>
2087
+ <type>Hard</type>
2088
+ <color>Black</color>
2089
+ </kiln>
2090
+ <kiln>
2091
+ <id>2</id>
2092
+ <name>John</name>
2093
+ <type>Soft</type>
2094
+ <color>White</color>
2095
+ </kiln>
2096
+ </collection>
2097
+ ----
2098
+
2099
+ [source,ruby]
2100
+ ----
2101
+ > parsed = Kiln.from_xml(xml)
2102
+ # => [
2103
+ #<Kiln:0x0000000104ac7240 @id="1", @name="Nick", @type="Hard", @color="Black">,
2104
+ #<Kiln:0x0000000104ac7240 @id="2", @name="John", @type="Soft", @color="White">
2105
+ #]
2106
+
2107
+ > bad_xml = <<~HERE
2108
+ <collection>
2109
+ <kiln>
2110
+ <name>Nick</name>
2111
+ <id>1</id>
2112
+ <color>Black</color>
2113
+ <type>Hard</type>
2114
+ </kiln>
2115
+ </collection>
2116
+ HERE
2117
+ > parsed = Kiln.from_xml(bad_xml)
2118
+ # => Lutaml::Model::ValidationError: Element 'name' is out of order in 'kiln' element
2119
+ ----
2120
+ ====
2121
+
2122
+
1923
2123
  [[xml-schema-location]]
1924
2124
  ==== Automatic support of `xsi:schemaLocation`
1925
2125
 
@@ -160,7 +160,9 @@ module Lutaml
160
160
  # Use the default value if the value is nil
161
161
  value = default if value.nil?
162
162
 
163
- valid_value!(value) && valid_collection!(value, self) && valid_pattern!(value)
163
+ valid_value!(value) &&
164
+ valid_collection!(value, self) &&
165
+ valid_pattern!(value)
164
166
  end
165
167
 
166
168
  def validate_collection_range
@@ -182,12 +184,14 @@ module Lutaml
182
184
 
183
185
  if range.begin.negative?
184
186
  raise ArgumentError,
185
- "Invalid collection range: #{range}. Begin must be non-negative."
187
+ "Invalid collection range: #{range}. " \
188
+ "Begin must be non-negative."
186
189
  end
187
190
 
188
191
  if range.end && range.end < range.begin
189
192
  raise ArgumentError,
190
- "Invalid collection range: #{range}. End must be greater than or equal to begin."
193
+ "Invalid collection range: #{range}. " \
194
+ "End must be greater than or equal to begin."
191
195
  end
192
196
  end
193
197
 
@@ -243,9 +247,9 @@ module Lutaml
243
247
  return value if type <= Serialize && value.is_a?(type.model)
244
248
 
245
249
  value ||= [] if collection?
246
- if value.is_a?(Array)
247
- value.map { |v| cast(v, format, options) }
248
- elsif type <= Serialize && castable?(value, format)
250
+ return value.map { |v| cast(v, format, options) } if value.is_a?(Array)
251
+
252
+ if type <= Serialize && castable?(value, format)
249
253
  type.apply_mappings(value, format, options)
250
254
  elsif !value.nil? && !value.is_a?(type)
251
255
  type.send(:"from_#{format}", value)
@@ -266,7 +270,10 @@ module Lutaml
266
270
  end
267
271
 
268
272
  def serialize_model(value, format, options)
269
- type.as(format, value, options) if Utils.present?(value)
273
+ return unless Utils.present?(value)
274
+ return value.class.as(format, value, options) if value.is_a?(type)
275
+
276
+ type.as(format, value, options)
270
277
  end
271
278
 
272
279
  def serialize_value(value, format)
@@ -563,7 +563,8 @@ module Lutaml
563
563
  end
564
564
 
565
565
  if rule.using_custom_methods? || attr.type == Lutaml::Model::Type::Hash
566
- return children.first
566
+ return_child = attr.type == Lutaml::Model::Type::Hash || !attr.collection? if attr
567
+ return return_child ? children.first : children
567
568
  end
568
569
 
569
570
  if Utils.present?(children)
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Lutaml
4
4
  module Model
5
- VERSION = "0.6.5"
5
+ VERSION = "0.6.7"
6
6
  end
7
7
  end
@@ -43,8 +43,8 @@ module CDATA
43
43
  map_element "address", to: :address
44
44
  end
45
45
 
46
- def house_from_xml(model, node)
47
- model.house = node.children.first.text
46
+ def house_from_xml(model, nodes)
47
+ model.house = nodes.first.children.first.text
48
48
  end
49
49
 
50
50
  def house_to_xml(model, _parent, doc)
@@ -53,8 +53,8 @@ module CDATA
53
53
  end
54
54
  end
55
55
 
56
- def city_from_xml(model, node)
57
- model.city = node.children.first.text
56
+ def city_from_xml(model, nodes)
57
+ model.city = nodes.first.children.first.text
58
58
  end
59
59
 
60
60
  def city_to_xml(model, _parent, doc)
@@ -117,8 +117,8 @@ module CDATA
117
117
 
118
118
  def child_from_xml(model, value)
119
119
  model.child_mapper ||= CustomModelChild.new
120
- model.child_mapper.street = value.find_child_by_name("street").text
121
- model.child_mapper.city = value.find_child_by_name("city").text
120
+ model.child_mapper.street = value.first.find_child_by_name("street").text
121
+ model.child_mapper.city = value.first.find_child_by_name("city").text
122
122
  end
123
123
  end
124
124
 
@@ -46,8 +46,8 @@ module CollectionTests
46
46
  map_element "address", to: :address
47
47
  end
48
48
 
49
- def city_from_xml(model, node)
50
- model.city = node.text
49
+ def city_from_xml(model, nodes)
50
+ model.city = nodes.first.text
51
51
  end
52
52
 
53
53
  def city_to_xml(model, parent, doc)
@@ -95,10 +95,10 @@ class CustomModelParentMapper < Lutaml::Model::Serializable
95
95
  doc.add_element(parent, child_el)
96
96
  end
97
97
 
98
- def child_from_xml(model, value)
98
+ def child_from_xml(model, values)
99
99
  model.child_mapper ||= CustomModelChild.new
100
- model.child_mapper.street = value.find_child_by_name("street").text
101
- model.child_mapper.city = value.find_child_by_name("city").text
100
+ model.child_mapper.street = values.first.find_child_by_name("street").text
101
+ model.child_mapper.city = values.first.find_child_by_name("city").text
102
102
  end
103
103
  end
104
104
 
@@ -67,8 +67,8 @@ class CustomSerialization < Lutaml::Model::Serializable
67
67
  doc.add_element(parent, el)
68
68
  end
69
69
 
70
- def name_from_xml(model, value)
71
- model.full_name = value.text.sub(/^XML Masterpiece: /, "")
70
+ def name_from_xml(model, values)
71
+ model.full_name = values.first.text.sub(/^XML Masterpiece: /, "")
72
72
  end
73
73
 
74
74
  def size_to_xml(model, parent, doc)
@@ -85,8 +85,8 @@ class CustomSerialization < Lutaml::Model::Serializable
85
85
  doc.add_element(parent, color_element)
86
86
  end
87
87
 
88
- def color_from_xml(model, value)
89
- model.color = value.text.downcase
88
+ def color_from_xml(model, values)
89
+ model.color = values.first.text.downcase
90
90
  end
91
91
 
92
92
  def description_to_xml(model, parent, doc)
@@ -118,8 +118,8 @@ class GrammarInfo < Lutaml::Model::Serializable
118
118
  doc["part_of_speech"] = model.part_of_speech
119
119
  end
120
120
 
121
- def part_of_speech_from_xml(model, node)
122
- model.part_of_speech = node.text
121
+ def part_of_speech_from_xml(model, nodes)
122
+ model.part_of_speech = nodes.first.text
123
123
  end
124
124
 
125
125
  def part_of_speech_to_xml(model, parent, doc)
@@ -50,6 +50,71 @@ module InheritanceSpec
50
50
  root "child"
51
51
  end
52
52
  end
53
+
54
+ class Reference < Lutaml::Model::Serializable
55
+ end
56
+
57
+ class FirstRefMapper < Reference
58
+ attribute :name, :string
59
+ attribute :id, :string
60
+
61
+ key_value do
62
+ map "name", to: :name
63
+ map "id", to: :id
64
+ end
65
+ end
66
+
67
+ class SecondRefMapper < Reference
68
+ attribute :name, :string
69
+ attribute :desc, :string
70
+
71
+ key_value do
72
+ map "name", to: :name
73
+ map "desc", to: :desc
74
+ end
75
+ end
76
+
77
+ class FirstRef
78
+ attr_accessor :name, :id
79
+
80
+ def initialize(id:, name:)
81
+ @id = id
82
+ @name = name
83
+ end
84
+ end
85
+
86
+ class SecondRef
87
+ attr_accessor :name, :desc
88
+
89
+ def initialize(desc:, name:)
90
+ @desc = desc
91
+ @name = name
92
+ end
93
+ end
94
+
95
+ class FirstRefMapperWithCustomModel < Reference
96
+ model FirstRef
97
+
98
+ attribute :name, :string
99
+ attribute :id, :string
100
+
101
+ key_value do
102
+ map "name", to: :name
103
+ map "id", to: :id
104
+ end
105
+ end
106
+
107
+ class SecondRefMapperWithCustomModel < Reference
108
+ model SecondRef
109
+
110
+ attribute :name, :string
111
+ attribute :desc, :string
112
+
113
+ key_value do
114
+ map "name", to: :name
115
+ map "desc", to: :desc
116
+ end
117
+ end
53
118
  end
54
119
 
55
120
  RSpec.describe "Inheritance" do
@@ -114,4 +179,88 @@ RSpec.describe "Inheritance" do
114
179
  expect(parsed.to_xml).to eq(xml)
115
180
  end
116
181
  end
182
+
183
+ context "when parent class is given in type" do
184
+ before do
185
+ test_class = Class.new(Lutaml::Model::Serializable) do
186
+ attribute :klass, InheritanceSpec::Reference
187
+
188
+ key_value do
189
+ map "klass", to: :klass
190
+ end
191
+ end
192
+
193
+ stub_const("TestClass", test_class)
194
+ end
195
+
196
+ context "without custom models" do
197
+ let(:first_ref_mapper) do
198
+ InheritanceSpec::FirstRefMapper.new(
199
+ name: "first_mapper",
200
+ id: "one",
201
+ )
202
+ end
203
+
204
+ let(:first_ref_mapper_yaml) do
205
+ <<~YAML
206
+ ---
207
+ klass:
208
+ name: first_mapper
209
+ id: one
210
+ YAML
211
+ end
212
+
213
+ let(:second_ref_mapper) do
214
+ InheritanceSpec::SecondRefMapper.new(
215
+ name: "second_mapper",
216
+ desc: "second mapper",
217
+ )
218
+ end
219
+
220
+ let(:second_ref_mapper_yaml) do
221
+ <<~YAML
222
+ ---
223
+ klass:
224
+ name: second_mapper
225
+ desc: second mapper
226
+ YAML
227
+ end
228
+
229
+ it "outputs correct yaml for first_ref_mapper class" do
230
+ expect(TestClass.new(klass: first_ref_mapper).to_yaml)
231
+ .to eq(first_ref_mapper_yaml)
232
+ end
233
+
234
+ it "outputs correct yaml for second_ref_mapper class" do
235
+ expect(TestClass.new(klass: second_ref_mapper).to_yaml)
236
+ .to eq(second_ref_mapper_yaml)
237
+ end
238
+ end
239
+
240
+ context "when not using custom models" do
241
+ let(:first_ref) do
242
+ InheritanceSpec::FirstRef.new(
243
+ name: "first",
244
+ id: "one",
245
+ )
246
+ end
247
+
248
+ let(:second_ref) do
249
+ InheritanceSpec::SecondRef.new(
250
+ name: "second",
251
+ desc: "second",
252
+ )
253
+ end
254
+
255
+ it "outputs correct yaml for first_ref class" do
256
+ expect { TestClass.new(klass: first_ref).to_yaml }
257
+ .to raise_error(Lutaml::Model::IncorrectModelError)
258
+ end
259
+
260
+ it "outputs correct yaml for second_ref class" do
261
+ expect { TestClass.new(klass: second_ref).to_yaml }
262
+ .to raise_error(Lutaml::Model::IncorrectModelError)
263
+ end
264
+ end
265
+ end
117
266
  end
@@ -105,8 +105,8 @@ module MultipleMapping
105
105
  doc.add_element(parent, el)
106
106
  end
107
107
 
108
- def name_from_xml(model, value)
109
- model.full_name = value.text.sub(/^XML Model: /, "")
108
+ def name_from_xml(model, values)
109
+ model.full_name = values.first.text.sub(/^XML Model: /, "")
110
110
  end
111
111
 
112
112
  def color_to_xml(model, parent, doc)
@@ -115,8 +115,8 @@ module MultipleMapping
115
115
  doc.add_element(parent, el)
116
116
  end
117
117
 
118
- def color_from_xml(model, value)
119
- model.color = value.text.downcase
118
+ def color_from_xml(model, values)
119
+ model.color = values.first.text.downcase
120
120
  end
121
121
 
122
122
  def size_to_xml(model, parent, doc)
@@ -125,8 +125,8 @@ module MultipleMapping
125
125
  doc.add_element(parent, el)
126
126
  end
127
127
 
128
- def size_from_xml(model, value)
129
- model.size = (value.text.to_i || 0) - 10
128
+ def size_from_xml(model, values)
129
+ model.size = (values.first.text.to_i || 0) - 10
130
130
  end
131
131
 
132
132
  def desc_to_xml(model, parent, doc)
@@ -135,8 +135,8 @@ module MultipleMapping
135
135
  doc.add_element(parent, el)
136
136
  end
137
137
 
138
- def desc_from_xml(model, value)
139
- model.description = value.text.sub(/^XML Description: /, "")
138
+ def desc_from_xml(model, values)
139
+ model.description = values.first.text.sub(/^XML Description: /, "")
140
140
  end
141
141
  end
142
142
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lutaml-model
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.5
4
+ version: 0.6.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-02-21 00:00:00.000000000 Z
11
+ date: 2025-02-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: base64