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,221 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+ require "lutaml/model"
5
+
6
+ module DefaultsSpec
7
+ class GlazeTag < Lutaml::Model::Serializable
8
+ attribute :text, :string, default: -> { "" }
9
+
10
+ xml do
11
+ root "Tag"
12
+ map_content to: :text
13
+ end
14
+ end
15
+
16
+ class Glaze < Lutaml::Model::Serializable
17
+ attribute :name, :string, default: -> { "Standard Glaze" }
18
+ attribute :color, :string, default: -> { "Clear" }
19
+ attribute :opacity, :string, default: -> { "Opaque" }
20
+ attribute :temperature, :integer, default: -> { 1050 }
21
+ attribute :firing_time, :integer, default: -> { 60 }
22
+ attribute :balance, "Decimal", default: -> { BigDecimal("0.0") }
23
+ attribute :tags, GlazeTag, collection: true
24
+ attribute :properties, :hash, default: -> { { food_safe: true } }
25
+ attribute :status, :string, default: -> { "active" }
26
+ attribute :batch_number, :integer, default: -> { 0 }
27
+ attribute :manufacturer, :string, default: -> { "example@glazes.com" }
28
+ attribute :type, :string, values: %w[earthenware stoneware porcelain], default: -> {
29
+ "stoneware"
30
+ }
31
+
32
+ xml do
33
+ root "Glaze"
34
+ map_element "Name", to: :name
35
+ map_element "Color", to: :color
36
+ map_element "Opacity", to: :opacity, render_default: true
37
+ map_element "Temperature", to: :temperature
38
+ map_element "FiringTime", to: :firing_time, render_default: true
39
+ map_element "Balance", to: :balance
40
+ map_element "Tags", to: :tags
41
+ map_element "Properties", to: :properties
42
+ map_element "Status", to: :status
43
+ map_element "BatchNumber", to: :batch_number
44
+ map_element "Manufacturer", to: :manufacturer
45
+ map_element "Type", to: :type
46
+ end
47
+
48
+ json do
49
+ map "name", to: :name
50
+ map "color", to: :color
51
+ map "opacity", to: :opacity, render_default: true
52
+ map "temperature", to: :temperature
53
+ map "firingTime", to: :firing_time, render_default: true
54
+ map "balance", to: :balance
55
+ map "tags", to: :tags
56
+ map "properties", to: :properties
57
+ map "status", to: :status
58
+ map "batchNumber", to: :batch_number
59
+ map "manufacturer", to: :manufacturer
60
+ map "type", to: :type
61
+ end
62
+
63
+ yaml do
64
+ map "name", to: :name
65
+ map "color", to: :color
66
+ end
67
+ end
68
+
69
+ # Class for testing default values without render_default option
70
+ class BasicGlaze < Lutaml::Model::Serializable
71
+ attribute :name, :string, default: -> { "Basic Glaze" }
72
+ attribute :temperature, :integer, default: -> { 1050 }
73
+ attribute :opacity, :string, default: -> { "Opaque" }
74
+
75
+ xml do
76
+ root "BasicGlaze"
77
+ map_element "name", to: :name
78
+ map_element "temperature", to: :temperature
79
+ map_element "opacity", to: :opacity
80
+ end
81
+
82
+ json do
83
+ map "name", to: :name
84
+ map "temperature", to: :temperature
85
+ map "opacity", to: :opacity
86
+ end
87
+ end
88
+
89
+ # Class for testing explicit render_default: false
90
+ class NoDefaultGlaze < Lutaml::Model::Serializable
91
+ attribute :name, :string, default: -> { "No Default Glaze" }
92
+ attribute :temperature, :integer, default: -> { 1050 }
93
+ attribute :opacity, :string, default: -> { "Opaque" }
94
+
95
+ xml do
96
+ root "NoDefaultGlaze"
97
+ map_element "name", to: :name, render_default: false
98
+ map_element "temperature", to: :temperature, render_default: false
99
+ map_element "opacity", to: :opacity, render_default: false
100
+ end
101
+
102
+ json do
103
+ map "name", to: :name, render_default: false
104
+ map "temperature", to: :temperature, render_default: false
105
+ map "opacity", to: :opacity, render_default: false
106
+ end
107
+ end
108
+ end
109
+
110
+ RSpec.describe DefaultsSpec::Glaze do
111
+ describe "Default value initialization" do
112
+ subject(:default_model) { described_class.new }
113
+
114
+ it "initializes with correct default values" do
115
+ expect(default_model.name).to eq("Standard Glaze")
116
+ expect(default_model.color).to eq("Clear")
117
+ expect(default_model.opacity).to eq("Opaque")
118
+ expect(default_model.temperature).to eq(1050)
119
+ expect(default_model.firing_time).to eq(60)
120
+ expect(default_model.balance).to eq(BigDecimal("0.0"))
121
+ expect(default_model.tags).to eq([])
122
+ expect(default_model.properties).to eq({ food_safe: true })
123
+ expect(default_model.status).to eq("active")
124
+ expect(default_model.batch_number).to eq(0)
125
+ expect(default_model.manufacturer).to eq("example@glazes.com")
126
+ expect(default_model.type).to eq("stoneware")
127
+ end
128
+ end
129
+
130
+ describe "Default value rendering behavior" do
131
+ context "with XML serialization" do
132
+ context "when value is default" do
133
+ it "does not serialize when render_default is false" do
134
+ glaze = DefaultsSpec::NoDefaultGlaze.new
135
+ xml = glaze.to_xml
136
+
137
+ expect(xml).not_to include("<name>")
138
+ expect(xml).not_to include("<temperature>")
139
+ expect(xml).not_to include("<opacity>")
140
+ end
141
+
142
+ it "does not serialize when render_default is not set" do
143
+ glaze = DefaultsSpec::BasicGlaze.new
144
+ xml = glaze.to_xml
145
+
146
+ expect(xml).not_to include("<name>")
147
+ expect(xml).not_to include("<temperature>")
148
+ expect(xml).not_to include("<opacity>")
149
+ end
150
+
151
+ it "serializes when render_default is true" do
152
+ glaze = described_class.new
153
+ xml = glaze.to_xml
154
+
155
+ expect(xml).to include("<Opacity>Opaque</Opacity>")
156
+ expect(xml).to include("<FiringTime>60</FiringTime>")
157
+ end
158
+ end
159
+
160
+ context "when value is not default" do
161
+ it "serializes regardless of render_default setting" do
162
+ glaze = DefaultsSpec::NoDefaultGlaze.new(
163
+ name: "Custom Glaze",
164
+ temperature: 1200,
165
+ opacity: "Translucent",
166
+ )
167
+ xml = glaze.to_xml
168
+
169
+ expect(xml).to include("<name>Custom Glaze</name>")
170
+ expect(xml).to include("<temperature>1200</temperature>")
171
+ expect(xml).to include("<opacity>Translucent</opacity>")
172
+ end
173
+ end
174
+ end
175
+
176
+ context "with JSON serialization" do
177
+ context "when value is default" do
178
+ it "does not serialize when render_default is false" do
179
+ glaze = DefaultsSpec::NoDefaultGlaze.new
180
+ json = JSON.parse(glaze.to_json)
181
+
182
+ expect(json).not_to have_key("name")
183
+ expect(json).not_to have_key("temperature")
184
+ expect(json).not_to have_key("opacity")
185
+ end
186
+
187
+ it "does not serialize when render_default is not set" do
188
+ glaze = DefaultsSpec::BasicGlaze.new
189
+ json = JSON.parse(glaze.to_json)
190
+
191
+ expect(json).not_to have_key("name")
192
+ expect(json).not_to have_key("temperature")
193
+ expect(json).not_to have_key("opacity")
194
+ end
195
+
196
+ it "serializes when render_default is true" do
197
+ glaze = described_class.new
198
+ json = JSON.parse(glaze.to_json)
199
+
200
+ expect(json["opacity"]).to eq("Opaque")
201
+ expect(json["firingTime"]).to eq(60)
202
+ end
203
+ end
204
+
205
+ context "when value is not default" do
206
+ it "serializes regardless of render_default setting" do
207
+ glaze = DefaultsSpec::NoDefaultGlaze.new(
208
+ name: "Custom Glaze",
209
+ temperature: 1200,
210
+ opacity: "Translucent",
211
+ )
212
+ json = JSON.parse(glaze.to_json)
213
+
214
+ expect(json["name"]).to eq("Custom Glaze")
215
+ expect(json["temperature"]).to eq(1200)
216
+ expect(json["opacity"]).to eq("Translucent")
217
+ end
218
+ end
219
+ end
220
+ end
221
+ end
@@ -0,0 +1,340 @@
1
+ require "spec_helper"
2
+ require "lutaml/model"
3
+
4
+ module Delegation
5
+ class Glaze < Lutaml::Model::Serializable
6
+ attribute :color, Lutaml::Model::Type::String
7
+ attribute :finish, Lutaml::Model::Type::String
8
+ end
9
+
10
+ class Ceramic < Lutaml::Model::Serializable
11
+ attribute :type, Lutaml::Model::Type::String
12
+ attribute :glaze, Glaze
13
+
14
+ json do
15
+ map "type", to: :type
16
+ map "color", to: :color, delegate: :glaze
17
+ map "finish", to: :finish, delegate: :glaze
18
+ end
19
+
20
+ yaml do
21
+ map "type", to: :type
22
+ map "color", to: :color, delegate: :glaze
23
+ map "finish", to: :finish, delegate: :glaze
24
+ end
25
+
26
+ toml do
27
+ map "type", to: :type
28
+ map "color", to: :color, delegate: :glaze
29
+ map "finish", to: :finish, delegate: :glaze
30
+ end
31
+
32
+ xml do
33
+ root "delegation"
34
+ map_element "type", to: :type
35
+ map_element "color", to: :color, delegate: :glaze
36
+ map_element "finish", to: :finish, delegate: :glaze
37
+ end
38
+ end
39
+
40
+ class NamespacedTypeCeramic < Lutaml::Model::Serializable
41
+ attribute :type, Lutaml::Model::Type::String
42
+ attribute :glaze, Glaze
43
+
44
+ yaml do
45
+ map "type", to: :type
46
+ map "color", to: :color, delegate: :glaze
47
+ map "finish", to: :finish, delegate: :glaze
48
+ end
49
+
50
+ xml do
51
+ root "delegation"
52
+ map_element "type",
53
+ to: :type,
54
+ namespace: "https://example.com/type/1.2",
55
+ prefix: "type"
56
+ map_element "color", to: :color, delegate: :glaze
57
+ map_element "finish", to: :finish, delegate: :glaze
58
+ end
59
+ end
60
+ end
61
+
62
+ RSpec.describe Delegation do
63
+ let(:yaml_data) do
64
+ <<~YAML
65
+ type: Vase
66
+ color: Blue
67
+ finish: Glossy
68
+ YAML
69
+ end
70
+
71
+ let(:delegation1) { Delegation::NamespacedTypeCeramic.from_yaml(yaml_data) }
72
+ let(:delegation) { Delegation::Ceramic.from_yaml(yaml_data) }
73
+
74
+ it "deserializes from YAML with delegation" do
75
+ expect(delegation.type).to eq("Vase")
76
+ expect(delegation.glaze.color).to eq("Blue")
77
+ expect(delegation.glaze.finish).to eq("Glossy")
78
+ end
79
+
80
+ it "serializes to YAML with delegation" do
81
+ expected_yaml = <<~YAML
82
+ ---
83
+ type: Vase
84
+ color: Blue
85
+ finish: Glossy
86
+ YAML
87
+ expect(delegation.to_yaml.strip).to eq(expected_yaml.strip)
88
+ end
89
+
90
+ it "serializes to JSON with delegation and filtering" do
91
+ expected_json = {
92
+ type: "Vase",
93
+ color: "Blue",
94
+ }.to_json
95
+
96
+ generated_json = delegation.to_json(only: %i[type color])
97
+
98
+ expect(JSON.parse(generated_json)).to eq(JSON.parse(expected_json))
99
+ end
100
+
101
+ it "serializes to JSON with pretty formatting" do
102
+ expected_pretty_json = {
103
+ type: "Vase",
104
+ color: "Blue",
105
+ }.to_json
106
+
107
+ generated_json = delegation.to_json(only: %i[type color], pretty: true)
108
+
109
+ expect(generated_json.strip).to eq(expected_pretty_json.strip)
110
+ end
111
+
112
+ it "serializes to XML with pretty formatting" do
113
+ expected_pretty_xml = <<~XML
114
+ <delegation>
115
+ <type>Vase</type>
116
+ <color>Blue</color>
117
+ <finish>Glossy</finish>
118
+ </delegation>
119
+ XML
120
+
121
+ generated_xml = delegation.to_xml(pretty: true).strip
122
+
123
+ expect(generated_xml).to be_equivalent_to(expected_pretty_xml.strip)
124
+ end
125
+
126
+ it "does not provide XML declaration if no declaration option provided" do
127
+ xml_data = delegation.to_xml(pretty: true)
128
+ expect(xml_data).not_to include("<?xml")
129
+ end
130
+
131
+ it "provides XML declaration with default version" \
132
+ "if declaration: true option provided" do
133
+ xml_data = delegation.to_xml(pretty: true, declaration: true)
134
+ expect(xml_data).to include('<?xml version="1.0"?>')
135
+ end
136
+
137
+ it "provides XML declaration with specified version" \
138
+ "if declaration: '1.1' option provided" do
139
+ xml_data = delegation.to_xml(pretty: true, declaration: "1.1")
140
+ expect(xml_data).to include('<?xml version="1.1"?>')
141
+ end
142
+
143
+ it "provides XML declaration without encoding" \
144
+ "if encoding option not provided" do
145
+ xml_data = delegation.to_xml(pretty: true, declaration: true)
146
+ expect(xml_data).to include('<?xml version="1.0"?>')
147
+ expect(xml_data).not_to include("encoding=")
148
+ end
149
+
150
+ it "provides XML declaration with UTF-8 encoding" \
151
+ "if encoding: true option provided" do
152
+ xml_data = delegation.to_xml(
153
+ pretty: true,
154
+ declaration: true,
155
+ encoding: true,
156
+ )
157
+ expect(xml_data).to include('<?xml version="1.0" encoding="UTF-8"?>')
158
+ end
159
+
160
+ it "provides XML declaration with specified encoding" \
161
+ "if encoding: 'ASCII' option provided" do
162
+ xml_data = delegation.to_xml(
163
+ pretty: true,
164
+ declaration: true,
165
+ encoding: "ASCII",
166
+ )
167
+ expect(xml_data).to include('<?xml version="1.0" encoding="ASCII"?>')
168
+ end
169
+
170
+ it "sets the namespace of a particular attribute inside <delegation>" do
171
+ Delegation::Ceramic.class_eval do
172
+ attribute :date, Lutaml::Model::Type::Date
173
+
174
+ xml do
175
+ root "delegation"
176
+ map_attribute "date",
177
+ to: :date,
178
+ namespace: "https://example.com/delegation/1.2",
179
+ prefix: "del"
180
+ map_element "type", to: :type
181
+ map_element "color", to: :color, delegate: :glaze
182
+ map_element "finish", to: :finish, delegate: :glaze
183
+ end
184
+ end
185
+
186
+ delegation_class = Delegation::Ceramic
187
+ delegation = delegation_class.new(
188
+ type: "Vase",
189
+ glaze: Delegation::Glaze.new(
190
+ color: "Blue",
191
+ finish: "Glossy",
192
+ ),
193
+ date: "2024-06-08",
194
+ )
195
+
196
+ xml_data = delegation.to_xml(
197
+ pretty: true,
198
+ declaration: true,
199
+ encoding: "UTF-8",
200
+ )
201
+
202
+ delegation_attributes = [
203
+ 'xmlns:del="https://example.com/delegation/1.2"',
204
+ 'del:date="2024-06-08"',
205
+ ]
206
+
207
+ expect(xml_data).to include("<delegation #{delegation_attributes.join(' ')}>")
208
+ end
209
+
210
+ it "sets the default namespace of <delegation>" do
211
+ Delegation::Ceramic.class_eval do
212
+ xml do
213
+ root "delegation"
214
+ namespace "https://example.com/delegation/1.2"
215
+ map_element "type", to: :type
216
+ map_element "color", to: :color, delegate: :glaze
217
+ map_element "finish", to: :finish, delegate: :glaze
218
+ end
219
+ end
220
+
221
+ delegation_class = Delegation::Ceramic
222
+ delegation = delegation_class.from_yaml(yaml_data)
223
+ xml_data = delegation.to_xml(
224
+ pretty: true,
225
+ declaration: true,
226
+ encoding: "UTF-8",
227
+ )
228
+ expect(xml_data).to(
229
+ include('<delegation xmlns="https://example.com/delegation/1.2" xmlns:del="https://example.com/delegation/1.2">'),
230
+ )
231
+ end
232
+
233
+ it "sets the namespace of <delegation> with a prefix" do
234
+ Delegation::Ceramic.class_eval do
235
+ xml do
236
+ root "delegation"
237
+ namespace "https://example.com/delegation/1.2", "del"
238
+ map_element "type", to: :type
239
+ map_element "color", to: :color, delegate: :glaze
240
+ map_element "finish", to: :finish, delegate: :glaze
241
+ end
242
+ end
243
+
244
+ delegation_class = Delegation::Ceramic
245
+ delegation = delegation_class.from_yaml(yaml_data)
246
+ xml_data = delegation.to_xml(
247
+ pretty: true,
248
+ declaration: true,
249
+ encoding: "UTF-8",
250
+ )
251
+
252
+ expect(xml_data).to(
253
+ include(
254
+ '<del:delegation xmlns:del="https://example.com/delegation/1.2">',
255
+ ),
256
+ )
257
+ end
258
+
259
+ it "sets the namespace of <delegation> and also" \
260
+ "a particular element inside using :inherit" do
261
+ Delegation::Ceramic.class_eval do
262
+ xml do
263
+ root "delegation"
264
+ namespace "https://example.com/delegation/1.2", "del"
265
+ map_element "type", to: :type # , namespace: :inherit
266
+ map_element "color", to: :color, delegate: :glaze
267
+ map_element "finish", to: :finish, delegate: :glaze
268
+ end
269
+ end
270
+
271
+ delegation_class = Delegation::Ceramic
272
+ delegation = delegation_class.from_yaml(yaml_data)
273
+ xml_data = delegation.to_xml(
274
+ pretty: true,
275
+ declaration: true,
276
+ encoding: "UTF-8",
277
+ )
278
+
279
+ delegation_attribute = 'xmlns:del="https://example.com/delegation/1.2">'
280
+
281
+ expect(xml_data).to include("<del:delegation #{delegation_attribute}")
282
+ expect(xml_data).to include("<del:type>Vase</del:type>")
283
+ end
284
+
285
+ it "sets the namespace of <delegation> and also" \
286
+ "a particular attribute inside using :inherit" do
287
+ Delegation::Ceramic.class_eval do
288
+ attribute :date, Lutaml::Model::Type::Date
289
+
290
+ xml do
291
+ root "delegation"
292
+ namespace "https://example.com/delegation/1.1", "del1"
293
+ map_attribute "date",
294
+ to: :date,
295
+ namespace: "https://example.com/delegation/1.2",
296
+ prefix: "del2"
297
+
298
+ map_element "type", to: :type, namespace: :inherit
299
+ map_element "color", to: :color, delegate: :glaze
300
+ map_element "finish", to: :finish, delegate: :glaze
301
+ end
302
+ end
303
+
304
+ delegation_class = Delegation::Ceramic
305
+ delegation = delegation_class.new(
306
+ type: "Vase",
307
+ glaze: Delegation::Glaze.new(
308
+ color: "Blue",
309
+ finish: "Glossy",
310
+ ),
311
+ date: "2024-06-08",
312
+ )
313
+
314
+ xml_data = delegation.to_xml(
315
+ pretty: true,
316
+ declaration: true,
317
+ encoding: "UTF-8",
318
+ )
319
+
320
+ delegation_attributes = [
321
+ 'xmlns:del1="https://example.com/delegation/1.1"',
322
+ 'xmlns:del2="https://example.com/delegation/1.2"',
323
+ 'del2:date="2024-06-08"',
324
+ ]
325
+
326
+ expect(xml_data).to include("<del1:delegation #{delegation_attributes.join(' ')}>")
327
+ expect(xml_data).to include("<del1:type>Vase</del1:type>")
328
+ end
329
+
330
+ it "sets the namespace of a particular element inside Ceramic" do
331
+ delegation1 = Delegation::NamespacedTypeCeramic.from_yaml(yaml_data)
332
+ xml_data = delegation1.to_xml(
333
+ pretty: true,
334
+ declaration: true,
335
+ encoding: "UTF-8",
336
+ )
337
+ expect(xml_data).to include('<delegation xmlns:type="https://example.com/type/1.2">')
338
+ expect(xml_data).to include("<type:type>Vase</type:type>")
339
+ end
340
+ end
@@ -0,0 +1,92 @@
1
+ require "spec_helper"
2
+ require "lutaml/model"
3
+
4
+ module InheritanceSpec
5
+ class Parent < Lutaml::Model::Serializable
6
+ attribute :text, Lutaml::Model::Type::String
7
+ attribute :id, Lutaml::Model::Type::String
8
+ attribute :name, Lutaml::Model::Type::String
9
+
10
+ xml do
11
+ map_content to: :text
12
+
13
+ map_attribute "id", to: :id
14
+ map_element "name", to: :name
15
+ end
16
+ end
17
+
18
+ class Child1 < Parent
19
+ attribute :age, Lutaml::Model::Type::Integer
20
+
21
+ xml do
22
+ root "child"
23
+
24
+ map_element "age", to: :age
25
+ end
26
+ end
27
+
28
+ class Child2 < Parent
29
+ attribute :age, Lutaml::Model::Type::Integer
30
+
31
+ xml do
32
+ root "child_two"
33
+
34
+ map_element "gender", to: :age
35
+ end
36
+ end
37
+ end
38
+
39
+ RSpec.describe "Inheritance" do
40
+ subject(:child_object) do
41
+ InheritanceSpec::Child1.new(
42
+ {
43
+ text: "Some text",
44
+ name: "John Doe",
45
+ id: "foobar",
46
+ age: 30,
47
+ },
48
+ )
49
+ end
50
+
51
+ let(:expected_xml) do
52
+ '<child id="foobar"><name>John Doe</name><age>30</age>Some text</child>'
53
+ end
54
+
55
+ it "uses parent attributes" do
56
+ expect(child_object.to_xml(pretty: true)).to eq(expected_xml)
57
+ end
58
+
59
+ context "with multiple child classes" do
60
+ describe "Child1" do
61
+ let(:child1) { InheritanceSpec::Child1 }
62
+
63
+ it "has correct mappings" do
64
+ expect(child1.mappings_for(:xml).mappings.count).to eq(4)
65
+ end
66
+
67
+ it "has correct attributes" do
68
+ expect(child1.attributes.count).to eq(4)
69
+ end
70
+
71
+ it "has correct model" do
72
+ expect(child1.model).to eq(child1)
73
+ end
74
+ end
75
+
76
+ describe "Child2" do
77
+ let(:child2) { InheritanceSpec::Child2 }
78
+
79
+ it "has correct mappings" do
80
+ expect(child2.mappings_for(:xml).mappings.count).to eq(4)
81
+ end
82
+
83
+ it "has correct attributes" do
84
+ expect(child2.attributes.count).to eq(4)
85
+ end
86
+
87
+ it "has correct model" do
88
+ expect(child2.model).to eq(child2)
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,37 @@
1
+ require "spec_helper"
2
+ require "lutaml/model/json_adapter/standard_json_adapter"
3
+ require "lutaml/model/json_adapter/multi_json_adapter"
4
+ require_relative "../../fixtures/sample_model"
5
+
6
+ RSpec.shared_examples "a JSON adapter" do |adapter_class|
7
+ let(:attributes) { { name: "John Doe", age: 30 } }
8
+ let(:model) { SampleModel.new(attributes) }
9
+
10
+ let(:expected_json) do
11
+ if adapter_class == Lutaml::Model::JsonAdapter::StandardJsonAdapter
12
+ JSON.generate(attributes)
13
+ elsif adapter_class == Lutaml::Model::JsonAdapter::MultiJsonAdapter
14
+ MultiJson.dump(attributes)
15
+ end
16
+ end
17
+
18
+ it "serializes to JSON" do
19
+ json = adapter_class.new(attributes).to_json
20
+ expect(json).to eq(expected_json)
21
+ end
22
+
23
+ it "deserializes from JSON" do
24
+ doc = adapter_class.parse(expected_json)
25
+ new_model = SampleModel.new(doc.to_h)
26
+ expect(new_model.name).to eq("John Doe")
27
+ expect(new_model.age).to eq(30)
28
+ end
29
+ end
30
+
31
+ RSpec.describe Lutaml::Model::JsonAdapter::StandardJsonAdapter do
32
+ it_behaves_like "a JSON adapter", described_class
33
+ end
34
+
35
+ RSpec.describe Lutaml::Model::JsonAdapter::MultiJsonAdapter do
36
+ it_behaves_like "a JSON adapter", described_class
37
+ end