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,299 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "lutaml/model"
|
3
|
+
|
4
|
+
module CollectionTests
|
5
|
+
class Pot < Lutaml::Model::Serializable
|
6
|
+
attribute :material, Lutaml::Model::Type::String
|
7
|
+
|
8
|
+
xml do
|
9
|
+
root "pot"
|
10
|
+
map_element "material", to: :material
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class Kiln < Lutaml::Model::Serializable
|
15
|
+
attribute :brand, Lutaml::Model::Type::String
|
16
|
+
attribute :pots, Pot, collection: 0..2
|
17
|
+
attribute :temperatures, Lutaml::Model::Type::Integer, collection: true
|
18
|
+
attribute :operators, Lutaml::Model::Type::String, collection: (1..),
|
19
|
+
default: -> {
|
20
|
+
["Default Operator"]
|
21
|
+
}
|
22
|
+
attribute :sensors, Lutaml::Model::Type::String, collection: 1..3,
|
23
|
+
default: -> {
|
24
|
+
["Default Sensor"]
|
25
|
+
}
|
26
|
+
|
27
|
+
xml do
|
28
|
+
root "kiln"
|
29
|
+
map_attribute "brand", to: :brand
|
30
|
+
map_element "pot", to: :pots
|
31
|
+
map_element "temperature", to: :temperatures
|
32
|
+
map_element "operator", to: :operators
|
33
|
+
map_element "sensor", to: :sensors
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class Address < Lutaml::Model::Serializable
|
38
|
+
attribute :street, :string
|
39
|
+
attribute :city, :string
|
40
|
+
attribute :address, Address
|
41
|
+
|
42
|
+
xml do
|
43
|
+
root "address"
|
44
|
+
map_element "street", to: :street
|
45
|
+
map_element "city", with: { from: :city_from_xml, to: :city_to_xml }
|
46
|
+
map_element "address", to: :address
|
47
|
+
end
|
48
|
+
|
49
|
+
def city_from_xml(model, node)
|
50
|
+
model.city = node
|
51
|
+
end
|
52
|
+
|
53
|
+
def city_to_xml(model, parent, doc)
|
54
|
+
doc.add_element(parent, "<city>#{model.city}</city>")
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
RSpec.describe CollectionTests do
|
60
|
+
let(:pots) { [{ material: "clay" }, { material: "ceramic" }] }
|
61
|
+
let(:temperatures) { [1200, 1300, 1400] }
|
62
|
+
let(:operators) { ["John", "Jane"] }
|
63
|
+
let(:sensors) { ["Temp1", "Temp2"] }
|
64
|
+
let(:attributes) do
|
65
|
+
{
|
66
|
+
brand: "Skutt",
|
67
|
+
pots: pots,
|
68
|
+
temperatures: temperatures,
|
69
|
+
operators: operators,
|
70
|
+
sensors: sensors,
|
71
|
+
}
|
72
|
+
end
|
73
|
+
let(:model) { CollectionTests::Kiln.new(attributes) }
|
74
|
+
|
75
|
+
let(:model_xml) do
|
76
|
+
<<~XML
|
77
|
+
<kiln brand="Skutt">
|
78
|
+
<pot>
|
79
|
+
<material>clay</material>
|
80
|
+
</pot>
|
81
|
+
<pot>
|
82
|
+
<material>ceramic</material>
|
83
|
+
</pot>
|
84
|
+
<temperature>1200</temperature>
|
85
|
+
<temperature>1300</temperature>
|
86
|
+
<temperature>1400</temperature>
|
87
|
+
<operator>John</operator>
|
88
|
+
<operator>Jane</operator>
|
89
|
+
<sensor>Temp1</sensor>
|
90
|
+
<sensor>Temp2</sensor>
|
91
|
+
</kiln>
|
92
|
+
XML
|
93
|
+
end
|
94
|
+
|
95
|
+
it "initializes with default values" do
|
96
|
+
default_model = CollectionTests::Kiln.new
|
97
|
+
expect(default_model.brand).to be_nil
|
98
|
+
expect(default_model.pots).to eq([])
|
99
|
+
expect(default_model.temperatures).to eq([])
|
100
|
+
expect(default_model.operators).to eq(["Default Operator"])
|
101
|
+
expect(default_model.sensors).to eq(["Default Sensor"])
|
102
|
+
end
|
103
|
+
|
104
|
+
it "serializes to XML" do
|
105
|
+
expected_xml = model_xml.strip
|
106
|
+
expect(model.to_xml.strip).to eq(expected_xml)
|
107
|
+
end
|
108
|
+
|
109
|
+
it "deserializes from XML" do
|
110
|
+
sample = CollectionTests::Kiln.from_xml(model_xml)
|
111
|
+
expect(sample.brand).to eq("Skutt")
|
112
|
+
expect(sample.pots.size).to eq(2)
|
113
|
+
expect(sample.pots[0].material).to eq("clay")
|
114
|
+
expect(sample.pots[1].material).to eq("ceramic")
|
115
|
+
expect(sample.temperatures).to eq([1200, 1300, 1400])
|
116
|
+
expect(sample.operators).to eq(["John", "Jane"])
|
117
|
+
expect(sample.sensors).to eq(["Temp1", "Temp2"])
|
118
|
+
end
|
119
|
+
|
120
|
+
it "round-trips XML" do
|
121
|
+
xml = model.to_xml
|
122
|
+
new_model = CollectionTests::Kiln.from_xml(xml)
|
123
|
+
expect(new_model.brand).to eq(model.brand)
|
124
|
+
expect(new_model.pots.size).to eq(model.pots.size)
|
125
|
+
model.pots.each_with_index do |pot, index|
|
126
|
+
expect(new_model.pots[index].material).to eq(pot.material)
|
127
|
+
end
|
128
|
+
expect(new_model.temperatures).to eq(model.temperatures)
|
129
|
+
expect(new_model.operators).to eq(model.operators)
|
130
|
+
expect(new_model.sensors).to eq(model.sensors)
|
131
|
+
end
|
132
|
+
|
133
|
+
context "when model contains self as attribute" do
|
134
|
+
let(:xml) do
|
135
|
+
<<~XML
|
136
|
+
<address>
|
137
|
+
<street>A</street>
|
138
|
+
<city>B</city>
|
139
|
+
<address>
|
140
|
+
<street>C</street>
|
141
|
+
<city>D</city>
|
142
|
+
</address>
|
143
|
+
</address>
|
144
|
+
XML
|
145
|
+
end
|
146
|
+
|
147
|
+
it "deserializes from XML" do
|
148
|
+
model = CollectionTests::Address.from_xml(xml)
|
149
|
+
|
150
|
+
expect(model.street).to eq("A")
|
151
|
+
expect(model.city).to eq("B")
|
152
|
+
expect(model.address.street).to eq("C")
|
153
|
+
expect(model.address.city).to eq("D")
|
154
|
+
end
|
155
|
+
|
156
|
+
it "round-trips XML" do
|
157
|
+
model = CollectionTests::Address.from_xml(xml)
|
158
|
+
|
159
|
+
expect(model.to_xml).to be_equivalent_to(xml)
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
context "when collection counts are below given ranges" do
|
164
|
+
let(:invalid_attributes) do
|
165
|
+
attributes.merge(operators: [], sensors: [])
|
166
|
+
end
|
167
|
+
|
168
|
+
it "raises ValidationError containing CollectionCountOutOfRangeError for operators" do
|
169
|
+
kiln = CollectionTests::Kiln.new(invalid_attributes)
|
170
|
+
expect do
|
171
|
+
kiln.validate!
|
172
|
+
end.to raise_error(Lutaml::Model::ValidationError) do |error|
|
173
|
+
expect(error).to include(Lutaml::Model::CollectionCountOutOfRangeError)
|
174
|
+
expect(error.error_messages).to include(a_string_matching(/operators count is 0, must be at least 1/))
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
it "raises ValidationError containing CollectionCountOutOfRangeError for sensors" do
|
179
|
+
kiln = CollectionTests::Kiln.new(attributes.merge(sensors: []))
|
180
|
+
expect do
|
181
|
+
kiln.validate!
|
182
|
+
end.to raise_error(Lutaml::Model::ValidationError) do |error|
|
183
|
+
expect(error).to include(Lutaml::Model::CollectionCountOutOfRangeError)
|
184
|
+
expect(error.error_messages).to include(a_string_matching(/sensors count is 0, must be between 1 and 3/))
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
context "when collection counts are below given ranges" do
|
190
|
+
let(:invalid_attributes) do
|
191
|
+
attributes.merge(operators: [], sensors: [])
|
192
|
+
end
|
193
|
+
|
194
|
+
it "raises CollectionCountOutOfRangeError" do
|
195
|
+
kiln = CollectionTests::Kiln.new(invalid_attributes)
|
196
|
+
expect do
|
197
|
+
kiln.validate!
|
198
|
+
end.to raise_error(Lutaml::Model::ValidationError) do |error|
|
199
|
+
expect(error).to include(Lutaml::Model::CollectionCountOutOfRangeError)
|
200
|
+
expect(error.error_messages).to include(a_string_matching(/operators count is 0, must be at least 1/))
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
context "when collection with unbounded maximum exceeds minimum" do
|
206
|
+
let(:valid_attributes) do
|
207
|
+
attributes.merge(operators: ["John", "Jane", "Jim", "Jessica"])
|
208
|
+
end
|
209
|
+
|
210
|
+
it "creates the model without errors" do
|
211
|
+
expect { CollectionTests::Kiln.new(valid_attributes) }.not_to raise_error
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
context "when deserializing XML with invalid collection counts" do
|
216
|
+
let(:invalid_xml) do
|
217
|
+
<<~XML
|
218
|
+
<kiln brand="Skutt">
|
219
|
+
<pot>
|
220
|
+
<material>clay</material>
|
221
|
+
</pot>
|
222
|
+
<pot>
|
223
|
+
<material>ceramic</material>
|
224
|
+
</pot>
|
225
|
+
<pot>
|
226
|
+
<material>porcelain</material>
|
227
|
+
</pot>
|
228
|
+
<temperature>1200</temperature>
|
229
|
+
<operator>John</operator>
|
230
|
+
<sensor>Temp1</sensor>
|
231
|
+
<sensor>Temp2</sensor>
|
232
|
+
<sensor>Temp3</sensor>
|
233
|
+
<sensor>Temp4</sensor>
|
234
|
+
</kiln>
|
235
|
+
XML
|
236
|
+
end
|
237
|
+
|
238
|
+
it "raises ValidationError containing CollectionCountOutOfRangeError" do
|
239
|
+
expect do
|
240
|
+
CollectionTests::Kiln.from_xml(invalid_xml).validate!
|
241
|
+
end.to raise_error(Lutaml::Model::ValidationError) do |error|
|
242
|
+
expect(error).to include(Lutaml::Model::CollectionCountOutOfRangeError)
|
243
|
+
expect(error.error_messages).to include(a_string_matching(/pots count is 3, must be between 0 and 2/))
|
244
|
+
end
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
context "when specifying invalid collection ranges" do
|
249
|
+
it "raises an error for a range with only an upper bound" do
|
250
|
+
expect do
|
251
|
+
Class.new(Lutaml::Model::Serializable) do
|
252
|
+
attribute :invalid_range, Lutaml::Model::Type::String, collection: ..3
|
253
|
+
end
|
254
|
+
end.to raise_error(ArgumentError, /Invalid collection range/)
|
255
|
+
end
|
256
|
+
|
257
|
+
it "raises an error for a range where max is less than min" do
|
258
|
+
expect do
|
259
|
+
Class.new(Lutaml::Model::Serializable) do
|
260
|
+
attribute :invalid_range, Lutaml::Model::Type::String,
|
261
|
+
collection: 9..3
|
262
|
+
end
|
263
|
+
end.to raise_error(ArgumentError, /Invalid collection range/)
|
264
|
+
end
|
265
|
+
|
266
|
+
it "raises an error for a negative range" do
|
267
|
+
expect do
|
268
|
+
Class.new(Lutaml::Model::Serializable) do
|
269
|
+
attribute :invalid_range, Lutaml::Model::Type::String,
|
270
|
+
collection: -2..1
|
271
|
+
end
|
272
|
+
end.to raise_error(ArgumentError, /Invalid collection range/)
|
273
|
+
end
|
274
|
+
|
275
|
+
it "allows a range with only a lower bound" do
|
276
|
+
expect do
|
277
|
+
Class.new(Lutaml::Model::Serializable) do
|
278
|
+
attribute :valid_range, Lutaml::Model::Type::String, collection: 1..
|
279
|
+
end
|
280
|
+
end.not_to raise_error
|
281
|
+
end
|
282
|
+
|
283
|
+
it "allows a range with both lower and upper bounds" do
|
284
|
+
expect do
|
285
|
+
Class.new(Lutaml::Model::Serializable) do
|
286
|
+
attribute :valid_range, Lutaml::Model::Type::String, collection: 1..3
|
287
|
+
end
|
288
|
+
end.not_to raise_error
|
289
|
+
end
|
290
|
+
|
291
|
+
it "allows a range with zero as the lower bound" do
|
292
|
+
expect do
|
293
|
+
Class.new(Lutaml::Model::Serializable) do
|
294
|
+
attribute :valid_range, Lutaml::Model::Type::String, collection: 0..3
|
295
|
+
end
|
296
|
+
end.not_to raise_error
|
297
|
+
end
|
298
|
+
end
|
299
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
# Simple model with basic attributes
|
4
|
+
class ComparableGlaze < Lutaml::Model::Serializable
|
5
|
+
attribute :color, :string
|
6
|
+
attribute :temperature, :integer
|
7
|
+
attribute :food_safe, :boolean
|
8
|
+
end
|
9
|
+
|
10
|
+
# Model with a nested Serializable object
|
11
|
+
class ComparableCeramic < Lutaml::Model::Serializable
|
12
|
+
attribute :type, :string
|
13
|
+
attribute :glaze, ComparableGlaze
|
14
|
+
end
|
15
|
+
|
16
|
+
# Model with a deeply nested Serializable object
|
17
|
+
class ComparableCeramicCollection < Lutaml::Model::Serializable
|
18
|
+
attribute :name, :string
|
19
|
+
attribute :featured_piece, ComparableCeramic # This creates a two-level nesting
|
20
|
+
end
|
21
|
+
|
22
|
+
RSpec.describe Lutaml::Model::ComparableModel do
|
23
|
+
describe "comparisons" do
|
24
|
+
context "with simple types (Glaze)" do
|
25
|
+
it "compares equal objects with basic attributes" do
|
26
|
+
glaze1 = ComparableGlaze.new(color: "Blue", temperature: 1200,
|
27
|
+
food_safe: true)
|
28
|
+
glaze2 = ComparableGlaze.new(color: "Blue", temperature: 1200,
|
29
|
+
food_safe: true)
|
30
|
+
expect(glaze1).to eq(glaze2)
|
31
|
+
expect(glaze1.hash).to eq(glaze2.hash)
|
32
|
+
end
|
33
|
+
|
34
|
+
it "compares unequal objects with basic attributes" do
|
35
|
+
glaze1 = ComparableGlaze.new(color: "Blue", temperature: 1200,
|
36
|
+
food_safe: true)
|
37
|
+
glaze2 = ComparableGlaze.new(color: "Red", temperature: 1000,
|
38
|
+
food_safe: false)
|
39
|
+
expect(glaze1).not_to eq(glaze2)
|
40
|
+
expect(glaze1.hash).not_to eq(glaze2.hash)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
context "with nested Serializable objects (Ceramic)" do
|
45
|
+
it "compares equal objects with one level of nesting" do
|
46
|
+
# Here, we're comparing Ceramic objects that contain Glaze objects
|
47
|
+
glaze1 = ComparableGlaze.new(color: "Blue", temperature: 1200,
|
48
|
+
food_safe: true)
|
49
|
+
glaze2 = ComparableGlaze.new(color: "Blue", temperature: 1200,
|
50
|
+
food_safe: true)
|
51
|
+
ceramic1 = ComparableCeramic.new(type: "Bowl", glaze: glaze1)
|
52
|
+
ceramic2 = ComparableCeramic.new(type: "Bowl", glaze: glaze2)
|
53
|
+
expect(ceramic1).to eq(ceramic2)
|
54
|
+
expect(ceramic1.hash).to eq(ceramic2.hash)
|
55
|
+
end
|
56
|
+
|
57
|
+
it "compares unequal objects with one level of nesting" do
|
58
|
+
# Here, we're comparing Ceramic objects with different Glaze objects
|
59
|
+
glaze1 = ComparableGlaze.new(color: "Blue", temperature: 1200,
|
60
|
+
food_safe: true)
|
61
|
+
glaze2 = ComparableGlaze.new(color: "Red", temperature: 1000,
|
62
|
+
food_safe: false)
|
63
|
+
ceramic1 = ComparableCeramic.new(type: "Bowl", glaze: glaze1)
|
64
|
+
ceramic2 = ComparableCeramic.new(type: "Plate", glaze: glaze2)
|
65
|
+
expect(ceramic1).not_to eq(ceramic2)
|
66
|
+
expect(ceramic1.hash).not_to eq(ceramic2.hash)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
context "with deeply nested Serializable objects (CeramicCollection)" do
|
71
|
+
it "compares equal objects with two levels of nesting" do
|
72
|
+
# This test compares CeramicCollection objects that contain Ceramic objects,
|
73
|
+
# which in turn contain Glaze objects - a two-level deep nesting
|
74
|
+
glaze1 = ComparableGlaze.new(color: "Blue", temperature: 1200,
|
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)
|
86
|
+
end
|
87
|
+
|
88
|
+
it "compares unequal objects with two levels of nesting" do
|
89
|
+
# This test compares CeramicCollection objects that are different at every level:
|
90
|
+
# the collection name, the ceramic type, and the glaze properties
|
91
|
+
glaze1 = ComparableGlaze.new(color: "Blue", temperature: 1200,
|
92
|
+
food_safe: true)
|
93
|
+
glaze2 = ComparableGlaze.new(color: "Red", temperature: 1000,
|
94
|
+
food_safe: false)
|
95
|
+
ceramic1 = ComparableCeramic.new(type: "Bowl", glaze: glaze1)
|
96
|
+
ceramic2 = ComparableCeramic.new(type: "Plate", glaze: glaze2)
|
97
|
+
collection1 = ComparableCeramicCollection.new(name: "Blue Collection",
|
98
|
+
featured_piece: ceramic1)
|
99
|
+
collection2 = ComparableCeramicCollection.new(name: "Red Collection",
|
100
|
+
featured_piece: ceramic2)
|
101
|
+
expect(collection1).not_to eq(collection2)
|
102
|
+
expect(collection1.hash).not_to eq(collection2.hash)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|