lutaml-model 0.6.7 → 0.7.1
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/.github/workflows/dependent-repos-todo.json +7 -0
- data/.github/workflows/dependent-repos.json +17 -9
- data/.rubocop_todo.yml +18 -33
- data/README.adoc +4380 -2557
- data/lib/lutaml/model/attribute.rb +94 -15
- data/lib/lutaml/model/choice.rb +7 -0
- data/lib/lutaml/model/comparable_model.rb +48 -9
- data/lib/lutaml/model/error/collection_count_out_of_range_error.rb +1 -1
- data/lib/lutaml/model/error/polymorphic_error.rb +9 -0
- data/lib/lutaml/model/error.rb +1 -0
- data/lib/lutaml/model/mapping/json_mapping.rb +17 -0
- data/lib/lutaml/model/{key_value_mapping.rb → mapping/key_value_mapping.rb} +58 -14
- data/lib/lutaml/model/{key_value_mapping_rule.rb → mapping/key_value_mapping_rule.rb} +18 -2
- data/lib/lutaml/model/mapping/mapping_rule.rb +299 -0
- data/lib/lutaml/model/mapping/toml_mapping.rb +25 -0
- data/lib/lutaml/model/{xml_mapping.rb → mapping/xml_mapping.rb} +97 -15
- data/lib/lutaml/model/{xml_mapping_rule.rb → mapping/xml_mapping_rule.rb} +20 -3
- data/lib/lutaml/model/mapping/yaml_mapping.rb +17 -0
- data/lib/lutaml/model/mapping.rb +14 -0
- data/lib/lutaml/model/schema/xml_compiler.rb +15 -15
- data/lib/lutaml/model/sequence.rb +2 -2
- data/lib/lutaml/model/serialize.rb +247 -97
- data/lib/lutaml/model/type/date.rb +1 -1
- data/lib/lutaml/model/type/date_time.rb +2 -2
- data/lib/lutaml/model/type/hash.rb +1 -1
- data/lib/lutaml/model/type/time.rb +2 -2
- data/lib/lutaml/model/type/time_without_date.rb +2 -2
- data/lib/lutaml/model/uninitialized_class.rb +64 -0
- data/lib/lutaml/model/utils.rb +14 -0
- data/lib/lutaml/model/validation.rb +1 -0
- data/lib/lutaml/model/version.rb +1 -1
- data/lib/lutaml/model/xml_adapter/nokogiri_adapter.rb +1 -1
- data/lib/lutaml/model/xml_adapter/oga_adapter.rb +1 -1
- data/lib/lutaml/model/xml_adapter/ox_adapter.rb +1 -1
- data/lib/lutaml/model/xml_adapter/xml_document.rb +38 -17
- data/lib/lutaml/model/xml_adapter/xml_element.rb +17 -7
- data/lib/lutaml/model.rb +1 -0
- data/spec/benchmarks/xml_parsing_benchmark_spec.rb +3 -3
- data/spec/fixtures/person.rb +5 -5
- data/spec/lutaml/model/attribute_spec.rb +37 -1
- data/spec/lutaml/model/cdata_spec.rb +2 -2
- data/spec/lutaml/model/collection_spec.rb +50 -2
- data/spec/lutaml/model/comparable_model_spec.rb +92 -27
- data/spec/lutaml/model/defaults_spec.rb +1 -1
- data/spec/lutaml/model/enum_spec.rb +1 -1
- data/spec/lutaml/model/group_spec.rb +316 -14
- data/spec/lutaml/model/key_value_mapping_spec.rb +41 -3
- data/spec/lutaml/model/polymorphic_spec.rb +348 -0
- data/spec/lutaml/model/render_empty_spec.rb +194 -0
- data/spec/lutaml/model/render_nil_spec.rb +206 -22
- data/spec/lutaml/model/simple_model_spec.rb +9 -9
- data/spec/lutaml/model/value_map_spec.rb +240 -0
- data/spec/lutaml/model/xml/namespace/nested_with_explicit_namespace_spec.rb +85 -0
- data/spec/lutaml/model/xml/xml_element_spec.rb +93 -0
- data/spec/lutaml/model/xml_mapping_rule_spec.rb +102 -2
- data/spec/lutaml/model/xml_mapping_spec.rb +45 -3
- data/spec/sample_model_spec.rb +3 -3
- metadata +20 -8
- data/lib/lutaml/model/mapping_rule.rb +0 -109
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "singleton"
|
4
|
+
|
5
|
+
module Lutaml
|
6
|
+
module Model
|
7
|
+
class UninitializedClass
|
8
|
+
include Singleton
|
9
|
+
|
10
|
+
def to_s
|
11
|
+
self
|
12
|
+
end
|
13
|
+
|
14
|
+
def inspect
|
15
|
+
"unititialized"
|
16
|
+
end
|
17
|
+
|
18
|
+
def uninitialized?
|
19
|
+
true
|
20
|
+
end
|
21
|
+
|
22
|
+
def match?(_args)
|
23
|
+
false
|
24
|
+
end
|
25
|
+
|
26
|
+
def include?(_args)
|
27
|
+
false
|
28
|
+
end
|
29
|
+
|
30
|
+
def gsub(_, _)
|
31
|
+
self
|
32
|
+
end
|
33
|
+
|
34
|
+
def to_yaml
|
35
|
+
nil
|
36
|
+
end
|
37
|
+
|
38
|
+
def to_f
|
39
|
+
self
|
40
|
+
end
|
41
|
+
|
42
|
+
def size
|
43
|
+
0
|
44
|
+
end
|
45
|
+
|
46
|
+
def encoding
|
47
|
+
# same as default encoding for string
|
48
|
+
"".encoding
|
49
|
+
end
|
50
|
+
|
51
|
+
def method_missing(method, *_args, &_block)
|
52
|
+
if method.end_with?("?")
|
53
|
+
false
|
54
|
+
else
|
55
|
+
super
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def respond_to_missing?(method_name, _include_private = false)
|
60
|
+
method_name.end_with?("?")
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
data/lib/lutaml/model/utils.rb
CHANGED
@@ -34,6 +34,16 @@ module Lutaml
|
|
34
34
|
.downcase
|
35
35
|
end
|
36
36
|
|
37
|
+
def initialized?(value)
|
38
|
+
return true unless value.respond_to?(:initialized?)
|
39
|
+
|
40
|
+
value.initialized?
|
41
|
+
end
|
42
|
+
|
43
|
+
def uninitialized?(value)
|
44
|
+
!initialized?(value)
|
45
|
+
end
|
46
|
+
|
37
47
|
def present?(value)
|
38
48
|
!blank?(value)
|
39
49
|
end
|
@@ -49,6 +59,10 @@ module Lutaml
|
|
49
59
|
collection.empty?
|
50
60
|
end
|
51
61
|
|
62
|
+
def empty?(value)
|
63
|
+
value.respond_to?(:empty?) ? value.empty? : false
|
64
|
+
end
|
65
|
+
|
52
66
|
def add_method_if_not_defined(klass, method_name, &block)
|
53
67
|
unless klass.method_defined?(method_name)
|
54
68
|
klass.class_eval do
|
data/lib/lutaml/model/version.rb
CHANGED
@@ -53,7 +53,7 @@ module Lutaml
|
|
53
53
|
end
|
54
54
|
|
55
55
|
def build_ordered_element(xml, element, options = {})
|
56
|
-
mapper_class = options
|
56
|
+
mapper_class = determine_mapper_class(element, options)
|
57
57
|
xml_mapping = mapper_class.mappings_for(:xml)
|
58
58
|
return xml unless xml_mapping
|
59
59
|
|
@@ -113,7 +113,7 @@ module Lutaml
|
|
113
113
|
private
|
114
114
|
|
115
115
|
def build_ordered_element(builder, element, options = {})
|
116
|
-
mapper_class = options
|
116
|
+
mapper_class = determine_mapper_class(element, options)
|
117
117
|
xml_mapping = mapper_class.mappings_for(:xml)
|
118
118
|
return xml unless xml_mapping
|
119
119
|
|
@@ -46,7 +46,7 @@ module Lutaml
|
|
46
46
|
private
|
47
47
|
|
48
48
|
def build_ordered_element(builder, element, options = {})
|
49
|
-
mapper_class = options
|
49
|
+
mapper_class = determine_mapper_class(element, options)
|
50
50
|
xml_mapping = mapper_class.mappings_for(:xml)
|
51
51
|
return xml unless xml_mapping
|
52
52
|
|
@@ -158,7 +158,7 @@ module Lutaml
|
|
158
158
|
value = transform_method.call(value)
|
159
159
|
end
|
160
160
|
|
161
|
-
if value.is_a?(Array)
|
161
|
+
if value.is_a?(Array) && !Utils.empty_collection?(value)
|
162
162
|
value.each do |item|
|
163
163
|
add_to_xml(xml, element, prefix, item, options)
|
164
164
|
end
|
@@ -168,12 +168,18 @@ module Lutaml
|
|
168
168
|
|
169
169
|
return if !render_element?(rule, element, value)
|
170
170
|
|
171
|
+
value = rule.render_value_for(value)
|
172
|
+
|
171
173
|
if value && (attribute&.type&.<= Lutaml::Model::Serialize)
|
172
174
|
handle_nested_elements(
|
173
175
|
xml,
|
174
176
|
value,
|
175
177
|
options.merge({ rule: rule, attribute: attribute }),
|
176
178
|
)
|
179
|
+
elsif value.nil?
|
180
|
+
xml.create_and_add_element(rule.name, attributes: { "xsi:nil" => true })
|
181
|
+
elsif Utils.empty?(value)
|
182
|
+
xml.create_and_add_element(rule.name)
|
177
183
|
elsif rule.raw_mapping?
|
178
184
|
xml.add_xml_fragment(xml, value)
|
179
185
|
elsif rule.prefix_set?
|
@@ -205,19 +211,12 @@ module Lutaml
|
|
205
211
|
end
|
206
212
|
|
207
213
|
def build_unordered_element(xml, element, options = {})
|
208
|
-
mapper_class = options
|
214
|
+
mapper_class = determine_mapper_class(element, options)
|
209
215
|
xml_mapping = mapper_class.mappings_for(:xml)
|
210
216
|
return xml unless xml_mapping
|
211
217
|
|
212
|
-
attributes =
|
213
|
-
|
214
|
-
xml_mapping, options).merge(attributes)&.compact
|
215
|
-
|
216
|
-
prefix = if options.key?(:namespace_prefix)
|
217
|
-
options[:namespace_prefix]
|
218
|
-
elsif xml_mapping.namespace_prefix
|
219
|
-
xml_mapping.namespace_prefix
|
220
|
-
end
|
218
|
+
attributes = build_element_attributes(element, xml_mapping, options)
|
219
|
+
prefix = determine_namespace_prefix(options, xml_mapping)
|
221
220
|
|
222
221
|
prefixed_xml = xml.add_namespace_prefix(prefix)
|
223
222
|
tag_name = options[:tag_name] || xml_mapping.root_element
|
@@ -234,6 +233,7 @@ module Lutaml
|
|
234
233
|
end
|
235
234
|
|
236
235
|
mappings = xml_mapping.elements + [xml_mapping.raw_mapping].compact
|
236
|
+
|
237
237
|
mappings.each do |element_rule|
|
238
238
|
attribute_def = attribute_definition_for(element, element_rule,
|
239
239
|
mapper_class: mapper_class)
|
@@ -241,7 +241,7 @@ module Lutaml
|
|
241
241
|
if attribute_def
|
242
242
|
value = attribute_value_for(element, element_rule)
|
243
243
|
|
244
|
-
next if
|
244
|
+
next if !element_rule.render?(value, element)
|
245
245
|
|
246
246
|
value = [value] if attribute_def.collection? && !value.is_a?(Array)
|
247
247
|
end
|
@@ -271,6 +271,8 @@ module Lutaml
|
|
271
271
|
text = content_rule.serialize(element)
|
272
272
|
text = text.join if text.is_a?(Array)
|
273
273
|
|
274
|
+
return unless content_rule.render?(text, element)
|
275
|
+
|
274
276
|
xml.add_text(xml, text, cdata: content_rule.cdata)
|
275
277
|
end
|
276
278
|
end
|
@@ -289,11 +291,7 @@ module Lutaml
|
|
289
291
|
end
|
290
292
|
|
291
293
|
def render_element?(rule, element, value)
|
292
|
-
|
293
|
-
end
|
294
|
-
|
295
|
-
def render_value?(rule, value)
|
296
|
-
rule.attribute? || rule.render_nil? || !value.nil?
|
294
|
+
rule.render?(value, element)
|
297
295
|
end
|
298
296
|
|
299
297
|
def render_default?(rule, element)
|
@@ -433,6 +431,29 @@ module Lutaml
|
|
433
431
|
def cdata
|
434
432
|
@root.cdata
|
435
433
|
end
|
434
|
+
|
435
|
+
private
|
436
|
+
|
437
|
+
def determine_mapper_class(element, options)
|
438
|
+
if options[:mapper_class] && element.is_a?(options[:mapper_class])
|
439
|
+
element.class
|
440
|
+
else
|
441
|
+
options[:mapper_class] || element.class
|
442
|
+
end
|
443
|
+
end
|
444
|
+
|
445
|
+
def determine_namespace_prefix(options, mapping)
|
446
|
+
return options[:namespace_prefix] if options.key?(:namespace_prefix)
|
447
|
+
|
448
|
+
mapping.namespace_prefix
|
449
|
+
end
|
450
|
+
|
451
|
+
def build_element_attributes(element, mapping, options)
|
452
|
+
xml_attributes = options[:xml_attributes] ||= {}
|
453
|
+
attributes = build_attributes(element, mapping, options)
|
454
|
+
|
455
|
+
attributes.merge(xml_attributes)&.compact
|
456
|
+
end
|
436
457
|
end
|
437
458
|
end
|
438
459
|
end
|
@@ -127,9 +127,17 @@ module Lutaml
|
|
127
127
|
end
|
128
128
|
|
129
129
|
def text
|
130
|
+
return @text if children.empty?
|
130
131
|
return text_children.map(&:text) if children.count > 1
|
131
132
|
|
132
|
-
|
133
|
+
text_children.map(&:text).join
|
134
|
+
end
|
135
|
+
|
136
|
+
def cdata
|
137
|
+
return @text if children.empty?
|
138
|
+
return cdata_children.map(&:text) if children.count > 1
|
139
|
+
|
140
|
+
cdata_children.map(&:text).join
|
133
141
|
end
|
134
142
|
|
135
143
|
def cdata_children
|
@@ -140,6 +148,10 @@ module Lutaml
|
|
140
148
|
find_children_by_name("text")
|
141
149
|
end
|
142
150
|
|
151
|
+
def [](name)
|
152
|
+
find_attribute_value(name) || find_children_by_name(name)
|
153
|
+
end
|
154
|
+
|
143
155
|
def find_attribute_value(attribute_name)
|
144
156
|
if attribute_name.is_a?(Array)
|
145
157
|
attributes.values.find do |attr|
|
@@ -164,15 +176,13 @@ module Lutaml
|
|
164
176
|
find_children_by_name(name).first
|
165
177
|
end
|
166
178
|
|
167
|
-
def cdata
|
168
|
-
return cdata_children.map(&:text) if children.count > 1
|
169
|
-
|
170
|
-
@text
|
171
|
-
end
|
172
|
-
|
173
179
|
def to_h
|
174
180
|
document.to_h
|
175
181
|
end
|
182
|
+
|
183
|
+
def nil_element?
|
184
|
+
find_attribute_value("xsi:nil") == "true"
|
185
|
+
end
|
176
186
|
end
|
177
187
|
end
|
178
188
|
end
|
data/lib/lutaml/model.rb
CHANGED
@@ -61,9 +61,9 @@ RSpec.describe "LutaML Model Performance" do
|
|
61
61
|
end
|
62
62
|
|
63
63
|
thresholds = {
|
64
|
-
"Nokogiri Adapter" =>
|
65
|
-
"Ox Adapter" =>
|
66
|
-
"Oga Adapter" =>
|
64
|
+
"Nokogiri Adapter" => 3,
|
65
|
+
"Ox Adapter" => 7,
|
66
|
+
"Oga Adapter" => 3,
|
67
67
|
}
|
68
68
|
|
69
69
|
report.entries.each do |entry|
|
data/spec/fixtures/person.rb
CHANGED
@@ -17,11 +17,11 @@ class Person < Lutaml::Model::Serializable
|
|
17
17
|
map_element "FirstName",
|
18
18
|
to: :first_name,
|
19
19
|
namespace: "http://example.com/nsp1",
|
20
|
-
prefix: "nsp1"
|
20
|
+
prefix: "nsp1", render_empty: :omit
|
21
21
|
map_element "LastName",
|
22
22
|
to: :last_name,
|
23
23
|
namespace: "http://example.com/nsp1",
|
24
|
-
prefix: "nsp1"
|
24
|
+
prefix: "nsp1", render_empty: :as_blank
|
25
25
|
map_element "Age", to: :age
|
26
26
|
map_element "Height", to: :height
|
27
27
|
map_element "Birthdate", to: :birthdate
|
@@ -31,9 +31,9 @@ class Person < Lutaml::Model::Serializable
|
|
31
31
|
end
|
32
32
|
|
33
33
|
json do
|
34
|
-
map "firstName", to: :first_name
|
35
|
-
map "lastName", to: :last_name
|
36
|
-
map "age", to: :age
|
34
|
+
map "firstName", to: :first_name, render_empty: :omit
|
35
|
+
map "lastName", to: :last_name, render_empty: :as_empty
|
36
|
+
map "age", to: :age, render_empty: :as_nil
|
37
37
|
map "height", to: :height
|
38
38
|
map "birthdate", to: :birthdate
|
39
39
|
map "lastLogin", to: :last_login
|
@@ -136,7 +136,9 @@ RSpec.describe Lutaml::Model::Attribute do
|
|
136
136
|
context "when default is not set" do
|
137
137
|
let(:attribute) { described_class.new("name", :string) }
|
138
138
|
|
139
|
-
it
|
139
|
+
it "returns uninitialized" do
|
140
|
+
expect(attribute.default).to be(Lutaml::Model::UninitializedClass.instance)
|
141
|
+
end
|
140
142
|
end
|
141
143
|
|
142
144
|
context "when default is set as a proc" do
|
@@ -165,4 +167,38 @@ RSpec.describe Lutaml::Model::Attribute do
|
|
165
167
|
end
|
166
168
|
end
|
167
169
|
end
|
170
|
+
|
171
|
+
describe "#deep_dup" do
|
172
|
+
let(:duplicate_attribute) { Lutaml::Model::Utils.deep_dup(attribute) }
|
173
|
+
|
174
|
+
context "when deep_dup method is not defined and instance is deep_duplicated" do
|
175
|
+
let(:attribute) { described_class.new("name", :string) }
|
176
|
+
|
177
|
+
before do
|
178
|
+
described_class.alias_method :orig_deep_dup, :deep_dup
|
179
|
+
described_class.undef_method :deep_dup
|
180
|
+
end
|
181
|
+
|
182
|
+
after do
|
183
|
+
described_class.alias_method :deep_dup, :orig_deep_dup
|
184
|
+
attribute.options.delete(:foo)
|
185
|
+
end
|
186
|
+
|
187
|
+
it "confirms that options values are linked of original and duplicate instances" do
|
188
|
+
duplicate_attribute
|
189
|
+
attribute.options[:foo] = "bar"
|
190
|
+
expect(duplicate_attribute.options).to include(:foo)
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
context "when deep_dup method is defined and instance is deep_duplicated" do
|
195
|
+
let(:attribute) { described_class.new("name", :string) }
|
196
|
+
|
197
|
+
it "confirms that options values are not linked of original and duplicate instances" do
|
198
|
+
duplicate_attribute
|
199
|
+
attribute.options[:foo] = "bar"
|
200
|
+
expect(duplicate_attribute.options).not_to include(:foo)
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
168
204
|
end
|
@@ -117,8 +117,8 @@ module CDATA
|
|
117
117
|
|
118
118
|
def child_from_xml(model, value)
|
119
119
|
model.child_mapper ||= CustomModelChild.new
|
120
|
-
model.child_mapper.street = value.first.find_child_by_name("street").
|
121
|
-
model.child_mapper.city = value.first.find_child_by_name("city").
|
120
|
+
model.child_mapper.street = value.first.find_child_by_name("street").cdata
|
121
|
+
model.child_mapper.city = value.first.find_child_by_name("city").cdata
|
122
122
|
end
|
123
123
|
end
|
124
124
|
|
@@ -54,6 +54,16 @@ module CollectionTests
|
|
54
54
|
doc.add_element(parent, "<city>#{model.city}</city>")
|
55
55
|
end
|
56
56
|
end
|
57
|
+
|
58
|
+
class ReturnNilTest < Lutaml::Model::Serializable
|
59
|
+
attribute :default_items, :string, collection: true
|
60
|
+
attribute :regular_items, :string, collection: true, initialize_empty: true
|
61
|
+
|
62
|
+
yaml do
|
63
|
+
map "default_items", to: :default_items
|
64
|
+
map "regular_items", to: :regular_items, render_default: true, render_empty: true
|
65
|
+
end
|
66
|
+
end
|
57
67
|
end
|
58
68
|
|
59
69
|
RSpec.describe CollectionTests do
|
@@ -95,8 +105,8 @@ RSpec.describe CollectionTests do
|
|
95
105
|
it "initializes with default values" do
|
96
106
|
default_model = CollectionTests::Kiln.new
|
97
107
|
expect(default_model.brand).to be_nil
|
98
|
-
expect(default_model.pots).to
|
99
|
-
expect(default_model.temperatures).to
|
108
|
+
expect(default_model.pots).to be_nil
|
109
|
+
expect(default_model.temperatures).to be_nil
|
100
110
|
expect(default_model.operators).to eq(["Default Operator"])
|
101
111
|
expect(default_model.sensors).to eq(["Default Sensor"])
|
102
112
|
end
|
@@ -280,4 +290,42 @@ RSpec.describe CollectionTests do
|
|
280
290
|
end.not_to raise_error
|
281
291
|
end
|
282
292
|
end
|
293
|
+
|
294
|
+
context "when using initialize_empty option with collections" do
|
295
|
+
let(:parsed) { CollectionTests::ReturnNilTest.from_yaml(yaml) }
|
296
|
+
let(:model) { CollectionTests::ReturnNilTest.new }
|
297
|
+
|
298
|
+
let(:yaml) do
|
299
|
+
<<~YAML
|
300
|
+
---
|
301
|
+
regular_items: ~
|
302
|
+
YAML
|
303
|
+
end
|
304
|
+
|
305
|
+
it "sets nil value when reading from YAML with nil value" do
|
306
|
+
expect(parsed.default_items).to be_nil
|
307
|
+
expect(parsed.regular_items).to be_nil
|
308
|
+
end
|
309
|
+
|
310
|
+
it "initializes with empty array when initialize_empty is true" do
|
311
|
+
expect(model.regular_items).to eq([])
|
312
|
+
end
|
313
|
+
|
314
|
+
it "preserves initialize_empty behavior when serializing and deserializing" do
|
315
|
+
expected_yaml = <<~YAML
|
316
|
+
---
|
317
|
+
regular_items: []
|
318
|
+
YAML
|
319
|
+
|
320
|
+
expect(model.to_yaml).to eq(expected_yaml)
|
321
|
+
end
|
322
|
+
|
323
|
+
it "raises StandardError for initialize_empty without collection" do
|
324
|
+
expect do
|
325
|
+
Class.new(Lutaml::Model::Serializable) do
|
326
|
+
attribute :invalid_range, :string, initialize_empty: true
|
327
|
+
end
|
328
|
+
end.to raise_error(StandardError, /Invalid option `initialize_empty` given without `collection: true` option/)
|
329
|
+
end
|
330
|
+
end
|
283
331
|
end
|
@@ -19,6 +19,11 @@ class ComparableCeramicCollection < Lutaml::Model::Serializable
|
|
19
19
|
attribute :featured_piece, ComparableCeramic # This creates a two-level nesting
|
20
20
|
end
|
21
21
|
|
22
|
+
class RecursiveNode < Lutaml::Model::Serializable
|
23
|
+
attribute :name, :string
|
24
|
+
attribute :next_node, RecursiveNode
|
25
|
+
end
|
26
|
+
|
22
27
|
RSpec.describe Lutaml::Model::ComparableModel do
|
23
28
|
describe "comparisons" do
|
24
29
|
context "with simple types (Glaze)" do
|
@@ -68,38 +73,98 @@ RSpec.describe Lutaml::Model::ComparableModel do
|
|
68
73
|
end
|
69
74
|
|
70
75
|
context "with deeply nested Serializable objects (CeramicCollection)" do
|
76
|
+
let(:first_collection) do
|
77
|
+
ComparableCeramicCollection.new(
|
78
|
+
name: "Blue Collection",
|
79
|
+
featured_piece: ComparableCeramic.new(
|
80
|
+
type: "Bowl",
|
81
|
+
glaze: ComparableGlaze.new(
|
82
|
+
color: "Blue",
|
83
|
+
temperature: 1200,
|
84
|
+
food_safe: true,
|
85
|
+
),
|
86
|
+
),
|
87
|
+
)
|
88
|
+
end
|
89
|
+
|
90
|
+
let(:second_collection) do
|
91
|
+
ComparableCeramicCollection.new(
|
92
|
+
name: "Blue Collection",
|
93
|
+
featured_piece: ComparableCeramic.new(
|
94
|
+
type: "Bowl",
|
95
|
+
glaze: ComparableGlaze.new(
|
96
|
+
color: "Blue",
|
97
|
+
temperature: 1200,
|
98
|
+
food_safe: true,
|
99
|
+
),
|
100
|
+
),
|
101
|
+
)
|
102
|
+
end
|
103
|
+
|
71
104
|
it "compares equal objects with two levels of nesting" do
|
72
105
|
# This test compares CeramicCollection objects that contain Ceramic objects,
|
73
106
|
# which in turn contain Glaze objects - a two-level deep nesting
|
74
|
-
|
75
|
-
food_safe: true)
|
76
|
-
glaze2 = ComparableGlaze.new(color: "Blue", temperature: 1200,
|
77
|
-
food_safe: true)
|
78
|
-
ceramic1 = ComparableCeramic.new(type: "Bowl", glaze: glaze1)
|
79
|
-
ceramic2 = ComparableCeramic.new(type: "Bowl", glaze: glaze2)
|
80
|
-
collection1 = ComparableCeramicCollection.new(name: "Blue Collection",
|
81
|
-
featured_piece: ceramic1)
|
82
|
-
collection2 = ComparableCeramicCollection.new(name: "Blue Collection",
|
83
|
-
featured_piece: ceramic2)
|
84
|
-
expect(collection1).to eq(collection2)
|
85
|
-
expect(collection1.hash).to eq(collection2.hash)
|
107
|
+
expect(first_collection).to eq(second_collection)
|
86
108
|
end
|
87
109
|
|
88
|
-
it "
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
110
|
+
it "generates same hash for objects with two levels of nesting" do
|
111
|
+
expect(first_collection.hash).to eq(second_collection.hash)
|
112
|
+
end
|
113
|
+
|
114
|
+
context "with deeply nested objects that are not equal" do
|
115
|
+
before do
|
116
|
+
second_collection.name = "Red Collection"
|
117
|
+
second_collection.featured_piece.type = "Plate"
|
118
|
+
second_collection.featured_piece.glaze.color = "Red"
|
119
|
+
end
|
120
|
+
|
121
|
+
it "compares unequal objects with two levels of nesting" do
|
122
|
+
# This test compares CeramicCollection objects that are different at every level:
|
123
|
+
# the collection name, the ceramic type, and the glaze properties
|
124
|
+
expect(first_collection).not_to eq(second_collection)
|
125
|
+
end
|
126
|
+
|
127
|
+
it "generates different hashes for objects with two levels of nesting" do
|
128
|
+
expect(first_collection.hash).not_to eq(second_collection.hash)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
context "with recursive relationships" do
|
134
|
+
let(:first_recursive_node) do
|
135
|
+
node1 = RecursiveNode.new(name: "A")
|
136
|
+
node2 = RecursiveNode.new(name: "B", next_node: node1)
|
137
|
+
node1.next_node = node2
|
138
|
+
node1
|
139
|
+
end
|
140
|
+
|
141
|
+
let(:second_recursive_node) do
|
142
|
+
node1 = RecursiveNode.new(name: "A")
|
143
|
+
node2 = RecursiveNode.new(name: "B", next_node: node1)
|
144
|
+
node1.next_node = node2
|
145
|
+
node1
|
146
|
+
end
|
147
|
+
|
148
|
+
describe ".eql?" do
|
149
|
+
it "compares equal objects" do
|
150
|
+
expect(first_recursive_node).to eq(second_recursive_node)
|
151
|
+
end
|
152
|
+
|
153
|
+
it "compares unequal objects" do
|
154
|
+
second_recursive_node.name = "X"
|
155
|
+
expect(first_recursive_node).not_to eq(second_recursive_node)
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
describe ".hash" do
|
160
|
+
it "returns the same hash for equal objects" do
|
161
|
+
expect(first_recursive_node.hash).to eq(second_recursive_node.hash)
|
162
|
+
end
|
163
|
+
|
164
|
+
it "returns different hashes for unequal objects" do
|
165
|
+
second_recursive_node.name = "X"
|
166
|
+
expect(first_recursive_node.hash).not_to eq(second_recursive_node.hash)
|
167
|
+
end
|
103
168
|
end
|
104
169
|
end
|
105
170
|
end
|
@@ -143,7 +143,7 @@ RSpec.describe DefaultsSpec::Glaze do
|
|
143
143
|
expect(default_model.temperature).to eq(1050)
|
144
144
|
expect(default_model.firing_time).to eq(60)
|
145
145
|
expect(default_model.balance).to eq(BigDecimal("0.0"))
|
146
|
-
expect(default_model.tags).to
|
146
|
+
expect(default_model.tags).to be_nil
|
147
147
|
expect(default_model.properties).to eq({ food_safe: true })
|
148
148
|
expect(default_model.status).to eq("active")
|
149
149
|
expect(default_model.batch_number).to eq(0)
|
@@ -5,7 +5,7 @@ module EnumSpec
|
|
5
5
|
class WithEnum < Lutaml::Model::Serializable
|
6
6
|
attribute :without_enum, :string
|
7
7
|
attribute :single_value, :string, values: %w[user admin super_admin]
|
8
|
-
attribute :multi_value, :string, values: %w[singular dual plural], collection: true
|
8
|
+
attribute :multi_value, :string, values: %w[singular dual plural], collection: true, initialize_empty: true
|
9
9
|
end
|
10
10
|
end
|
11
11
|
|