lutaml-model 0.6.5 → 0.6.6

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 07d4f2ac62702a8ac27070d4d3246e59c367937cf50b649979ecb3c525f9b60e
4
- data.tar.gz: 31adf8c870b4196ea69a065559ebbb5c2f35719aba3256597d9e24474bcbfa57
3
+ metadata.gz: 5097f4bf4d2b95fe089f40da159330218a69558acc9e3401784f12fc566bae00
4
+ data.tar.gz: 45a29395158a5362446677cfa7340b2e739a715ef7639c934f9a93a3e2b166cb
5
5
  SHA512:
6
- metadata.gz: 5ef7e89a66c74f214202d44a5b8a55d7c2aedde5129e2723f36128e8651a019cd578df6d24c375b2f760f81cd9dcd2d85ed6ae5f8972345467c13466da71c527
7
- data.tar.gz: 74aff7dee7711d4c9ec88730332b91f15ba530b92946b50b1cb218157856c1c31e3c6d9bdf292b8d9ab2832586c2ee61ae29d49a8254fecbd44cc92510f6667e
6
+ metadata.gz: 7bccace8ffeabe88a60b06e610e0b168772981d4a2b6db956f9b70f295f5522c909e0f0001fed6396fdbd44169d9a191b6b5de2d94cf6a2135bbd6a226dd7669
7
+ data.tar.gz: 19ab03f407b11348eaf01f37db05151e82de56c0c9bcac1fb2e76f27597b7eea450e6d600216be5260ac04ccbe3111a858409a7a9ed33dc559397a6b885ad8fe
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)
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Lutaml
4
4
  module Model
5
- VERSION = "0.6.5"
5
+ VERSION = "0.6.6"
6
6
  end
7
7
  end
@@ -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
metadata CHANGED
@@ -1,7 +1,7 @@
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.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.