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 +4 -4
- data/.github/workflows/dependent-repos.json +2 -1
- data/.rubocop_todo.yml +2 -2
- data/README.adoc +350 -150
- data/lib/lutaml/model/attribute.rb +14 -7
- data/lib/lutaml/model/serialize.rb +2 -1
- data/lib/lutaml/model/version.rb +1 -1
- data/spec/lutaml/model/cdata_spec.rb +6 -6
- data/spec/lutaml/model/collection_spec.rb +2 -2
- data/spec/lutaml/model/custom_model_spec.rb +3 -3
- data/spec/lutaml/model/custom_serialization_spec.rb +6 -6
- data/spec/lutaml/model/inheritance_spec.rb +149 -0
- data/spec/lutaml/model/multiple_mapping_spec.rb +8 -8
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: da09d917512e0b5f82b847627920cb874b9bf4506ef568d95b45dd26aefef054
|
4
|
+
data.tar.gz: c31a188213089b94d752ba6e679ba8ac80f33c9f8e4f36f5bffddebf949b4a10
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4b3f701841e4ac8d6b76f1afb7cccf1e2476200e9842ab1c5f3c664b4ec064f79831886963b0b7436ea16a8944158d7d50abe7a1e29b786e08486ad0fb2a0e10
|
7
|
+
data.tar.gz: c2c4ddb8166ccde0274acc9f38ba36a2058de7b3fc61c88ab82cc8f29cbc046d43e6844da874367720d59b4c35553a9cb5adccbfd17429a1be52a8f623f96992
|
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)
|
@@ -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
|
-
|
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)
|
data/lib/lutaml/model/version.rb
CHANGED
@@ -43,8 +43,8 @@ module CDATA
|
|
43
43
|
map_element "address", to: :address
|
44
44
|
end
|
45
45
|
|
46
|
-
def house_from_xml(model,
|
47
|
-
model.house =
|
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,
|
57
|
-
model.city =
|
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
|
|
@@ -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,
|
98
|
+
def child_from_xml(model, values)
|
99
99
|
model.child_mapper ||= CustomModelChild.new
|
100
|
-
model.child_mapper.street =
|
101
|
-
model.child_mapper.city =
|
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,
|
71
|
-
model.full_name =
|
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,
|
89
|
-
model.color =
|
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,
|
122
|
-
model.part_of_speech =
|
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,
|
109
|
-
model.full_name =
|
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,
|
119
|
-
model.color =
|
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,
|
129
|
-
model.size = (
|
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,
|
139
|
-
model.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.
|
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-
|
11
|
+
date: 2025-02-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: base64
|