lutaml-model 0.6.4 → 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 +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
|