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.
- checksums.yaml +4 -4
- data/.rubocop_todo.yml +35 -16
- data/README.adoc +274 -28
- data/lib/lutaml/model/attribute.rb +18 -8
- data/lib/lutaml/model/error/type_error.rb +9 -0
- data/lib/lutaml/model/error/unknown_type_error.rb +9 -0
- data/lib/lutaml/model/error/validation_error.rb +0 -1
- data/lib/lutaml/model/error.rb +2 -0
- data/lib/lutaml/model/serialize.rb +6 -1
- data/lib/lutaml/model/type/boolean.rb +38 -0
- data/lib/lutaml/model/type/date.rb +35 -0
- data/lib/lutaml/model/type/date_time.rb +32 -4
- data/lib/lutaml/model/type/decimal.rb +42 -0
- data/lib/lutaml/model/type/float.rb +37 -0
- data/lib/lutaml/model/type/hash.rb +62 -0
- data/lib/lutaml/model/type/integer.rb +41 -0
- data/lib/lutaml/model/type/string.rb +49 -0
- data/lib/lutaml/model/type/time.rb +49 -0
- data/lib/lutaml/model/type/time_without_date.rb +37 -5
- data/lib/lutaml/model/type/value.rb +52 -0
- data/lib/lutaml/model/type.rb +50 -114
- data/lib/lutaml/model/version.rb +1 -1
- data/lib/lutaml/model/xml_adapter/builder/nokogiri.rb +5 -2
- data/lib/lutaml/model/xml_adapter/ox_adapter.rb +2 -1
- data/lib/lutaml/model/xml_adapter/xml_document.rb +0 -2
- data/lutaml-model.gemspec +1 -1
- data/spec/address_spec.rb +170 -0
- data/spec/fixtures/address.rb +33 -0
- data/spec/fixtures/person.rb +73 -0
- data/spec/fixtures/sample_model.rb +40 -0
- data/spec/fixtures/vase.rb +38 -0
- data/spec/fixtures/xml/special_char.xml +13 -0
- data/spec/lutaml/model/attribute_spec.rb +112 -0
- data/spec/lutaml/model/collection_spec.rb +299 -0
- data/spec/lutaml/model/comparable_model_spec.rb +106 -0
- data/spec/lutaml/model/custom_model_spec.rb +410 -0
- data/spec/lutaml/model/custom_serialization_spec.rb +170 -0
- data/spec/lutaml/model/defaults_spec.rb +221 -0
- data/spec/lutaml/model/delegation_spec.rb +340 -0
- data/spec/lutaml/model/inheritance_spec.rb +92 -0
- data/spec/lutaml/model/json_adapter_spec.rb +37 -0
- data/spec/lutaml/model/key_value_mapping_spec.rb +86 -0
- data/spec/lutaml/model/map_content_spec.rb +118 -0
- data/spec/lutaml/model/mixed_content_spec.rb +625 -0
- data/spec/lutaml/model/namespace_spec.rb +57 -0
- data/spec/lutaml/model/ordered_content_spec.rb +83 -0
- data/spec/lutaml/model/render_nil_spec.rb +138 -0
- data/spec/lutaml/model/schema/json_schema_spec.rb +79 -0
- data/spec/lutaml/model/schema/relaxng_schema_spec.rb +60 -0
- data/spec/lutaml/model/schema/xsd_schema_spec.rb +55 -0
- data/spec/lutaml/model/schema/yaml_schema_spec.rb +47 -0
- data/spec/lutaml/model/serializable_spec.rb +297 -0
- data/spec/lutaml/model/serializable_validation_spec.rb +85 -0
- data/spec/lutaml/model/simple_model_spec.rb +314 -0
- data/spec/lutaml/model/toml_adapter_spec.rb +39 -0
- data/spec/lutaml/model/type/boolean_spec.rb +54 -0
- data/spec/lutaml/model/type/date_spec.rb +118 -0
- data/spec/lutaml/model/type/date_time_spec.rb +127 -0
- data/spec/lutaml/model/type/decimal_spec.rb +125 -0
- data/spec/lutaml/model/type/float_spec.rb +191 -0
- data/spec/lutaml/model/type/hash_spec.rb +63 -0
- data/spec/lutaml/model/type/integer_spec.rb +145 -0
- data/spec/lutaml/model/type/string_spec.rb +150 -0
- data/spec/lutaml/model/type/time_spec.rb +142 -0
- data/spec/lutaml/model/type/time_without_date_spec.rb +125 -0
- data/spec/lutaml/model/type_spec.rb +276 -0
- data/spec/lutaml/model/utils_spec.rb +79 -0
- data/spec/lutaml/model/validation_spec.rb +83 -0
- data/spec/lutaml/model/with_child_mapping_spec.rb +174 -0
- data/spec/lutaml/model/xml_adapter/nokogiri_adapter_spec.rb +56 -0
- data/spec/lutaml/model/xml_adapter/oga_adapter_spec.rb +56 -0
- data/spec/lutaml/model/xml_adapter/ox_adapter_spec.rb +61 -0
- data/spec/lutaml/model/xml_adapter/xml_namespace_spec.rb +251 -0
- data/spec/lutaml/model/xml_adapter_spec.rb +178 -0
- data/spec/lutaml/model/xml_mapping_spec.rb +863 -0
- data/spec/lutaml/model/yaml_adapter_spec.rb +30 -0
- data/spec/lutaml/model_spec.rb +1 -0
- data/spec/person_spec.rb +161 -0
- data/spec/spec_helper.rb +33 -0
- metadata +66 -2
@@ -0,0 +1,410 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
class CustomModelChild
|
4
|
+
attr_accessor :street, :city
|
5
|
+
end
|
6
|
+
|
7
|
+
class CustomModelParent
|
8
|
+
attr_accessor :first_name, :middle_name, :last_name, :child_mapper
|
9
|
+
|
10
|
+
def name
|
11
|
+
"#{first_name} #{last_name}"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class CustomModelChildMapper < Lutaml::Model::Serializable
|
16
|
+
model CustomModelChild
|
17
|
+
|
18
|
+
attribute :street, Lutaml::Model::Type::String
|
19
|
+
attribute :city, Lutaml::Model::Type::String
|
20
|
+
|
21
|
+
xml do
|
22
|
+
map_element :street, to: :street
|
23
|
+
map_element :city, to: :city
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
class CustomModelParentMapper < Lutaml::Model::Serializable
|
28
|
+
model CustomModelParent
|
29
|
+
|
30
|
+
attribute :first_name, Lutaml::Model::Type::String
|
31
|
+
attribute :middle_name, Lutaml::Model::Type::String
|
32
|
+
attribute :last_name, Lutaml::Model::Type::String
|
33
|
+
attribute :child_mapper, CustomModelChildMapper
|
34
|
+
|
35
|
+
xml do
|
36
|
+
map_element :first_name, to: :first_name
|
37
|
+
map_element :middle_name, to: :middle_name
|
38
|
+
map_element :last_name, to: :last_name
|
39
|
+
map_element :CustomModelChild,
|
40
|
+
with: { to: :child_to_xml, from: :child_from_xml }
|
41
|
+
end
|
42
|
+
|
43
|
+
def child_to_xml(model, parent, doc)
|
44
|
+
child_el = doc.create_element("CustomModelChild")
|
45
|
+
street_el = doc.create_element("street")
|
46
|
+
city_el = doc.create_element("city")
|
47
|
+
|
48
|
+
doc.add_text(street_el, model.child_mapper.street)
|
49
|
+
doc.add_text(city_el, model.child_mapper.city)
|
50
|
+
|
51
|
+
doc.add_element(child_el, street_el)
|
52
|
+
doc.add_element(child_el, city_el)
|
53
|
+
doc.add_element(parent, child_el)
|
54
|
+
end
|
55
|
+
|
56
|
+
def child_from_xml(model, value)
|
57
|
+
model.child_mapper ||= CustomModelChild.new
|
58
|
+
|
59
|
+
model.child_mapper.street = value["street"].text
|
60
|
+
model.child_mapper.city = value["city"].text
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
module CustomModelSpecs
|
65
|
+
class TextElement < Lutaml::Model::Serializable
|
66
|
+
attribute :sup, :string
|
67
|
+
attribute :sub, :string
|
68
|
+
attribute :text, :string
|
69
|
+
|
70
|
+
xml do
|
71
|
+
root "text-element"
|
72
|
+
|
73
|
+
map_content to: :text
|
74
|
+
|
75
|
+
map_element "sup", to: :sup
|
76
|
+
map_element "sub", to: :sub
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
class Id
|
81
|
+
attr_accessor :id
|
82
|
+
end
|
83
|
+
|
84
|
+
class Docid < Lutaml::Model::Serializable
|
85
|
+
model Id
|
86
|
+
attribute :id, TextElement
|
87
|
+
|
88
|
+
xml do
|
89
|
+
root "docid", mixed: true
|
90
|
+
|
91
|
+
map_content to: :text, delegate: :id
|
92
|
+
map_element :sub, to: :sub, delegate: :id
|
93
|
+
map_element :sup, to: :sup, delegate: :id
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
class BibliographicItem
|
98
|
+
attr_accessor :type, :title, :schema_version, :language
|
99
|
+
|
100
|
+
def initialize(**attr)
|
101
|
+
@type = attr["type"]
|
102
|
+
@title = attr["title"]
|
103
|
+
@schema_version = attr["schema_version"]
|
104
|
+
@language = attr["language"]
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
class Bibdata < Lutaml::Model::Serializable
|
109
|
+
model BibliographicItem
|
110
|
+
end
|
111
|
+
|
112
|
+
class MixedWithNestedContent < Lutaml::Model::Serializable
|
113
|
+
attribute :street, :string, raw: true
|
114
|
+
attribute :city, :string, raw: true
|
115
|
+
attribute :bibdata, Bibdata
|
116
|
+
|
117
|
+
xml do
|
118
|
+
root "MixedWithNestedContent", mixed: true
|
119
|
+
|
120
|
+
map_element "street", to: :street
|
121
|
+
map_element "city", to: :city
|
122
|
+
map_element "bibdata",
|
123
|
+
to: :bibdata,
|
124
|
+
with: { from: :bibdata_from_xml, to: :bibdata_to_xml }
|
125
|
+
end
|
126
|
+
|
127
|
+
def bibdata_from_xml(model, value)
|
128
|
+
model.bibdata = BibliographicItem.new(
|
129
|
+
"type" => value["type"],
|
130
|
+
"title" => value["title"],
|
131
|
+
"language" => value["title"]["language"],
|
132
|
+
"schema_version" => value["schema-version"],
|
133
|
+
)
|
134
|
+
end
|
135
|
+
|
136
|
+
def bibdata_to_xml(model, _parent, doc)
|
137
|
+
attributes = {
|
138
|
+
"type" => model.bibdata.type,
|
139
|
+
"schema-version" => model.bibdata.schema_version,
|
140
|
+
}
|
141
|
+
lang = model.bibdata.language
|
142
|
+
|
143
|
+
doc.create_and_add_element("bibdata", attributes: attributes) do
|
144
|
+
doc.create_and_add_element("title", attributes: { language: lang }) do
|
145
|
+
doc.add_text(doc, model.bibdata.title.text)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
RSpec.describe "CustomModel" do
|
153
|
+
let(:parent_mapper) { CustomModelParentMapper }
|
154
|
+
let(:child_mapper) { CustomModelChildMapper }
|
155
|
+
let(:parent_model) { CustomModelParent }
|
156
|
+
let(:child_model) { CustomModelChild }
|
157
|
+
|
158
|
+
context "with JSON mapping" do
|
159
|
+
let(:input_json) do
|
160
|
+
{
|
161
|
+
first_name: "John",
|
162
|
+
last_name: "Doe",
|
163
|
+
child_mapper: {
|
164
|
+
street: "Oxford Street",
|
165
|
+
city: "London",
|
166
|
+
},
|
167
|
+
}.to_json
|
168
|
+
end
|
169
|
+
|
170
|
+
describe ".from_json" do
|
171
|
+
it "maps JSON string to custom model" do
|
172
|
+
instance = parent_mapper.from_json(input_json)
|
173
|
+
|
174
|
+
expect(instance.class).to eq(parent_model)
|
175
|
+
expect(instance.first_name).to eq("John")
|
176
|
+
expect(instance.last_name).to eq("Doe")
|
177
|
+
expect(instance.name).to eq("John Doe")
|
178
|
+
|
179
|
+
expect(instance.child_mapper.class).to eq(child_model)
|
180
|
+
expect(instance.child_mapper.street).to eq("Oxford Street")
|
181
|
+
expect(instance.child_mapper.city).to eq("London")
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
describe ".to_json" do
|
186
|
+
it "with wrong model raises an exception" do
|
187
|
+
msg = /argument is a 'String' but should be a '#{parent_model.name}/
|
188
|
+
|
189
|
+
expect do
|
190
|
+
parent_mapper.to_json("")
|
191
|
+
end.to raise_error(Lutaml::Model::IncorrectModelError, msg)
|
192
|
+
end
|
193
|
+
|
194
|
+
it "with correct model converts objects to json" do
|
195
|
+
instance = parent_mapper.from_json(input_json)
|
196
|
+
|
197
|
+
expect(parent_mapper.to_json(instance)).to eq(input_json)
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
context "with YAML mapping" do
|
203
|
+
let(:input_yaml) do
|
204
|
+
{
|
205
|
+
"first_name" => "John",
|
206
|
+
"last_name" => "Doe",
|
207
|
+
"child_mapper" => {
|
208
|
+
"street" => "Oxford Street",
|
209
|
+
"city" => "London",
|
210
|
+
},
|
211
|
+
}.to_yaml
|
212
|
+
end
|
213
|
+
|
214
|
+
describe ".from_yaml" do
|
215
|
+
it "maps YAML to custom model" do
|
216
|
+
instance = parent_mapper.from_yaml(input_yaml)
|
217
|
+
|
218
|
+
expect(instance.class).to eq(parent_model)
|
219
|
+
expect(instance.first_name).to eq("John")
|
220
|
+
expect(instance.last_name).to eq("Doe")
|
221
|
+
expect(instance.name).to eq("John Doe")
|
222
|
+
|
223
|
+
expect(instance.child_mapper.class).to eq(child_model)
|
224
|
+
expect(instance.child_mapper.street).to eq("Oxford Street")
|
225
|
+
expect(instance.child_mapper.city).to eq("London")
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
describe ".to_yaml" do
|
230
|
+
it "with wrong model raises an exception" do
|
231
|
+
msg = /argument is a 'String' but should be a '#{parent_model.name}/
|
232
|
+
|
233
|
+
expect do
|
234
|
+
parent_mapper.to_yaml("")
|
235
|
+
end.to raise_error(Lutaml::Model::IncorrectModelError, msg)
|
236
|
+
end
|
237
|
+
|
238
|
+
it "with correct model converts objects to yaml" do
|
239
|
+
instance = parent_mapper.from_yaml(input_yaml)
|
240
|
+
|
241
|
+
expect(parent_mapper.to_yaml(instance)).to eq(input_yaml)
|
242
|
+
end
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
context "with TOML mapping" do
|
247
|
+
let(:input_toml) do
|
248
|
+
<<~TOML
|
249
|
+
first_name = "John"
|
250
|
+
last_name = "Doe"
|
251
|
+
[child_mapper]
|
252
|
+
city = "London"
|
253
|
+
street = "Oxford Street"
|
254
|
+
TOML
|
255
|
+
end
|
256
|
+
|
257
|
+
describe ".from_toml" do
|
258
|
+
it "maps TOML content to custom model" do
|
259
|
+
instance = parent_mapper.from_toml(input_toml)
|
260
|
+
|
261
|
+
expect(instance.class).to eq(parent_model)
|
262
|
+
expect(instance.first_name).to eq("John")
|
263
|
+
expect(instance.last_name).to eq("Doe")
|
264
|
+
expect(instance.name).to eq("John Doe")
|
265
|
+
|
266
|
+
expect(instance.child_mapper.class).to eq(child_model)
|
267
|
+
expect(instance.child_mapper.street).to eq("Oxford Street")
|
268
|
+
expect(instance.child_mapper.city).to eq("London")
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
describe ".to_toml" do
|
273
|
+
it "with wrong model raises an exception" do
|
274
|
+
msg = /argument is a 'String' but should be a '#{parent_model.name}/
|
275
|
+
|
276
|
+
expect do
|
277
|
+
parent_mapper.to_toml("")
|
278
|
+
end.to raise_error(Lutaml::Model::IncorrectModelError, msg)
|
279
|
+
end
|
280
|
+
|
281
|
+
it "with correct model converts objects to toml" do
|
282
|
+
instance = parent_mapper.from_toml(input_toml)
|
283
|
+
|
284
|
+
expect(parent_mapper.to_toml(instance)).to eq(input_toml)
|
285
|
+
end
|
286
|
+
end
|
287
|
+
end
|
288
|
+
|
289
|
+
context "with XML mapping" do
|
290
|
+
let(:input_xml) do
|
291
|
+
<<~XML
|
292
|
+
<CustomModelParent>
|
293
|
+
<first_name>John</first_name>
|
294
|
+
<last_name>Doe</last_name>
|
295
|
+
<CustomModelChild>
|
296
|
+
<street>Oxford Street</street>
|
297
|
+
<city>London</city>
|
298
|
+
</CustomModelChild>
|
299
|
+
</CustomModelParent>
|
300
|
+
XML
|
301
|
+
end
|
302
|
+
|
303
|
+
describe ".from_xml" do
|
304
|
+
it "maps XML content to custom model using custom methods" do
|
305
|
+
instance = parent_mapper.from_xml(input_xml)
|
306
|
+
|
307
|
+
expect(instance.class).to eq(parent_model)
|
308
|
+
expect(instance.first_name).to eq("John")
|
309
|
+
expect(instance.last_name).to eq("Doe")
|
310
|
+
expect(instance.name).to eq("John Doe")
|
311
|
+
|
312
|
+
expect(instance.child_mapper.class).to eq(child_model)
|
313
|
+
expect(instance.child_mapper.street).to eq("Oxford Street")
|
314
|
+
expect(instance.child_mapper.city).to eq("London")
|
315
|
+
end
|
316
|
+
end
|
317
|
+
|
318
|
+
describe ".to_xml" do
|
319
|
+
it "with wrong model raises an exception" do
|
320
|
+
msg = /argument is a 'String' but should be a '#{parent_model.name}/
|
321
|
+
|
322
|
+
expect do
|
323
|
+
parent_mapper.to_xml("")
|
324
|
+
end.to raise_error(Lutaml::Model::IncorrectModelError, msg)
|
325
|
+
end
|
326
|
+
|
327
|
+
it "with correct model converts objects to xml using custom methods" do
|
328
|
+
instance = parent_mapper.from_xml(input_xml)
|
329
|
+
result_xml = parent_mapper.to_xml(instance)
|
330
|
+
|
331
|
+
expect(result_xml.gsub(/\s+/, "")).to eq(input_xml.gsub(/\s+/, ""))
|
332
|
+
end
|
333
|
+
end
|
334
|
+
|
335
|
+
describe "custom serialization methods" do
|
336
|
+
it "uses custom to_xml method for child_mapper" do
|
337
|
+
instance = parent_model.new
|
338
|
+
instance.first_name = "John"
|
339
|
+
instance.last_name = "Doe"
|
340
|
+
instance.child_mapper = child_model.new
|
341
|
+
instance.child_mapper.street = "Custom Street"
|
342
|
+
instance.child_mapper.city = "Custom City"
|
343
|
+
|
344
|
+
result_xml = parent_mapper.to_xml(instance)
|
345
|
+
expect(result_xml).to include("<CustomModelChild>")
|
346
|
+
expect(result_xml).to include("<street>Custom Street</street>")
|
347
|
+
expect(result_xml).to include("<city>Custom City</city>")
|
348
|
+
end
|
349
|
+
|
350
|
+
it "uses custom from_xml method for child_mapper" do
|
351
|
+
custom_xml = <<~XML
|
352
|
+
<CustomModelParent>
|
353
|
+
<first_name>Jane</first_name>
|
354
|
+
<last_name>Smith</last_name>
|
355
|
+
<CustomModelChild>
|
356
|
+
<street>Custom Avenue</street>
|
357
|
+
<city>New City</city>
|
358
|
+
</CustomModelChild>
|
359
|
+
</CustomModelParent>
|
360
|
+
XML
|
361
|
+
|
362
|
+
instance = parent_mapper.from_xml(custom_xml)
|
363
|
+
expect(instance.child_mapper.street).to eq("Custom Avenue")
|
364
|
+
expect(instance.child_mapper.city).to eq("New City")
|
365
|
+
end
|
366
|
+
|
367
|
+
it "uses delegate to for child mapper class" do
|
368
|
+
xml = "<docid>Str<sub>2</sub>text<sup>1</sup>123</docid>"
|
369
|
+
|
370
|
+
docid = CustomModelSpecs::Docid.from_xml(xml)
|
371
|
+
|
372
|
+
expect(CustomModelSpecs::Docid.to_xml(docid)).to eq(xml)
|
373
|
+
end
|
374
|
+
end
|
375
|
+
|
376
|
+
describe "custom methods with custom model" do
|
377
|
+
it "uses delegate to for child mapper class" do
|
378
|
+
xml = <<~XML
|
379
|
+
<MixedWithNestedContent>
|
380
|
+
<street>
|
381
|
+
A <p>b</p> B <p>c</p> C
|
382
|
+
</street>
|
383
|
+
<bibdata type="collection" schema-version="v1.2.8">
|
384
|
+
<title language="en">
|
385
|
+
JCGM Collection 1
|
386
|
+
</title>
|
387
|
+
</bibdata>
|
388
|
+
</MixedWithNestedContent>
|
389
|
+
XML
|
390
|
+
|
391
|
+
expected_xml = <<~XML
|
392
|
+
<MixedWithNestedContent>
|
393
|
+
<street>
|
394
|
+
A <p>b</p> B <p>c</p> C
|
395
|
+
</street>
|
396
|
+
<bibdata type="collection" schema-version="v1.2.8">
|
397
|
+
<title language="en">
|
398
|
+
JCGM Collection 1
|
399
|
+
</title>
|
400
|
+
</bibdata>
|
401
|
+
</MixedWithNestedContent>
|
402
|
+
XML
|
403
|
+
|
404
|
+
bibdata = CustomModelSpecs::MixedWithNestedContent.from_xml(xml)
|
405
|
+
|
406
|
+
expect(bibdata.to_xml).to be_equivalent_to(expected_xml)
|
407
|
+
end
|
408
|
+
end
|
409
|
+
end
|
410
|
+
end
|
@@ -0,0 +1,170 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "lutaml/model"
|
3
|
+
|
4
|
+
class CustomSerialization < Lutaml::Model::Serializable
|
5
|
+
attribute :full_name, :string
|
6
|
+
attribute :size, :integer
|
7
|
+
attribute :color, :string
|
8
|
+
attribute :description, :string
|
9
|
+
|
10
|
+
json do
|
11
|
+
map "name", with: { to: :name_to_json, from: :name_from_json }
|
12
|
+
map "color", with: { to: :color_to_json, from: :color_from_json }
|
13
|
+
map "size", with: { to: :size_to_json, from: :size_from_json }
|
14
|
+
map "description",
|
15
|
+
with: { to: :description_to_json, from: :description_from_json }
|
16
|
+
end
|
17
|
+
|
18
|
+
xml do
|
19
|
+
root "CustomSerialization"
|
20
|
+
|
21
|
+
# name, color are used to test XML elements with custom methods
|
22
|
+
map_element "Name", with: { to: :name_to_xml, from: :name_from_xml }
|
23
|
+
map_element "Color", with: { to: :color_to_xml, from: :color_from_xml }
|
24
|
+
|
25
|
+
# size is used to test XML attribute with custom methods
|
26
|
+
map_attribute "Size", with: { to: :size_to_xml, from: :size_from_xml }
|
27
|
+
|
28
|
+
# description is used to test XML textual content
|
29
|
+
map_content with: { to: :description_to_xml, from: :description_from_xml }
|
30
|
+
end
|
31
|
+
|
32
|
+
def name_to_json(model, doc)
|
33
|
+
doc["name"] = "JSON Masterpiece: #{model.full_name}"
|
34
|
+
end
|
35
|
+
|
36
|
+
def name_from_json(model, value)
|
37
|
+
model.full_name = value.sub(/^JSON Masterpiece: /, "")
|
38
|
+
end
|
39
|
+
|
40
|
+
def size_to_json(model, doc)
|
41
|
+
doc["size"] = model.size + 3
|
42
|
+
end
|
43
|
+
|
44
|
+
def size_from_json(model, value)
|
45
|
+
model.size = value - 3
|
46
|
+
end
|
47
|
+
|
48
|
+
def color_to_json(model, doc)
|
49
|
+
doc["color"] = model.color.upcase
|
50
|
+
end
|
51
|
+
|
52
|
+
def color_from_json(model, value)
|
53
|
+
model.color = value.downcase
|
54
|
+
end
|
55
|
+
|
56
|
+
def description_to_json(model, doc)
|
57
|
+
doc["description"] = "JSON Description: #{model.description}"
|
58
|
+
end
|
59
|
+
|
60
|
+
def description_from_json(model, value)
|
61
|
+
model.description = value.sub(/^JSON Description: /, "")
|
62
|
+
end
|
63
|
+
|
64
|
+
def name_to_xml(model, parent, doc)
|
65
|
+
el = doc.create_element("Name")
|
66
|
+
doc.add_text(el, "XML Masterpiece: #{model.full_name}")
|
67
|
+
doc.add_element(parent, el)
|
68
|
+
end
|
69
|
+
|
70
|
+
def name_from_xml(model, value)
|
71
|
+
model.full_name = value.sub(/^XML Masterpiece: /, "")
|
72
|
+
end
|
73
|
+
|
74
|
+
def size_to_xml(model, parent, doc)
|
75
|
+
doc.add_attribute(parent, "Size", model.size + 3)
|
76
|
+
end
|
77
|
+
|
78
|
+
def size_from_xml(model, value)
|
79
|
+
model.size = value.to_i - 3
|
80
|
+
end
|
81
|
+
|
82
|
+
def color_to_xml(model, parent, doc)
|
83
|
+
color_element = doc.create_element("Color")
|
84
|
+
doc.add_text(color_element, model.color.upcase)
|
85
|
+
doc.add_element(parent, color_element)
|
86
|
+
end
|
87
|
+
|
88
|
+
def color_from_xml(model, value)
|
89
|
+
model.color = value.downcase
|
90
|
+
end
|
91
|
+
|
92
|
+
def description_to_xml(model, parent, doc)
|
93
|
+
doc.add_text(parent, "XML Description: #{model.description}")
|
94
|
+
end
|
95
|
+
|
96
|
+
def description_from_xml(model, value)
|
97
|
+
model.description = value.join.strip.sub(/^XML Description: /, "")
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
RSpec.describe CustomSerialization do
|
102
|
+
let(:attributes) do
|
103
|
+
{
|
104
|
+
full_name: "Vase",
|
105
|
+
size: 12,
|
106
|
+
color: "blue",
|
107
|
+
description: "A beautiful ceramic vase",
|
108
|
+
}
|
109
|
+
end
|
110
|
+
let(:model) { described_class.new(attributes) }
|
111
|
+
|
112
|
+
context "with JSON serialization" do
|
113
|
+
it "serializes to JSON with custom methods" do
|
114
|
+
expected_json = {
|
115
|
+
name: "JSON Masterpiece: Vase",
|
116
|
+
color: "BLUE",
|
117
|
+
size: 15,
|
118
|
+
description: "JSON Description: A beautiful ceramic vase",
|
119
|
+
}.to_json
|
120
|
+
|
121
|
+
expect(model.to_json).to eq(expected_json)
|
122
|
+
end
|
123
|
+
|
124
|
+
it "deserializes from JSON with custom methods" do
|
125
|
+
json = {
|
126
|
+
name: "JSON Masterpiece: Vase",
|
127
|
+
color: "BLUE",
|
128
|
+
size: 15,
|
129
|
+
description: "JSON Description: A beautiful ceramic vase",
|
130
|
+
}.to_json
|
131
|
+
|
132
|
+
ceramic = described_class.from_json(json)
|
133
|
+
|
134
|
+
expect(ceramic.full_name).to eq(model.full_name)
|
135
|
+
expect(ceramic.size).to eq(model.size)
|
136
|
+
expect(ceramic.color).to eq(model.color)
|
137
|
+
expect(ceramic.description).to eq(model.description)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
context "with XML serialization" do
|
142
|
+
it "serializes to XML with custom methods" do
|
143
|
+
expected_xml = <<~XML
|
144
|
+
<CustomSerialization Size="15">
|
145
|
+
<Name>XML Masterpiece: Vase</Name>
|
146
|
+
<Color>BLUE</Color>
|
147
|
+
XML Description: A beautiful ceramic vase
|
148
|
+
</CustomSerialization>
|
149
|
+
XML
|
150
|
+
|
151
|
+
expect(model.to_xml).to be_equivalent_to(expected_xml)
|
152
|
+
end
|
153
|
+
|
154
|
+
it "deserializes from XML with custom methods" do
|
155
|
+
xml = <<~XML
|
156
|
+
<CustomSerialization Size="15">
|
157
|
+
<Name>XML Masterpiece: Vase</Name>
|
158
|
+
<Color>BLUE</Color>
|
159
|
+
XML Description: A beautiful ceramic vase
|
160
|
+
</CustomSerialization>
|
161
|
+
XML
|
162
|
+
|
163
|
+
ceramic = described_class.from_xml(xml)
|
164
|
+
expect(ceramic.full_name).to eq(model.full_name)
|
165
|
+
expect(ceramic.size).to eq(model.size)
|
166
|
+
expect(ceramic.color).to eq(model.color)
|
167
|
+
expect(ceramic.description).to eq(model.description)
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|