lutaml-model 0.6.4 → 0.6.6
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/.rubocop_todo.yml +5 -5
- data/README.adoc +350 -112
- data/lib/lutaml/model/attribute.rb +65 -30
- data/lib/lutaml/model/serialize.rb +17 -2
- data/lib/lutaml/model/version.rb +1 -1
- data/spec/lutaml/model/attribute_spec.rb +30 -1
- data/spec/lutaml/model/inheritance_spec.rb +149 -0
- data/spec/lutaml/model/serializable_spec.rb +35 -6
- data/spec/lutaml/model/xml/derived_attributes_spec.rb +78 -0
- metadata +2 -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-
|
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
|
@@ -68,7 +68,7 @@ Metrics/AbcSize:
|
|
68
68
|
Metrics/BlockLength:
|
69
69
|
Max: 46
|
70
70
|
|
71
|
-
# Offense count:
|
71
|
+
# Offense count: 47
|
72
72
|
# Configuration parameters: AllowedMethods, AllowedPatterns, Max.
|
73
73
|
Metrics/CyclomaticComplexity:
|
74
74
|
Exclude:
|
@@ -85,7 +85,7 @@ Metrics/CyclomaticComplexity:
|
|
85
85
|
- 'lib/lutaml/model/xml_adapter/ox_adapter.rb'
|
86
86
|
- 'lib/lutaml/model/xml_adapter/xml_document.rb'
|
87
87
|
|
88
|
-
# Offense count:
|
88
|
+
# Offense count: 86
|
89
89
|
# Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns.
|
90
90
|
Metrics/MethodLength:
|
91
91
|
Max: 45
|
@@ -95,7 +95,7 @@ Metrics/MethodLength:
|
|
95
95
|
Metrics/ParameterLists:
|
96
96
|
Max: 15
|
97
97
|
|
98
|
-
# Offense count:
|
98
|
+
# Offense count: 36
|
99
99
|
# Configuration parameters: AllowedMethods, AllowedPatterns, Max.
|
100
100
|
Metrics/PerceivedComplexity:
|
101
101
|
Exclude:
|
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,25 +342,14 @@ same class and all their attributes are equal.
|
|
344
342
|
----
|
345
343
|
|
346
344
|
|
345
|
+
== Value types
|
347
346
|
|
348
|
-
|
349
|
-
|
350
|
-
=== Supported attribute value types
|
347
|
+
=== General types
|
351
348
|
|
352
|
-
|
353
|
-
|
354
|
-
Lutaml::Model supports the following attribute types, they can be
|
355
|
-
referred by a string, a symbol, or their class constant.
|
349
|
+
Lutaml::Model supports the following attribute value types.
|
356
350
|
|
357
351
|
Every type has a corresponding Ruby class and a serialization format type.
|
358
352
|
|
359
|
-
Syntax:
|
360
|
-
|
361
|
-
[source,ruby]
|
362
|
-
----
|
363
|
-
attribute :name_of_attribute, {symbol | string | class}
|
364
|
-
----
|
365
|
-
|
366
353
|
.Mapping between Lutaml::Model::Type classes, Ruby equivalents and serialization format types
|
367
354
|
|===
|
368
355
|
| Lutaml::Model::Type | Ruby class | XML | JSON | YAML | Example value
|
@@ -385,33 +372,7 @@ attribute :name_of_attribute, {symbol | string | class}
|
|
385
372
|
|===
|
386
373
|
|
387
374
|
|
388
|
-
|
389
|
-
[example]
|
390
|
-
====
|
391
|
-
[source,ruby]
|
392
|
-
----
|
393
|
-
class Studio < Lutaml::Model::Serializable
|
394
|
-
# The following are equivalent
|
395
|
-
attribute :location, :string
|
396
|
-
attribute :potter, "String"
|
397
|
-
attribute :kiln, :string
|
398
|
-
end
|
399
|
-
----
|
400
|
-
|
401
|
-
[source,ruby]
|
402
|
-
----
|
403
|
-
> s = Studio.new(location: 'London', potter: 'John Doe', kiln: 'Kiln 1')
|
404
|
-
> # <Studio:0x0000000104ac7240 @location="London", @potter="John Doe", @kiln="Kiln 1">
|
405
|
-
> s.location
|
406
|
-
> # "London"
|
407
|
-
> s.potter
|
408
|
-
> # "John Doe"
|
409
|
-
> s.kiln
|
410
|
-
> # "Kiln 1"
|
411
|
-
----
|
412
|
-
====
|
413
|
-
|
414
|
-
==== Decimal type
|
375
|
+
=== Decimal type
|
415
376
|
|
416
377
|
WARNING: Decimal is an optional feature.
|
417
378
|
|
@@ -434,7 +395,7 @@ If the `bigdecimal` library is not loaded, usage of the `Decimal` type will
|
|
434
395
|
raise a `Lutaml::Model::TypeNotSupportedError`.
|
435
396
|
|
436
397
|
|
437
|
-
|
398
|
+
=== Custom type
|
438
399
|
|
439
400
|
A custom class can be used as an attribute type. The custom class must inherit
|
440
401
|
from `Lutaml::Model::Type::Value` or a class that inherits from it.
|
@@ -453,7 +414,6 @@ set as `value`. Casts the value to the custom type.
|
|
453
414
|
string). Takes the internal `value` and converts it into an output suitable for
|
454
415
|
serialization.
|
455
416
|
|
456
|
-
|
457
417
|
.Using a custom value type to normalize a postcode with minimal methods
|
458
418
|
[example]
|
459
419
|
====
|
@@ -482,8 +442,7 @@ end
|
|
482
442
|
----
|
483
443
|
====
|
484
444
|
|
485
|
-
|
486
|
-
==== Serialization of custom types
|
445
|
+
=== Serialization of custom types
|
487
446
|
|
488
447
|
The serialization of custom types can be made to differ per serialization format
|
489
448
|
by defining methods in the class definitions. This requires additional methods
|
@@ -570,7 +529,105 @@ part lost due to the inability of JSON to handle high-precision date-time.
|
|
570
529
|
====
|
571
530
|
|
572
531
|
|
573
|
-
|
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
|
574
631
|
|
575
632
|
Define attributes as collections (arrays or hashes) to store multiple values
|
576
633
|
using the `collection` option.
|
@@ -644,61 +701,122 @@ end
|
|
644
701
|
----
|
645
702
|
====
|
646
703
|
|
704
|
+
=== Derived attributes
|
647
705
|
|
648
|
-
|
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.
|
649
707
|
|
650
|
-
|
708
|
+
Syntax:
|
651
709
|
|
652
|
-
|
710
|
+
[source,ruby]
|
711
|
+
----
|
712
|
+
attribute :name_of_attribute, method: :instance_method_name
|
713
|
+
----
|
653
714
|
|
654
|
-
.
|
715
|
+
.Defining methods as attributes
|
655
716
|
[example]
|
656
717
|
====
|
657
718
|
[source,ruby]
|
658
719
|
----
|
659
|
-
class
|
660
|
-
attribute :
|
661
|
-
attribute :
|
662
|
-
attribute :
|
663
|
-
attribute :color, :string
|
720
|
+
class Invoice < Lutaml::Model::Serializable
|
721
|
+
attribute :subtotal, :float
|
722
|
+
attribute :tax, :float
|
723
|
+
attribute :total, method: :total_value
|
664
724
|
|
665
|
-
|
666
|
-
|
667
|
-
map_element :id, to: :id
|
668
|
-
map_element :name, to: :name
|
669
|
-
map_element :type, to: :type
|
670
|
-
map_element :color, to: :color
|
671
|
-
end
|
725
|
+
def total_value
|
726
|
+
subtotal + tax
|
672
727
|
end
|
673
728
|
end
|
674
729
|
|
675
|
-
|
676
|
-
|
730
|
+
i = Invoice.new(subtotal: 100.0, tax: 12.0)
|
731
|
+
i.total
|
732
|
+
#=> 112.0
|
677
733
|
|
678
|
-
|
679
|
-
|
680
|
-
|
681
|
-
|
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}
|
682
756
|
end
|
683
757
|
----
|
684
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
|
+
====
|
685
769
|
[source,ruby]
|
686
770
|
----
|
687
|
-
|
688
|
-
|
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
|
689
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.
|
690
790
|
====
|
691
791
|
|
692
792
|
|
693
|
-
=== Reusable Classes with Import
|
694
793
|
|
695
|
-
|
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.
|
696
806
|
|
697
|
-
|
698
|
-
|
699
|
-
|
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`.
|
816
|
+
|
817
|
+
Namespaces are not supported in importable models. If `namespace` is defined with
|
818
|
+
`no_root`, `NoRootNamespaceError` will raise.
|
700
819
|
|
701
|
-
NOTE: This feature works with XML. Import order determines how elements and attributes are overwritten.
|
702
820
|
|
703
821
|
[example]
|
704
822
|
====
|
@@ -756,39 +874,11 @@ end
|
|
756
874
|
> parsed = GroupOfItems.from_xml(xml)
|
757
875
|
> # Lutaml::Model::NoRootMappingError: "GroupOfItems has `no_root`, it allowed only for reusable models"
|
758
876
|
----
|
759
|
-
|
760
|
-
NOTE: Models with `no_root` can only be parsed through **Parent Models**. Direct calling `from_xml` will raise `NoRootMappingError`.
|
761
|
-
And if `namespace` is defined with `no_root`, `NoRootNamespaceError` will raise.
|
762
|
-
|
763
|
-
====
|
764
|
-
|
765
|
-
|
766
|
-
=== Choice
|
767
|
-
|
768
|
-
The `choice` option ensures that elements from the specified range are included.
|
769
|
-
|
770
|
-
NOTE: Attribute-level definitions are supported. This can be used with both key_value and xml mappings.
|
771
|
-
|
772
|
-
.Using the `choice` option to define a set of attributes with a range.
|
773
|
-
[example]
|
774
877
|
====
|
775
|
-
[source,ruby]
|
776
|
-
----
|
777
|
-
class Studio < Lutaml::Model::Serializable
|
778
|
-
choice(min: 1, max: 3) do
|
779
|
-
choice(min: 1, max: 2) do
|
780
|
-
attribute :prefix, :string
|
781
|
-
attribute :forename, :string
|
782
|
-
end
|
783
878
|
|
784
|
-
attribute :completeName, :string
|
785
|
-
end
|
786
|
-
end
|
787
|
-
----
|
788
|
-
====
|
789
879
|
|
790
880
|
|
791
|
-
===
|
881
|
+
=== Value validation
|
792
882
|
|
793
883
|
==== General
|
794
884
|
|
@@ -1100,6 +1190,56 @@ end
|
|
1100
1190
|
----
|
1101
1191
|
====
|
1102
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
|
+
|
1103
1243
|
[[xml-map-all]]
|
1104
1244
|
==== Mapping all XML content
|
1105
1245
|
|
@@ -1882,6 +2022,104 @@ end
|
|
1882
2022
|
====
|
1883
2023
|
|
1884
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
|
+
|
1885
2123
|
[[xml-schema-location]]
|
1886
2124
|
==== Automatic support of `xsi:schemaLocation`
|
1887
2125
|
|
@@ -13,23 +13,20 @@ module Lutaml
|
|
13
13
|
transform
|
14
14
|
choice
|
15
15
|
sequence
|
16
|
+
method_name
|
16
17
|
].freeze
|
17
18
|
|
18
19
|
def initialize(name, type, options = {})
|
19
20
|
@name = name
|
20
|
-
|
21
|
-
validate_type!(type)
|
22
|
-
@type = cast_type!(type)
|
23
|
-
|
24
|
-
validate_options!(options)
|
25
21
|
@options = options
|
26
22
|
|
27
|
-
|
23
|
+
validate_presence!(type, options[:method_name])
|
24
|
+
process_type!(type) if type
|
25
|
+
process_options!
|
26
|
+
end
|
28
27
|
|
29
|
-
|
30
|
-
|
31
|
-
@options[:default] = -> { [] } unless options[:default]
|
32
|
-
end
|
28
|
+
def derived?
|
29
|
+
type.nil?
|
33
30
|
end
|
34
31
|
|
35
32
|
def delegate
|
@@ -40,6 +37,10 @@ module Lutaml
|
|
40
37
|
@options[:transform] || {}
|
41
38
|
end
|
42
39
|
|
40
|
+
def method_name
|
41
|
+
@options[:method_name]
|
42
|
+
end
|
43
|
+
|
43
44
|
def cast_type!(type)
|
44
45
|
case type
|
45
46
|
when Symbol
|
@@ -159,7 +160,9 @@ module Lutaml
|
|
159
160
|
# Use the default value if the value is nil
|
160
161
|
value = default if value.nil?
|
161
162
|
|
162
|
-
valid_value!(value) &&
|
163
|
+
valid_value!(value) &&
|
164
|
+
valid_collection!(value, self) &&
|
165
|
+
valid_pattern!(value)
|
163
166
|
end
|
164
167
|
|
165
168
|
def validate_collection_range
|
@@ -181,12 +184,14 @@ module Lutaml
|
|
181
184
|
|
182
185
|
if range.begin.negative?
|
183
186
|
raise ArgumentError,
|
184
|
-
"Invalid collection range: #{range}.
|
187
|
+
"Invalid collection range: #{range}. " \
|
188
|
+
"Begin must be non-negative."
|
185
189
|
end
|
186
190
|
|
187
191
|
if range.end && range.end < range.begin
|
188
192
|
raise ArgumentError,
|
189
|
-
"Invalid collection range: #{range}.
|
193
|
+
"Invalid collection range: #{range}. " \
|
194
|
+
"End must be greater than or equal to begin."
|
190
195
|
end
|
191
196
|
end
|
192
197
|
|
@@ -231,29 +236,20 @@ module Lutaml
|
|
231
236
|
|
232
237
|
def serialize(value, format, options = {})
|
233
238
|
return if value.nil?
|
239
|
+
return value if derived?
|
240
|
+
return serialize_array(value, format, options) if value.is_a?(Array)
|
241
|
+
return serialize_model(value, format, options) if type <= Serialize
|
234
242
|
|
235
|
-
|
236
|
-
value.map do |v|
|
237
|
-
serialize(v, format, options)
|
238
|
-
end
|
239
|
-
elsif type <= Serialize
|
240
|
-
if Utils.present?(value)
|
241
|
-
type.public_send(:"as_#{format}", value, options)
|
242
|
-
end
|
243
|
-
else
|
244
|
-
# Convert to Value instance if not already
|
245
|
-
value = type.new(value) unless value.is_a?(Type::Value)
|
246
|
-
value.send(:"to_#{format}")
|
247
|
-
end
|
243
|
+
serialize_value(value, format)
|
248
244
|
end
|
249
245
|
|
250
246
|
def cast(value, format, options = {})
|
251
247
|
return value if type <= Serialize && value.is_a?(type.model)
|
252
248
|
|
253
249
|
value ||= [] if collection?
|
254
|
-
if value.is_a?(Array)
|
255
|
-
|
256
|
-
|
250
|
+
return value.map { |v| cast(v, format, options) } if value.is_a?(Array)
|
251
|
+
|
252
|
+
if type <= Serialize && castable?(value, format)
|
257
253
|
type.apply_mappings(value, format, options)
|
258
254
|
elsif !value.nil? && !value.is_a?(type)
|
259
255
|
type.send(:"from_#{format}", value)
|
@@ -269,6 +265,44 @@ module Lutaml
|
|
269
265
|
(format == :xml && value.is_a?(Lutaml::Model::XmlAdapter::XmlElement))
|
270
266
|
end
|
271
267
|
|
268
|
+
def serialize_array(value, format, options)
|
269
|
+
value.map { |v| serialize(v, format, options) }
|
270
|
+
end
|
271
|
+
|
272
|
+
def serialize_model(value, format, options)
|
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)
|
277
|
+
end
|
278
|
+
|
279
|
+
def serialize_value(value, format)
|
280
|
+
value = type.new(value) unless value.is_a?(Type::Value)
|
281
|
+
value.send(:"to_#{format}")
|
282
|
+
end
|
283
|
+
|
284
|
+
def validate_presence!(type, method_name)
|
285
|
+
return if type || method_name
|
286
|
+
|
287
|
+
raise ArgumentError, "method or type must be set for an attribute"
|
288
|
+
end
|
289
|
+
|
290
|
+
def process_type!(type)
|
291
|
+
validate_type!(type)
|
292
|
+
@type = cast_type!(type)
|
293
|
+
end
|
294
|
+
|
295
|
+
def process_options!
|
296
|
+
validate_options!(@options)
|
297
|
+
@raw = !!@options[:raw]
|
298
|
+
set_default_for_collection if collection?
|
299
|
+
end
|
300
|
+
|
301
|
+
def set_default_for_collection
|
302
|
+
validate_collection_range
|
303
|
+
@options[:default] ||= -> { [] }
|
304
|
+
end
|
305
|
+
|
272
306
|
def validate_options!(options)
|
273
307
|
if (invalid_opts = options.keys - ALLOWED_OPTIONS).any?
|
274
308
|
raise StandardError,
|
@@ -277,7 +311,8 @@ module Lutaml
|
|
277
311
|
|
278
312
|
if options.key?(:pattern) && type != Lutaml::Model::Type::String
|
279
313
|
raise StandardError,
|
280
|
-
"Invalid option `pattern` given for `#{name}`,
|
314
|
+
"Invalid option `pattern` given for `#{name}`, " \
|
315
|
+
"`pattern` is only allowed for :string type"
|
281
316
|
end
|
282
317
|
|
283
318
|
true
|
@@ -96,6 +96,11 @@ module Lutaml
|
|
96
96
|
|
97
97
|
# Define an attribute for the model
|
98
98
|
def attribute(name, type, options = {})
|
99
|
+
if type.is_a?(Hash)
|
100
|
+
options[:method_name] = type[:method]
|
101
|
+
type = nil
|
102
|
+
end
|
103
|
+
|
99
104
|
attr = Attribute.new(name, type, options)
|
100
105
|
attributes[name] = attr
|
101
106
|
|
@@ -106,6 +111,10 @@ module Lutaml
|
|
106
111
|
options[:values],
|
107
112
|
collection: options[:collection],
|
108
113
|
)
|
114
|
+
elsif attr.derived? && name != attr.method_name
|
115
|
+
define_method(name) do
|
116
|
+
public_send(attr.method_name)
|
117
|
+
end
|
109
118
|
else
|
110
119
|
define_method(name) do
|
111
120
|
instance_variable_get(:"@#{name}")
|
@@ -283,6 +292,10 @@ module Lutaml
|
|
283
292
|
end
|
284
293
|
end
|
285
294
|
|
295
|
+
def as(format, instance, options = {})
|
296
|
+
public_send(:"as_#{format}", instance, options)
|
297
|
+
end
|
298
|
+
|
286
299
|
def key_value(&block)
|
287
300
|
Lutaml::Model::Config::KEY_VALUE_FORMATS.each do |format|
|
288
301
|
mappings[format] ||= KeyValueMapping.new
|
@@ -484,8 +497,7 @@ module Lutaml
|
|
484
497
|
return instance unless doc
|
485
498
|
|
486
499
|
if options[:default_namespace].nil?
|
487
|
-
options[:default_namespace] =
|
488
|
-
mappings_for(:xml)&.namespace_uri
|
500
|
+
options[:default_namespace] = mappings_for(:xml)&.namespace_uri
|
489
501
|
end
|
490
502
|
mappings = options[:mappings] || mappings_for(:xml).mappings
|
491
503
|
|
@@ -517,6 +529,7 @@ module Lutaml
|
|
517
529
|
raise "Attribute '#{rule.to}' not found in #{self}" unless valid_rule?(rule)
|
518
530
|
|
519
531
|
attr = attribute_for_rule(rule)
|
532
|
+
next if attr&.derived?
|
520
533
|
|
521
534
|
value = if rule.raw_mapping?
|
522
535
|
doc.root.inner_xml
|
@@ -703,6 +716,8 @@ module Lutaml
|
|
703
716
|
end
|
704
717
|
|
705
718
|
self.class.attributes.each do |name, attr|
|
719
|
+
next if attr.derived?
|
720
|
+
|
706
721
|
value = if attrs.key?(name) || attrs.key?(name.to_s)
|
707
722
|
attr_value(attrs, name, attr)
|
708
723
|
else
|
data/lib/lutaml/model/version.rb
CHANGED
@@ -7,6 +7,10 @@ RSpec.describe Lutaml::Model::Attribute do
|
|
7
7
|
described_class.new("name", :string)
|
8
8
|
end
|
9
9
|
|
10
|
+
let(:method_attr) do
|
11
|
+
described_class.new("name", nil, method_name: nil)
|
12
|
+
end
|
13
|
+
|
10
14
|
let(:test_record_class) do
|
11
15
|
Class.new(Lutaml::Model::Serializable) do
|
12
16
|
attribute :age, :integer
|
@@ -33,6 +37,13 @@ RSpec.describe Lutaml::Model::Attribute do
|
|
33
37
|
.to("avatar.png")
|
34
38
|
end
|
35
39
|
|
40
|
+
it "raises error if both type and method_name are not given" do
|
41
|
+
expect { method_attr }.to raise_error(
|
42
|
+
ArgumentError,
|
43
|
+
"method or type must be set for an attribute",
|
44
|
+
)
|
45
|
+
end
|
46
|
+
|
36
47
|
describe "#validate_options!" do
|
37
48
|
let(:validate_options) { name_attr.method(:validate_options!) }
|
38
49
|
|
@@ -103,7 +114,25 @@ RSpec.describe Lutaml::Model::Attribute do
|
|
103
114
|
end
|
104
115
|
end
|
105
116
|
|
106
|
-
describe "#
|
117
|
+
describe "#derived?" do
|
118
|
+
context "when type is set" do
|
119
|
+
let(:attribute) { described_class.new("name", :string) }
|
120
|
+
|
121
|
+
it "returns false" do
|
122
|
+
expect(attribute.derived?).to be(false)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
context "when type is nil and method_name is set" do
|
127
|
+
let(:attribute) { described_class.new("name", nil, method_name: :tmp) }
|
128
|
+
|
129
|
+
it "returns true" do
|
130
|
+
expect(attribute.derived?).to be(true)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
describe "#default" do
|
107
136
|
context "when default is not set" do
|
108
137
|
let(:attribute) { described_class.new("name", :string) }
|
109
138
|
|
@@ -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
|
@@ -112,13 +112,42 @@ RSpec.describe Lutaml::Model::Serializable do
|
|
112
112
|
end
|
113
113
|
|
114
114
|
describe ".attribute" do
|
115
|
-
|
115
|
+
before do
|
116
|
+
stub_const("TestClass", Class.new(described_class))
|
117
|
+
end
|
118
|
+
|
119
|
+
context "when method_name is given" do
|
120
|
+
let(:attribute) do
|
121
|
+
TestClass.attribute("test", method: :foobar)
|
122
|
+
end
|
116
123
|
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
124
|
+
it "adds derived attribute" do
|
125
|
+
expect { attribute }
|
126
|
+
.to change { TestClass.attributes["test"] }
|
127
|
+
.from(nil)
|
128
|
+
.to(Lutaml::Model::Attribute)
|
129
|
+
end
|
130
|
+
|
131
|
+
it "returns true for derived?" do
|
132
|
+
expect(attribute.derived?).to be(true)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
context "when type is given" do
|
137
|
+
let(:attribute) do
|
138
|
+
TestClass.attribute("foo", Lutaml::Model::Type::String)
|
139
|
+
end
|
140
|
+
|
141
|
+
it "adds the attribute and getter setter for that attribute" do
|
142
|
+
expect { attribute }
|
143
|
+
.to change { TestClass.attributes.keys }.from([]).to(["foo"])
|
144
|
+
.and change { TestClass.new.respond_to?(:foo) }.from(false).to(true)
|
145
|
+
.and change { TestClass.new.respond_to?(:foo=) }.from(false).to(true)
|
146
|
+
end
|
147
|
+
|
148
|
+
it "returns false for derived?" do
|
149
|
+
expect(attribute.derived?).to be(false)
|
150
|
+
end
|
122
151
|
end
|
123
152
|
end
|
124
153
|
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "spec_helper"
|
4
|
+
|
5
|
+
module DerivedAttributesSpecs
|
6
|
+
class Ceramic < Lutaml::Model::Serializable
|
7
|
+
attribute :name, :string
|
8
|
+
attribute :value, :float
|
9
|
+
end
|
10
|
+
|
11
|
+
class CeramicCollection < Lutaml::Model::Serializable
|
12
|
+
attribute :items, Ceramic, collection: true
|
13
|
+
attribute :total_value, method: :total_value
|
14
|
+
|
15
|
+
# Derived property
|
16
|
+
def total_value
|
17
|
+
items.sum(&:value)
|
18
|
+
end
|
19
|
+
|
20
|
+
xml do
|
21
|
+
root "ceramic-collection"
|
22
|
+
map_element "total-value", to: :total_value
|
23
|
+
map_element "item", to: :items
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
RSpec.describe "XML::DerivedAttributes" do
|
29
|
+
let(:xml) do
|
30
|
+
<<~XML.strip
|
31
|
+
<ceramic-collection>
|
32
|
+
<total-value>2500.0</total-value>
|
33
|
+
<item>
|
34
|
+
<name>Ancient Vase</name>
|
35
|
+
<value>1500.0</value>
|
36
|
+
</item>
|
37
|
+
<item>
|
38
|
+
<name>Historic Bowl</name>
|
39
|
+
<value>1000.0</value>
|
40
|
+
</item>
|
41
|
+
</ceramic-collection>
|
42
|
+
XML
|
43
|
+
end
|
44
|
+
|
45
|
+
let(:ancient_vase) do
|
46
|
+
DerivedAttributesSpecs::Ceramic.new(name: "Ancient Vase", value: 1500.0)
|
47
|
+
end
|
48
|
+
|
49
|
+
let(:historic_bowl) do
|
50
|
+
DerivedAttributesSpecs::Ceramic.new(name: "Historic Bowl", value: 1000.0)
|
51
|
+
end
|
52
|
+
|
53
|
+
let(:ceramic_collection) do
|
54
|
+
DerivedAttributesSpecs::CeramicCollection.new(
|
55
|
+
items: [ancient_vase, historic_bowl],
|
56
|
+
)
|
57
|
+
end
|
58
|
+
|
59
|
+
describe ".from_xml" do
|
60
|
+
let(:parsed) do
|
61
|
+
DerivedAttributesSpecs::CeramicCollection.from_xml(xml)
|
62
|
+
end
|
63
|
+
|
64
|
+
it "correctly parses items" do
|
65
|
+
expect(parsed).to eq(ceramic_collection)
|
66
|
+
end
|
67
|
+
|
68
|
+
it "correctly calculates total-value" do
|
69
|
+
expect(parsed.total_value).to eq(2500)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
describe ".to_xml" do
|
74
|
+
it "convert to correct xml" do
|
75
|
+
expect(ceramic_collection.to_xml).to eq(xml)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
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.
|
4
|
+
version: 0.6.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ribose Inc.
|
@@ -257,6 +257,7 @@ files:
|
|
257
257
|
- spec/lutaml/model/utils_spec.rb
|
258
258
|
- spec/lutaml/model/validation_spec.rb
|
259
259
|
- spec/lutaml/model/with_child_mapping_spec.rb
|
260
|
+
- spec/lutaml/model/xml/derived_attributes_spec.rb
|
260
261
|
- spec/lutaml/model/xml_adapter/nokogiri_adapter_spec.rb
|
261
262
|
- spec/lutaml/model/xml_adapter/oga_adapter_spec.rb
|
262
263
|
- spec/lutaml/model/xml_adapter/ox_adapter_spec.rb
|