lutaml-model 0.3.24 → 0.3.26

Sign up to get free protection for your applications and to get access to all the features.
Files changed (88) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +36 -17
  3. data/README.adoc +396 -30
  4. data/lib/lutaml/model/attribute.rb +52 -27
  5. data/lib/lutaml/model/error/pattern_not_matched_error.rb +17 -0
  6. data/lib/lutaml/model/error/type_error.rb +9 -0
  7. data/lib/lutaml/model/error/unknown_type_error.rb +9 -0
  8. data/lib/lutaml/model/error/validation_error.rb +0 -1
  9. data/lib/lutaml/model/error.rb +3 -0
  10. data/lib/lutaml/model/mapping_hash.rb +8 -0
  11. data/lib/lutaml/model/serialize.rb +10 -5
  12. data/lib/lutaml/model/type/boolean.rb +38 -0
  13. data/lib/lutaml/model/type/date.rb +35 -0
  14. data/lib/lutaml/model/type/date_time.rb +32 -4
  15. data/lib/lutaml/model/type/decimal.rb +42 -0
  16. data/lib/lutaml/model/type/float.rb +37 -0
  17. data/lib/lutaml/model/type/hash.rb +62 -0
  18. data/lib/lutaml/model/type/integer.rb +41 -0
  19. data/lib/lutaml/model/type/string.rb +49 -0
  20. data/lib/lutaml/model/type/time.rb +49 -0
  21. data/lib/lutaml/model/type/time_without_date.rb +37 -5
  22. data/lib/lutaml/model/type/value.rb +52 -0
  23. data/lib/lutaml/model/type.rb +50 -114
  24. data/lib/lutaml/model/validation.rb +2 -1
  25. data/lib/lutaml/model/version.rb +1 -1
  26. data/lib/lutaml/model/xml_adapter/builder/nokogiri.rb +12 -3
  27. data/lib/lutaml/model/xml_adapter/builder/ox.rb +7 -1
  28. data/lib/lutaml/model/xml_adapter/nokogiri_adapter.rb +3 -1
  29. data/lib/lutaml/model/xml_adapter/ox_adapter.rb +12 -8
  30. data/lib/lutaml/model/xml_adapter/xml_document.rb +6 -8
  31. data/lib/lutaml/model/xml_mapping.rb +6 -2
  32. data/lib/lutaml/model/xml_mapping_rule.rb +7 -1
  33. data/lutaml-model.gemspec +1 -1
  34. data/spec/address_spec.rb +170 -0
  35. data/spec/fixtures/address.rb +33 -0
  36. data/spec/fixtures/person.rb +73 -0
  37. data/spec/fixtures/sample_model.rb +40 -0
  38. data/spec/fixtures/vase.rb +38 -0
  39. data/spec/fixtures/xml/special_char.xml +13 -0
  40. data/spec/lutaml/model/attribute_spec.rb +139 -0
  41. data/spec/lutaml/model/cdata_spec.rb +520 -0
  42. data/spec/lutaml/model/collection_spec.rb +299 -0
  43. data/spec/lutaml/model/comparable_model_spec.rb +106 -0
  44. data/spec/lutaml/model/custom_model_spec.rb +410 -0
  45. data/spec/lutaml/model/custom_serialization_spec.rb +170 -0
  46. data/spec/lutaml/model/defaults_spec.rb +221 -0
  47. data/spec/lutaml/model/delegation_spec.rb +340 -0
  48. data/spec/lutaml/model/inheritance_spec.rb +92 -0
  49. data/spec/lutaml/model/json_adapter_spec.rb +37 -0
  50. data/spec/lutaml/model/key_value_mapping_spec.rb +86 -0
  51. data/spec/lutaml/model/map_content_spec.rb +118 -0
  52. data/spec/lutaml/model/mixed_content_spec.rb +625 -0
  53. data/spec/lutaml/model/namespace_spec.rb +57 -0
  54. data/spec/lutaml/model/ordered_content_spec.rb +83 -0
  55. data/spec/lutaml/model/render_nil_spec.rb +138 -0
  56. data/spec/lutaml/model/schema/json_schema_spec.rb +79 -0
  57. data/spec/lutaml/model/schema/relaxng_schema_spec.rb +60 -0
  58. data/spec/lutaml/model/schema/xsd_schema_spec.rb +55 -0
  59. data/spec/lutaml/model/schema/yaml_schema_spec.rb +47 -0
  60. data/spec/lutaml/model/serializable_spec.rb +297 -0
  61. data/spec/lutaml/model/serializable_validation_spec.rb +90 -0
  62. data/spec/lutaml/model/simple_model_spec.rb +314 -0
  63. data/spec/lutaml/model/toml_adapter_spec.rb +39 -0
  64. data/spec/lutaml/model/type/boolean_spec.rb +54 -0
  65. data/spec/lutaml/model/type/date_spec.rb +118 -0
  66. data/spec/lutaml/model/type/date_time_spec.rb +127 -0
  67. data/spec/lutaml/model/type/decimal_spec.rb +125 -0
  68. data/spec/lutaml/model/type/float_spec.rb +191 -0
  69. data/spec/lutaml/model/type/hash_spec.rb +63 -0
  70. data/spec/lutaml/model/type/integer_spec.rb +145 -0
  71. data/spec/lutaml/model/type/string_spec.rb +150 -0
  72. data/spec/lutaml/model/type/time_spec.rb +142 -0
  73. data/spec/lutaml/model/type/time_without_date_spec.rb +125 -0
  74. data/spec/lutaml/model/type_spec.rb +276 -0
  75. data/spec/lutaml/model/utils_spec.rb +79 -0
  76. data/spec/lutaml/model/validation_spec.rb +83 -0
  77. data/spec/lutaml/model/with_child_mapping_spec.rb +174 -0
  78. data/spec/lutaml/model/xml_adapter/nokogiri_adapter_spec.rb +56 -0
  79. data/spec/lutaml/model/xml_adapter/oga_adapter_spec.rb +56 -0
  80. data/spec/lutaml/model/xml_adapter/ox_adapter_spec.rb +61 -0
  81. data/spec/lutaml/model/xml_adapter/xml_namespace_spec.rb +251 -0
  82. data/spec/lutaml/model/xml_adapter_spec.rb +178 -0
  83. data/spec/lutaml/model/xml_mapping_spec.rb +863 -0
  84. data/spec/lutaml/model/yaml_adapter_spec.rb +30 -0
  85. data/spec/lutaml/model_spec.rb +1 -0
  86. data/spec/person_spec.rb +161 -0
  87. data/spec/spec_helper.rb +33 -0
  88. metadata +68 -2
@@ -0,0 +1,297 @@
1
+ module SerializeableSpec
2
+ class TestModel
3
+ attr_accessor :name, :age
4
+
5
+ def initialize(name: nil, age: nil)
6
+ @name = name
7
+ @age = age
8
+ end
9
+ end
10
+
11
+ class TestModelMapper < Lutaml::Model::Serializable
12
+ model TestModel
13
+
14
+ attribute :name, Lutaml::Model::Type::String
15
+ attribute :age, Lutaml::Model::Type::String
16
+ end
17
+
18
+ class TestMapper < Lutaml::Model::Serializable
19
+ attribute :name, Lutaml::Model::Type::String
20
+ attribute :age, Lutaml::Model::Type::String
21
+
22
+ yaml do
23
+ map :na, to: :name
24
+ map :ag, to: :age
25
+ end
26
+ end
27
+
28
+ ### XML root mapping
29
+
30
+ class RecordDate < Lutaml::Model::Serializable
31
+ attribute :content, :string
32
+
33
+ xml do
34
+ root "recordDate"
35
+ map_content to: :content
36
+ end
37
+ end
38
+
39
+ class OriginInfo < Lutaml::Model::Serializable
40
+ attribute :date_issued, RecordDate, collection: true
41
+
42
+ xml do
43
+ root "originInfo"
44
+ map_element "dateIssued", to: :date_issued
45
+ end
46
+ end
47
+
48
+ ### Enumeration
49
+
50
+ class Ceramic < Lutaml::Model::Serializable
51
+ attribute :type, :string
52
+ attribute :firing_temperature, :integer
53
+ end
54
+
55
+ class CeramicCollection < Lutaml::Model::Serializable
56
+ attribute :featured_piece,
57
+ Ceramic,
58
+ values: [
59
+ Ceramic.new(type: "Porcelain", firing_temperature: 1300),
60
+ Ceramic.new(type: "Stoneware", firing_temperature: 1200),
61
+ Ceramic.new(type: "Earthenware", firing_temperature: 1000),
62
+ ]
63
+ end
64
+
65
+ class GlazeTechnique < Lutaml::Model::Serializable
66
+ attribute :name, :string, values: ["Celadon", "Raku", "Majolica"]
67
+ end
68
+ end
69
+
70
+ RSpec.describe Lutaml::Model::Serializable do
71
+ describe ".model" do
72
+ it "sets the model for the class" do
73
+ expect do
74
+ described_class.model(SerializeableSpec::TestModel)
75
+ end.to change(
76
+ described_class, :model
77
+ )
78
+ .from(nil)
79
+ .to(SerializeableSpec::TestModel)
80
+ end
81
+ end
82
+
83
+ describe ".attribute" do
84
+ subject(:mapper) { described_class.new }
85
+
86
+ it "adds the attribute and getter setter for that attribute" do
87
+ expect { described_class.attribute("foo", Lutaml::Model::Type::String) }
88
+ .to change { described_class.attributes.keys }.from([]).to(["foo"])
89
+ .and change { mapper.respond_to?(:foo) }.from(false).to(true)
90
+ .and change { mapper.respond_to?(:foo=) }.from(false).to(true)
91
+ end
92
+ end
93
+
94
+ describe ".hash_representation" do
95
+ context "when model is separate" do
96
+ let(:instance) do
97
+ SerializeableSpec::TestModel.new(name: "John", age: 18)
98
+ end
99
+
100
+ let(:expected_hash) do
101
+ {
102
+ "name" => "John",
103
+ "age" => "18",
104
+ }
105
+ end
106
+
107
+ it "return hash representation" do
108
+ generate_hash = SerializeableSpec::TestModelMapper.hash_representation(
109
+ instance, :yaml
110
+ )
111
+ expect(generate_hash).to eq(expected_hash)
112
+ end
113
+ end
114
+
115
+ context "when model is self" do
116
+ let(:instance) do
117
+ SerializeableSpec::TestMapper.new(name: "John", age: 18)
118
+ end
119
+
120
+ let(:expected_hash) do
121
+ {
122
+ na: "John",
123
+ ag: "18",
124
+ }
125
+ end
126
+
127
+ it "return hash representation" do
128
+ generate_hash = SerializeableSpec::TestMapper.hash_representation(
129
+ instance, :yaml
130
+ )
131
+ expect(generate_hash).to eq(expected_hash)
132
+ end
133
+ end
134
+ end
135
+
136
+ describe ".mappings_for" do
137
+ context "when mapping is defined" do
138
+ it "returns the defined mapping" do
139
+ actual_mappings = SerializeableSpec::TestMapper.mappings_for(:yaml).mappings
140
+
141
+ expect(actual_mappings[0].name).to eq(:na)
142
+ expect(actual_mappings[0].to).to eq(:name)
143
+
144
+ expect(actual_mappings[1].name).to eq(:ag)
145
+ expect(actual_mappings[1].to).to eq(:age)
146
+ end
147
+ end
148
+
149
+ context "when mapping is not defined" do
150
+ it "maps attributes to mappings" do
151
+ allow(SerializeableSpec::TestMapper.mappings).to receive(:[]).with(:yaml).and_return(nil)
152
+
153
+ actual_mappings = SerializeableSpec::TestMapper.mappings_for(:yaml).mappings
154
+
155
+ expect(actual_mappings[0].name).to eq("name")
156
+ expect(actual_mappings[0].to).to eq(:name)
157
+
158
+ expect(actual_mappings[1].name).to eq("age")
159
+ expect(actual_mappings[1].to).to eq(:age)
160
+ end
161
+ end
162
+ end
163
+
164
+ describe ".apply_child_mappings" do
165
+ let(:child_mappings) do
166
+ {
167
+ id: :key,
168
+ path: %i[path link],
169
+ name: %i[path name],
170
+ }
171
+ end
172
+
173
+ let(:hash) do
174
+ {
175
+ "foo" => {
176
+ "path" => {
177
+ "link" => "link one",
178
+ "name" => "one",
179
+ },
180
+ },
181
+ "abc" => {
182
+ "path" => {
183
+ "link" => "link two",
184
+ "name" => "two",
185
+ },
186
+ },
187
+ "hello" => {
188
+ "path" => {
189
+ "link" => "link three",
190
+ "name" => "three",
191
+ },
192
+ },
193
+ }
194
+ end
195
+
196
+ let(:expected_value) do
197
+ [
198
+ { id: "foo", path: "link one", name: "one" },
199
+ { id: "abc", path: "link two", name: "two" },
200
+ { id: "hello", path: "link three", name: "three" },
201
+ ]
202
+ end
203
+
204
+ it "generates hash based on child_mappings" do
205
+ expect(described_class.apply_child_mappings(hash,
206
+ child_mappings)).to eq(expected_value)
207
+ end
208
+ end
209
+
210
+ describe "XML root name override" do
211
+ it "uses root name defined at the component class" do
212
+ record_date = SerializeableSpec::RecordDate.new(content: "2021-01-01")
213
+ expected_xml = "<recordDate>2021-01-01</recordDate>"
214
+ expect(record_date.to_xml).to eq(expected_xml)
215
+ end
216
+
217
+ it "uses mapped element name at the aggregating class, overriding root name" do
218
+ origin_info = SerializeableSpec::OriginInfo.new(date_issued: [SerializeableSpec::RecordDate.new(content: "2021-01-01")])
219
+ expected_xml = <<~XML
220
+ <originInfo><dateIssued>2021-01-01</dateIssued></originInfo>
221
+ XML
222
+ expect(origin_info.to_xml).to be_equivalent_to(expected_xml)
223
+ end
224
+ end
225
+
226
+ describe "String enumeration" do
227
+ context "when assigning an invalid value" do
228
+ it "raises an error after creation after validate" do
229
+ glaze = SerializeableSpec::GlazeTechnique.new(name: "Celadon")
230
+ glaze.name = "Tenmoku"
231
+ expect do
232
+ glaze.validate!
233
+ end.to raise_error(Lutaml::Model::ValidationError) do |error|
234
+ expect(error).to include(Lutaml::Model::InvalidValueError)
235
+ expect(error.error_messages).to include("name is `Tenmoku`, must be one of the following [Celadon, Raku, Majolica]")
236
+ end
237
+ end
238
+ end
239
+
240
+ context "when assigning a valid value" do
241
+ it "changes the value after creation" do
242
+ glaze = SerializeableSpec::GlazeTechnique.new(name: "Celadon")
243
+ glaze.name = "Raku"
244
+ expect(glaze.name).to eq("Raku")
245
+ end
246
+
247
+ it "assigns the value during creation" do
248
+ glaze = SerializeableSpec::GlazeTechnique.new(name: "Majolica")
249
+ expect(glaze.name).to eq("Majolica")
250
+ end
251
+ end
252
+ end
253
+
254
+ describe "Serializable object enumeration" do
255
+ context "when assigning an invalid value" do
256
+ it "raises ValidationError containing InvalidValueError after creation" do
257
+ glaze = SerializeableSpec::GlazeTechnique.new(name: "Celadon")
258
+ glaze.name = "Tenmoku"
259
+ expect do
260
+ glaze.validate!
261
+ end.to raise_error(Lutaml::Model::ValidationError) do |error|
262
+ expect(error).to include(Lutaml::Model::InvalidValueError)
263
+ expect(error.error_messages).to include(a_string_matching(/name is `Tenmoku`, must be one of the following/))
264
+ end
265
+ end
266
+
267
+ it "raises ValidationError containing InvalidValueError during creation" do
268
+ expect do
269
+ SerializeableSpec::GlazeTechnique.new(name: "Crystalline").validate!
270
+ end.to raise_error(Lutaml::Model::ValidationError) do |error|
271
+ expect(error).to include(Lutaml::Model::InvalidValueError)
272
+ expect(error.error_messages).to include(a_string_matching(/name is `Crystalline`, must be one of the following/))
273
+ end
274
+ end
275
+ end
276
+
277
+ context "when assigning a valid value" do
278
+ it "changes the value after creation" do
279
+ collection = SerializeableSpec::CeramicCollection.new(
280
+ featured_piece: SerializeableSpec::Ceramic.new(type: "Porcelain",
281
+ firing_temperature: 1300),
282
+ )
283
+ collection.featured_piece = SerializeableSpec::Ceramic.new(type: "Stoneware",
284
+ firing_temperature: 1200)
285
+ expect(collection.featured_piece.type).to eq("Stoneware")
286
+ end
287
+
288
+ it "assigns the value during creation" do
289
+ collection = SerializeableSpec::CeramicCollection.new(
290
+ featured_piece: SerializeableSpec::Ceramic.new(type: "Earthenware",
291
+ firing_temperature: 1000),
292
+ )
293
+ expect(collection.featured_piece.type).to eq("Earthenware")
294
+ end
295
+ end
296
+ end
297
+ end
@@ -0,0 +1,90 @@
1
+ require "spec_helper"
2
+
3
+ class TestSerializable < Lutaml::Model::Serializable
4
+ attribute :name, :string, values: ["Alice", "Bob", "Charlie"]
5
+ attribute :email, :string, pattern: /.*?\S+@.+\.\S+/
6
+ attribute :age, :integer, collection: 1..3
7
+
8
+ xml do
9
+ root "test"
10
+ map_element "name", to: :name
11
+ map_element "age", to: :age
12
+ end
13
+
14
+ json do
15
+ map "name", to: :name
16
+ map "age", to: :age
17
+ end
18
+
19
+ yaml do
20
+ map "name", to: :name
21
+ map "age", to: :age
22
+ end
23
+
24
+ toml do
25
+ map "name", to: :name
26
+ map "age", to: :age
27
+ end
28
+ end
29
+
30
+ RSpec.describe Lutaml::Model::Serializable do
31
+ let(:valid_instance) do
32
+ TestSerializable.new(name: "Alice", age: [30], email: "alice@gmail.com")
33
+ end
34
+
35
+ let(:invalid_instance) do
36
+ TestSerializable.new(name: "David", age: [25, 30, 35, 40], email: "david@gmail")
37
+ end
38
+
39
+ describe "serialization methods" do
40
+ it "does not raise validation errors when calling to_xml" do
41
+ expect { invalid_instance.to_xml }.not_to raise_error
42
+ end
43
+
44
+ it "does not raise validation errors when calling to_json" do
45
+ expect { invalid_instance.to_json }.not_to raise_error
46
+ end
47
+
48
+ it "does not raise validation errors when calling to_yaml" do
49
+ expect { invalid_instance.to_yaml }.not_to raise_error
50
+ end
51
+
52
+ it "does not raise validation errors when calling to_toml" do
53
+ expect { invalid_instance.to_toml }.not_to raise_error
54
+ end
55
+ end
56
+
57
+ describe "setting attributes" do
58
+ it "does not raise validation errors when setting valid attributes" do
59
+ expect { valid_instance.name = "Bob" }.not_to raise_error
60
+ expect { valid_instance.age = [25, 30] }.not_to raise_error
61
+ end
62
+
63
+ it "does not raise validation errors when setting invalid attributes" do
64
+ expect { invalid_instance.name = "David" }.not_to raise_error
65
+ expect { invalid_instance.age = [25, 30, 35, 40] }.not_to raise_error
66
+ end
67
+ end
68
+
69
+ describe "validate method" do
70
+ it "returns errors for invalid attributes" do
71
+ errors = invalid_instance.validate
72
+ expect(errors).not_to be_empty
73
+ expect(errors[0]).to be_a(Lutaml::Model::InvalidValueError)
74
+ expect(errors[1]).to be_a(Lutaml::Model::PatternNotMatchedError)
75
+ expect(errors[2]).to be_a(Lutaml::Model::CollectionCountOutOfRangeError)
76
+ end
77
+ end
78
+
79
+ describe "validate! method" do
80
+ it "raises ValidationError for invalid attributes" do
81
+ expect do
82
+ invalid_instance.validate!
83
+ end.to raise_error(Lutaml::Model::ValidationError)
84
+ end
85
+
86
+ it "does not raise ValidationError for valid attributes" do
87
+ expect { valid_instance.validate! }.not_to raise_error
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,314 @@
1
+ require "spec_helper"
2
+ require "lutaml/model"
3
+
4
+ module SimpleModel
5
+ class Address < Lutaml::Model::Serializable
6
+ attribute :street, :string
7
+ attribute :city, :string
8
+
9
+ xml do
10
+ root "address"
11
+ map_element "street", to: :street
12
+ map_element "city", to: :city
13
+ end
14
+
15
+ yaml do
16
+ map "street", to: :street
17
+ map "city", to: :city
18
+ end
19
+
20
+ json do
21
+ map "street", to: :street
22
+ map "city", to: :city
23
+ end
24
+
25
+ toml do
26
+ map "street", to: :street
27
+ map "city", to: :city
28
+ end
29
+ end
30
+
31
+ class Room < Lutaml::Model::Serializable
32
+ attribute :name, :string, default: -> { "Unnamed room" }
33
+ attribute :size, :integer
34
+
35
+ xml do
36
+ root "room"
37
+ map_element "name", to: :name
38
+ map_element "size", to: :size
39
+ end
40
+
41
+ yaml do
42
+ map "name", to: :name
43
+ map "size", to: :size
44
+ end
45
+
46
+ json do
47
+ map "name", to: :name
48
+ map "size", to: :size
49
+ end
50
+
51
+ toml do
52
+ map "name", to: :name
53
+ map "size", to: :size
54
+ end
55
+ end
56
+
57
+ class Building < Lutaml::Model::Serializable
58
+ attribute :name, :string, default: -> { "Unnamed building" }
59
+ attribute :address, Address
60
+ attribute :rooms, Room, collection: true
61
+
62
+ xml do
63
+ root "building"
64
+ map_attribute "name", to: :name
65
+ map_element "address", to: :address
66
+ map_element "room", to: :rooms
67
+ end
68
+
69
+ yaml do
70
+ map "name", to: :name
71
+ map "address", to: :address
72
+ map "rooms", to: :rooms
73
+ end
74
+
75
+ json do
76
+ map "name", to: :name
77
+ map "address", to: :address
78
+ map "rooms", to: :rooms
79
+ end
80
+
81
+ toml do
82
+ map "name", to: :name
83
+ map "address", to: :address
84
+ map "rooms", to: :rooms
85
+ end
86
+ end
87
+ end
88
+
89
+ RSpec.describe SimpleModel do
90
+ let(:address) do
91
+ SimpleModel::Address.new(street: "123 Main St", city: "Anytown")
92
+ end
93
+ let(:rooms) do
94
+ [SimpleModel::Room.new(name: "Conference Room", size: 30),
95
+ SimpleModel::Room.new(name: "Office", size: 20)]
96
+ end
97
+ let(:attributes) { { name: "Headquarters", address: address, rooms: rooms } }
98
+ let(:model) { SimpleModel::Building.new(attributes) }
99
+
100
+ let(:model_xml) do
101
+ <<~XML
102
+ <building name="Headquarters">
103
+ <address>
104
+ <street>123 Main St</street>
105
+ <city>Anytown</city>
106
+ </address>
107
+ <room>
108
+ <name>Conference Room</name>
109
+ <size>30</size>
110
+ </room>
111
+ <room>
112
+ <name>Office</name>
113
+ <size>20</size>
114
+ </room>
115
+ </building>
116
+ XML
117
+ end
118
+
119
+ let(:model_yaml) do
120
+ <<~YAML
121
+ ---
122
+ name: Headquarters
123
+ address:
124
+ street: 123 Main St
125
+ city: Anytown
126
+ rooms:
127
+ - name: Conference Room
128
+ size: 30
129
+ - name: Office
130
+ size: 20
131
+ YAML
132
+ end
133
+
134
+ let(:model_json) do
135
+ {
136
+ name: "Headquarters",
137
+ address: {
138
+ street: "123 Main St",
139
+ city: "Anytown",
140
+ },
141
+ rooms: [
142
+ {
143
+ name: "Conference Room",
144
+ size: 30,
145
+ },
146
+ {
147
+ name: "Office",
148
+ size: 20,
149
+ },
150
+ ],
151
+ }.to_json
152
+ end
153
+
154
+ let(:model_toml) do
155
+ <<~TOML
156
+ name = "Headquarters"
157
+ [address]
158
+ city = "Anytown"
159
+ street = "123 Main St"
160
+ [[rooms]]
161
+ name = "Conference Room"
162
+ size = 30
163
+ [[rooms]]
164
+ name = "Office"
165
+ size = 20
166
+ TOML
167
+ end
168
+
169
+ let(:model_yaml_missing_element) do
170
+ <<~YAML
171
+ ---
172
+ name: Headquarters
173
+ rooms:
174
+ - name: Conference Room
175
+ size: 30
176
+ - name: Office
177
+ size: 20
178
+ YAML
179
+ end
180
+
181
+ it "initializes with default values" do
182
+ default_model = SimpleModel::Building.new
183
+ expect(default_model.name).to eq("Unnamed building")
184
+ expect(default_model.address).to be_nil
185
+ expect(default_model.rooms).to eq([])
186
+ end
187
+
188
+ it "serializes to XML" do
189
+ expected_xml = model_xml.strip
190
+ expect(model.to_xml.strip).to eq(expected_xml)
191
+ end
192
+
193
+ it "deserializes from XML" do
194
+ sample = SimpleModel::Building.from_xml(model_xml)
195
+ expect(sample.name).to eq("Headquarters")
196
+ expect(sample.address.street).to eq("123 Main St")
197
+ expect(sample.address.city).to eq("Anytown")
198
+ expect(sample.rooms.size).to eq(2)
199
+ expect(sample.rooms[0].name).to eq("Conference Room")
200
+ expect(sample.rooms[0].size).to eq(30)
201
+ expect(sample.rooms[1].name).to eq("Office")
202
+ expect(sample.rooms[1].size).to eq(20)
203
+ end
204
+
205
+ it "round-trips XML" do
206
+ xml = model.to_xml
207
+ new_model = SimpleModel::Building.from_xml(xml)
208
+ expect(new_model.name).to eq(model.name)
209
+ expect(new_model.address.street).to eq(model.address.street)
210
+ expect(new_model.address.city).to eq(model.address.city)
211
+ expect(new_model.rooms.size).to eq(model.rooms.size)
212
+ model.rooms.each_with_index do |room, index|
213
+ expect(new_model.rooms[index].name).to eq(room.name)
214
+ expect(new_model.rooms[index].size).to eq(room.size)
215
+ end
216
+ end
217
+
218
+ it "serializes to YAML" do
219
+ expected_yaml = model_yaml.strip
220
+ expect(model.to_yaml.strip).to eq(expected_yaml)
221
+ end
222
+
223
+ it "deserializes from YAML" do
224
+ sample = SimpleModel::Building.from_yaml(model_yaml)
225
+ expect(sample.name).to eq("Headquarters")
226
+ expect(sample.address.street).to eq("123 Main St")
227
+ expect(sample.address.city).to eq("Anytown")
228
+ expect(sample.rooms.size).to eq(2)
229
+ expect(sample.rooms[0].name).to eq("Conference Room")
230
+ expect(sample.rooms[0].size).to eq(30)
231
+ expect(sample.rooms[1].name).to eq("Office")
232
+ expect(sample.rooms[1].size).to eq(20)
233
+ end
234
+
235
+ it "round-trips YAML" do
236
+ yaml = model.to_yaml
237
+ new_model = SimpleModel::Building.from_yaml(yaml)
238
+ expect(new_model.name).to eq(model.name)
239
+ expect(new_model.address.street).to eq(model.address.street)
240
+ expect(new_model.address.city).to eq(model.address.city)
241
+ expect(new_model.rooms.size).to eq(model.rooms.size)
242
+ model.rooms.each_with_index do |room, index|
243
+ expect(new_model.rooms[index].name).to eq(room.name)
244
+ expect(new_model.rooms[index].size).to eq(room.size)
245
+ end
246
+ end
247
+
248
+ it "serializes to YAML without the omitted element" do
249
+ model_without_address = SimpleModel::Building.new(name: "Headquarters",
250
+ rooms: rooms)
251
+ expected_yaml = model_yaml_missing_element.strip
252
+ expect(model_without_address.to_yaml.strip).to eq(expected_yaml)
253
+ end
254
+
255
+ it "serializes to JSON" do
256
+ expected_json = model_json.strip
257
+ expect(model.to_json.strip).to eq(expected_json)
258
+ end
259
+
260
+ it "deserializes from JSON" do
261
+ sample = SimpleModel::Building.from_json(model_json)
262
+ expect(sample.name).to eq("Headquarters")
263
+ expect(sample.address.street).to eq("123 Main St")
264
+ expect(sample.address.city).to eq("Anytown")
265
+ expect(sample.rooms.size).to eq(2)
266
+ expect(sample.rooms[0].name).to eq("Conference Room")
267
+ expect(sample.rooms[0].size).to eq(30)
268
+ expect(sample.rooms[1].name).to eq("Office")
269
+ expect(sample.rooms[1].size).to eq(20)
270
+ end
271
+
272
+ it "round-trips JSON" do
273
+ json = model.to_json
274
+ new_model = SimpleModel::Building.from_json(json)
275
+ expect(new_model.name).to eq(model.name)
276
+ expect(new_model.address.street).to eq(model.address.street)
277
+ expect(new_model.address.city).to eq(model.address.city)
278
+ expect(new_model.rooms.size).to eq(model.rooms.size)
279
+ model.rooms.each_with_index do |room, index|
280
+ expect(new_model.rooms[index].name).to eq(room.name)
281
+ expect(new_model.rooms[index].size).to eq(room.size)
282
+ end
283
+ end
284
+
285
+ it "serializes to TOML" do
286
+ expected_toml = model_toml.strip
287
+ expect(model.to_toml.strip).to eq(expected_toml)
288
+ end
289
+
290
+ it "deserializes from TOML" do
291
+ sample = SimpleModel::Building.from_toml(model_toml)
292
+ expect(sample.name).to eq("Headquarters")
293
+ expect(sample.address.street).to eq("123 Main St")
294
+ expect(sample.address.city).to eq("Anytown")
295
+ expect(sample.rooms.size).to eq(2)
296
+ expect(sample.rooms[0].name).to eq("Conference Room")
297
+ expect(sample.rooms[0].size).to eq(30)
298
+ expect(sample.rooms[1].name).to eq("Office")
299
+ expect(sample.rooms[1].size).to eq(20)
300
+ end
301
+
302
+ it "round-trips TOML" do
303
+ toml = model.to_toml
304
+ new_model = SimpleModel::Building.from_toml(toml)
305
+ expect(new_model.name).to eq(model.name)
306
+ expect(new_model.address.street).to eq(model.address.street)
307
+ expect(new_model.address.city).to eq(model.address.city)
308
+ expect(new_model.rooms.size).to eq(model.rooms.size)
309
+ model.rooms.each_with_index do |room, index|
310
+ expect(new_model.rooms[index].name).to eq(room.name)
311
+ expect(new_model.rooms[index].size).to eq(room.size)
312
+ end
313
+ end
314
+ end