zermelo 1.1.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (66) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +10 -0
  3. data/README.md +76 -52
  4. data/lib/zermelo/associations/association_data.rb +4 -3
  5. data/lib/zermelo/associations/class_methods.rb +37 -50
  6. data/lib/zermelo/associations/index.rb +3 -1
  7. data/lib/zermelo/associations/multiple.rb +247 -0
  8. data/lib/zermelo/associations/range_index.rb +44 -0
  9. data/lib/zermelo/associations/singular.rb +193 -0
  10. data/lib/zermelo/associations/unique_index.rb +4 -3
  11. data/lib/zermelo/backend.rb +120 -0
  12. data/lib/zermelo/backends/{influxdb_backend.rb → influxdb.rb} +87 -31
  13. data/lib/zermelo/backends/{redis_backend.rb → redis.rb} +53 -58
  14. data/lib/zermelo/backends/stub.rb +43 -0
  15. data/lib/zermelo/filter.rb +194 -0
  16. data/lib/zermelo/filters/index_range.rb +22 -0
  17. data/lib/zermelo/filters/{influxdb_filter.rb → influxdb.rb} +12 -11
  18. data/lib/zermelo/filters/redis.rb +173 -0
  19. data/lib/zermelo/filters/steps/list_step.rb +48 -30
  20. data/lib/zermelo/filters/steps/set_step.rb +148 -89
  21. data/lib/zermelo/filters/steps/sort_step.rb +2 -2
  22. data/lib/zermelo/record.rb +53 -0
  23. data/lib/zermelo/records/attributes.rb +32 -0
  24. data/lib/zermelo/records/class_methods.rb +12 -25
  25. data/lib/zermelo/records/{influxdb_record.rb → influxdb.rb} +3 -4
  26. data/lib/zermelo/records/instance_methods.rb +9 -8
  27. data/lib/zermelo/records/key.rb +3 -1
  28. data/lib/zermelo/records/redis.rb +17 -0
  29. data/lib/zermelo/records/stub.rb +17 -0
  30. data/lib/zermelo/version.rb +1 -1
  31. data/spec/lib/zermelo/associations/index_spec.rb +70 -1
  32. data/spec/lib/zermelo/associations/multiple_spec.rb +1084 -0
  33. data/spec/lib/zermelo/associations/range_index_spec.rb +77 -0
  34. data/spec/lib/zermelo/associations/singular_spec.rb +149 -0
  35. data/spec/lib/zermelo/associations/unique_index_spec.rb +58 -2
  36. data/spec/lib/zermelo/filter_spec.rb +363 -0
  37. data/spec/lib/zermelo/locks/redis_lock_spec.rb +3 -3
  38. data/spec/lib/zermelo/records/instance_methods_spec.rb +206 -0
  39. data/spec/spec_helper.rb +9 -1
  40. data/spec/support/mock_logger.rb +48 -0
  41. metadata +31 -46
  42. data/lib/zermelo/associations/belongs_to.rb +0 -115
  43. data/lib/zermelo/associations/has_and_belongs_to_many.rb +0 -128
  44. data/lib/zermelo/associations/has_many.rb +0 -120
  45. data/lib/zermelo/associations/has_one.rb +0 -109
  46. data/lib/zermelo/associations/has_sorted_set.rb +0 -124
  47. data/lib/zermelo/backends/base.rb +0 -115
  48. data/lib/zermelo/filters/base.rb +0 -212
  49. data/lib/zermelo/filters/redis_filter.rb +0 -111
  50. data/lib/zermelo/filters/steps/sorted_set_step.rb +0 -156
  51. data/lib/zermelo/records/base.rb +0 -62
  52. data/lib/zermelo/records/redis_record.rb +0 -27
  53. data/spec/lib/zermelo/associations/belongs_to_spec.rb +0 -6
  54. data/spec/lib/zermelo/associations/has_many_spec.rb +0 -6
  55. data/spec/lib/zermelo/associations/has_one_spec.rb +0 -6
  56. data/spec/lib/zermelo/associations/has_sorted_set.spec.rb +0 -6
  57. data/spec/lib/zermelo/backends/influxdb_backend_spec.rb +0 -0
  58. data/spec/lib/zermelo/backends/moneta_backend_spec.rb +0 -0
  59. data/spec/lib/zermelo/filters/influxdb_filter_spec.rb +0 -0
  60. data/spec/lib/zermelo/filters/redis_filter_spec.rb +0 -0
  61. data/spec/lib/zermelo/records/influxdb_record_spec.rb +0 -434
  62. data/spec/lib/zermelo/records/key_spec.rb +0 -6
  63. data/spec/lib/zermelo/records/redis_record_spec.rb +0 -1461
  64. data/spec/lib/zermelo/records/type_validator_spec.rb +0 -6
  65. data/spec/lib/zermelo/version_spec.rb +0 -6
  66. data/spec/lib/zermelo_spec.rb +0 -6
@@ -0,0 +1,1084 @@
1
+ require 'spec_helper'
2
+ require 'zermelo/records/redis'
3
+ require 'zermelo/records/influxdb'
4
+ require 'zermelo/associations/multiple'
5
+
6
+ describe Zermelo::Associations::Multiple do
7
+
8
+ context 'has_many' do
9
+
10
+ shared_examples "has_many functions work", :has_many => true do
11
+
12
+ let(:parent) {
13
+ create_parent(:id => '8')
14
+ parent_class.find_by_id('8')
15
+ }
16
+
17
+ it "sets a parent/child has_many relationship between two records" do
18
+ child = child_class.new(:id => '3', :important => true)
19
+ expect(child.save).to be_truthy
20
+
21
+ children = parent.children.all
22
+
23
+ expect(children).to be_an(Array)
24
+ expect(children).to be_empty
25
+
26
+ parent.children << child
27
+
28
+ children = parent.children.all
29
+
30
+ expect(children).to be_an(Array)
31
+ expect(children.size).to eq(1)
32
+ end
33
+
34
+ it "loads a child from a parent's has_many relationship" do
35
+ create_child(parent, :id => '3')
36
+
37
+ children = parent.children.all
38
+
39
+ expect(children).to be_an(Array)
40
+ expect(children.size).to eq(1)
41
+ child = children.first
42
+ expect(child).to be_a(child_class)
43
+ expect(child.id).to eq('3')
44
+ end
45
+
46
+ it "loads a parent from a child's belongs_to relationship" do
47
+ create_child(parent, :id => '3')
48
+ child = child_class.find_by_id('3')
49
+
50
+ other_parent = child.parent
51
+ expect(other_parent).not_to be_nil
52
+ expect(other_parent).to be_a(parent_class)
53
+ expect(other_parent.id).to eq('8')
54
+ end
55
+
56
+ it "deletes a record from the set" do
57
+ create_child(parent, :id => '3')
58
+ create_child(parent, :id => '4')
59
+
60
+ expect(parent.children.count).to eq(2)
61
+ child = child_class.find_by_id('3')
62
+ parent.children.remove(child)
63
+ expect(parent.children.count).to eq(1)
64
+ expect(parent.children.ids).to eq(['4'])
65
+ end
66
+
67
+ it "deletes a record from the set by id" do
68
+ create_child(parent, :id => '3')
69
+ create_child(parent, :id => '4')
70
+
71
+ expect(parent.children.count).to eq(2)
72
+ parent.children.remove_ids('3')
73
+ expect(parent.children.count).to eq(1)
74
+ expect(parent.children.ids).to eq(['4'])
75
+ end
76
+
77
+ it "clears all records from the set" do
78
+ create_child(parent, :id => '3')
79
+ create_child(parent, :id => '4')
80
+
81
+ expect(parent.children.count).to eq(2)
82
+ child = child_class.find_by_id('3')
83
+ parent.children.clear
84
+ expect(parent.children.count).to eq(0)
85
+ expect(parent.children.ids).to eq([])
86
+ end
87
+
88
+ it "does not add a child if the before_add callback raises an exception" # do
89
+ # create_child(nil, :id => '6', :important => true)
90
+ # child = child_class.find_by_id('6')
91
+
92
+ # expect(parent.children).to be_empty
93
+ # expect {
94
+ # parent.children << child
95
+ # }.to raise_error
96
+ # expect(parent.children).to be_empty
97
+ # end
98
+
99
+ it 'calls the before/after_read callbacks as part of query execution' # do
100
+ # expect(parent.read).to be_nil
101
+ # expect(parent.children).to be_empty
102
+ # expect(parent.read).to eq([:pre, :post])
103
+ # end
104
+
105
+ it 'raises an error when calling add on has_many without an argument' do
106
+ expect {
107
+ parent.children.add
108
+ }.to raise_error("No records to add")
109
+ end
110
+
111
+ it 'raises an error when calling delete on has_many without an argument' do
112
+ expect {
113
+ parent.children.remove
114
+ }.to raise_error("No records to remove")
115
+ end
116
+
117
+ context 'filters' do
118
+
119
+ before do
120
+ create_child(parent, :id => '3', :important => true)
121
+ create_child(parent, :id => '4', :important => true)
122
+ create_child(parent, :id => '5', :important => false)
123
+ end
124
+
125
+ it "by indexed attribute values" do
126
+ important_kids = parent.children.intersect(:important => true).all
127
+ expect(important_kids).not_to be_nil
128
+ expect(important_kids).to be_an(Array)
129
+ expect(important_kids.size).to eq(2)
130
+ expect(important_kids.map(&:id)).to match_array(['3', '4'])
131
+ end
132
+
133
+ it "by intersecting ids" do
134
+ important_kids = parent.children.intersect(:important => true, :id => ['4', '5']).all
135
+ expect(important_kids).not_to be_nil
136
+ expect(important_kids).to be_an(Array)
137
+ expect(important_kids.size).to eq(1)
138
+ expect(important_kids.map(&:id)).to match_array(['4'])
139
+ end
140
+
141
+ it "applies chained intersect and union filters to a has_many association" do
142
+ create_child(parent, :id => '3', :important => true)
143
+ create_child(parent, :id => '4', :important => false)
144
+
145
+ result = parent.children.intersect(:important => true).union(:id => '4').all
146
+ expect(result).to be_an(Array)
147
+ expect(result.size).to eq(2)
148
+ expect(result.map(&:id)).to eq(['3', '4'])
149
+ end
150
+
151
+ it "checks whether a record id exists through a has_many filter" do
152
+ expect(parent.children.intersect(:important => true).exists?('3')).to be true
153
+ expect(parent.children.intersect(:important => true).exists?('5')).to be false
154
+ end
155
+
156
+ it "finds a record through a has_many filter" do
157
+ child = parent.children.intersect(:important => true).find_by_id('3')
158
+ expect(child).not_to be_nil
159
+ expect(child).to be_a(child_class)
160
+ expect(child.id).to eq('3')
161
+ end
162
+
163
+ it 'returns associated ids for multiple parent ids' do
164
+ create_parent(:id => '9')
165
+ parent_9 = parent_class.find_by_id('9')
166
+
167
+ create_child(parent_9, :id => '6', :important => false)
168
+
169
+ create_parent(:id => '10')
170
+
171
+ assoc_ids = parent_class.intersect(:id => [ '8', '9', '10']).
172
+ associated_ids_for(:children)
173
+ expect(assoc_ids).to eq('8' => Set.new(['3', '4', '5']),
174
+ '9' => Set.new(['6']),
175
+ '10' => Set.new())
176
+
177
+ assoc_parent_ids = child_class.intersect(:id => ['3', '4', '5', '6']).
178
+ associated_ids_for(:parent)
179
+ expect(assoc_parent_ids).to eq('3' => '8',
180
+ '4' => '8',
181
+ '5' => '8',
182
+ '6' => '9')
183
+ end
184
+
185
+ end
186
+ end
187
+
188
+ context 'redis', :redis => true, :has_many => true do
189
+
190
+ let(:redis) { Zermelo.redis }
191
+
192
+ module ZermeloExamples
193
+ class AssociationsHasManyParentRedis
194
+ include Zermelo::Records::Redis
195
+ has_many :children, :class_name => 'ZermeloExamples::AssociationsHasManyChildRedis',
196
+ :inverse_of => :parent
197
+ end
198
+
199
+ class AssociationsHasManyChildRedis
200
+ include Zermelo::Records::Redis
201
+ define_attributes :important => :boolean
202
+ index_by :important
203
+ belongs_to :parent, :class_name => 'ZermeloExamples::AssociationsHasManyParentRedis',
204
+ :inverse_of => :children
205
+ end
206
+ end
207
+
208
+ let(:parent_class) { ZermeloExamples::AssociationsHasManyParentRedis }
209
+ let(:child_class) { ZermeloExamples::AssociationsHasManyChildRedis }
210
+
211
+ # parent and child keys
212
+ let(:pk) { 'associations_has_many_parent_redis' }
213
+ let(:ck) { 'associations_has_many_child_redis' }
214
+
215
+ def create_parent(attrs = {})
216
+ redis.sadd("#{pk}::attrs:ids", attrs[:id])
217
+ end
218
+
219
+ def create_child(parent, attrs = {})
220
+ redis.sadd("#{pk}:#{parent.id}:assocs:children_ids", attrs[:id]) unless parent.nil?
221
+
222
+ redis.hmset("#{ck}:#{attrs[:id]}:attrs",
223
+ {'important' => attrs[:important]}.to_a.flatten)
224
+
225
+ redis.sadd("#{ck}::indices:by_important:boolean:#{!!attrs[:important]}", attrs[:id])
226
+ redis.hmset("#{ck}:#{attrs[:id]}:assocs:belongs_to",
227
+ {'parent_id' => parent.id}.to_a.flatten) unless parent.nil?
228
+ redis.sadd("#{ck}::attrs:ids", attrs[:id])
229
+ end
230
+
231
+ it "sets a parent/child has_many relationship between two records" do
232
+ create_parent(:id => '8')
233
+
234
+ child = child_class.new(:id => '3')
235
+ expect(child.save).to be true
236
+
237
+ parent = parent_class.find_by_id('8')
238
+ parent.children << child
239
+
240
+ expect(redis.keys('*')).to match_array(["#{pk}::attrs:ids",
241
+ "#{pk}:8:assocs:children_ids",
242
+ "#{ck}::attrs:ids",
243
+ "#{ck}::indices:by_important:null:null",
244
+ "#{ck}:3:assocs:belongs_to"])
245
+
246
+ expect(redis.smembers("#{pk}::attrs:ids")).to eq(['8'])
247
+ expect(redis.smembers("#{pk}:8:assocs:children_ids")).to eq(['3'])
248
+
249
+ expect(redis.smembers("#{ck}::attrs:ids")).to eq(['3'])
250
+ end
251
+
252
+ it "removes a parent/child has_many relationship between two records" do
253
+ create_parent(:id => '8')
254
+ parent = parent_class.find_by_id('8')
255
+
256
+ create_child(parent, :id => '3', :important => true)
257
+ child = child_class.find_by_id('3')
258
+
259
+ expect(redis.smembers("#{ck}::attrs:ids")).to eq(['3'])
260
+ expect(redis.smembers("#{pk}:8:assocs:children_ids")).to eq(['3'])
261
+
262
+ parent.children.remove(child)
263
+
264
+ expect(redis.smembers("#{ck}::attrs:ids")).to eq(['3']) # child not deleted
265
+ expect(redis.smembers("#{pk}:8:assocs:children_ids")).to eq([]) # but association is
266
+ end
267
+
268
+ it 'clears the belongs_to association when the child record is deleted' do
269
+ create_parent(:id => '8')
270
+ parent = parent_class.find_by_id('8')
271
+
272
+ time = Time.now
273
+
274
+ create_child(parent, :id => '6', :important => true)
275
+ child = child_class.find_by_id('6')
276
+
277
+ expect(redis.keys).to match_array(["#{pk}::attrs:ids",
278
+ "#{pk}:8:assocs:children_ids",
279
+ "#{ck}::attrs:ids",
280
+ "#{ck}::indices:by_important:boolean:true",
281
+ "#{ck}:6:attrs",
282
+ "#{ck}:6:assocs:belongs_to"])
283
+
284
+ child.destroy
285
+
286
+ expect(redis.keys).to match_array(["#{pk}::attrs:ids"])
287
+ end
288
+
289
+ it "clears the belongs_to association when the parent record is deleted" do
290
+ create_parent(:id => '8', :name => 'John Jones',
291
+ :email => 'jjones@parent.com', :active => 'true')
292
+ parent = parent_class.find_by_id('8')
293
+
294
+ time = Time.now
295
+
296
+ create_child(parent, :id => '6', :name => 'Martin Luther King', :important => true)
297
+ child = child_class.find_by_id('6')
298
+
299
+ expect(redis.keys).to match_array(["#{pk}::attrs:ids",
300
+ "#{pk}:8:assocs:children_ids",
301
+ "#{ck}::attrs:ids",
302
+ "#{ck}::indices:by_important:boolean:true",
303
+ "#{ck}:6:attrs",
304
+ "#{ck}:6:assocs:belongs_to"])
305
+
306
+ parent.destroy
307
+
308
+ expect(redis.keys).to match_array(["#{ck}::attrs:ids",
309
+ "#{ck}::indices:by_important:boolean:true",
310
+ "#{ck}:6:attrs"])
311
+ end
312
+
313
+ end
314
+
315
+ context 'influxdb', :influxdb => true, :has_many => true do
316
+
317
+ let(:influxdb) { Zermelo.influxdb }
318
+
319
+ module ZermeloExamples
320
+ class AssociationsHasManyParentInfluxDB
321
+ include Zermelo::Records::InfluxDB
322
+ has_many :children, :class_name => 'ZermeloExamples::AssociationsHasManyChildInfluxDB',
323
+ :inverse_of => :parent
324
+ end
325
+
326
+ class AssociationsHasManyChildInfluxDB
327
+ include Zermelo::Records::InfluxDB
328
+ define_attributes :important => :boolean
329
+ index_by :important
330
+ belongs_to :parent, :class_name => 'ZermeloExamples::AssociationsHasManyParentInfluxDB',
331
+ :inverse_of => :children
332
+ end
333
+ end
334
+
335
+ let(:parent_class) { ZermeloExamples::AssociationsHasManyParentInfluxDB }
336
+ let(:child_class) { ZermeloExamples::AssociationsHasManyChildInfluxDB }
337
+
338
+ # parent and child keys
339
+ let(:pk) { 'associations_has_many_parent_influx_db' }
340
+ let(:ck) { 'associations_has_many_child_influx_db' }
341
+
342
+ def create_parent(attrs = {})
343
+ Zermelo.influxdb.write_point("#{pk}/#{attrs[:id]}", attrs)
344
+ end
345
+
346
+ def create_child(par, attrs = {})
347
+ attrs[:important] = attrs[:important].to_s unless attrs[:important].nil?
348
+ Zermelo.influxdb.write_point("#{ck}/#{attrs[:id]}", attrs)
349
+ par.children.add(child_class.find_by_id!(attrs[:id]))
350
+ end
351
+
352
+ end
353
+
354
+ end
355
+
356
+
357
+
358
+ context 'has_and_belongs_to_many' do
359
+
360
+ shared_examples "has_and_belongs_to_many functions work", :has_and_belongs_to_many => true do
361
+
362
+ before do
363
+ create_primary(:id => '8', :active => true)
364
+ create_secondary(:id => '2')
365
+ end
366
+
367
+ it "loads a record from a has_and_belongs_to_many relationship" do
368
+ primary = primary_class.find_by_id('8')
369
+ secondary = secondary_class.find_by_id('2')
370
+
371
+ secondary.primaries << primary
372
+
373
+ secondaries = primary.secondaries.all
374
+
375
+ expect(secondaries).to be_an(Array)
376
+ expect(secondaries.size).to eq(1)
377
+ other_secondary = secondaries.first
378
+ expect(other_secondary).to be_a(secondary_class)
379
+ expect(other_secondary.id).to eq(secondary.id)
380
+ end
381
+
382
+ it 'raises an error when calling add on has_and_belongs_to_many without an argument' do
383
+ primary = primary_class.find_by_id('8')
384
+
385
+ expect {
386
+ primary.secondaries.add
387
+ }.to raise_error("No records to add")
388
+ end
389
+
390
+ it 'raises an error when calling delete on has_and_belongs_to_many without an argument' do
391
+ primary = primary_class.find_by_id('8')
392
+
393
+ expect {
394
+ primary.secondaries.remove
395
+ }.to raise_error("No records to remove")
396
+ end
397
+
398
+ it "deletes a record from the set" do
399
+ create_primary(:id => '9', :active => false)
400
+ primary = primary_class.find_by_id('8')
401
+ primary_2 = primary_class.find_by_id('9')
402
+ secondary = secondary_class.find_by_id('2')
403
+
404
+ secondary.primaries.add(primary, primary_2)
405
+
406
+ expect(secondary.primaries.count).to eq(2)
407
+ expect(primary.secondaries.count).to eq(1)
408
+ expect(primary_2.secondaries.count).to eq(1)
409
+
410
+ secondary.primaries.remove(primary)
411
+
412
+ expect(secondary.primaries.count).to eq(1)
413
+ expect(primary.secondaries.count).to eq(0)
414
+ expect(primary_2.secondaries.count).to eq(1)
415
+ expect(secondary.primaries.ids).to eq(['9'])
416
+ expect(primary.secondaries.ids).to eq([])
417
+ expect(primary_2.secondaries.ids).to eq(['2'])
418
+ end
419
+
420
+ it "deletes a record from the set by id" do
421
+ create_primary(:id => '9', :active => false)
422
+ primary = primary_class.find_by_id('8')
423
+ primary_2 = primary_class.find_by_id('9')
424
+ secondary = secondary_class.find_by_id('2')
425
+
426
+ secondary.primaries.add(primary, primary_2)
427
+
428
+ expect(secondary.primaries.count).to eq(2)
429
+ expect(primary.secondaries.count).to eq(1)
430
+ expect(primary_2.secondaries.count).to eq(1)
431
+
432
+ secondary.primaries.remove_ids('8')
433
+
434
+ expect(secondary.primaries.count).to eq(1)
435
+ expect(primary.secondaries.count).to eq(0)
436
+ expect(primary_2.secondaries.count).to eq(1)
437
+ expect(secondary.primaries.ids).to eq(['9'])
438
+ expect(primary.secondaries.ids).to eq([])
439
+ expect(primary_2.secondaries.ids).to eq(['2'])
440
+ end
441
+
442
+ it "clears all records from the set" do
443
+ create_primary(:id => '9', :active => false)
444
+ primary = primary_class.find_by_id('8')
445
+ primary_2 = primary_class.find_by_id('9')
446
+ secondary = secondary_class.find_by_id('2')
447
+
448
+ secondary.primaries.add(primary, primary_2)
449
+
450
+ expect(secondary.primaries.count).to eq(2)
451
+ expect(primary.secondaries.count).to eq(1)
452
+ expect(primary_2.secondaries.count).to eq(1)
453
+
454
+ secondary.primaries.clear
455
+
456
+ expect(secondary.primaries.count).to eq(0)
457
+ expect(primary.secondaries.count).to eq(0)
458
+ expect(primary_2.secondaries.count).to eq(0)
459
+ expect(secondary.primaries.ids).to eq([])
460
+ expect(primary.secondaries.ids).to eq([])
461
+ expect(primary_2.secondaries.ids).to eq([])
462
+ end
463
+
464
+ context 'filters' do
465
+
466
+ it "filters has_and_belongs_to_many records by indexed attribute values" do
467
+ create_primary(:id => '9', :active => false)
468
+ create_primary(:id => '10', :active => true)
469
+
470
+ primary = primary_class.find_by_id('8')
471
+ primary_2 = primary_class.find_by_id('9')
472
+ primary_3 = primary_class.find_by_id('10')
473
+ secondary = secondary_class.find_by_id('2')
474
+
475
+ primary.secondaries << secondary
476
+ primary_2.secondaries << secondary
477
+ primary_3.secondaries << secondary
478
+
479
+ primaries = secondary.primaries.intersect(:active => true).all
480
+ expect(primaries).not_to be_nil
481
+ expect(primaries).to be_an(Array)
482
+ expect(primaries.size).to eq(2)
483
+ expect(primaries.map(&:id)).to match_array(['8', '10'])
484
+ end
485
+
486
+ it "checks whether a record id exists through a has_and_belongs_to_many filter" do
487
+ create_primary(:id => '9', :active => false)
488
+
489
+ primary = primary_class.find_by_id('8')
490
+ primary_2 = primary_class.find_by_id('9')
491
+ secondary = secondary_class.find_by_id('2')
492
+
493
+ primary.secondaries << secondary
494
+ primary_2.secondaries << secondary
495
+
496
+ expect(secondary.primaries.intersect(:active => false).exists?('9')).to be true
497
+ expect(secondary.primaries.intersect(:active => false).exists?('8')).to be false
498
+ end
499
+
500
+ it "finds a record through a has_and_belongs_to_many filter" do
501
+ create_primary(:id => '9', :active => false)
502
+
503
+ primary = primary_class.find_by_id('8')
504
+ primary_2 = primary_class.find_by_id('9')
505
+ secondary = secondary_class.find_by_id('2')
506
+
507
+ primary.secondaries << secondary
508
+ primary_2.secondaries << secondary
509
+
510
+ james = secondary.primaries.intersect(:active => false).find_by_id('9')
511
+ expect(james).not_to be_nil
512
+ expect(james).to be_a(primary_class)
513
+ expect(james.id).to eq(primary_2.id)
514
+ end
515
+
516
+ it 'clears a has_and_belongs_to_many association when a record is deleted'
517
+
518
+ it 'returns associated ids for multiple parent ids' do
519
+ create_primary(:id => '9', :active => false)
520
+ primary_9 = primary_class.find_by_id('9')
521
+
522
+ create_primary(:id => '10', :active => true)
523
+ primary_10 = primary_class.find_by_id('10')
524
+
525
+ create_secondary(:id => '3')
526
+ create_secondary(:id => '4')
527
+
528
+ secondary_2 = secondary_class.find_by_id('2')
529
+ secondary_3 = secondary_class.find_by_id('3')
530
+ secondary_4 = secondary_class.find_by_id('4')
531
+
532
+ primary_9.secondaries.add(secondary_2)
533
+ primary_10.secondaries.add(secondary_3, secondary_4)
534
+
535
+ assoc_ids = primary_class.intersect(:id => ['8', '9', '10']).
536
+ associated_ids_for(:secondaries)
537
+ expect(assoc_ids).to eq('8' => Set.new([]),
538
+ '9' => Set.new(['2']),
539
+ '10' => Set.new(['3', '4']))
540
+ end
541
+ end
542
+ end
543
+
544
+ context 'redis', :redis => true, :has_and_belongs_to_many => true do
545
+
546
+ let(:redis) { Zermelo.redis }
547
+
548
+ module ZermeloExamples
549
+ class AssociationsHasAndBelongsToManyPrimaryRedis
550
+ include Zermelo::Records::Redis
551
+ define_attributes :active => :boolean
552
+ index_by :active
553
+ has_and_belongs_to_many :secondaries,
554
+ :class_name => 'ZermeloExamples::AssociationsHasAndBelongsToManySecondaryRedis',
555
+ :inverse_of => :primaries
556
+ end
557
+
558
+ class AssociationsHasAndBelongsToManySecondaryRedis
559
+ include Zermelo::Records::Redis
560
+ # define_attributes :important => :boolean
561
+ # index_by :important
562
+ has_and_belongs_to_many :primaries,
563
+ :class_name => 'ZermeloExamples::AssociationsHasAndBelongsToManyPrimaryRedis',
564
+ :inverse_of => :secondaries
565
+ end
566
+ end
567
+
568
+ let(:primary_class) { ZermeloExamples::AssociationsHasAndBelongsToManyPrimaryRedis }
569
+ let(:secondary_class) { ZermeloExamples::AssociationsHasAndBelongsToManySecondaryRedis }
570
+
571
+ # primary and secondary keys
572
+ let(:pk) { 'associations_has_and_belongs_to_many_primary_redis' }
573
+ let(:sk) { 'associations_has_and_belongs_to_many_secondary_redis' }
574
+
575
+ def create_primary(attrs = {})
576
+ redis.sadd("#{pk}::attrs:ids", attrs[:id])
577
+ redis.hmset("#{pk}:#{attrs[:id]}:attrs",
578
+ {'active' => attrs[:active]}.to_a.flatten)
579
+ redis.sadd("#{pk}::indices:by_active:boolean:#{!!attrs[:active]}", attrs[:id])
580
+ end
581
+
582
+ def create_secondary(attrs = {})
583
+ redis.sadd("#{sk}::attrs:ids", attrs[:id])
584
+ end
585
+
586
+ it "sets a has_and_belongs_to_many relationship between two records" do
587
+ create_primary(:id => '8', :active => true)
588
+ create_secondary(:id => '2')
589
+
590
+ primary = primary_class.find_by_id('8')
591
+ secondary = secondary_class.find_by_id('2')
592
+
593
+ primary.secondaries << secondary
594
+
595
+ expect(redis.keys('*')).to match_array([
596
+ "#{pk}::attrs:ids",
597
+ "#{pk}::indices:by_active:boolean:true",
598
+ "#{pk}:8:attrs",
599
+ "#{pk}:8:assocs:secondaries_ids",
600
+ "#{sk}::attrs:ids",
601
+ "#{sk}:2:assocs:primaries_ids"
602
+ ])
603
+
604
+ expect(redis.smembers("#{pk}::attrs:ids")).to eq(['8'])
605
+ expect(redis.smembers("#{pk}::indices:by_active:boolean:true")).to eq(['8'])
606
+ expect(redis.hgetall("#{pk}:8:attrs")).to eq('active' => 'true')
607
+ expect(redis.smembers("#{pk}:8:assocs:secondaries_ids")).to eq(['2'])
608
+
609
+ expect(redis.smembers("#{sk}::attrs:ids")).to eq(['2'])
610
+ expect(redis.smembers("#{sk}:2:assocs:primaries_ids")).to eq(['8'])
611
+ end
612
+
613
+ it "removes a has_and_belongs_to_many relationship between two records" do
614
+ create_primary(:id => '8', :active => true)
615
+ create_secondary(:id => '2')
616
+
617
+ primary = primary_class.find_by_id('8')
618
+ secondary = secondary_class.find_by_id('2')
619
+
620
+ secondary.primaries << primary
621
+
622
+ expect(redis.smembers("#{sk}::attrs:ids")).to eq(['2'])
623
+ expect(redis.smembers("#{pk}:8:assocs:secondaries_ids")).to eq(['2'])
624
+
625
+ primary.secondaries.remove(secondary)
626
+
627
+ expect(redis.smembers("#{sk}::attrs:ids")).to eq(['2']) # secondary not deleted
628
+ expect(redis.smembers("#{pk}:8:assocs:secondaries_ids")).to eq([]) # but association is
629
+ end
630
+
631
+ it 'clears a has_and_belongs_to_many association when a record is deleted'
632
+
633
+ end
634
+
635
+ end
636
+
637
+ context 'has_sorted_set' do
638
+
639
+ shared_examples "has_sorted_set functions work", :has_sorted_set => true do
640
+
641
+ let(:time) { Time.now }
642
+
643
+ # TODO
644
+ end
645
+
646
+ context 'redis', :redis => true, :has_sorted_set => true do
647
+
648
+ let(:redis) { Zermelo.redis }
649
+
650
+ module ZermeloExamples
651
+ class AssociationsHasSortedSetParentRedis
652
+ include Zermelo::Records::Redis
653
+ has_sorted_set :children, :class_name => 'ZermeloExamples::AssociationsHasSortedSetChildRedis',
654
+ :inverse_of => :parent, :key => :timestamp
655
+ has_sorted_set :reversed_children, :class_name => 'ZermeloExamples::AssociationsHasSortedSetChildRedis',
656
+ :inverse_of => :reversed_parent, :key => :timestamp, :order => :desc
657
+ end
658
+
659
+ class AssociationsHasSortedSetChildRedis
660
+ include Zermelo::Records::Redis
661
+ define_attributes :emotion => :string,
662
+ :timestamp => :timestamp
663
+ index_by :emotion
664
+ range_index_by :timestamp
665
+ belongs_to :parent, :class_name => 'ZermeloExamples::AssociationsHasSortedSetParentRedis',
666
+ :inverse_of => :children
667
+ belongs_to :reversed_parent, :class_name => 'ZermeloExamples::AssociationsHasSortedSetParentRedis',
668
+ :inverse_of => :reversed_children
669
+ end
670
+ end
671
+
672
+ let(:parent_class) { ZermeloExamples::AssociationsHasSortedSetParentRedis }
673
+ let(:child_class) { ZermeloExamples::AssociationsHasSortedSetChildRedis }
674
+
675
+ # parent and child keys
676
+ let(:pk) { 'associations_has_sorted_set_parent_redis' }
677
+ let(:ck) { 'associations_has_sorted_set_child_redis' }
678
+
679
+ def create_parent(attrs = {})
680
+ redis.sadd("#{pk}::attrs:ids", attrs[:id])
681
+ end
682
+
683
+ def create_child(parent, attrs = {})
684
+ redis.zadd("#{pk}:#{parent.id}:assocs:children_ids", attrs[:timestamp].to_f, attrs[:id]) unless parent.nil?
685
+
686
+ redis.hmset("#{ck}:#{attrs[:id]}:attrs", {:emotion => attrs[:emotion],
687
+ 'timestamp' => attrs[:timestamp].to_f}.to_a.flatten)
688
+
689
+ redis.sadd("#{ck}::indices:by_emotion:string:#{attrs[:emotion]}", attrs[:id])
690
+ redis.zadd("#{ck}::indices:by_timestamp", attrs[:timestamp].to_f, attrs[:id])
691
+ redis.hmset("#{ck}:#{attrs[:id]}:assocs:belongs_to",
692
+ {'parent_id' => parent.id}.to_a.flatten) unless parent.nil?
693
+ redis.sadd("#{ck}::attrs:ids", attrs[:id])
694
+ end
695
+
696
+ def create_reversed_child(parent, attrs = {})
697
+ redis.zadd("#{pk}:#{parent.id}:assocs:reversed_children_ids", attrs[:timestamp].to_f, attrs[:id]) unless parent.nil?
698
+
699
+ redis.hmset("#{ck}:#{attrs[:id]}:attrs", {:emotion => attrs[:emotion],
700
+ 'timestamp' => attrs[:timestamp].to_f}.to_a.flatten)
701
+
702
+ redis.sadd("#{ck}::indices:by_emotion:string:#{attrs[:emotion]}", attrs[:id])
703
+ redis.zadd("#{ck}::indices:by_timestamp", attrs[:timestamp].to_f, attrs[:id])
704
+ redis.hmset("#{ck}:#{attrs[:id]}:assocs:belongs_to",
705
+ {'reversed_parent_id' => parent.id}.to_a.flatten) unless parent.nil?
706
+ redis.sadd("#{ck}::attrs:ids", attrs[:id])
707
+ end
708
+
709
+ let(:parent) {
710
+ create_parent(:id => '8')
711
+ parent_class.find_by_id('8')
712
+ }
713
+
714
+ it "sets a parent/child has_sorted_set relationship between two records in redis" do
715
+ child = child_class.new(:id => '4', :emotion => 'indifferent',
716
+ :timestamp => time)
717
+ expect(child.save).to be_truthy
718
+
719
+ parent.children << child
720
+
721
+ expect(redis.keys('*')).to match_array(["#{pk}::attrs:ids",
722
+ "#{pk}:8:assocs:children_ids",
723
+ "#{ck}::attrs:ids",
724
+ "#{ck}::indices:by_emotion:string:indifferent",
725
+ "#{ck}::indices:by_timestamp",
726
+ "#{ck}:4:attrs",
727
+ "#{ck}:4:assocs:belongs_to"])
728
+
729
+ expect(redis.smembers("#{ck}::attrs:ids")).to eq(['4'])
730
+ expect(redis.hgetall("#{ck}:4:attrs")).to eq(
731
+ {'emotion' => 'indifferent', 'timestamp' => time.to_f.to_s}
732
+ )
733
+ expect(redis.hgetall("#{ck}:4:assocs:belongs_to")).to eq(
734
+ {'parent_id' => '8'}
735
+ )
736
+
737
+ result = redis.zrange("#{pk}:8:assocs:children_ids", 0, -1,
738
+ :with_scores => true) # .should == [['4', time.to_f]]
739
+ expect(result.size).to eq(1)
740
+ expect(result.first.first).to eq('4')
741
+ expect(result.first.last).to be_within(0.001).of(time.to_f)
742
+ end
743
+
744
+ it "loads a child from a parent's has_sorted_set relationship" do
745
+ create_child(parent, :id => '4', :emotion => 'indifferent', :timestamp => time)
746
+ child = child_class.find_by_id('4')
747
+
748
+ children = parent.children.all
749
+
750
+ expect(children).to be_an(Array)
751
+ expect(children.size).to eq(1)
752
+ child = children.first
753
+ expect(child).to be_a(child_class)
754
+ expect(child.timestamp).to be_within(1).of(time) # ignore fractional differences
755
+ end
756
+
757
+ it "removes a parent/child has_sorted_set relationship between two records" do
758
+ create_child(parent, :id => '4', :emotion => 'indifferent', :timestamp => time)
759
+ child = child_class.find_by_id('4')
760
+
761
+ expect(redis.smembers("#{ck}::attrs:ids")).to eq(['4'])
762
+ expect(redis.zrange("#{pk}:8:assocs:children_ids", 0, -1)).to eq(['4'])
763
+
764
+ parent.children.remove(child)
765
+
766
+ expect(redis.smembers("#{ck}::attrs:ids")).to eq(['4']) # child not deleted
767
+ expect(redis.zrange("#{pk}:8:assocs:children_ids", 0, -1)).to eq([]) # but association is
768
+ end
769
+
770
+ it "clears the belongs_to association when the parent record is deleted" do
771
+ create_child(parent, :id => '6', :timestamp => time,
772
+ :emotion => 'upset')
773
+
774
+ expect(redis.keys).to match_array(["#{pk}::attrs:ids",
775
+ "#{pk}:8:assocs:children_ids",
776
+ "#{ck}::attrs:ids",
777
+ "#{ck}::indices:by_timestamp",
778
+ "#{ck}::indices:by_emotion:string:upset",
779
+ "#{ck}:6:attrs",
780
+ "#{ck}:6:assocs:belongs_to"])
781
+
782
+ parent.destroy
783
+
784
+ expect(redis.keys).to match_array(["#{ck}::attrs:ids",
785
+ "#{ck}::indices:by_timestamp",
786
+ "#{ck}::indices:by_emotion:string:upset",
787
+ "#{ck}:6:attrs"])
788
+ end
789
+
790
+ it 'sets the score in a sorted set appropriately when assigned from the belongs_to' do
791
+ create_child(nil, :id => '4', :timestamp => time - 20,
792
+ :emotion => 'upset')
793
+
794
+ child = child_class.find_by_id!('4')
795
+ child.parent = parent
796
+
797
+ expect(redis.zrange("#{pk}:8:assocs:children_ids", 0, -1, :with_scores => true)).to eq([['4', (time - 20).to_f]])
798
+ end
799
+
800
+ it 'returns the first record' do
801
+ create_child(parent, :id => '4', :timestamp => time - 20,
802
+ :emotion => 'upset')
803
+ create_child(parent, :id => '5', :timestamp => time - 10,
804
+ :emotion => 'happy')
805
+ create_child(parent, :id => '6', :timestamp => time,
806
+ :emotion => 'upset')
807
+
808
+ child = parent.children.first
809
+ expect(child).not_to be_nil
810
+ expect(child.id).to eq('4')
811
+ end
812
+
813
+ it 'returns the last record for first if reversed' do
814
+ create_reversed_child(parent, :id => '4', :timestamp => time - 20,
815
+ :emotion => 'upset')
816
+ create_reversed_child(parent, :id => '5', :timestamp => time - 10,
817
+ :emotion => 'happy')
818
+ create_reversed_child(parent, :id => '6', :timestamp => time,
819
+ :emotion => 'upset')
820
+
821
+ child = parent.reversed_children.first
822
+ expect(child).not_to be_nil
823
+ expect(child.id).to eq('6')
824
+ end
825
+
826
+ it 'returns the last record' do
827
+ create_child(parent, :id => '4', :timestamp => time - 20,
828
+ :emotion => 'upset')
829
+ create_child(parent, :id => '5', :timestamp => time - 10,
830
+ :emotion => 'happy')
831
+ create_child(parent, :id => '6', :timestamp => time,
832
+ :emotion => 'upset')
833
+
834
+ child = parent.children.last
835
+ expect(child).not_to be_nil
836
+ expect(child.id).to eq('6')
837
+ end
838
+
839
+ it 'returns the first record for last if reversed' do
840
+ create_reversed_child(parent, :id => '4', :timestamp => time - 20,
841
+ :emotion => 'upset')
842
+ create_reversed_child(parent, :id => '5', :timestamp => time - 10,
843
+ :emotion => 'happy')
844
+ create_reversed_child(parent, :id => '6', :timestamp => time,
845
+ :emotion => 'upset')
846
+
847
+ child = parent.reversed_children.last
848
+ expect(child).not_to be_nil
849
+ expect(child.id).to eq('4')
850
+ end
851
+
852
+ it 'returns associated ids for multiple parent ids' do
853
+ create_parent(:id => '9')
854
+
855
+ create_parent(:id => '10')
856
+ parent_10 = parent_class.find_by_id('10')
857
+
858
+ time = Time.now.to_i
859
+
860
+ create_child(parent, :id => '3', :timestamp => time - 20,
861
+ :emotion => 'ok')
862
+ create_child(parent, :id => '4', :timestamp => time - 10,
863
+ :emotion => 'ok')
864
+ create_child(parent_10, :id => '5', :timestamp => time,
865
+ :emotion => 'not_ok')
866
+
867
+ assoc_ids = parent_class.intersect(:id => ['8', '9', '10']).
868
+ associated_ids_for(:children)
869
+ expect(assoc_ids).to eq('8' => ['3', '4'],
870
+ '9' => [],
871
+ '10' => ['5'])
872
+ end
873
+
874
+ it "deletes a record from the set" do
875
+ create_child(parent, :id => '3', :timestamp => time - 20,
876
+ :emotion => 'ok')
877
+ create_child(parent, :id => '4', :timestamp => time - 10,
878
+ :emotion => 'ok')
879
+
880
+ expect(parent.children.count).to eq(2)
881
+ child = child_class.find_by_id('3')
882
+ parent.children.remove(child)
883
+ expect(parent.children.count).to eq(1)
884
+ expect(parent.children.ids).to eq(['4'])
885
+ end
886
+
887
+ it "deletes a record from the set by id" do
888
+ create_child(parent, :id => '3', :timestamp => time - 20,
889
+ :emotion => 'ok')
890
+ create_child(parent, :id => '4', :timestamp => time - 10,
891
+ :emotion => 'ok')
892
+
893
+ expect(parent.children.count).to eq(2)
894
+ parent.children.remove_ids('3')
895
+ expect(parent.children.count).to eq(1)
896
+ expect(parent.children.ids).to eq(['4'])
897
+ end
898
+
899
+ it "clears all records from the set" do
900
+ create_child(parent, :id => '3', :timestamp => time - 20,
901
+ :emotion => 'ok')
902
+ create_child(parent, :id => '4', :timestamp => time - 10,
903
+ :emotion => 'ok')
904
+
905
+ expect(parent.children.count).to eq(2)
906
+ child = child_class.find_by_id('3')
907
+ parent.children.clear
908
+ expect(parent.children.count).to eq(0)
909
+ expect(parent.children.ids).to eq([])
910
+ end
911
+
912
+ context 'filters' do
913
+ before do
914
+ create_child(parent, :id => '4', :timestamp => time - 20,
915
+ :emotion => 'upset')
916
+ create_child(parent, :id => '5', :timestamp => time - 10,
917
+ :emotion => 'happy')
918
+ create_child(parent, :id => '6', :timestamp => time,
919
+ :emotion => 'upset')
920
+ end
921
+
922
+ it "by indexed attribute values" do
923
+ upset_children = parent.children.intersect(:emotion => 'upset').all
924
+ expect(upset_children).not_to be_nil
925
+ expect(upset_children).to be_an(Array)
926
+ expect(upset_children.size).to eq(2)
927
+ expect(upset_children.map(&:id)).to eq(['4', '6'])
928
+ end
929
+
930
+ it "by indexed attribute values with a regex search" do
931
+ upset_children = parent.children.intersect(:emotion => /^ups/).all
932
+ expect(upset_children).not_to be_nil
933
+ expect(upset_children).to be_an(Array)
934
+ expect(upset_children.size).to eq(2)
935
+ expect(upset_children.map(&:id)).to eq(['4', '6'])
936
+ end
937
+
938
+ it "a subset of a sorted set by index" do
939
+ range = Zermelo::Filters::IndexRange.new(0, 1)
940
+ children = parent.children.intersect(:timestamp => range).all
941
+ expect(children).not_to be_nil
942
+ expect(children).to be_an(Array)
943
+ expect(children.size).to eq(2)
944
+ expect(children.map(&:id)).to eq(['4', '5'])
945
+ end
946
+
947
+ it "a reversed subset of a sorted set by index" do
948
+ range = Zermelo::Filters::IndexRange.new(1, 2)
949
+ children = parent.children.intersect(:timestamp => range).sort(:id, :desc => true).all
950
+ expect(children).not_to be_nil
951
+ expect(children).to be_an(Array)
952
+ expect(children.size).to eq(2)
953
+ expect(children.map(&:id)).to eq(['6', '5'])
954
+ end
955
+
956
+ it "a subset of a sorted set by score" do
957
+ range = Zermelo::Filters::IndexRange.new(time - 25, time - 5, :by_score => true)
958
+ children = parent.children.intersect(:timestamp => range).all
959
+ expect(children).not_to be_nil
960
+ expect(children).to be_an(Array)
961
+ expect(children.size).to eq(2)
962
+ expect(children.map(&:id)).to eq(['4', '5'])
963
+ end
964
+
965
+ it "a reversed subset of a sorted set by score" do
966
+ range = Zermelo::Filters::IndexRange.new(time - 25, time - 5, :by_score => true)
967
+ children = parent.children.intersect(:timestamp => range).
968
+ sort(:timestamp, :desc => true).all
969
+ expect(children).not_to be_nil
970
+ expect(children).to be_an(Array)
971
+ expect(children.size).to eq(2)
972
+ expect(children.map(&:id)).to eq(['5', '4'])
973
+ end
974
+
975
+ it "checks whether a record exists" do
976
+ expect(parent.children.intersect(:emotion => 'upset').exists?('4')).to be true
977
+ expect(parent.children.intersect(:emotion => 'upset').exists?('5')).to be false
978
+ end
979
+
980
+ it "finds a record" do
981
+ child = parent.children.intersect(:emotion => 'upset').find_by_id('4')
982
+ expect(child).not_to be_nil
983
+ expect(child).to be_a(child_class)
984
+ expect(child.id).to eq('4')
985
+ end
986
+
987
+ it "the union of a sorted set by index"
988
+ it "a reversed union of a sorted set by index"
989
+
990
+ it "the union of a sorted set by score"
991
+ it "a reversed union of a sorted set by score"
992
+
993
+ it "ANDs multiple union arguments, not ORs them" do
994
+ children = parent.children.intersect(:id => ['4']).
995
+ union(:emotion => 'upset', :id => ['4', '6']).all
996
+ expect(children).not_to be_nil
997
+ expect(children).to be_an(Array)
998
+ expect(children.size).to eq(2)
999
+ expect(children.map(&:id)).to eq(['4', '6'])
1000
+ end
1001
+
1002
+ it "ANDs multiple diff arguments, not ORs them" do
1003
+ children = parent.children.diff(:emotion => 'upset', :id => ['4', '5']).all
1004
+ expect(children).not_to be_nil
1005
+ expect(children).to be_an(Array)
1006
+ expect(children.size).to eq(2)
1007
+ expect(children.map(&:id)).to eq(['5', '6'])
1008
+ end
1009
+
1010
+ it "the exclusion of a sorted set by index" do
1011
+ range = Zermelo::Filters::IndexRange.new(0, 1)
1012
+ children = parent.children.diff(:timestamp => range).all
1013
+ expect(children).not_to be_nil
1014
+ expect(children).to be_an(Array)
1015
+ expect(children.size).to eq(1)
1016
+ expect(children.map(&:id)).to eq(['6'])
1017
+ end
1018
+
1019
+ it "a reversed exclusion of a sorted set by index" do
1020
+ range = Zermelo::Filters::IndexRange.new(2, 2)
1021
+ children = parent.children.diff(:timestamp => range).sort(:id, :desc => true).all
1022
+ expect(children).not_to be_nil
1023
+ expect(children).to be_an(Array)
1024
+ expect(children.size).to eq(2)
1025
+ expect(children.map(&:id)).to eq(['5', '4'])
1026
+ end
1027
+
1028
+ it "the exclusion of a sorted set by score" do
1029
+ range = Zermelo::Filters::IndexRange.new(time - 25, time - 5, :by_score => true)
1030
+ children = parent.children.diff(:timestamp => range).all
1031
+ expect(children).not_to be_nil
1032
+ expect(children).to be_an(Array)
1033
+ expect(children.size).to eq(1)
1034
+ expect(children.map(&:id)).to eq(['6'])
1035
+ end
1036
+
1037
+ it "a reversed exclusion of a sorted set by score" do
1038
+ range = Zermelo::Filters::IndexRange.new(time - 5, time, :by_score => true)
1039
+ children = parent.children.diff(:timestamp => range).sort(:timestamp, :desc => true).all
1040
+ expect(children).not_to be_nil
1041
+ expect(children).to be_an(Array)
1042
+ expect(children.size).to eq(2)
1043
+ expect(children.map(&:id)).to eq(['5', '4'])
1044
+ end
1045
+
1046
+ end
1047
+
1048
+ end
1049
+
1050
+ context 'influxdb', :influxdb => true, :has_sorted_set => true do
1051
+
1052
+ let(:influxdb) { Zermelo.influxdb }
1053
+
1054
+ module ZermeloExamples
1055
+ class AssociationsHasSortedSetParentInfluxDB
1056
+ include Zermelo::Records::InfluxDB
1057
+ has_sorted_set :children, :class_name => 'ZermeloExamples::AssociationsHasSortedSetChildInfluxDB',
1058
+ :inverse_of => :parent, :key => :timestamp
1059
+ end
1060
+
1061
+ class AssociationsHasSortedSetChildInfluxDB
1062
+ include Zermelo::Records::InfluxDB
1063
+ define_attributes :emotion => :string,
1064
+ :timestamp => :timestamp
1065
+ index_by :emotion
1066
+ range_index_by :timestamp
1067
+ belongs_to :parent, :class_name => 'ZermeloExamples::AssociationsHasSortedSetParentInfluxDB',
1068
+ :inverse_of => :children
1069
+ end
1070
+ end
1071
+
1072
+ let(:parent_class) { ZermeloExamples::AssociationsHasSortedSetParentInfluxDB }
1073
+ let(:child_class) { ZermeloExamples::AssociationsHasSortedSetChildInfluxDB }
1074
+
1075
+ # parent and child keys
1076
+ let(:pk) { 'associations_has_sorted_set_parent_influx_db' }
1077
+ let(:ck) { 'associations_has_sorted_set_child_influx_db' }
1078
+
1079
+ # TODO
1080
+ end
1081
+
1082
+ end
1083
+
1084
+ end