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,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 &lt;p&gt;b&lt;/p&gt; B &lt;p&gt;c&lt;/p&gt; 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