lutaml-model 0.3.24 → 0.3.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +35 -16
  3. data/README.adoc +274 -28
  4. data/lib/lutaml/model/attribute.rb +18 -8
  5. data/lib/lutaml/model/error/type_error.rb +9 -0
  6. data/lib/lutaml/model/error/unknown_type_error.rb +9 -0
  7. data/lib/lutaml/model/error/validation_error.rb +0 -1
  8. data/lib/lutaml/model/error.rb +2 -0
  9. data/lib/lutaml/model/serialize.rb +6 -1
  10. data/lib/lutaml/model/type/boolean.rb +38 -0
  11. data/lib/lutaml/model/type/date.rb +35 -0
  12. data/lib/lutaml/model/type/date_time.rb +32 -4
  13. data/lib/lutaml/model/type/decimal.rb +42 -0
  14. data/lib/lutaml/model/type/float.rb +37 -0
  15. data/lib/lutaml/model/type/hash.rb +62 -0
  16. data/lib/lutaml/model/type/integer.rb +41 -0
  17. data/lib/lutaml/model/type/string.rb +49 -0
  18. data/lib/lutaml/model/type/time.rb +49 -0
  19. data/lib/lutaml/model/type/time_without_date.rb +37 -5
  20. data/lib/lutaml/model/type/value.rb +52 -0
  21. data/lib/lutaml/model/type.rb +50 -114
  22. data/lib/lutaml/model/version.rb +1 -1
  23. data/lib/lutaml/model/xml_adapter/builder/nokogiri.rb +5 -2
  24. data/lib/lutaml/model/xml_adapter/ox_adapter.rb +2 -1
  25. data/lib/lutaml/model/xml_adapter/xml_document.rb +0 -2
  26. data/lutaml-model.gemspec +1 -1
  27. data/spec/address_spec.rb +170 -0
  28. data/spec/fixtures/address.rb +33 -0
  29. data/spec/fixtures/person.rb +73 -0
  30. data/spec/fixtures/sample_model.rb +40 -0
  31. data/spec/fixtures/vase.rb +38 -0
  32. data/spec/fixtures/xml/special_char.xml +13 -0
  33. data/spec/lutaml/model/attribute_spec.rb +112 -0
  34. data/spec/lutaml/model/collection_spec.rb +299 -0
  35. data/spec/lutaml/model/comparable_model_spec.rb +106 -0
  36. data/spec/lutaml/model/custom_model_spec.rb +410 -0
  37. data/spec/lutaml/model/custom_serialization_spec.rb +170 -0
  38. data/spec/lutaml/model/defaults_spec.rb +221 -0
  39. data/spec/lutaml/model/delegation_spec.rb +340 -0
  40. data/spec/lutaml/model/inheritance_spec.rb +92 -0
  41. data/spec/lutaml/model/json_adapter_spec.rb +37 -0
  42. data/spec/lutaml/model/key_value_mapping_spec.rb +86 -0
  43. data/spec/lutaml/model/map_content_spec.rb +118 -0
  44. data/spec/lutaml/model/mixed_content_spec.rb +625 -0
  45. data/spec/lutaml/model/namespace_spec.rb +57 -0
  46. data/spec/lutaml/model/ordered_content_spec.rb +83 -0
  47. data/spec/lutaml/model/render_nil_spec.rb +138 -0
  48. data/spec/lutaml/model/schema/json_schema_spec.rb +79 -0
  49. data/spec/lutaml/model/schema/relaxng_schema_spec.rb +60 -0
  50. data/spec/lutaml/model/schema/xsd_schema_spec.rb +55 -0
  51. data/spec/lutaml/model/schema/yaml_schema_spec.rb +47 -0
  52. data/spec/lutaml/model/serializable_spec.rb +297 -0
  53. data/spec/lutaml/model/serializable_validation_spec.rb +85 -0
  54. data/spec/lutaml/model/simple_model_spec.rb +314 -0
  55. data/spec/lutaml/model/toml_adapter_spec.rb +39 -0
  56. data/spec/lutaml/model/type/boolean_spec.rb +54 -0
  57. data/spec/lutaml/model/type/date_spec.rb +118 -0
  58. data/spec/lutaml/model/type/date_time_spec.rb +127 -0
  59. data/spec/lutaml/model/type/decimal_spec.rb +125 -0
  60. data/spec/lutaml/model/type/float_spec.rb +191 -0
  61. data/spec/lutaml/model/type/hash_spec.rb +63 -0
  62. data/spec/lutaml/model/type/integer_spec.rb +145 -0
  63. data/spec/lutaml/model/type/string_spec.rb +150 -0
  64. data/spec/lutaml/model/type/time_spec.rb +142 -0
  65. data/spec/lutaml/model/type/time_without_date_spec.rb +125 -0
  66. data/spec/lutaml/model/type_spec.rb +276 -0
  67. data/spec/lutaml/model/utils_spec.rb +79 -0
  68. data/spec/lutaml/model/validation_spec.rb +83 -0
  69. data/spec/lutaml/model/with_child_mapping_spec.rb +174 -0
  70. data/spec/lutaml/model/xml_adapter/nokogiri_adapter_spec.rb +56 -0
  71. data/spec/lutaml/model/xml_adapter/oga_adapter_spec.rb +56 -0
  72. data/spec/lutaml/model/xml_adapter/ox_adapter_spec.rb +61 -0
  73. data/spec/lutaml/model/xml_adapter/xml_namespace_spec.rb +251 -0
  74. data/spec/lutaml/model/xml_adapter_spec.rb +178 -0
  75. data/spec/lutaml/model/xml_mapping_spec.rb +863 -0
  76. data/spec/lutaml/model/yaml_adapter_spec.rb +30 -0
  77. data/spec/lutaml/model_spec.rb +1 -0
  78. data/spec/person_spec.rb +161 -0
  79. data/spec/spec_helper.rb +33 -0
  80. metadata +66 -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,85 @@
1
+ require "spec_helper"
2
+
3
+ class TestSerializable < Lutaml::Model::Serializable
4
+ attribute :name, :string, values: ["Alice", "Bob", "Charlie"]
5
+ attribute :age, :integer, collection: 1..3
6
+
7
+ xml do
8
+ root "test"
9
+ map_element "name", to: :name
10
+ map_element "age", to: :age
11
+ end
12
+
13
+ json do
14
+ map "name", to: :name
15
+ map "age", to: :age
16
+ end
17
+
18
+ yaml do
19
+ map "name", to: :name
20
+ map "age", to: :age
21
+ end
22
+
23
+ toml do
24
+ map "name", to: :name
25
+ map "age", to: :age
26
+ end
27
+ end
28
+
29
+ RSpec.describe Lutaml::Model::Serializable do
30
+ let(:valid_instance) { TestSerializable.new(name: "Alice", age: [30]) }
31
+ let(:invalid_instance) do
32
+ TestSerializable.new(name: "David", age: [25, 30, 35, 40])
33
+ end
34
+
35
+ describe "serialization methods" do
36
+ it "does not raise validation errors when calling to_xml" do
37
+ expect { invalid_instance.to_xml }.not_to raise_error
38
+ end
39
+
40
+ it "does not raise validation errors when calling to_json" do
41
+ expect { invalid_instance.to_json }.not_to raise_error
42
+ end
43
+
44
+ it "does not raise validation errors when calling to_yaml" do
45
+ expect { invalid_instance.to_yaml }.not_to raise_error
46
+ end
47
+
48
+ it "does not raise validation errors when calling to_toml" do
49
+ expect { invalid_instance.to_toml }.not_to raise_error
50
+ end
51
+ end
52
+
53
+ describe "setting attributes" do
54
+ it "does not raise validation errors when setting valid attributes" do
55
+ expect { valid_instance.name = "Bob" }.not_to raise_error
56
+ expect { valid_instance.age = [25, 30] }.not_to raise_error
57
+ end
58
+
59
+ it "does not raise validation errors when setting invalid attributes" do
60
+ expect { invalid_instance.name = "David" }.not_to raise_error
61
+ expect { invalid_instance.age = [25, 30, 35, 40] }.not_to raise_error
62
+ end
63
+ end
64
+
65
+ describe "validate method" do
66
+ it "returns errors for invalid attributes" do
67
+ errors = invalid_instance.validate
68
+ expect(errors).not_to be_empty
69
+ expect(errors.first).to be_a(Lutaml::Model::InvalidValueError)
70
+ expect(errors.last).to be_a(Lutaml::Model::CollectionCountOutOfRangeError)
71
+ end
72
+ end
73
+
74
+ describe "validate! method" do
75
+ it "raises ValidationError for invalid attributes" do
76
+ expect do
77
+ invalid_instance.validate!
78
+ end.to raise_error(Lutaml::Model::ValidationError)
79
+ end
80
+
81
+ it "does not raise ValidationError for valid attributes" do
82
+ expect { valid_instance.validate! }.not_to raise_error
83
+ end
84
+ end
85
+ 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