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.
- checksums.yaml +4 -4
- data/.rubocop_todo.yml +36 -17
- data/README.adoc +396 -30
- data/lib/lutaml/model/attribute.rb +52 -27
- data/lib/lutaml/model/error/pattern_not_matched_error.rb +17 -0
- 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 +3 -0
- data/lib/lutaml/model/mapping_hash.rb +8 -0
- data/lib/lutaml/model/serialize.rb +10 -5
- 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/validation.rb +2 -1
- data/lib/lutaml/model/version.rb +1 -1
- data/lib/lutaml/model/xml_adapter/builder/nokogiri.rb +12 -3
- data/lib/lutaml/model/xml_adapter/builder/ox.rb +7 -1
- data/lib/lutaml/model/xml_adapter/nokogiri_adapter.rb +3 -1
- data/lib/lutaml/model/xml_adapter/ox_adapter.rb +12 -8
- data/lib/lutaml/model/xml_adapter/xml_document.rb +6 -8
- data/lib/lutaml/model/xml_mapping.rb +6 -2
- data/lib/lutaml/model/xml_mapping_rule.rb +7 -1
- 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 +139 -0
- data/spec/lutaml/model/cdata_spec.rb +520 -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 +90 -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 +68 -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
|