schron 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +3 -0
  3. data/Gemfile +4 -0
  4. data/Rakefile +19 -0
  5. data/lib/schron.rb +4 -0
  6. data/lib/schron/archive.rb +88 -0
  7. data/lib/schron/archive/interface.rb +52 -0
  8. data/lib/schron/datastore/interface.rb +60 -0
  9. data/lib/schron/datastore/memory.rb +152 -0
  10. data/lib/schron/datastore/mongo.rb +173 -0
  11. data/lib/schron/datastore/mongo/metadata.rb +49 -0
  12. data/lib/schron/datastore/mongo/serializer.rb +38 -0
  13. data/lib/schron/datastore/sequel.rb +137 -0
  14. data/lib/schron/datastore/serializer.rb +46 -0
  15. data/lib/schron/dsl.rb +28 -0
  16. data/lib/schron/error.rb +7 -0
  17. data/lib/schron/id.rb +22 -0
  18. data/lib/schron/identity_map.rb +26 -0
  19. data/lib/schron/paginated_results.rb +28 -0
  20. data/lib/schron/paging.rb +5 -0
  21. data/lib/schron/query.rb +122 -0
  22. data/lib/schron/repository.rb +35 -0
  23. data/lib/schron/repository/interface.rb +36 -0
  24. data/lib/schron/test.rb +22 -0
  25. data/lib/schron/test/archive_examples.rb +133 -0
  26. data/lib/schron/test/datastore_examples.rb +419 -0
  27. data/lib/schron/test/entity.rb +21 -0
  28. data/lib/schron/test/repository_examples.rb +68 -0
  29. data/lib/schron/util.rb +27 -0
  30. data/schron.gemspec +24 -0
  31. data/spec/lib/schron/archive_spec.rb +26 -0
  32. data/spec/lib/schron/datastore/memory_spec.rb +9 -0
  33. data/spec/lib/schron/datastore/mongo_spec.rb +23 -0
  34. data/spec/lib/schron/datastore/sequel_spec.rb +40 -0
  35. data/spec/lib/schron/dsl_spec.rb +46 -0
  36. data/spec/lib/schron/identity_map_spec.rb +36 -0
  37. data/spec/lib/schron/paginaged_results_spec.rb +27 -0
  38. data/spec/lib/schron/query_spec.rb +46 -0
  39. data/spec/lib/schron/repository_spec.rb +27 -0
  40. data/spec/spec_helper.rb +9 -0
  41. data/spec/support/test_entities.rb +15 -0
  42. metadata +192 -0
@@ -0,0 +1,35 @@
1
+ require 'schron/query'
2
+ require 'schron/archive'
3
+ require 'schron/dsl'
4
+ require 'schron/repository/interface'
5
+
6
+ module Schron
7
+ module Repository
8
+
9
+ include Schron::Archive::Interface
10
+ include Schron::Repository::Interface
11
+
12
+ include Schron::Archive
13
+
14
+ def self.included(archive_class)
15
+ archive_class.extend Schron::DSL
16
+ end
17
+
18
+ def update(object)
19
+ maybe_load(@datastore.update(@kind, dump(object)))
20
+ end
21
+
22
+ def multi_update(objects)
23
+ load_all(@datastore.multi_update(@kind, dump_all(objects)))
24
+ end
25
+
26
+ def remove(id)
27
+ @datastore.remove(@kind, id) && nil
28
+ end
29
+
30
+ def multi_remove(ids)
31
+ @datastore.multi_remove(@kind, ids) && nil
32
+ end
33
+
34
+ end
35
+ end
@@ -0,0 +1,36 @@
1
+ require 'schron/archive/interface'
2
+
3
+ module Schron
4
+ module Repository
5
+ module Interface
6
+
7
+ include Schron::Archive::Interface
8
+
9
+ def update(object)
10
+ raise NotImplementedError
11
+ end
12
+
13
+ def multi_update(objects)
14
+ raise NotImplementedError
15
+ end
16
+
17
+ def remove(id)
18
+ raise NotImplementedError
19
+ end
20
+
21
+ def multi_remove(ids)
22
+ raise NotImplementedError
23
+ end
24
+
25
+ def dump(object)
26
+ raise NotImplementedError
27
+ end
28
+
29
+ def load(attributes)
30
+ raise NotImplementedError
31
+ end
32
+
33
+
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,22 @@
1
+ Dir[File.join(__dir__, 'test', '**', '*.rb')].each do |f|
2
+ require f
3
+ end
4
+
5
+ module Schron
6
+ module Test
7
+ module_function
8
+
9
+ def expect_same_ids(entity_set1, entity_set2)
10
+ ids1 = entity_set1.map(&:id)
11
+ ids2 = entity_set2.map(&:id)
12
+ expect(ids1.to_set).to eq(ids2.to_set)
13
+ end
14
+
15
+ def expect_same_entity(e1, e2)
16
+ expect(e1.id).to eq(e2.id)
17
+ expect(e1.attributes).to eq(e2.attributes)
18
+ end
19
+
20
+
21
+ end
22
+ end
@@ -0,0 +1,133 @@
1
+ if defined?(RSpec)
2
+
3
+ require 'schron/query'
4
+ require 'schron/test'
5
+
6
+ # Be sure to set the archive before inclusion
7
+ # let(:archive) { ... }
8
+ shared_examples "schron archive" do
9
+
10
+ include Schron::Test
11
+
12
+ let(:entity_class) { archive.entity_class }
13
+
14
+ describe 'schron archive behavior' do
15
+
16
+ describe 'query' do
17
+ it 'yields a query with the archives database and kind' do
18
+ entity = archive.insert(entity_class.new)
19
+ expect(archive.datastore).to receive(:exec_query).and_call_original
20
+ results = archive.query do |q|
21
+ expect(q.kind).to eq(archive.kind)
22
+ end
23
+ expect_same_entity results.first, entity
24
+ end
25
+ end
26
+
27
+ describe 'first' do
28
+ it 'yields a query, automatically adding limit 1 and returning the first result' do
29
+ entity = archive.insert(entity_class.new)
30
+ expect(archive.datastore).to receive(:exec_query).and_call_original
31
+ result = archive.first do |q|
32
+ expect(q.kind).to eq(archive.kind)
33
+ end
34
+ expect_same_entity result, entity
35
+ end
36
+ end
37
+
38
+ describe 'get' do
39
+ it 'returns nil when the id is not found' do
40
+ expect(archive.get('123')).to be_nil
41
+ end
42
+
43
+ it 'does not call load if no data is found' do
44
+ expect(archive).not_to receive(:load)
45
+ expect(archive.get('123'))
46
+ end
47
+
48
+ it 'returns the loaded object when present' do
49
+ david = archive.insert(entity_class.new(id: '1', name: 'david'))
50
+ expect_same_entity archive.get(david.id), david
51
+ end
52
+ end
53
+
54
+ describe 'multi_get' do
55
+ it 'returns objects by id' do
56
+ entities = archive.multi_insert([
57
+ entity_class.new(a: '1'),
58
+ entity_class.new(b: '2')
59
+ ])
60
+
61
+ results = archive.multi_get(entities.map(&:id))
62
+ expect_same_ids results, entities
63
+ end
64
+
65
+ it 'returns an empty array when given an empty array' do
66
+ expect_same_ids archive.multi_get([]), []
67
+ end
68
+
69
+ it 'does not call load if no results are found' do
70
+ expect(archive).not_to receive(:load)
71
+ archive.multi_get(['123'])
72
+ end
73
+
74
+ it 'returns an empty array when no ids are found' do
75
+ expect_same_ids archive.multi_get(['12']), []
76
+ end
77
+
78
+ it 'ignores non existent ids mixed in with existing ones' do
79
+ a = archive.insert(entity_class.new)
80
+ results = archive.multi_get([a.id, 'made-up'])
81
+ expect_same_ids results, [a]
82
+ end
83
+ end
84
+
85
+ describe 'insert' do
86
+ it 'returns the inserted object with an id set when none is provided' do
87
+ object = archive.insert(entity_class.new)
88
+ expect(object.id).not_to be_nil
89
+ expect_same_entity archive.get(object.id), object
90
+ end
91
+
92
+ it 'returns the inserted object, using the id passed in when provided' do
93
+ object = archive.insert(entity_class.new(id: '23'))
94
+ expect(object.id).to eq('23')
95
+ expect_same_entity archive.get('23'), object
96
+ end
97
+ end
98
+
99
+ describe 'multi_insert' do
100
+ it 'returns the inserted objects, with ids set when none are provided' do
101
+ objects = archive.multi_insert([entity_class.new, entity_class.new])
102
+ objects.each do |object|
103
+ expect_same_entity archive.get(object.id), object
104
+ end
105
+ end
106
+
107
+ it 'returns the inserted objects, using the id passed in if provided' do
108
+ objects = archive.multi_insert([
109
+ entity_class.new(id: '23'),
110
+ entity_class.new(id: 'shrike')
111
+ ])
112
+ expect(objects.map(&:id).to_set).to eq(Set.new(['23', 'shrike']))
113
+ end
114
+ end
115
+
116
+ describe 'load' do
117
+ it 'should pass the attributes to the constructor of the entity class' do
118
+ expect(archive.entity_class).to receive(:new).with({a: 'b', c: 'd'})
119
+ archive.load(a: 'b', c: 'd')
120
+ end
121
+ end
122
+
123
+ describe 'dump' do
124
+ it 'should call attributes on the object' do
125
+ obj = double(attributes: {id: 3, name: 'david'})
126
+ hash = archive.dump(obj)
127
+ expect(hash).to eq(id: 3, name: 'david')
128
+ end
129
+ end
130
+ end
131
+ end
132
+
133
+ end
@@ -0,0 +1,419 @@
1
+ if defined?(RSpec)
2
+
3
+ require 'schron/query'
4
+ shared_examples 'schron datastore' do
5
+ let(:entity_kind) { :entities }
6
+ let(:named_kind) { :named }
7
+ let(:with_types_kind) { :with_types }
8
+
9
+ describe 'datastore interface' do
10
+ describe 'get' do
11
+ it 'should return nil if no matching id is found' do
12
+ expect(ds.get(entity_kind, 'no-data')).to be_nil
13
+ end
14
+
15
+ it 'should return the data if present' do
16
+ data = ds.insert(named_kind, {name: "dave"})
17
+ expect(ds.get(named_kind, data[:id])).to eq(name: "dave", id: data[:id])
18
+ end
19
+
20
+ it 'should return a copy of the data' do
21
+ data = ds.insert(entity_kind, {})
22
+ data = ds.get(entity_kind, data[:id])
23
+ data[:change] = 'true'
24
+ expect(ds.get(entity_kind, data[:id])[:change]).to be_nil
25
+ end
26
+ end
27
+
28
+ describe 'multi get' do
29
+ it 'should return an empty array if passed an empty array' do
30
+ expect(ds.multi_get(entity_kind, [])).to eq([])
31
+ end
32
+
33
+ it 'should return data by id' do
34
+ d1 = ds.insert(named_kind, {name: 'a'})
35
+ d2 = ds.insert(named_kind, {name: 'b'})
36
+ ids = [d1[:id], d2[:id]]
37
+ expect(ds.multi_get(named_kind, ids)).to include(d1, d2)
38
+ end
39
+
40
+ it 'should not return nils when ids dont exist' do
41
+ d = ds.insert(entity_kind, {})
42
+ expect(ds.multi_get(entity_kind, ['no-data', d[:id]])).to eq([d])
43
+ end
44
+
45
+ it 'should return copies of the data' do
46
+ data = ds.insert(entity_kind, {})
47
+ data_list = ds.multi_get(entity_kind, [data[:id]])
48
+ data_list.first[:changed] = 'true'
49
+ retrieved = ds.multi_get(entity_kind, data_list.map{ |d| d[:id] })
50
+ expect(retrieved).to eq([{id: data[:id]}])
51
+ end
52
+
53
+ it 'should return the data in the order of the requested ids' do
54
+ d1 = ds.insert(named_kind, {name: 'a'})
55
+ d2 = ds.insert(named_kind, {name: 'b'})
56
+ ids = [d1[:id], d2[:id]]
57
+ expect(ds.multi_get(named_kind, ids)).to eq([d1, d2])
58
+ end
59
+
60
+ end
61
+
62
+ describe 'insert' do
63
+ it 'should assign an id if none is provided' do
64
+ data = ds.insert(entity_kind, {})
65
+ expect(data[:id]).not_to be_nil
66
+ end
67
+
68
+ it 'should use the provided id if present' do
69
+ data = ds.insert(entity_kind, {id: '123'})
70
+ expect(data[:id]).to eq('123')
71
+ end
72
+
73
+ it 'should return a copy of the data' do
74
+ data = ds.insert(entity_kind, {})
75
+ data[:a] = 'b'
76
+ expect(ds.get(entity_kind, data[:id])[:a]).to be_nil
77
+ end
78
+
79
+ end
80
+
81
+ describe 'multi insert' do
82
+ it 'inserts all the data and returns it' do
83
+ data = ds.multi_insert(named_kind, [{name: 'a'}, {name: 'b'}])
84
+ expect(data.map{ |d| d[:name] }).to include('a', 'b')
85
+ expect(data.size).to eq(2)
86
+ end
87
+
88
+ it 'assigns ids when none are provided' do
89
+ data = ds.multi_insert(entity_kind, [{}, {}])
90
+ expect(data.any?{ |d| d[:id].nil? }).to be(false)
91
+ end
92
+
93
+ it 'uses provided ids when present' do
94
+ data = ds.multi_insert(entity_kind, [{id: '123'}, {id: '456'}])
95
+ expect(data.map{ |d| d[:id] }).to eq(['123', '456'])
96
+ end
97
+ end
98
+
99
+ describe 'update' do
100
+ it 'should raise an error if no id is provided' do
101
+ expect { ds.update(entity_kind, {}) }.to raise_error
102
+ end
103
+
104
+ it 'should update the data by id' do
105
+ inserted = ds.insert(named_kind, {})
106
+ inserted[:name] = 'david'
107
+ updated = ds.update(named_kind, inserted)
108
+ expect(updated[:name]).to eq('david')
109
+ expect(updated[:id]).to eq(inserted[:id])
110
+ end
111
+
112
+ it 'should return a copy of the data' do
113
+ data = ds.insert(named_kind, {})
114
+ data = ds.update(named_kind, data.merge(name: 'joe'))
115
+ data[:c] = 'd'
116
+ expect(ds.get(named_kind, data[:id])[:c]).to be_nil
117
+ end
118
+ end
119
+
120
+ describe 'multi update' do
121
+ it 'raises an error if any ids are missing' do
122
+ expect do
123
+ ds.multi_update(entity_kind, [{id: '123'}, {}])
124
+ end.to raise_error
125
+ end
126
+
127
+ it 'updates the data by ids' do
128
+ inserted = ds.multi_insert(named_kind, [{}, {}])
129
+ inserted[0][:name] = 'a'
130
+ inserted[1][:name] = 'b'
131
+ updated = ds.multi_update(named_kind, inserted)
132
+ expect(updated.map{ |d| d[:name] }.to_set).to eq(Set.new(['a', 'b']))
133
+ expect(updated.map{ |d| d[:id] }.to_set).to eq(Set.new(inserted.map{ |d| d[:id] }))
134
+ end
135
+
136
+ it 'returns copies of the data' do
137
+ data = ds.insert(named_kind, {})
138
+ data = ds.multi_update(named_kind, [data.merge(name: 'joe')]).first
139
+ data[:c] = 'd'
140
+ expect(ds.get(named_kind, data[:id])[:c]).to be_nil
141
+ end
142
+ end
143
+
144
+ describe 'remove' do
145
+ it 'should remove data from the store by id' do
146
+ data = ds.insert(entity_kind, {})
147
+ ds.remove(entity_kind, data[:id])
148
+ expect(ds.get(entity_kind, data[:id])).to be_nil
149
+ end
150
+
151
+ it 'should return nil' do
152
+ data = ds.insert(entity_kind, {})
153
+ expect(ds.remove(entity_kind, data[:id])).to be_nil
154
+ end
155
+ end
156
+
157
+ describe 'multi remove' do
158
+ it 'should remove the data from the store by ids' do
159
+ data = ds.multi_insert(entity_kind, [{}, {}])
160
+ ids = data.map { |d| d[:id] }
161
+ ds.multi_remove(entity_kind, ids)
162
+ expect(ds.multi_get(entity_kind, ids)).to be_empty
163
+ end
164
+
165
+ it 'should return nil' do
166
+ data = ds.insert(entity_kind, {})
167
+ expect(ds.multi_remove(entity_kind, [data[:id]])).to be_nil
168
+ end
169
+ end
170
+
171
+ describe 'exec query' do
172
+ let(:query) { Schron::Query.new(named_kind, ds) }
173
+ let(:no_result_query) { Schron::Query.new(entity_kind, ds) }
174
+
175
+ before(:each) do
176
+ @c = ds.insert(named_kind, name: 'c')
177
+ @a1 = ds.insert(named_kind, name: 'a')
178
+ @b = ds.insert(named_kind, name: 'b')
179
+ @a2 = ds.insert(named_kind, name: 'a')
180
+ @nil = ds.insert(named_kind, {name: nil})
181
+ end
182
+
183
+ describe 'filters' do
184
+ it 'filters by equality with a hash' do
185
+ found = query.filter(name: 'b').all
186
+ expect(found.size).to eq(1)
187
+ expect(found.first[:id]).to eq(@b[:id])
188
+ end
189
+
190
+ it 'filters by inclusion with a hash' do
191
+ found = query.filter(name: ['b', 'c']).all
192
+ expect(found.size).to eq(2)
193
+ expect(found.map{ |h| h[:id] }).to include(@b[:id], @c[:id])
194
+ end
195
+
196
+ it 'filters by "=" operator' do
197
+ found = query.filter(:name, '=', 'b').all
198
+ expect(found.size).to eq(1)
199
+ expect(found.first[:id]).to eq(@b[:id])
200
+ end
201
+
202
+ it 'filters by "in" operator' do
203
+ found = query.filter(:name, 'in', ['a', 'b']).all
204
+ expect(found.size).to eq(3)
205
+ expect(found.map{ |h| h[:id] }).to include(@a1[:id], @a2[:id], @b[:id])
206
+ end
207
+
208
+ it 'filters by ">" operator' do
209
+ found = query.filter(:name, '>', 'a').all
210
+ expect(found.size).to be(2)
211
+ expect(found.map{ |h| h[:id] }).to include(@b[:id], @c[:id])
212
+ end
213
+
214
+ it 'filters by ">=" operator' do
215
+ found = query.filter(:name, '>=', 'a').all
216
+ expect(found.size).to be(4)
217
+ expect(found.map{ |h| h[:id] }).to include(@a1[:id], @a2[:id], @b[:id], @c[:id])
218
+ end
219
+
220
+ it 'filters by "<" operator' do
221
+ found = query.filter(:name, '<', 'b').all
222
+ expect(found.size).to be(2)
223
+ expect(found.map{ |h| h[:id] }).to include(@a1[:id], @a2[:id])
224
+ end
225
+
226
+ it 'filters by "<=" operator' do
227
+ found = query.filter(:name, '<=', 'b').all
228
+ expect(found.size).to be(3)
229
+ expect(found.map{ |h| h[:id] }).to include(@a1[:id], @a2[:id], @b[:id])
230
+ end
231
+
232
+ it 'filters by "!=" operator' do
233
+ found = query.filter(:name, '!=', 'a').all
234
+ expect(found.size).to eq(3)
235
+ expect(found.map{ |h| h[:id] }).to include(@c[:id], @b[:id], @nil[:id])
236
+ end
237
+
238
+ it 'returns an empty list if no results match' do
239
+ expect(query.filter(name: '123').all).to eq([])
240
+ end
241
+
242
+ context 'nil filtering' do
243
+ it 'filters by = nil' do
244
+ found = query.filter(name: nil).all
245
+ expect(found.size).to eq(1)
246
+ expect(found.first[:id]).to eq(@nil[:id])
247
+ end
248
+
249
+ it 'filters by != nil' do
250
+ found = query.filter(:name, '!=', nil).all
251
+ expect(found.size).to eq(4)
252
+ expect(found.map{ |h| h[:id] }).to include(@a1[:id], @a2[:id], @b[:id], @c[:id])
253
+ end
254
+
255
+ it 'filters by in with nil' do
256
+ found = query.filter(:name, 'in', [nil, 'b']).all
257
+ expect(found.size).to eq(2)
258
+ expect(found.map{ |h| h[:id] }).to include(@b[:id], @nil[:id])
259
+ end
260
+ end
261
+ end
262
+
263
+ describe 'limit' do
264
+ it 'limits the results' do
265
+ found = query.limit(1).all
266
+ expect(found.size).to eq(1)
267
+ end
268
+
269
+ it 'returns an empty list if no results match' do
270
+ q = no_result_query
271
+ expect(q.limit(2).all).to eq([])
272
+ end
273
+ end
274
+
275
+ describe 'offset' do
276
+ it 'offsets the results' do
277
+ all = 4.times.reduce([]) do |ary, i|
278
+ ary + query.limit(1).offset(i).all
279
+ end
280
+ all_ids = all.map { |h| h[:id] }
281
+ expect(all_ids).to include(@a1[:id], @a2[:id], @b[:id], @c[:id])
282
+ end
283
+
284
+ it 'returns an empty list if no results match' do
285
+ expect(query.offset(10).all).to eq([])
286
+ end
287
+ end
288
+
289
+ describe 'sorts' do
290
+ it 'sorts in asc direction by default' do
291
+ found = query.sort(:name).all
292
+ expect(found.map{ |h| h[:name] }).to eq(
293
+ [nil, 'a', 'a', 'b', 'c']
294
+ )
295
+ end
296
+
297
+ it 'sorts ascending' do
298
+ found = query.sort(:name, :asc).all
299
+ expect(found.map{ |h| h[:name] }).to eq([nil, 'a', 'a', 'b', 'c'])
300
+ end
301
+
302
+ it 'sorts descending' do
303
+ found = query.sort(:name, :desc).all
304
+ expect(found.map { |h| h[:name] }).to eq(['c', 'b', 'a', 'a', nil])
305
+ end
306
+
307
+ it 'sorts by multiple fields' do
308
+ found = query.sort(:name).sort(:id).all
309
+ expected = [@nil[:id]] + [@a1[:id], @a2[:id]].sort + [@b[:id], @c[:id]]
310
+ expect(found.map { |h| h[:id] }).to eq(expected)
311
+ end
312
+
313
+ it 'returns an empty list if no results match' do
314
+ expect(no_result_query.sort(:id).all).to eq([])
315
+ end
316
+ end
317
+
318
+ end
319
+ end
320
+
321
+ describe 'datastore type handling' do
322
+
323
+ describe 'arrays' do
324
+ xit 'saves and loads empty arrays' do
325
+ data = ds.insert(with_types_kind, {
326
+ array: []
327
+ })
328
+ loaded = ds.get(with_types_kind, data[:id])
329
+ expect(loaded[:array]).to eq([])
330
+ end
331
+ xit 'stores and loads arrays of strings, numbers and arrays' do
332
+ data = ds.insert(with_types_kind, {
333
+ array: [1, "a", ["b"]]
334
+ })
335
+ loaded = ds.get(with_types_kind, data[:id])
336
+ expect(loaded[:array]).to eq([1, "a", ["b"]])
337
+ end
338
+ end
339
+
340
+ describe 'dates and times' do
341
+ xit 'saves and loads Date' do
342
+ data = ds.insert(with_types_kind, {
343
+ date: Date.new(2010, 10, 6)
344
+ })
345
+ loaded = ds.get(with_types_kind, data[:id])
346
+ expect(loaded[:date]).to eq(Date.new(2010, 10, 6))
347
+ end
348
+
349
+ xit 'saves and loads Time' do
350
+ data = ds.insert(with_types_kind, {
351
+ time: Time.new(2013, 10, 6)
352
+ })
353
+ loaded = ds.get(with_types_kind, data[:id])
354
+ expect(loaded[:time]).to eq(Time.new(2013, 10, 6))
355
+ end
356
+
357
+ xit 'saves and loads DateTime' do
358
+ data = ds.insert(with_types_kind, {
359
+ date_time: DateTime.new(2013, 10, 6)
360
+ })
361
+ loaded = ds.get(with_types_kind, data[:id])
362
+ expect(loaded[:date_time]).to eq(DateTime.new(2013, 10, 6))
363
+ end
364
+ end
365
+
366
+ describe 'sets' do
367
+ xit 'saves and loads Sets of numbers, strings and arrays' do
368
+ data = ds.insert(with_types_kind, {
369
+ set: Set.new([1, "2", ["a"]])
370
+ })
371
+ loaded = ds.get(with_types_kind, data[:id])
372
+ expect(loaded[:set]).to eq(Set.new([1, "2", ["a"]]))
373
+ end
374
+ end
375
+
376
+ describe 'nested hashes' do
377
+ xit 'saves and loads nested hashes' do
378
+ data = ds.insert(with_types_kind, {
379
+ hash: {a: {b: 'c'}, d: 3}
380
+ })
381
+ loaded = ds.get(with_types_kind, data[:id])
382
+ expect(loaded[:hash]).to eq({a: {b: 'c'}, d: 3})
383
+ end
384
+ end
385
+
386
+ describe 'recursive structure' do
387
+ xit 'saves and loads' do
388
+ data = ds.insert(with_types_kind, {
389
+ name: 'score',
390
+ children: [
391
+ {name: 'diet', children: [
392
+ {name: 'eating'},
393
+ {name: 'not eating'}
394
+ ]},
395
+ {name: 'exercise', children: [
396
+ {name: 'workout'},
397
+ {name: 'sit'}
398
+ ]}
399
+ ]})
400
+ loaded = ds.get(with_types_kind, data[:id])
401
+ loaded.delete(:id)
402
+ expect(loaded).to eq({
403
+ name: 'score',
404
+ children: [
405
+ {name: 'diet', children: [
406
+ {name: 'eating'},
407
+ {name: 'not eating'}
408
+ ]},
409
+ {name: 'exercise', children: [
410
+ {name: 'workout'},
411
+ {name: 'sit'}
412
+ ]}
413
+ ]})
414
+ end
415
+ end
416
+ end
417
+ end
418
+
419
+ end