zermelo 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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