lutaml-model 0.3.2 → 0.3.4

Sign up to get free protection for your applications and to get access to all the features.
data/README.adoc CHANGED
@@ -69,16 +69,18 @@ Or install it yourself as:
69
69
  gem install lutaml-model
70
70
  ----
71
71
 
72
- == Defining a data model class
72
+ == Data model class
73
73
 
74
- === General
74
+ === Definition
75
+
76
+ ==== General
75
77
 
76
78
  There are two ways to define a data model in Lutaml::Model:
77
79
 
78
80
  * Inheriting from the `Lutaml::Model::Serializable` class
79
81
  * Including the `Lutaml::Model::Serialize` module
80
82
 
81
- === Definition through inheritance
83
+ ==== Definition through inheritance
82
84
 
83
85
  The simplest way to define a model is to create a class that inherits from
84
86
  `Lutaml::Model::Serializable`.
@@ -90,13 +92,13 @@ The `attribute` class method is used to define attributes.
90
92
  require 'lutaml/model'
91
93
 
92
94
  class Kiln < Lutaml::Model::Serializable
93
- attribute :brand, Lutaml::Model::Type::String
94
- attribute :capacity, Lutaml::Model::Type::Integer
95
- attribute :temperature, Lutaml::Model::Type::Integer
95
+ attribute :brand, :string
96
+ attribute :capacity, :integer
97
+ attribute :temperature, :integer
96
98
  end
97
99
  ----
98
100
 
99
- === Definition through inclusion
101
+ ==== Definition through inclusion
100
102
 
101
103
  If the model class already has a super class that it inherits from, the model
102
104
  can be extended using the `Lutaml::Model::Serialize` module.
@@ -108,12 +110,38 @@ require 'lutaml/model'
108
110
  class Kiln < SomeSuperClass
109
111
  include Lutaml::Model::Serialize
110
112
 
111
- attribute :brand, Lutaml::Model::Type::String
112
- attribute :capacity, Lutaml::Model::Type::Integer
113
- attribute :temperature, Lutaml::Model::Type::Integer
113
+ attribute :brand, :string
114
+ attribute :capacity, :integer
115
+ attribute :temperature, :integer
114
116
  end
115
117
  ----
116
118
 
119
+
120
+ === Comparison
121
+
122
+ A `Serialize` / `Serializable` object can be compared with another object of the
123
+ same class using the `==` operator. This is implemented through the
124
+ `ComparableModel` module.
125
+
126
+ Two objects are considered equal if they have the same class and all their
127
+ attributes are equal. This behavior differs from the typical Ruby behavior,
128
+ where two objects are considered equal only if they have the same object ID.
129
+
130
+ NOTE: Two `Serialize` objects will have the same `hash` value if they have the
131
+ same class and all their attributes are equal.
132
+
133
+ [source,ruby]
134
+ ----
135
+ > a = Kiln.new(brand: 'Kiln 1', capacity: 100, temperature: 1050)
136
+ > b = Kiln.new(brand: 'Kiln 1', capacity: 100, temperature: 1050)
137
+ > a == b
138
+ > # true
139
+ > a.hash == b.hash
140
+ > # true
141
+ ----
142
+
143
+
144
+
117
145
  == Defining attributes
118
146
 
119
147
  === Supported attribute value types
@@ -160,7 +188,7 @@ class Studio < Lutaml::Model::Serializable
160
188
  # The following are equivalent
161
189
  attribute :location, :string
162
190
  attribute :potter, "String"
163
- attribute :kiln, Lutaml::Model::Type::String
191
+ attribute :kiln, :string
164
192
  end
165
193
  ----
166
194
 
@@ -196,8 +224,8 @@ attribute :name_of_attribute, Type, collection: true
196
224
  [source,ruby]
197
225
  ----
198
226
  class Studio < Lutaml::Model::Serializable
199
- attribute :location, Lutaml::Model::Type::String
200
- attribute :potters, Lutaml::Model::Type::String, collection: true
227
+ attribute :location, :string
228
+ attribute :potters, :string, collection: true
201
229
  end
202
230
  ----
203
231
 
@@ -224,25 +252,85 @@ Syntax:
224
252
  attribute :name_of_attribute, Type, values: [value1, value2, ...]
225
253
  ----
226
254
 
227
- .Using the `values` directive to define acceptable values for an attribute
255
+ The values set inside the `values:` option can be of any type, but they must
256
+ match the type of the attribute. The values are compared using the `==` operator,
257
+ so the type must implement the `==` method.
258
+
259
+ .Using the `values` directive to define acceptable values for an attribute (basic types)
260
+ [example]
261
+ ====
262
+ [source,ruby]
263
+ ----
264
+ class GlazeTechnique < Lutaml::Model::Serializable
265
+ attribute :name, :string, values: ["Celadon", "Raku", "Majolica"]
266
+ end
267
+ ----
268
+
269
+ [source,ruby]
270
+ ----
271
+ > GlazeTechnique.new(name: "Celadon").name
272
+ > # "Celadon"
273
+ > GlazeTechnique.new(name: "Raku").name
274
+ > # "Raku"
275
+ > GlazeTechnique.new(name: "Majolica").name
276
+ > # "Majolica"
277
+ > GlazeTechnique.new(name: "Earthenware").name
278
+ > # Lutaml::Model::InvalidValueError: Invalid value for attribute 'name'
279
+ ----
280
+ ====
281
+
282
+ The values can be Serialize objects, which are compared using the `==`
283
+ and the `hash` methods through the Lutaml::Model::ComparableModel module.
284
+
285
+
286
+ .Using the `values` directive to define acceptable values for an attribute (Serializable objects)
228
287
  [example]
229
288
  ====
230
289
  [source,ruby]
231
290
  ----
232
291
  class Ceramic < Lutaml::Model::Serializable
233
- attribute :type, Lutaml::Model::Type::String,
234
- values: ['Porcelain', 'Earthenware', 'Stoneware']
292
+ attribute :type, :string
293
+ attribute :firing_temperature, :integer
294
+ end
295
+
296
+ class CeramicCollection < Lutaml::Model::Serializable
297
+ attribute :featured_piece,
298
+ Ceramic,
299
+ values: [
300
+ Ceramic.new(type: "Porcelain", firing_temperature: 1300),
301
+ Ceramic.new(type: "Stoneware", firing_temperature: 1200),
302
+ Ceramic.new(type: "Earthenware", firing_temperature: 1000),
303
+ ]
235
304
  end
236
305
  ----
237
306
 
238
307
  [source,ruby]
239
308
  ----
240
- > Ceramic.new(type: 'Porcelain').type
241
- > # "Porcelain"
242
- > Ceramic.new(type: 'Earthenware').type
243
- > # "Earthenware"
244
- > Ceramic.new(type: 'Bone China').type
245
- > # Lutaml::Model::InvalidValueError: Invalid value for attribute 'type'
309
+ > CeramicCollection.new(featured_piece: Ceramic.new(type: "Porcelain", firing_temperature: 1300)).featured_piece
310
+ > # Ceramic:0x0000000104ac7240 @type="Porcelain", @firing_temperature=1300
311
+ > CeramicCollection.new(featured_piece: Ceramic.new(type: "Bone China", firing_temperature: 1300)).featured_piece
312
+ > # Lutaml::Model::InvalidValueError: Invalid value for attribute 'featured_piece'
313
+ ----
314
+ ====
315
+
316
+ Serialize provides a `validate` method that checks if all its attributes have
317
+ valid values. This is necessary for the case when a value is valid at the
318
+ component level, but not accepted at the aggregation level.
319
+
320
+ If a change has been made at the component level (a nested attribute has
321
+ changed), the aggregation level needs to call the `validate` method to verify
322
+ acceptance of the newly updated component.
323
+
324
+ .Using the `validate` method to check if all attributes have valid values
325
+ [example]
326
+ ====
327
+ [source,ruby]
328
+ ----
329
+ > collection = CeramicCollection.new(featured_piece: Ceramic.new(type: "Porcelain", firing_temperature: 1300))
330
+ > collection.featured_piece.firing_temperature = 1400
331
+ > # No error raised in changed nested attribute
332
+ > collection.validate
333
+ > # Lutaml::Model::InvalidValueError: Invalid value for attribute 'featured_piece'
246
334
  ----
247
335
  ====
248
336
 
@@ -266,8 +354,8 @@ attribute :name_of_attribute, Type, default: -> { value }
266
354
  [source,ruby]
267
355
  ----
268
356
  class Glaze < Lutaml::Model::Serializable
269
- attribute :color, Lutaml::Model::Type::String, default: -> { 'Clear' }
270
- attribute :temperature, Lutaml::Model::Type::Integer, default: -> { 1050 }
357
+ attribute :color, :string, default: -> { 'Clear' }
358
+ attribute :temperature, :integer, default: -> { 1050 }
271
359
  end
272
360
  ----
273
361
 
@@ -378,7 +466,7 @@ end
378
466
  [source,ruby]
379
467
  ----
380
468
  class Example < Lutaml::Model::Serializable
381
- attribute :name, Lutaml::Model::Type::String
469
+ attribute :name, :string
382
470
 
383
471
  xml do
384
472
  root 'example'
@@ -401,6 +489,42 @@ end
401
489
  ----
402
490
  ====
403
491
 
492
+ If an element is mapped to a model object with the XML `root` tag name set, the
493
+ mapped tag name will be used as the root name, overriding the root name.
494
+
495
+ .The mapped tag name is used as the root name
496
+ [example]
497
+ ====
498
+ [source,ruby]
499
+ ----
500
+ class RecordDate < Lutaml::Model::Serializable
501
+ attribute :content, :string
502
+
503
+ xml do
504
+ root "recordDate"
505
+ map_content to: :content
506
+ end
507
+ end
508
+
509
+ class OriginInfo < Lutaml::Model::Serializable
510
+ attribute :date_issued, RecordDate, collection: true
511
+
512
+ xml do
513
+ root "originInfo"
514
+ map_element "dateIssued", to: :date_issued
515
+ end
516
+ end
517
+ ----
518
+
519
+ [source,ruby]
520
+ ----
521
+ > RecordDate.new(date: "2021-01-01").to_xml
522
+ > #<recordDate>2021-01-01</recordDate>
523
+ > OriginInfo.new(date_issued: [RecordDate.new(date: "2021-01-01")]).to_xml
524
+ > #<originInfo><dateIssued>2021-01-01</dateIssued></originInfo>
525
+ ----
526
+ ====
527
+
404
528
  ==== Mapping attributes
405
529
 
406
530
  The `map_attribute` method maps an XML attribute to a data model attribute.
@@ -422,7 +546,7 @@ The following class will parse the XML snippet below:
422
546
  [source,ruby]
423
547
  ----
424
548
  class Example < Lutaml::Model::Serializable
425
- attribute :value, Lutaml::Model::Type::Integer
549
+ attribute :value, :integer
426
550
 
427
551
  xml do
428
552
  root 'example'
@@ -470,7 +594,7 @@ The following class will parse the XML snippet below:
470
594
  [source,ruby]
471
595
  ----
472
596
  class Example < Lutaml::Model::Serializable
473
- attribute :description, Lutaml::Model::Type::String
597
+ attribute :description, :string
474
598
 
475
599
  xml do
476
600
  root 'example'
@@ -504,9 +628,9 @@ The following class will parse the XML snippet below:
504
628
  [source,ruby]
505
629
  ----
506
630
  class Example < Lutaml::Model::Serializable
507
- attribute :name, Lutaml::Model::Type::String
508
- attribute :description, Lutaml::Model::Type::String
509
- attribute :value, Lutaml::Model::Type::Integer
631
+ attribute :name, :string
632
+ attribute :description, :string
633
+ attribute :value, :integer
510
634
 
511
635
  xml do
512
636
  root 'example'
@@ -554,8 +678,8 @@ end
554
678
  [source,ruby]
555
679
  ----
556
680
  class Ceramic < Lutaml::Model::Serializable
557
- attribute :type, Lutaml::Model::Type::String
558
- attribute :glaze, Lutaml::Model::Type::String
681
+ attribute :type, :string
682
+ attribute :glaze, :string
559
683
 
560
684
  xml do
561
685
  root 'Ceramic'
@@ -608,8 +732,8 @@ In this example, `glz` will be used for `Glaze` if it is added inside the
608
732
  [source,ruby]
609
733
  ----
610
734
  class Glaze < Lutaml::Model::Serializable
611
- attribute :color, Lutaml::Model::Type::String
612
- attribute :temperature, Lutaml::Model::Type::Integer
735
+ attribute :color, :string
736
+ attribute :temperature, :integer
613
737
 
614
738
  xml do
615
739
  root 'Glaze'
@@ -621,7 +745,7 @@ class Glaze < Lutaml::Model::Serializable
621
745
  end
622
746
 
623
747
  class Ceramic < Lutaml::Model::Serializable
624
- attribute :type, Lutaml::Model::Type::String
748
+ attribute :type, :string
625
749
  attribute :glaze, Glaze
626
750
 
627
751
  xml do
@@ -675,9 +799,9 @@ In this example, the `Type` element will inherit the namespace from the root.
675
799
  [source,ruby]
676
800
  ----
677
801
  class Ceramic < Lutaml::Model::Serializable
678
- attribute :type, Lutaml::Model::Type::String
679
- attribute :glaze, Lutaml::Model::Type::String
680
- attribute :color, Lutaml::Model::Type::String
802
+ attribute :type, :string
803
+ attribute :glaze, :string
804
+ attribute :color, :string
681
805
 
682
806
  xml do
683
807
  root 'Ceramic'
@@ -755,8 +879,8 @@ end
755
879
  [source,ruby]
756
880
  ----
757
881
  class Paragraph < Lutaml::Model::Serializable
758
- attribute :bold, Lutaml::Model::Type::String
759
- attribute :italic, Lutaml::Model::Type::String
882
+ attribute :bold, :string
883
+ attribute :italic, :string
760
884
 
761
885
  xml do
762
886
  root 'p', mixed: true
@@ -799,8 +923,8 @@ end
799
923
  [source,ruby]
800
924
  ----
801
925
  class Paragraph < Lutaml::Model::Serializable
802
- attribute :bold, Lutaml::Model::Type::String
803
- attribute :italic, Lutaml::Model::Type::String
926
+ attribute :bold, :string
927
+ attribute :italic, :string
804
928
 
805
929
  xml do
806
930
  root 'p'
@@ -859,8 +983,8 @@ end
859
983
  [source,ruby]
860
984
  ----
861
985
  class Example < Lutaml::Model::Serializable
862
- attribute :name, Lutaml::Model::Type::String
863
- attribute :value, Lutaml::Model::Type::Integer
986
+ attribute :name, :string
987
+ attribute :value, :integer
864
988
 
865
989
  json do
866
990
  map 'name', to: :name
@@ -907,8 +1031,8 @@ by referring to a Lutaml::Model class as an attribute class.
907
1031
  [source,ruby]
908
1032
  ----
909
1033
  class Glaze < Lutaml::Model::Serializable
910
- attribute :color, Lutaml::Model::Type::String
911
- attribute :temperature, Lutaml::Model::Type::Integer
1034
+ attribute :color, :string
1035
+ attribute :temperature, :integer
912
1036
 
913
1037
  json do
914
1038
  map 'color', to: :color
@@ -917,7 +1041,7 @@ class Glaze < Lutaml::Model::Serializable
917
1041
  end
918
1042
 
919
1043
  class Ceramic < Lutaml::Model::Serializable
920
- attribute :type, Lutaml::Model::Type::String
1044
+ attribute :type, :string
921
1045
  attribute :glaze, Glaze
922
1046
 
923
1047
  json do
@@ -947,6 +1071,158 @@ end
947
1071
  ----
948
1072
  ====
949
1073
 
1074
+
1075
+ === Separate serialization model
1076
+
1077
+ The `Serialize` module can be used to define only serialization mappings for a
1078
+ separately defined model (a Ruby class).
1079
+
1080
+ Syntax:
1081
+
1082
+ [source,ruby]
1083
+ ----
1084
+ class Foo < Lutaml::Model::Serializable
1085
+ model {DataModelClass}
1086
+
1087
+ # ...
1088
+ end
1089
+ ----
1090
+
1091
+ [example]
1092
+ .Using the `model` method to define serialization mappings for a separate model
1093
+ ====
1094
+ [source,ruby]
1095
+ ----
1096
+ class Ceramic
1097
+ attr_accessor :type, :glaze
1098
+
1099
+ def name
1100
+ "#{type} with #{glaze}"
1101
+ end
1102
+ end
1103
+
1104
+ class CeramicSerialization < Lutaml::Model::Serializable
1105
+ model Ceramic
1106
+
1107
+ xml do
1108
+ map_element 'type', to: :type
1109
+ map_element 'glaze', to: :glaze
1110
+ end
1111
+ end
1112
+ ----
1113
+
1114
+ [source,ruby]
1115
+ ----
1116
+ > Ceramic.new(type: "Porcelain", glaze: "Clear").name
1117
+ > # "Porcelain with Clear"
1118
+ > CeramicSerialization.from_xml(xml)
1119
+ > #<Ceramic:0x0000000104ac7240 @type="Porcelain", @glaze="Clear">
1120
+ > Ceramic.new(type: "Porcelain", glaze: "Clear").to_xml
1121
+ > #<Ceramic><type>Porcelain</type><glaze>Clear</glaze></Ceramic>
1122
+ ----
1123
+ ====
1124
+
1125
+
1126
+ === Rendering empty attributes and collections
1127
+
1128
+ By default, empty attributes and collections are not rendered in the output.
1129
+
1130
+ To render empty attributes and collections, use the `render_nil` option.
1131
+
1132
+ Syntax:
1133
+
1134
+ [source,ruby]
1135
+ ----
1136
+ xml do
1137
+ map_element 'key_value_model_attribute_name', to: :name_of_attribute, render_nil: true
1138
+ end
1139
+ ----
1140
+
1141
+ [source,ruby]
1142
+ ----
1143
+ json | yaml | toml do
1144
+ map 'key_value_model_attribute_name', to: :name_of_attribute, render_nil: true
1145
+ end
1146
+ ----
1147
+
1148
+ .Using the `render_nil` option to render empty attributes
1149
+ [example]
1150
+ ====
1151
+ [source,ruby]
1152
+ ----
1153
+ class Ceramic < Lutaml::Model::Serializable
1154
+ attribute :type, :string
1155
+ attribute :glaze, :string
1156
+
1157
+ xml do
1158
+ map_element 'type', to: :type, render_nil: true
1159
+ map_element 'glaze', to: :glaze
1160
+ end
1161
+
1162
+ json do
1163
+ map 'type', to: :type, render_nil: true
1164
+ map 'glaze', to: :glaze
1165
+ end
1166
+ end
1167
+ ----
1168
+
1169
+ [source,ruby]
1170
+ ----
1171
+ > Ceramic.new.to_json
1172
+ > # { 'type': null }
1173
+ > Ceramic.new(type: "Porcelain", glaze: "Clear").to_json
1174
+ > # { 'type': 'Porcelain', 'glaze': 'Clear' }
1175
+ ----
1176
+
1177
+ [source,ruby]
1178
+ ----
1179
+ > Ceramic.new.to_xml
1180
+ > # <Ceramic><type></type></Ceramic>
1181
+ > Ceramic.new(type: "Porcelain", glaze: "Clear").to_xml
1182
+ > # <Ceramic><type>Porcelain</type><glaze>Clear</glaze></Ceramic>
1183
+ ----
1184
+ ====
1185
+
1186
+ .Using the `render_nil` option to render empty attribute collections
1187
+ [example]
1188
+ ====
1189
+ [source,ruby]
1190
+ ----
1191
+ class Ceramic < Lutaml::Model::Serializable
1192
+ attribute :type, :string
1193
+ attribute :glazes, :string, collection: true
1194
+
1195
+ xml do
1196
+ map_element 'type', to: :type, render_nil: true
1197
+ map_element 'glazes', to: :glazes, render_nil: true
1198
+ end
1199
+
1200
+ json do
1201
+ map 'type', to: :type, render_nil: true
1202
+ map 'glazes', to: :glazes, render_nil: true
1203
+ end
1204
+ end
1205
+ ----
1206
+
1207
+ [source,ruby]
1208
+ ----
1209
+ > Ceramic.new.to_json
1210
+ > # { 'type': null, 'glazes': [] }
1211
+ > Ceramic.new(type: "Porcelain", glazes: ["Clear"]).to_json
1212
+ > # { 'type': 'Porcelain', 'glazes': ['Clear'] }
1213
+ ----
1214
+
1215
+ [source,ruby]
1216
+ ----
1217
+ > Ceramic.new.to_xml
1218
+ > # <Ceramic><type></type><glazes></glazes></Ceramic>
1219
+ > Ceramic.new(type: "Porcelain", glazes: ["Clear"]).to_xml
1220
+ > # <Ceramic><type>Porcelain</type><glazes>Clear</glazes></Ceramic>
1221
+ ----
1222
+ ====
1223
+
1224
+
1225
+
950
1226
  === Advanced attribute mapping
951
1227
 
952
1228
  ==== Attribute mapping delegation
@@ -970,8 +1246,8 @@ The following class will parse the JSON snippet below:
970
1246
  [source,ruby]
971
1247
  ----
972
1248
  class Glaze < Lutaml::Model::Serializable
973
- attribute :color, Lutaml::Model::Type::String
974
- attribute :temperature, Lutaml::Model::Type::Integer
1249
+ attribute :color, :string
1250
+ attribute :temperature, :integer
975
1251
 
976
1252
  json do
977
1253
  map 'color', to: :color
@@ -980,7 +1256,7 @@ class Glaze < Lutaml::Model::Serializable
980
1256
  end
981
1257
 
982
1258
  class Ceramic < Lutaml::Model::Serializable
983
- attribute :type, Lutaml::Model::Type::String
1259
+ attribute :type, :string
984
1260
  attribute :glaze, Glaze
985
1261
 
986
1262
  json do
@@ -1033,8 +1309,8 @@ The following class will parse the JSON snippet below:
1033
1309
  [source,ruby]
1034
1310
  ----
1035
1311
  class CustomCeramic < Lutaml::Model::Serializable
1036
- attribute :name, Lutaml::Model::Type::String
1037
- attribute :size, Lutaml::Model::Type::Integer
1312
+ attribute :name, :string
1313
+ attribute :size, :integer
1038
1314
 
1039
1315
  json do
1040
1316
  map 'name', to: :name, with: { to: :name_to_json, from: :name_from_json }
@@ -1214,9 +1490,9 @@ A model can be defined for this JSON as follows:
1214
1490
  [source,ruby]
1215
1491
  ----
1216
1492
  class Schema < Lutaml::Model::Serializable
1217
- attribute :id, Lutaml::Model::Type::String
1218
- attribute :link, Lutaml::Model::Type::String
1219
- attribute :name, Lutaml::Model::Type::String
1493
+ attribute :id, :string
1494
+ attribute :link, :string
1495
+ attribute :name, :string
1220
1496
  end
1221
1497
 
1222
1498
  class ChildMappingClass < Lutaml::Model::Serializable
data/exe/lutaml-model ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "bundler/setup"
5
+ require_relative "../lib/lutaml/model/cli"
6
+
7
+ Lutaml::Model::Cli.start(ARGV)