lutaml-model 0.3.3 → 0.3.5

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'
@@ -458,7 +546,7 @@ The following class will parse the XML snippet below:
458
546
  [source,ruby]
459
547
  ----
460
548
  class Example < Lutaml::Model::Serializable
461
- attribute :value, Lutaml::Model::Type::Integer
549
+ attribute :value, :integer
462
550
 
463
551
  xml do
464
552
  root 'example'
@@ -506,7 +594,7 @@ The following class will parse the XML snippet below:
506
594
  [source,ruby]
507
595
  ----
508
596
  class Example < Lutaml::Model::Serializable
509
- attribute :description, Lutaml::Model::Type::String
597
+ attribute :description, :string
510
598
 
511
599
  xml do
512
600
  root 'example'
@@ -540,9 +628,9 @@ The following class will parse the XML snippet below:
540
628
  [source,ruby]
541
629
  ----
542
630
  class Example < Lutaml::Model::Serializable
543
- attribute :name, Lutaml::Model::Type::String
544
- attribute :description, Lutaml::Model::Type::String
545
- attribute :value, Lutaml::Model::Type::Integer
631
+ attribute :name, :string
632
+ attribute :description, :string
633
+ attribute :value, :integer
546
634
 
547
635
  xml do
548
636
  root 'example'
@@ -590,8 +678,8 @@ end
590
678
  [source,ruby]
591
679
  ----
592
680
  class Ceramic < Lutaml::Model::Serializable
593
- attribute :type, Lutaml::Model::Type::String
594
- attribute :glaze, Lutaml::Model::Type::String
681
+ attribute :type, :string
682
+ attribute :glaze, :string
595
683
 
596
684
  xml do
597
685
  root 'Ceramic'
@@ -644,8 +732,8 @@ In this example, `glz` will be used for `Glaze` if it is added inside the
644
732
  [source,ruby]
645
733
  ----
646
734
  class Glaze < Lutaml::Model::Serializable
647
- attribute :color, Lutaml::Model::Type::String
648
- attribute :temperature, Lutaml::Model::Type::Integer
735
+ attribute :color, :string
736
+ attribute :temperature, :integer
649
737
 
650
738
  xml do
651
739
  root 'Glaze'
@@ -657,7 +745,7 @@ class Glaze < Lutaml::Model::Serializable
657
745
  end
658
746
 
659
747
  class Ceramic < Lutaml::Model::Serializable
660
- attribute :type, Lutaml::Model::Type::String
748
+ attribute :type, :string
661
749
  attribute :glaze, Glaze
662
750
 
663
751
  xml do
@@ -711,9 +799,9 @@ In this example, the `Type` element will inherit the namespace from the root.
711
799
  [source,ruby]
712
800
  ----
713
801
  class Ceramic < Lutaml::Model::Serializable
714
- attribute :type, Lutaml::Model::Type::String
715
- attribute :glaze, Lutaml::Model::Type::String
716
- attribute :color, Lutaml::Model::Type::String
802
+ attribute :type, :string
803
+ attribute :glaze, :string
804
+ attribute :color, :string
717
805
 
718
806
  xml do
719
807
  root 'Ceramic'
@@ -791,8 +879,8 @@ end
791
879
  [source,ruby]
792
880
  ----
793
881
  class Paragraph < Lutaml::Model::Serializable
794
- attribute :bold, Lutaml::Model::Type::String
795
- attribute :italic, Lutaml::Model::Type::String
882
+ attribute :bold, :string
883
+ attribute :italic, :string
796
884
 
797
885
  xml do
798
886
  root 'p', mixed: true
@@ -835,8 +923,8 @@ end
835
923
  [source,ruby]
836
924
  ----
837
925
  class Paragraph < Lutaml::Model::Serializable
838
- attribute :bold, Lutaml::Model::Type::String
839
- attribute :italic, Lutaml::Model::Type::String
926
+ attribute :bold, :string
927
+ attribute :italic, :string
840
928
 
841
929
  xml do
842
930
  root 'p'
@@ -895,8 +983,8 @@ end
895
983
  [source,ruby]
896
984
  ----
897
985
  class Example < Lutaml::Model::Serializable
898
- attribute :name, Lutaml::Model::Type::String
899
- attribute :value, Lutaml::Model::Type::Integer
986
+ attribute :name, :string
987
+ attribute :value, :integer
900
988
 
901
989
  json do
902
990
  map 'name', to: :name
@@ -943,8 +1031,8 @@ by referring to a Lutaml::Model class as an attribute class.
943
1031
  [source,ruby]
944
1032
  ----
945
1033
  class Glaze < Lutaml::Model::Serializable
946
- attribute :color, Lutaml::Model::Type::String
947
- attribute :temperature, Lutaml::Model::Type::Integer
1034
+ attribute :color, :string
1035
+ attribute :temperature, :integer
948
1036
 
949
1037
  json do
950
1038
  map 'color', to: :color
@@ -953,7 +1041,7 @@ class Glaze < Lutaml::Model::Serializable
953
1041
  end
954
1042
 
955
1043
  class Ceramic < Lutaml::Model::Serializable
956
- attribute :type, Lutaml::Model::Type::String
1044
+ attribute :type, :string
957
1045
  attribute :glaze, Glaze
958
1046
 
959
1047
  json do
@@ -983,6 +1071,158 @@ end
983
1071
  ----
984
1072
  ====
985
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
+
986
1226
  === Advanced attribute mapping
987
1227
 
988
1228
  ==== Attribute mapping delegation
@@ -1006,8 +1246,8 @@ The following class will parse the JSON snippet below:
1006
1246
  [source,ruby]
1007
1247
  ----
1008
1248
  class Glaze < Lutaml::Model::Serializable
1009
- attribute :color, Lutaml::Model::Type::String
1010
- attribute :temperature, Lutaml::Model::Type::Integer
1249
+ attribute :color, :string
1250
+ attribute :temperature, :integer
1011
1251
 
1012
1252
  json do
1013
1253
  map 'color', to: :color
@@ -1016,7 +1256,7 @@ class Glaze < Lutaml::Model::Serializable
1016
1256
  end
1017
1257
 
1018
1258
  class Ceramic < Lutaml::Model::Serializable
1019
- attribute :type, Lutaml::Model::Type::String
1259
+ attribute :type, :string
1020
1260
  attribute :glaze, Glaze
1021
1261
 
1022
1262
  json do
@@ -1051,10 +1291,30 @@ each serialization mapping block for `from` and `to`.
1051
1291
 
1052
1292
  Syntax:
1053
1293
 
1294
+ .XML serialization with custom methods
1054
1295
  [source,ruby]
1055
1296
  ----
1056
- xml | json | yaml | toml do
1057
- map 'key_value_model_attribute_name', to: :name_of_attribute, with: {
1297
+ xml do
1298
+ map_element 'element_name', to: :name_of_element, with: {
1299
+ to: :method_name_to_serialize,
1300
+ from: :method_name_to_deserialize
1301
+ }
1302
+ map_attribute 'attribute_name', to: :name_of_attribute, with: {
1303
+ to: :method_name_to_serialize,
1304
+ from: :method_name_to_deserialize
1305
+ }
1306
+ map_content, to: :name_of_content, with: {
1307
+ to: :method_name_to_serialize,
1308
+ from: :method_name_to_deserialize
1309
+ }
1310
+ end
1311
+ ----
1312
+
1313
+ .Key-value data model serialization with custom methods
1314
+ [source,ruby]
1315
+ ----
1316
+ json | yaml | toml do
1317
+ map 'attribute_name', to: :name_of_attribute, with: {
1058
1318
  to: :method_name_to_serialize,
1059
1319
  from: :method_name_to_deserialize
1060
1320
  }
@@ -1069,8 +1329,8 @@ The following class will parse the JSON snippet below:
1069
1329
  [source,ruby]
1070
1330
  ----
1071
1331
  class CustomCeramic < Lutaml::Model::Serializable
1072
- attribute :name, Lutaml::Model::Type::String
1073
- attribute :size, Lutaml::Model::Type::Integer
1332
+ attribute :name, :string
1333
+ attribute :size, :integer
1074
1334
 
1075
1335
  json do
1076
1336
  map 'name', to: :name, with: { to: :name_to_json, from: :name_from_json }
@@ -1250,9 +1510,9 @@ A model can be defined for this JSON as follows:
1250
1510
  [source,ruby]
1251
1511
  ----
1252
1512
  class Schema < Lutaml::Model::Serializable
1253
- attribute :id, Lutaml::Model::Type::String
1254
- attribute :link, Lutaml::Model::Type::String
1255
- attribute :name, Lutaml::Model::Type::String
1513
+ attribute :id, :string
1514
+ attribute :link, :string
1515
+ attribute :name, :string
1256
1516
  end
1257
1517
 
1258
1518
  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)