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 +4 -4
- data/.rubocop_todo.yml +2 -2
- data/README.adoc +350 -150
- data/lib/lutaml/model/attribute.rb +14 -7
- data/lib/lutaml/model/version.rb +1 -1
- data/spec/lutaml/model/inheritance_spec.rb +149 -0
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5097f4bf4d2b95fe089f40da159330218a69558acc9e3401784f12fc566bae00
|
4
|
+
data.tar.gz: 45a29395158a5362446677cfa7340b2e739a715ef7639c934f9a93a3e2b166cb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
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:
|
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
|
-
==
|
273
|
+
== Model
|
274
274
|
|
275
|
-
===
|
276
|
-
|
277
|
-
==== General
|
275
|
+
=== General
|
278
276
|
|
279
|
-
There are two ways to define
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
708
|
+
Syntax:
|
689
709
|
|
690
|
-
|
710
|
+
[source,ruby]
|
711
|
+
----
|
712
|
+
attribute :name_of_attribute, method: :instance_method_name
|
713
|
+
----
|
691
714
|
|
692
|
-
.
|
715
|
+
.Defining methods as attributes
|
693
716
|
[example]
|
694
717
|
====
|
695
718
|
[source,ruby]
|
696
719
|
----
|
697
|
-
class
|
698
|
-
attribute :
|
699
|
-
attribute :
|
700
|
-
attribute :
|
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
|
-
|
704
|
-
|
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
|
-
|
714
|
-
|
730
|
+
i = Invoice.new(subtotal: 100.0, tax: 12.0)
|
731
|
+
i.total
|
732
|
+
#=> 112.0
|
715
733
|
|
716
|
-
|
717
|
-
|
718
|
-
|
719
|
-
|
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
|
-
|
726
|
-
|
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
|
-
|
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
|
-
|
736
|
-
|
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
|
-
===
|
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) &&
|
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}.
|
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}.
|
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
|
-
|
248
|
-
|
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
|
-
|
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)
|
data/lib/lutaml/model/version.rb
CHANGED
@@ -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
|