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
@@ -1,1461 +0,0 @@
1
- require 'spec_helper'
2
- require 'zermelo/records/redis_record'
3
-
4
- # NB: also covers associations.rb, which is mixed in to Zermelo::Record
5
-
6
- describe Zermelo::Records::RedisRecord, :redis => true do
7
-
8
- module Zermelo
9
- class RedisExample
10
- include Zermelo::Records::RedisRecord
11
-
12
- define_attributes :name => :string,
13
- :email => :string,
14
- :active => :boolean
15
-
16
- validates :name, :presence => true
17
-
18
- has_many :children, :class_name => 'Zermelo::RedisExampleChild',
19
- :inverse_of => :example, :before_add => :fail_if_roger
20
-
21
- has_sorted_set :data, :class_name => 'Zermelo::RedisExampleDatum',
22
- :key => :timestamp, :inverse_of => :example
23
-
24
- has_and_belongs_to_many :templates, :class_name => 'Zermelo::Template',
25
- :inverse_of => :examples
26
-
27
- index_by :active
28
- unique_index_by :name
29
-
30
- def fail_if_roger(*childs)
31
- raise "Not adding child" if childs.any? {|c| 'Roger'.eql?(c.name) }
32
- end
33
- end
34
-
35
- class RedisUnsavable
36
- include Zermelo::Records::RedisRecord
37
-
38
- define_attributes :name => :string
39
-
40
- before_create :block_create
41
-
42
- def block_create
43
- false
44
- end
45
- end
46
-
47
- class RedisExampleChild
48
- include Zermelo::Records::RedisRecord
49
-
50
- define_attributes :name => :string,
51
- :important => :boolean
52
-
53
- index_by :important
54
-
55
- belongs_to :example, :class_name => 'Zermelo::RedisExample', :inverse_of => :children
56
-
57
- validates :name, :presence => true
58
- end
59
-
60
- class RedisExampleDatum
61
- include Zermelo::Records::RedisRecord
62
-
63
- define_attributes :timestamp => :timestamp,
64
- :summary => :string,
65
- :emotion => :string
66
-
67
- belongs_to :example, :class_name => 'Zermelo::RedisExample', :inverse_of => :data
68
-
69
- index_by :emotion
70
-
71
- validates :timestamp, :presence => true
72
- end
73
-
74
- class Template
75
- include Zermelo::Records::RedisRecord
76
-
77
- define_attributes :name => :string
78
-
79
- has_and_belongs_to_many :examples, :class_name => 'Zermelo::RedisExample',
80
- :inverse_of => :templates
81
-
82
- validates :name, :presence => true
83
- end
84
- end
85
-
86
- let(:redis) { Zermelo.redis }
87
-
88
- def create_example(attrs = {})
89
- redis.hmset("redis_example:#{attrs[:id]}:attrs",
90
- {'name' => attrs[:name], 'email' => attrs[:email], 'active' => attrs[:active]}.to_a.flatten)
91
- redis.sadd("redis_example::indices:by_active:boolean:#{!!attrs[:active]}", attrs[:id])
92
- name = attrs[:name].gsub(/%/, '%%').gsub(/ /, '%20').gsub(/:/, '%3A')
93
- redis.hset('redis_example::indices:by_name', "string:#{name}", attrs[:id])
94
- redis.sadd('redis_example::attrs:ids', attrs[:id])
95
- end
96
-
97
- it "is invalid without a name" do
98
- example = Zermelo::RedisExample.new(:id => '1', :email => 'jsmith@example.com')
99
- expect(example).not_to be_valid
100
-
101
- errs = example.errors
102
- expect(errs).not_to be_nil
103
- expect(errs[:name]).to eq(["can't be blank"])
104
- end
105
-
106
- it "adds a record's attributes to redis" do
107
- example = Zermelo::RedisExample.new(:id => '1', :name => 'John Smith',
108
- :email => 'jsmith@example.com', :active => true)
109
- expect(example).to be_valid
110
- expect(example.save).to be_truthy
111
-
112
- expect(redis.keys('*')).to match_array(['redis_example::attrs:ids',
113
- 'redis_example:1:attrs',
114
- 'redis_example::indices:by_name',
115
- 'redis_example::indices:by_active:boolean:true'])
116
- expect(redis.smembers('redis_example::attrs:ids')).to eq(['1'])
117
- expect(redis.hgetall('redis_example:1:attrs')).to eq(
118
- {'name' => 'John Smith', 'email' => 'jsmith@example.com', 'active' => 'true'}
119
- )
120
- expect(redis.hgetall('redis_example::indices:by_name')).to eq({'string:John%20Smith' => '1'})
121
- expect(redis.smembers('redis_example::indices:by_active:boolean:true')).to eq(
122
- ['1']
123
- )
124
- end
125
-
126
- it 'raises an RecordInvalid exception if validation fails while saving' do
127
- example = Zermelo::RedisExample.new(:id => '1', :email => 'jsmith@example.com')
128
-
129
- expect {
130
- example.save!
131
- }.to raise_error(Zermelo::Records::Errors::RecordInvalid)
132
- end
133
-
134
- it 'raises a RecordNotSaved exception if a callback blocks saving' do
135
- example = Zermelo::RedisUnsavable.new(:id => '1', :name => 'not saving')
136
-
137
- expect {
138
- example.save!
139
- }.to raise_error(Zermelo::Records::Errors::RecordNotSaved)
140
- end
141
-
142
- it "finds a record by id in redis" do
143
- create_example(:id => '8', :name => 'John Jones',
144
- :email => 'jjones@example.com', :active => 'true')
145
-
146
- example = Zermelo::RedisExample.find_by_id('8')
147
- expect(example).not_to be_nil
148
- expect(example.id).to eq('8')
149
- expect(example.name).to eq('John Jones')
150
- expect(example.email).to eq('jjones@example.com')
151
- end
152
-
153
- it "finds records by a uniquely indexed value in redis" do
154
- create_example(:id => '8', :name => 'John Jones',
155
- :email => 'jjones@example.com', :active => 'true')
156
-
157
- examples = Zermelo::RedisExample.intersect(:name => 'John Jones').all
158
- expect(examples).not_to be_nil
159
- expect(examples).to be_an(Array)
160
- expect(examples.size).to eq(1)
161
- example = examples.first
162
- expect(example.id).to eq('8')
163
- expect(example.name).to eq('John Jones')
164
- expect(example.email).to eq('jjones@example.com')
165
- end
166
-
167
- it 'finds records by regex match against an indexed value in redis'
168
-
169
- it 'finds records by regex match against a uniquely indexed value in redis' do
170
- create_example(:id => '8', :name => 'John Jones',
171
- :email => 'jjones@example.com', :active => 'true')
172
-
173
- examples = Zermelo::RedisExample.intersect(:name => /hn Jones/).all
174
- expect(examples).not_to be_nil
175
- expect(examples).to be_an(Array)
176
- expect(examples.size).to eq(1)
177
- example = examples.first
178
- expect(example.id).to eq('8')
179
- expect(example.name).to eq('John Jones')
180
- expect(example.email).to eq('jjones@example.com')
181
- end
182
-
183
- it 'cannot find records by regex match against non-string values' do
184
- create_example(:id => '8', :name => 'John Jones',
185
- :email => 'jjones@example.com', :active => true)
186
- create_example(:id => '9', :name => 'James Brown',
187
- :email => 'jbrown@example.com', :active => false)
188
-
189
- expect {
190
- Zermelo::RedisExample.intersect(:active => /alse/).all
191
- }.to raise_error
192
- end
193
-
194
- it "updates a record's attributes in redis" do
195
- create_example(:id => '8', :name => 'John Jones',
196
- :email => 'jjones@example.com', :active => 'true')
197
-
198
- example = Zermelo::RedisExample.find_by_id('8')
199
- example.name = 'Jane Janes'
200
- example.email = 'jjanes@example.com'
201
- expect(example.save).to be_truthy
202
-
203
- expect(redis.keys('*')).to match_array(['redis_example::attrs:ids',
204
- 'redis_example:8:attrs',
205
- 'redis_example::indices:by_name',
206
- 'redis_example::indices:by_active:boolean:true'])
207
- expect(redis.smembers('redis_example::attrs:ids')).to eq(['8'])
208
- expect(redis.hgetall('redis_example:8:attrs')).to eq(
209
- {'name' => 'Jane Janes', 'email' => 'jjanes@example.com', 'active' => 'true'}
210
- )
211
- expect(redis.smembers('redis_example::indices:by_active:boolean:true')).to eq(
212
- ['8']
213
- )
214
- end
215
-
216
- it "deletes a record's attributes from redis" do
217
- create_example(:id => '8', :name => 'John Jones',
218
- :email => 'jjones@example.com', :active => 'true')
219
-
220
- expect(redis.keys('*')).to match_array(['redis_example::attrs:ids',
221
- 'redis_example:8:attrs',
222
- 'redis_example::indices:by_name',
223
- 'redis_example::indices:by_active:boolean:true'])
224
-
225
- example = Zermelo::RedisExample.find_by_id('8')
226
- example.destroy
227
-
228
- expect(redis.keys('*')).to eq([])
229
- end
230
-
231
- it "resets changed state on refresh" do
232
- create_example(:id => '8', :name => 'John Jones',
233
- :email => 'jjones@example.com', :active => 'true')
234
- example = Zermelo::RedisExample.find_by_id('8')
235
-
236
- example.name = "King Henry VIII"
237
- expect(example.changed).to include('name')
238
- expect(example.changes).to eq({'name' => ['John Jones', 'King Henry VIII']})
239
-
240
- example.refresh
241
- expect(example.changed).to be_empty
242
- expect(example.changes).to be_empty
243
- end
244
-
245
- it "stores a string as an attribute value"
246
- it "stores an integer as an attribute value"
247
- it "stores a timestamp as an attribute value"
248
- it "stores a boolean as an attribute value"
249
-
250
- it "stores a list as an attribute value"
251
- it "stores a set as an attribute value"
252
- it "stores a hash as an attribute value"
253
-
254
- context 'pagination' do
255
-
256
- before do
257
- create_example(:id => '1', :name => 'mno')
258
- create_example(:id => '2', :name => 'abc')
259
- create_example(:id => '3', :name => 'jkl')
260
- create_example(:id => '4', :name => 'ghi')
261
- create_example(:id => '5', :name => 'def')
262
- end
263
-
264
- it "returns paginated query responses" do
265
- expect(Zermelo::RedisExample.sort(:id).page(1, :per_page => 3).map(&:id)).to eq(['1','2', '3'])
266
- expect(Zermelo::RedisExample.sort(:id).page(2, :per_page => 2).map(&:id)).to eq(['3','4'])
267
- expect(Zermelo::RedisExample.sort(:id).page(3, :per_page => 2).map(&:id)).to eq(['5'])
268
- expect(Zermelo::RedisExample.sort(:id).page(3, :per_page => 3).map(&:id)).to eq([])
269
-
270
- expect(Zermelo::RedisExample.sort(:name).page(1, :per_page => 3).map(&:id)).to eq(['2','5', '4'])
271
- expect(Zermelo::RedisExample.sort(:name).page(2, :per_page => 2).map(&:id)).to eq(['4','3'])
272
- expect(Zermelo::RedisExample.sort(:name).page(3, :per_page => 2).map(&:id)).to eq(['1'])
273
- expect(Zermelo::RedisExample.sort(:name).page(3, :per_page => 3).map(&:id)).to eq([])
274
- end
275
-
276
- end
277
-
278
- context 'filters' do
279
-
280
- let(:active) {
281
- create_example(:id => '8', :name => 'John Jones',
282
- :email => 'jjones@example.com', :active => true)
283
- }
284
-
285
- let(:inactive) {
286
- create_example(:id => '9', :name => 'James Brown',
287
- :email => 'jbrown@example.com', :active => false)
288
- }
289
-
290
- before do
291
- active; inactive
292
- end
293
-
294
- it 'can append to a filter chain fragment more than once' do
295
- inter = Zermelo::RedisExample.intersect(:active => true)
296
- expect(inter.ids).to eq(['8'])
297
-
298
- union = inter.union(:name => 'James Brown')
299
- expect(union.ids).to eq(['8', '9'])
300
-
301
- diff = inter.diff(:id => ['8'])
302
- expect(diff.ids).to eq([])
303
- end
304
-
305
- it "filters all class records by indexed attribute values" do
306
- example = Zermelo::RedisExample.intersect(:active => true).all
307
- expect(example).not_to be_nil
308
- expect(example).to be_an(Array)
309
- expect(example.size).to eq(1)
310
- expect(example.map(&:id)).to eq(['8'])
311
- end
312
-
313
- it 'filters by id attribute values' do
314
- example = Zermelo::RedisExample.intersect(:id => '9').all
315
- expect(example).not_to be_nil
316
- expect(example).to be_an(Array)
317
- expect(example.size).to eq(1)
318
- expect(example.map(&:id)).to eq(['9'])
319
- end
320
-
321
- it 'supports sequential intersection and union operations' do
322
- examples = Zermelo::RedisExample.intersect(:active => true).union(:active => false).all
323
- expect(examples).not_to be_nil
324
- expect(examples).to be_an(Array)
325
- expect(examples.size).to eq(2)
326
- expect(examples.map(&:id)).to match_array(['8', '9'])
327
- end
328
-
329
- it "ANDs multiple union arguments, not ORs them" do
330
- create_example(:id => '10', :name => 'Jay Johns',
331
- :email => 'jjohns@example.com', :active => true)
332
- examples = Zermelo::RedisExample.intersect(:id => ['8']).union(:id => ['9', '10'], :active => true).all
333
- expect(examples).not_to be_nil
334
- expect(examples).to be_an(Array)
335
- expect(examples.size).to eq(2)
336
- expect(examples.map(&:id)).to match_array(['8', '10'])
337
- end
338
-
339
- it 'supports a regex as argument in union after intersect' do
340
- create_example(:id => '10', :name => 'Jay Johns',
341
- :email => 'jjohns@example.com', :active => true)
342
- examples = Zermelo::RedisExample.intersect(:id => ['8']).union(:id => ['9', '10'], :name => [nil, /^Jam/]).all
343
- expect(examples).not_to be_nil
344
- expect(examples).to be_an(Array)
345
- expect(examples.size).to eq(2)
346
- expect(examples.map(&:id)).to match_array(['8', '9'])
347
- end
348
-
349
- it 'allows intersection operations across multiple values for an attribute' do
350
- create_example(:id => '10', :name => 'Jay Johns',
351
- :email => 'jjohns@example.com', :active => true)
352
-
353
- examples = Zermelo::RedisExample.intersect(:name => ['Jay Johns', 'James Brown']).all
354
- expect(examples).not_to be_nil
355
- expect(examples).to be_an(Array)
356
- expect(examples.size).to eq(2)
357
- expect(examples.map(&:id)).to match_array(['9', '10'])
358
- end
359
-
360
- it 'allows union operations across multiple values for an attribute' do
361
- create_example(:id => '10', :name => 'Jay Johns',
362
- :email => 'jjohns@example.com', :active => true)
363
-
364
- examples = Zermelo::RedisExample.intersect(:active => false).union(:name => ['Jay Johns', 'James Brown']).all
365
- expect(examples).not_to be_nil
366
- expect(examples).to be_an(Array)
367
- expect(examples.size).to eq(2)
368
- expect(examples.map(&:id)).to match_array(['9', '10'])
369
- end
370
-
371
- it 'filters by multiple id attribute values' do
372
- create_example(:id => '10', :name => 'Jay Johns',
373
- :email => 'jjohns@example.com', :active => true)
374
-
375
- example = Zermelo::RedisExample.intersect(:id => ['8', '10']).all
376
- expect(example).not_to be_nil
377
- expect(example).to be_an(Array)
378
- expect(example.size).to eq(2)
379
- expect(example.map(&:id)).to eq(['8', '10'])
380
- end
381
-
382
- it 'excludes particular records' do
383
- example = Zermelo::RedisExample.diff(:active => true).all
384
- expect(example).not_to be_nil
385
- expect(example).to be_an(Array)
386
- expect(example.size).to eq(1)
387
- expect(example.map(&:id)).to eq(['9'])
388
- end
389
-
390
- it 'sorts records by an attribute' do
391
- example = Zermelo::RedisExample.sort(:name, :order => 'alpha').all
392
- expect(example).not_to be_nil
393
- expect(example).to be_an(Array)
394
- expect(example.size).to eq(2)
395
- expect(example.map(&:id)).to eq(['9', '8'])
396
- end
397
-
398
- it "does not return a spurious record count when records don't exist" do
399
- scope = Zermelo::RedisExample.intersect(:id => ['3000', '5000'])
400
- expect(scope.all).to be_empty
401
- expect(scope.count).to eq 0
402
- end
403
-
404
- end
405
-
406
-
407
- context "has_many" do
408
-
409
- def create_child(parent, attrs = {})
410
- redis.sadd("redis_example:#{parent.id}:assocs:children_ids", attrs[:id]) unless parent.nil?
411
-
412
- redis.hmset("redis_example_child:#{attrs[:id]}:attrs",
413
- {'name' => attrs[:name], 'important' => !!attrs[:important]}.to_a.flatten)
414
-
415
- redis.hmset("redis_example_child:#{attrs[:id]}:assocs:belongs_to",
416
- {'example_id' => parent.id}.to_a.flatten) unless parent.nil?
417
-
418
- redis.sadd("redis_example_child::indices:by_important:boolean:#{!!attrs[:important]}", attrs[:id])
419
-
420
- redis.sadd('redis_example_child::attrs:ids', attrs[:id])
421
- end
422
-
423
- it "sets a parent/child has_many relationship between two records in redis" do
424
- create_example(:id => '8', :name => 'John Jones',
425
- :email => 'jjones@example.com', :active => 'true')
426
-
427
- child = Zermelo::RedisExampleChild.new(:id => '3', :name => 'Abel Tasman')
428
- expect(child.save).to be_truthy
429
-
430
- example = Zermelo::RedisExample.find_by_id('8')
431
- example.children << child
432
-
433
- expect(redis.keys('*')).to match_array(['redis_example::attrs:ids',
434
- 'redis_example::indices:by_name',
435
- 'redis_example::indices:by_active:boolean:true',
436
- 'redis_example:8:attrs',
437
- 'redis_example:8:assocs:children_ids',
438
- 'redis_example_child::attrs:ids',
439
- 'redis_example_child::indices:by_important:null:null',
440
- 'redis_example_child:3:attrs',
441
- 'redis_example_child:3:assocs:belongs_to'])
442
-
443
- expect(redis.smembers('redis_example::attrs:ids')).to eq(['8'])
444
- expect(redis.smembers('redis_example::indices:by_active:boolean:true')).to eq(
445
- ['8']
446
- )
447
- expect(redis.hgetall('redis_example:8:attrs')).to eq(
448
- {'name' => 'John Jones', 'email' => 'jjones@example.com', 'active' => 'true'}
449
- )
450
- expect(redis.smembers('redis_example:8:assocs:children_ids')).to eq(['3'])
451
-
452
- expect(redis.smembers('redis_example_child::attrs:ids')).to eq(['3'])
453
- expect(redis.hgetall('redis_example_child:3:attrs')).to eq(
454
- {'name' => 'Abel Tasman'}
455
- )
456
- end
457
-
458
- it "loads a child from a parent's has_many relationship" do
459
- create_example(:id => '8', :name => 'John Jones',
460
- :email => 'jjones@example.com', :active => 'true')
461
- example = Zermelo::RedisExample.find_by_id('8')
462
- create_child(example, :id => '3', :name => 'Abel Tasman')
463
-
464
- children = example.children.all
465
-
466
- expect(children).to be_an(Array)
467
- expect(children.size).to eq(1)
468
- child = children.first
469
- expect(child).to be_a(Zermelo::RedisExampleChild)
470
- expect(child.name).to eq('Abel Tasman')
471
- end
472
-
473
- it "loads a parent from a child's belongs_to relationship" do
474
- create_example(:id => '8', :name => 'John Jones',
475
- :email => 'jjones@example.com', :active => 'true')
476
- example = Zermelo::RedisExample.find_by_id('8')
477
- create_child(example, :id => '3', :name => 'Abel Tasman')
478
- child = Zermelo::RedisExampleChild.find_by_id('3')
479
-
480
- other_example = child.example
481
- expect(other_example).not_to be_nil
482
- expect(other_example).to be_a(Zermelo::RedisExample)
483
- expect(other_example.name).to eq('John Jones')
484
- end
485
-
486
- it "removes a parent/child has_many relationship between two records in redis" do
487
- create_example(:id => '8', :name => 'John Jones',
488
- :email => 'jjones@example.com', :active => 'true')
489
- example = Zermelo::RedisExample.find_by_id('8')
490
-
491
- create_child(example, :id => '3', :name => 'Abel Tasman')
492
- child = Zermelo::RedisExampleChild.find_by_id('3')
493
-
494
- expect(redis.smembers('redis_example_child::attrs:ids')).to eq(['3'])
495
- expect(redis.smembers('redis_example:8:assocs:children_ids')).to eq(['3'])
496
-
497
- example.children.delete(child)
498
-
499
- expect(redis.smembers('redis_example_child::attrs:ids')).to eq(['3']) # child not deleted
500
- expect(redis.smembers('redis_example:8:assocs:children_ids')).to eq([]) # but association is
501
- end
502
-
503
- it "filters has_many records by indexed attribute values" do
504
- create_example(:id => '8', :name => 'John Jones',
505
- :email => 'jjones@example.com', :active => 'true')
506
- example = Zermelo::RedisExample.find_by_id('8')
507
-
508
- create_child(example, :id => '3', :name => 'Martin Luther King', :important => true)
509
- create_child(example, :id => '4', :name => 'Julius Caesar', :important => true)
510
- create_child(example, :id => '5', :name => 'John Smith', :important => false)
511
-
512
- important_kids = example.children.intersect(:important => true).all
513
- expect(important_kids).not_to be_nil
514
- expect(important_kids).to be_an(Array)
515
- expect(important_kids.size).to eq(2)
516
- expect(important_kids.map(&:id)).to match_array(['3', '4'])
517
- end
518
-
519
- it "filters has_many records by intersecting ids" do
520
- create_example(:id => '8', :name => 'John Jones',
521
- :email => 'jjones@example.com', :active => 'true')
522
- example = Zermelo::RedisExample.find_by_id('8')
523
-
524
- create_child(example, :id => '3', :name => 'Martin Luther King', :important => true)
525
- create_child(example, :id => '4', :name => 'Julius Caesar', :important => true)
526
- create_child(example, :id => '5', :name => 'John Smith', :important => false)
527
-
528
- important_kids = example.children.intersect(:important => true, :id => ['4', '5']).all
529
- expect(important_kids).not_to be_nil
530
- expect(important_kids).to be_an(Array)
531
- expect(important_kids.size).to eq(1)
532
- expect(important_kids.map(&:id)).to match_array(['4'])
533
- end
534
-
535
- it "checks whether a record id exists through a has_many filter" do
536
- create_example(:id => '8', :name => 'John Jones',
537
- :email => 'jjones@example.com', :active => 'true')
538
- example = Zermelo::RedisExample.find_by_id('8')
539
-
540
- create_child(example, :id => '3', :name => 'Martin Luther King', :important => true)
541
- create_child(example, :id => '4', :name => 'Julius Caesar', :important => true)
542
- create_child(example, :id => '5', :name => 'John Smith', :important => false)
543
-
544
- expect(example.children.intersect(:important => true).exists?('3')).to be_truthy
545
- expect(example.children.intersect(:important => true).exists?('5')).to be_falsey
546
- end
547
-
548
- it "finds a record through a has_many filter" do
549
- create_example(:id => '8', :name => 'John Jones',
550
- :email => 'jjones@example.com', :active => 'true')
551
- example = Zermelo::RedisExample.find_by_id('8')
552
-
553
- create_child(example, :id => '3', :name => 'Martin Luther King', :important => true)
554
- create_child(example, :id => '4', :name => 'Julius Caesar', :important => true)
555
- create_child(example, :id => '5', :name => 'John Smith', :important => false)
556
-
557
- martin = example.children.intersect(:important => true).find_by_id('3')
558
- expect(martin).not_to be_nil
559
- expect(martin).to be_a(Zermelo::RedisExampleChild)
560
- expect(martin.id).to eq('3')
561
- end
562
-
563
- it "does not add a child if the before_add callback raises an exception" do
564
- create_example(:id => '8', :name => 'John Jones',
565
- :email => 'jjones@example.com', :active => 'true')
566
- example = Zermelo::RedisExample.find_by_id('8')
567
-
568
- create_child(nil, :id => '6', :name => 'Roger', :important => true)
569
- child = Zermelo::RedisExampleChild.find_by_id('6')
570
-
571
- expect(example.children).to be_empty
572
- expect {
573
- example.children << child
574
- }.to raise_error
575
- expect(example.children).to be_empty
576
- end
577
-
578
- it 'clears the belongs_to association when the child record is deleted' do
579
- create_example(:id => '8', :name => 'John Jones',
580
- :email => 'jjones@example.com', :active => 'true')
581
- example = Zermelo::RedisExample.find_by_id('8')
582
-
583
- time = Time.now
584
-
585
- create_child(example, :id => '6', :name => 'Martin Luther King', :important => true)
586
- child = Zermelo::RedisExampleChild.find_by_id('6')
587
-
588
- expect(redis.keys).to match_array(['redis_example::attrs:ids',
589
- 'redis_example::indices:by_name',
590
- 'redis_example::indices:by_active:boolean:true',
591
- 'redis_example:8:attrs',
592
- 'redis_example:8:assocs:children_ids',
593
- 'redis_example_child::attrs:ids',
594
- 'redis_example_child::indices:by_important:boolean:true',
595
- 'redis_example_child:6:attrs',
596
- 'redis_example_child:6:assocs:belongs_to'])
597
-
598
- child.destroy
599
-
600
- expect(redis.keys).to match_array(['redis_example::attrs:ids',
601
- 'redis_example::indices:by_name',
602
- 'redis_example::indices:by_active:boolean:true',
603
- 'redis_example:8:attrs'])
604
- end
605
-
606
- it "clears the belongs_to association when the parent record is deleted" do
607
- create_example(:id => '8', :name => 'John Jones',
608
- :email => 'jjones@example.com', :active => 'true')
609
- example = Zermelo::RedisExample.find_by_id('8')
610
-
611
- time = Time.now
612
-
613
- create_child(example, :id => '6', :name => 'Martin Luther King', :important => true)
614
- child = Zermelo::RedisExampleChild.find_by_id('6')
615
-
616
- expect(redis.keys).to match_array(['redis_example::attrs:ids',
617
- 'redis_example::indices:by_name',
618
- 'redis_example::indices:by_active:boolean:true',
619
- 'redis_example:8:attrs',
620
- 'redis_example:8:assocs:children_ids',
621
- 'redis_example_child::attrs:ids',
622
- 'redis_example_child::indices:by_important:boolean:true',
623
- 'redis_example_child:6:attrs',
624
- 'redis_example_child:6:assocs:belongs_to'])
625
-
626
- example.destroy
627
-
628
- expect(redis.keys).to match_array(['redis_example_child::attrs:ids',
629
- 'redis_example_child::indices:by_important:boolean:true',
630
- 'redis_example_child:6:attrs'])
631
- end
632
-
633
- it 'returns associated ids for multiple parent ids' do
634
- create_example(:id => '8', :name => 'John Jones',
635
- :email => 'jjones@example.com', :active => 'true')
636
- example_8 = Zermelo::RedisExample.find_by_id('8')
637
-
638
- create_example(:id => '9', :name => 'Jane Johnson',
639
- :email => 'jjohnson@example.com', :active => 'true')
640
- example_9 = Zermelo::RedisExample.find_by_id('9')
641
-
642
- create_example(:id => '10', :name => 'Jim Smith',
643
- :email => 'jsmith@example.com', :active => 'true')
644
-
645
- create_child(example_8, :id => '3', :name => 'abc', :important => false)
646
- create_child(example_9, :id => '4', :name => 'abc', :important => false)
647
- create_child(example_9, :id => '5', :name => 'abc', :important => false)
648
-
649
- assoc_ids = Zermelo::RedisExample.intersect(:id => [ '8', '9', '10']).
650
- associated_ids_for(:children)
651
- expect(assoc_ids).to eq('8' => Set.new(['3']),
652
- '9' => Set.new(['4', '5']),
653
- '10' => Set.new())
654
-
655
- assoc_parent_ids = Zermelo::RedisExampleChild.intersect(:id => ['3', '4', '5']).
656
- associated_ids_for(:example)
657
- expect(assoc_parent_ids).to eq('3' => '8',
658
- '4' => '9',
659
- '5' => '9')
660
- end
661
-
662
- end
663
-
664
- context "has_sorted_set" do
665
-
666
- def create_datum(parent, attrs = {})
667
- redis.zadd("redis_example:#{parent.id}:assocs:data_ids", attrs[:timestamp].to_i.to_f, attrs[:id])
668
-
669
- redis.hmset("redis_example_datum:#{attrs[:id]}:attrs",
670
- {'summary' => attrs[:summary], 'timestamp' => attrs[:timestamp].to_i.to_f,
671
- 'emotion' => attrs[:emotion]}.to_a.flatten)
672
-
673
- redis.sadd("redis_example_datum::indices:by_emotion:string:#{attrs[:emotion]}", attrs[:id])
674
- redis.hset("redis_example_datum:#{attrs[:id]}:assocs:belongs_to", 'example_id', parent.id)
675
-
676
- redis.sadd('redis_example_datum::attrs:ids', attrs[:id])
677
- end
678
-
679
- it "sets a parent/child has_sorted_set relationship between two records in redis" do
680
- create_example(:id => '8', :name => 'John Jones',
681
- :email => 'jjones@example.com', :active => 'true')
682
-
683
- time = Time.now
684
-
685
- data = Zermelo::RedisExampleDatum.new(:id => '4', :timestamp => time,
686
- :summary => "hello!")
687
- expect(data.save).to be_truthy
688
-
689
- example = Zermelo::RedisExample.find_by_id('8')
690
- example.data << data
691
-
692
- expect(redis.keys('*')).to match_array(['redis_example::attrs:ids',
693
- 'redis_example::indices:by_name',
694
- 'redis_example::indices:by_active:boolean:true',
695
- 'redis_example:8:attrs',
696
- 'redis_example:8:assocs:data_ids',
697
- 'redis_example_datum::attrs:ids',
698
- 'redis_example_datum::indices:by_emotion:null:null',
699
- 'redis_example_datum:4:attrs',
700
- 'redis_example_datum:4:assocs:belongs_to'])
701
-
702
- expect(redis.smembers('redis_example_datum::attrs:ids')).to eq(['4'])
703
- expect(redis.hgetall('redis_example_datum:4:attrs')).to eq(
704
- {'summary' => 'hello!', 'timestamp' => time.to_f.to_s}
705
- )
706
- expect(redis.hgetall('redis_example_datum:4:assocs:belongs_to')).to eq(
707
- {'example_id' => '8'}
708
- )
709
-
710
- result = redis.zrange('redis_example:8:assocs:data_ids', 0, -1,
711
- :with_scores => true) # .should == [['4', time.to_f]]
712
- expect(result.size).to eq(1)
713
- expect(result.first.first).to eq('4')
714
- expect(result.first.last).to be_within(0.001).of(time.to_f)
715
- end
716
-
717
- it "loads a child from a parent's has_sorted_set relationship" do
718
- create_example(:id => '8', :name => 'John Jones',
719
- :email => 'jjones@example.com', :active => 'true')
720
- example = Zermelo::RedisExample.find_by_id('8')
721
-
722
- time = Time.now
723
-
724
- create_datum(example, :id => '4', :summary => 'well then', :timestamp => time)
725
- datum = Zermelo::RedisExampleDatum.find_by_id('4')
726
-
727
- data = example.data.all
728
-
729
- expect(data).to be_an(Array)
730
- expect(data.size).to eq(1)
731
- datum = data.first
732
- expect(datum).to be_a(Zermelo::RedisExampleDatum)
733
- expect(datum.summary).to eq('well then')
734
- expect(datum.timestamp).to be_within(1).of(time) # ignore fractional differences
735
- end
736
-
737
- it "removes a parent/child has_sorted_set relationship between two records in redis" do
738
- create_example(:id => '8', :name => 'John Jones',
739
- :email => 'jjones@example.com', :active => 'true')
740
- example = Zermelo::RedisExample.find_by_id('8')
741
-
742
- time = Time.now
743
-
744
- create_datum(example, :id => '4', :summary => 'well then', :timestamp => time)
745
- datum = Zermelo::RedisExampleDatum.find_by_id('4')
746
-
747
- expect(redis.smembers('redis_example_datum::attrs:ids')).to eq(['4'])
748
- expect(redis.zrange('redis_example:8:assocs:data_ids', 0, -1)).to eq(['4'])
749
-
750
- example.data.delete(datum)
751
-
752
- expect(redis.smembers('redis_example_datum::attrs:ids')).to eq(['4']) # child not deleted
753
- expect(redis.zrange('redis_example:8:assocs.data_ids', 0, -1)).to eq([]) # but association is
754
- end
755
-
756
- it "filters has_sorted_set records by indexed attribute values" do
757
- create_example(:id => '8', :name => 'John Jones',
758
- :email => 'jjones@example.com', :active => 'true')
759
- example = Zermelo::RedisExample.find_by_id('8')
760
-
761
- time = Time.now
762
-
763
- create_datum(example, :id => '4', :summary => 'well then', :timestamp => time,
764
- :emotion => 'upset')
765
- create_datum(example, :id => '5', :summary => 'ok', :timestamp => time.to_i + 10,
766
- :emotion => 'happy')
767
- create_datum(example, :id => '6', :summary => 'aaargh', :timestamp => time.to_i + 20,
768
- :emotion => 'upset')
769
-
770
- upset_data = example.data.intersect(:emotion => 'upset').all
771
- expect(upset_data).not_to be_nil
772
- expect(upset_data).to be_an(Array)
773
- expect(upset_data.size).to eq(2)
774
- expect(upset_data.map(&:id)).to eq(['4', '6'])
775
- end
776
-
777
-
778
- it "filters has_sorted_set records by indexed attribute values with a regex search" do
779
- create_example(:id => '8', :name => 'John Jones',
780
- :email => 'jjones@example.com', :active => 'true')
781
- example = Zermelo::RedisExample.find_by_id('8')
782
-
783
- time = Time.now
784
-
785
- create_datum(example, :id => '4', :summary => 'well then', :timestamp => time,
786
- :emotion => 'upset')
787
- create_datum(example, :id => '5', :summary => 'ok', :timestamp => time.to_i + 10,
788
- :emotion => 'happy')
789
- create_datum(example, :id => '6', :summary => 'aaargh', :timestamp => time.to_i + 20,
790
- :emotion => 'upset')
791
-
792
- upset_data = example.data.intersect(:emotion => /^ups/).all
793
- expect(upset_data).not_to be_nil
794
- expect(upset_data).to be_an(Array)
795
- expect(upset_data.size).to eq(2)
796
- expect(upset_data.map(&:id)).to eq(['4', '6'])
797
- end
798
-
799
- it "retrieves a subset of a sorted set by index" do
800
- create_example(:id => '8', :name => 'John Jones',
801
- :email => 'jjones@example.com', :active => 'true')
802
- example = Zermelo::RedisExample.find_by_id('8')
803
-
804
- time = Time.now
805
-
806
- create_datum(example, :id => '4', :summary => 'well then', :timestamp => time,
807
- :emotion => 'upset')
808
- create_datum(example, :id => '5', :summary => 'ok', :timestamp => time.to_i + 10,
809
- :emotion => 'happy')
810
- create_datum(example, :id => '6', :summary => 'aaargh', :timestamp => time.to_i + 20,
811
- :emotion => 'upset')
812
-
813
- data = example.data.intersect_range(0, 1).all
814
- expect(data).not_to be_nil
815
- expect(data).to be_an(Array)
816
- expect(data.size).to eq(2)
817
- expect(data.map(&:id)).to eq(['4', '5'])
818
- end
819
-
820
- it "retrieves a reversed subset of a sorted set by index" do
821
- create_example(:id => '8', :name => 'John Jones',
822
- :email => 'jjones@example.com', :active => 'true')
823
- example = Zermelo::RedisExample.find_by_id('8')
824
-
825
- time = Time.now
826
-
827
- create_datum(example, :id => '4', :summary => 'well then', :timestamp => time.to_i,
828
- :emotion => 'upset')
829
- create_datum(example, :id => '5', :summary => 'ok', :timestamp => time.to_i + 10,
830
- :emotion => 'happy')
831
- create_datum(example, :id => '6', :summary => 'aaargh', :timestamp => time.to_i + 20,
832
- :emotion => 'upset')
833
-
834
- data = example.data.intersect_range(1, 2).sort(:id, :desc => true).all
835
-
836
- expect(data).not_to be_nil
837
- expect(data).to be_an(Array)
838
- expect(data.size).to eq(2)
839
- expect(data.map(&:id)).to eq(['6', '5'])
840
- end
841
-
842
- it "retrieves a subset of a sorted set by score" do
843
- create_example(:id => '8', :name => 'John Jones',
844
- :email => 'jjones@example.com', :active => 'true')
845
- example = Zermelo::RedisExample.find_by_id('8')
846
-
847
- time = Time.now
848
-
849
- create_datum(example, :id => '4', :summary => 'well then', :timestamp => time,
850
- :emotion => 'upset')
851
- create_datum(example, :id => '5', :summary => 'ok', :timestamp => time.to_i + 10,
852
- :emotion => 'happy')
853
- create_datum(example, :id => '6', :summary => 'aaargh', :timestamp => time.to_i + 20,
854
- :emotion => 'upset')
855
-
856
- data = example.data.intersect_range(time.to_i - 1, time.to_i + 15, :by_score => true).all
857
- expect(data).not_to be_nil
858
- expect(data).to be_an(Array)
859
- expect(data.size).to eq(2)
860
- expect(data.map(&:id)).to eq(['4', '5'])
861
- end
862
-
863
- it "retrieves a reversed subset of a sorted set by score" do
864
- create_example(:id => '8', :name => 'John Jones',
865
- :email => 'jjones@example.com', :active => 'true')
866
- example = Zermelo::RedisExample.find_by_id('8')
867
-
868
- time = Time.now
869
-
870
- create_datum(example, :id => '4', :summary => 'well then', :timestamp => time,
871
- :emotion => 'upset')
872
- create_datum(example, :id => '5', :summary => 'ok', :timestamp => time.to_i + 10,
873
- :emotion => 'happy')
874
- create_datum(example, :id => '6', :summary => 'aaargh', :timestamp => time.to_i + 20,
875
- :emotion => 'upset')
876
-
877
- data = example.data.intersect_range(time.to_i - 1, time.to_i + 15,
878
- :by_score => true).sort(:timestamp, :desc => true).all
879
- expect(data).not_to be_nil
880
- expect(data).to be_an(Array)
881
- expect(data.size).to eq(2)
882
- expect(data.map(&:id)).to eq(['5', '4'])
883
- end
884
-
885
- it "checks whether a record exists through a has_sorted_set filter" do
886
- create_example(:id => '8', :name => 'John Jones',
887
- :email => 'jjones@example.com', :active => 'true')
888
- example = Zermelo::RedisExample.find_by_id('8')
889
-
890
- time = Time.now
891
-
892
- create_datum(example, :id => '4', :summary => 'well then', :timestamp => time,
893
- :emotion => 'upset')
894
- create_datum(example, :id => '5', :summary => 'ok', :timestamp => time.to_i + 10,
895
- :emotion => 'happy')
896
- create_datum(example, :id => '6', :summary => 'aaargh', :timestamp => time.to_i + 20,
897
- :emotion => 'upset')
898
-
899
- expect(example.data.intersect(:emotion => 'upset').exists?('4')).to be_truthy
900
- expect(example.data.intersect(:emotion => 'upset').exists?('5')).to be_falsey
901
- end
902
-
903
- it "retrieves the union of a sorted set by index"
904
- it "retrieves a reversed union of a sorted set by index"
905
-
906
- it "retrieves the union of a sorted set by score"
907
- it "retrieves a reversed union of a sorted set by score"
908
-
909
- it "retrieves the exclusion of a sorted set by index" do
910
- create_example(:id => '8', :name => 'John Jones',
911
- :email => 'jjones@example.com', :active => 'true')
912
- example = Zermelo::RedisExample.find_by_id('8')
913
-
914
- time = Time.now
915
-
916
- create_datum(example, :id => '4', :summary => 'well then', :timestamp => time,
917
- :emotion => 'upset')
918
- create_datum(example, :id => '5', :summary => 'ok', :timestamp => time.to_i + 10,
919
- :emotion => 'happy')
920
- create_datum(example, :id => '6', :summary => 'aaargh', :timestamp => time.to_i + 20,
921
- :emotion => 'upset')
922
-
923
- data = example.data.diff_range(0, 1).all
924
- expect(data).not_to be_nil
925
- expect(data).to be_an(Array)
926
- expect(data.size).to eq(1)
927
- expect(data.map(&:id)).to eq(['6'])
928
- end
929
-
930
- it "retrieves a reversed exclusion of a sorted set by index" do
931
- create_example(:id => '8', :name => 'John Jones',
932
- :email => 'jjones@example.com', :active => 'true')
933
- example = Zermelo::RedisExample.find_by_id('8')
934
-
935
- time = Time.now
936
-
937
- create_datum(example, :id => '4', :summary => 'well then', :timestamp => time,
938
- :emotion => 'upset')
939
- create_datum(example, :id => '5', :summary => 'ok', :timestamp => time.to_i + 10,
940
- :emotion => 'happy')
941
- create_datum(example, :id => '6', :summary => 'aaargh', :timestamp => time.to_i + 20,
942
- :emotion => 'upset')
943
-
944
- data = example.data.diff_range(2, 2).sort(:id, :desc => true).all
945
- expect(data).not_to be_nil
946
- expect(data).to be_an(Array)
947
- expect(data.size).to eq(2)
948
- expect(data.map(&:id)).to eq(['5', '4'])
949
- end
950
-
951
- it "retrieves the exclusion of a sorted set by score" do
952
- create_example(:id => '8', :name => 'John Jones',
953
- :email => 'jjones@example.com', :active => 'true')
954
- example = Zermelo::RedisExample.find_by_id('8')
955
-
956
- time = Time.now
957
-
958
- create_datum(example, :id => '4', :summary => 'well then', :timestamp => time,
959
- :emotion => 'upset')
960
- create_datum(example, :id => '5', :summary => 'ok', :timestamp => time.to_i + 10,
961
- :emotion => 'happy')
962
- create_datum(example, :id => '6', :summary => 'aaargh', :timestamp => time.to_i + 20,
963
- :emotion => 'upset')
964
-
965
- data = example.data.diff_range(time.to_i - 1, time.to_i + 15, :by_score => true).all
966
- expect(data).not_to be_nil
967
- expect(data).to be_an(Array)
968
- expect(data.size).to eq(1)
969
- expect(data.map(&:id)).to eq(['6'])
970
- end
971
-
972
- it "retrieves a reversed exclusion of a sorted set by score" do
973
- create_example(:id => '8', :name => 'John Jones',
974
- :email => 'jjones@example.com', :active => 'true')
975
- example = Zermelo::RedisExample.find_by_id('8')
976
-
977
- time = Time.now
978
-
979
- create_datum(example, :id => '4', :summary => 'well then', :timestamp => time,
980
- :emotion => 'upset')
981
- create_datum(example, :id => '5', :summary => 'ok', :timestamp => time.to_i + 10,
982
- :emotion => 'happy')
983
- create_datum(example, :id => '6', :summary => 'aaargh', :timestamp => time.to_i + 20,
984
- :emotion => 'upset')
985
-
986
- data = example.data.diff_range(time.to_i - 1, time.to_i + 8, :by_score => true).
987
- sort(:timestamp, :desc => true).all
988
- expect(data).not_to be_nil
989
- expect(data).to be_an(Array)
990
- expect(data.size).to eq(2)
991
- expect(data.map(&:id)).to eq(['6', '5'])
992
- end
993
-
994
- it "finds a record through a has_sorted_set filter" do
995
- create_example(:id => '8', :name => 'John Jones',
996
- :email => 'jjones@example.com', :active => 'true')
997
- example = Zermelo::RedisExample.find_by_id('8')
998
-
999
- time = Time.now
1000
-
1001
- create_datum(example, :id => '4', :summary => 'well then', :timestamp => time,
1002
- :emotion => 'upset')
1003
- create_datum(example, :id => '5', :summary => 'ok', :timestamp => time.to_i + 10,
1004
- :emotion => 'happy')
1005
- create_datum(example, :id => '6', :summary => 'aaargh', :timestamp => time.to_i + 20,
1006
- :emotion => 'upset')
1007
-
1008
- wellthen = upset_data = example.data.intersect(:emotion => 'upset').find_by_id('4')
1009
- expect(wellthen).not_to be_nil
1010
- expect(wellthen).to be_a(Zermelo::RedisExampleDatum)
1011
- expect(wellthen.id).to eq('4')
1012
- end
1013
-
1014
- it 'clears the belongs_to association when the child record is deleted' do
1015
- create_example(:id => '8', :name => 'John Jones',
1016
- :email => 'jjones@example.com', :active => 'true')
1017
- example = Zermelo::RedisExample.find_by_id('8')
1018
-
1019
- time = Time.now
1020
-
1021
- create_datum(example, :id => '6', :summary => 'aaargh', :timestamp => time.to_i + 20,
1022
- :emotion => 'upset')
1023
- datum = Zermelo::RedisExampleDatum.find_by_id('6')
1024
-
1025
- expect(redis.keys).to match_array(['redis_example::attrs:ids',
1026
- 'redis_example::indices:by_name',
1027
- 'redis_example::indices:by_active:boolean:true',
1028
- 'redis_example:8:attrs',
1029
- 'redis_example:8:assocs:data_ids',
1030
- 'redis_example_datum::attrs:ids',
1031
- 'redis_example_datum::indices:by_emotion:string:upset',
1032
- 'redis_example_datum:6:attrs',
1033
- 'redis_example_datum:6:assocs:belongs_to'])
1034
-
1035
- datum.destroy
1036
-
1037
- expect(redis.keys).to match_array(['redis_example::attrs:ids',
1038
- 'redis_example::indices:by_name',
1039
- 'redis_example::indices:by_active:boolean:true',
1040
- 'redis_example:8:attrs'])
1041
- end
1042
-
1043
- it "clears the belongs_to association when the parent record is deleted" do
1044
- create_example(:id => '8', :name => 'John Jones',
1045
- :email => 'jjones@example.com', :active => 'true')
1046
- example = Zermelo::RedisExample.find_by_id('8')
1047
-
1048
- time = Time.now
1049
-
1050
- create_datum(example, :id => '6', :summary => 'aaargh', :timestamp => time.to_i + 20,
1051
- :emotion => 'upset')
1052
-
1053
- expect(redis.keys).to match_array(['redis_example::attrs:ids',
1054
- 'redis_example::indices:by_name',
1055
- 'redis_example::indices:by_active:boolean:true',
1056
- 'redis_example:8:attrs',
1057
- 'redis_example:8:assocs:data_ids',
1058
- 'redis_example_datum::attrs:ids',
1059
- 'redis_example_datum::indices:by_emotion:string:upset',
1060
- 'redis_example_datum:6:attrs',
1061
- 'redis_example_datum:6:assocs:belongs_to'])
1062
-
1063
- example.destroy
1064
-
1065
- expect(redis.keys).to match_array(['redis_example_datum::attrs:ids',
1066
- 'redis_example_datum::indices:by_emotion:string:upset',
1067
- 'redis_example_datum:6:attrs'])
1068
- end
1069
-
1070
- it 'returns associated ids for multiple parent ids' do
1071
- create_example(:id => '8', :name => 'John Jones',
1072
- :email => 'jjones@example.com', :active => 'true')
1073
- example_8 = Zermelo::RedisExample.find_by_id('8')
1074
-
1075
- create_example(:id => '9', :name => 'Jane Johnson',
1076
- :email => 'jjohnson@example.com', :active => 'true')
1077
-
1078
- create_example(:id => '10', :name => 'Jim Smith',
1079
- :email => 'jsmith@example.com', :active => 'true')
1080
- example_10 = Zermelo::RedisExample.find_by_id('10')
1081
-
1082
- time = Time.now.to_i
1083
-
1084
- create_datum(example_8, :id => '3', :summary => 'aaargh', :timestamp => time.to_i + 20,
1085
- :emotion => 'ok')
1086
- create_datum(example_8, :id => '4', :summary => 'aaargh', :timestamp => time.to_i + 30,
1087
- :emotion => 'ok')
1088
- create_datum(example_10, :id => '5', :summary => 'aaargh', :timestamp => time.to_i + 40,
1089
- :emotion => 'not_ok')
1090
-
1091
- assoc_ids = Zermelo::RedisExample.intersect(:id => ['8', '9', '10']).
1092
- associated_ids_for(:data)
1093
- expect(assoc_ids).to eq('8' => Set.new(['3', '4']),
1094
- '9' => Set.new(),
1095
- '10' => Set.new(['5']))
1096
- end
1097
-
1098
- end
1099
-
1100
- context "has_one" do
1101
-
1102
- class Zermelo::RedisExampleSpecial
1103
- include Zermelo::Records::RedisRecord
1104
-
1105
- define_attributes :name => :string
1106
-
1107
- belongs_to :example, :class_name => 'Zermelo::RedisExample', :inverse_of => :special
1108
-
1109
- validates :name, :presence => true
1110
- end
1111
-
1112
- class Zermelo::RedisExample
1113
- has_one :special, :class_name => 'Zermelo::RedisExampleSpecial', :inverse_of => :example
1114
- end
1115
-
1116
- it "sets and retrieves a record via a has_one association" do
1117
- create_example(:id => '8', :name => 'John Jones',
1118
- :email => 'jjones@example.com', :active => 'true')
1119
-
1120
- special = Zermelo::RedisExampleSpecial.new(:id => '22', :name => 'Bill Smith')
1121
- expect(special.save).to be_truthy
1122
-
1123
- example = Zermelo::RedisExample.find_by_id('8')
1124
- example.special = special
1125
-
1126
- expect(redis.keys('*')).to match_array(['redis_example::attrs:ids',
1127
- 'redis_example::indices:by_name',
1128
- 'redis_example::indices:by_active:boolean:true',
1129
- 'redis_example:8:attrs',
1130
- 'redis_example:8:assocs',
1131
- 'redis_example_special::attrs:ids',
1132
- 'redis_example_special:22:attrs',
1133
- 'redis_example_special:22:assocs:belongs_to'])
1134
-
1135
- expect(redis.hgetall('redis_example:8:assocs')).to eq("special_id" => "22")
1136
-
1137
- expect(redis.smembers('redis_example_special::attrs:ids')).to eq(['22'])
1138
- expect(redis.hgetall('redis_example_special:22:attrs')).to eq(
1139
- {'name' => 'Bill Smith'}
1140
- )
1141
-
1142
- expect(redis.hgetall('redis_example_special:22:assocs:belongs_to')).to eq(
1143
- {'example_id' => '8'}
1144
- )
1145
-
1146
- example2 = Zermelo::RedisExample.find_by_id('8')
1147
- special2 = example2.special
1148
- expect(special2).not_to be_nil
1149
-
1150
- expect(special2.id).to eq('22')
1151
- expect(special2.example.id).to eq('8')
1152
- end
1153
-
1154
- def create_special(parent, attrs = {})
1155
- redis.hmset("redis_example_special:#{attrs[:id]}:attrs", {'name' => attrs[:name]}.to_a.flatten)
1156
-
1157
- redis.hset("redis_example_special:#{attrs[:id]}:assocs:belongs_to", 'example_id', parent.id)
1158
- redis.hset("redis_example:#{parent.id}:assocs", 'special_id', attrs[:id])
1159
-
1160
- redis.sadd('redis_example_special::attrs:ids', attrs[:id])
1161
- end
1162
-
1163
- it 'clears the belongs_to association when the child record is deleted' do
1164
- create_example(:id => '8', :name => 'John Jones',
1165
- :email => 'jjones@example.com', :active => 'true')
1166
- example = Zermelo::RedisExample.find_by_id('8')
1167
- create_special(example, :id => '3', :name => 'Another Jones')
1168
- special = Zermelo::RedisExampleSpecial.find_by_id('3')
1169
-
1170
- expect(redis.keys).to match_array(['redis_example::attrs:ids',
1171
- 'redis_example::indices:by_name',
1172
- 'redis_example::indices:by_active:boolean:true',
1173
- 'redis_example:8:attrs',
1174
- 'redis_example:8:assocs',
1175
- 'redis_example_special::attrs:ids',
1176
- 'redis_example_special:3:attrs',
1177
- 'redis_example_special:3:assocs:belongs_to'])
1178
-
1179
- special.destroy
1180
-
1181
- expect(redis.keys).to match_array(['redis_example::attrs:ids',
1182
- 'redis_example::indices:by_name',
1183
- 'redis_example::indices:by_active:boolean:true',
1184
- 'redis_example:8:attrs'])
1185
- end
1186
-
1187
- it "clears the belongs_to association when the parent record is deleted" do
1188
- create_example(:id => '8', :name => 'John Jones',
1189
- :email => 'jjones@example.com', :active => 'true')
1190
- example = Zermelo::RedisExample.find_by_id('8')
1191
- create_special(example, :id => '3', :name => 'Another Jones')
1192
-
1193
- expect(redis.keys).to match_array(['redis_example::attrs:ids',
1194
- 'redis_example::indices:by_name',
1195
- 'redis_example::indices:by_active:boolean:true',
1196
- 'redis_example:8:attrs',
1197
- 'redis_example:8:assocs',
1198
- 'redis_example_special::attrs:ids',
1199
- 'redis_example_special:3:attrs',
1200
- 'redis_example_special:3:assocs:belongs_to'])
1201
-
1202
- example.destroy
1203
-
1204
- expect(redis.keys).to match_array(['redis_example_special::attrs:ids',
1205
- 'redis_example_special:3:attrs'])
1206
- end
1207
-
1208
- it 'returns associated ids for multiple parent ids' do
1209
- create_example(:id => '8', :name => 'John Jones',
1210
- :email => 'jjones@example.com', :active => 'true')
1211
-
1212
- create_example(:id => '9', :name => 'Jane Johnson',
1213
- :email => 'jjohnson@example.com', :active => 'true')
1214
- example_9 = Zermelo::RedisExample.find_by_id('9')
1215
-
1216
- create_example(:id => '10', :name => 'Jim Smith',
1217
- :email => 'jsmith@example.com', :active => 'true')
1218
- example_10 = Zermelo::RedisExample.find_by_id('10')
1219
-
1220
- time = Time.now.to_i
1221
-
1222
- create_special(example_9, :id => '3', :name => 'jkl')
1223
- create_special(example_10, :id => '4', :name => 'pqr')
1224
-
1225
- assoc_ids = Zermelo::RedisExample.intersect(:id => ['8', '9', '10']).
1226
- associated_ids_for(:special)
1227
- expect(assoc_ids).to eq('8' => nil,
1228
- '9' => '3',
1229
- '10' => '4')
1230
- end
1231
-
1232
- end
1233
-
1234
-
1235
- context 'sorting by multiple keys' do
1236
-
1237
- def create_template(attrs = {})
1238
- redis.hmset("template:#{attrs[:id]}:attrs", {'name' => attrs[:name]}.to_a.flatten)
1239
- redis.sadd('template::attrs:ids', attrs[:id])
1240
- end
1241
-
1242
- before do
1243
- create_template(:id => '1', :name => 'abc')
1244
- create_template(:id => '2', :name => 'def')
1245
- create_template(:id => '3', :name => 'abc')
1246
- create_template(:id => '4', :name => 'def')
1247
- end
1248
-
1249
- it 'sorts by multiple fields' do
1250
- expect(Zermelo::Template.sort(:name => :asc, :id => :desc).map(&:id)).to eq(['3', '1', '4', '2'])
1251
- end
1252
-
1253
- end
1254
-
1255
- context "has_and_belongs_to_many" do
1256
-
1257
- def create_template(attrs = {})
1258
- redis.hmset("template:#{attrs[:id]}:attrs", {'name' => attrs[:name]}.to_a.flatten)
1259
- redis.sadd('template::attrs:ids', attrs[:id])
1260
- end
1261
-
1262
- before(:each) do
1263
- create_example(:id => '8', :name => 'John Jones',
1264
- :email => 'jjones@example.com', :active => true)
1265
- create_template(:id => '2', :name => 'Template 1')
1266
- end
1267
-
1268
- it "sets a has_and_belongs_to_many relationship between two records in redis" do
1269
- example = Zermelo::RedisExample.find_by_id('8')
1270
- template = Zermelo::Template.find_by_id('2')
1271
-
1272
- example.templates << template
1273
-
1274
- expect(redis.keys('*')).to match_array(['redis_example::attrs:ids',
1275
- 'redis_example::indices:by_name',
1276
- 'redis_example::indices:by_active:boolean:true',
1277
- 'redis_example:8:attrs',
1278
- 'redis_example:8:assocs:templates_ids',
1279
- 'template::attrs:ids',
1280
- 'template:2:attrs',
1281
- 'template:2:assocs:examples_ids'])
1282
-
1283
- expect(redis.smembers('redis_example::attrs:ids')).to eq(['8'])
1284
- expect(redis.smembers('redis_example::indices:by_active:boolean:true')).to eq(['8'])
1285
- expect(redis.hgetall('redis_example:8:attrs')).to eq(
1286
- {'name' => 'John Jones', 'email' => 'jjones@example.com', 'active' => 'true'}
1287
- )
1288
- expect(redis.smembers('redis_example:8:assocs:templates_ids')).to eq(['2'])
1289
-
1290
- expect(redis.smembers('template::attrs:ids')).to eq(['2'])
1291
- expect(redis.hgetall('template:2:attrs')).to eq({'name' => 'Template 1'})
1292
- expect(redis.smembers('template:2:assocs:examples_ids')).to eq(['8'])
1293
- end
1294
-
1295
- it "loads a record from a has_and_belongs_to_many relationship" do
1296
- example = Zermelo::RedisExample.find_by_id('8')
1297
- template = Zermelo::Template.find_by_id('2')
1298
-
1299
- template.examples << example
1300
-
1301
- templates = example.templates.all
1302
-
1303
- expect(templates).to be_an(Array)
1304
- expect(templates.size).to eq(1)
1305
- other_template = templates.first
1306
- expect(other_template).to be_a(Zermelo::Template)
1307
- expect(other_template.id).to eq(template.id)
1308
- end
1309
-
1310
- it "removes a has_and_belongs_to_many relationship between two records in redis" do
1311
- example = Zermelo::RedisExample.find_by_id('8')
1312
- template = Zermelo::Template.find_by_id('2')
1313
-
1314
- template.examples << example
1315
-
1316
- expect(redis.smembers('template::attrs:ids')).to eq(['2'])
1317
- expect(redis.smembers('redis_example:8:assocs:templates_ids')).to eq(['2'])
1318
-
1319
- example.templates.delete(template)
1320
-
1321
- expect(redis.smembers('template::attrs:ids')).to eq(['2']) # template not deleted
1322
- expect(redis.smembers('redis_example:8:assocs:templates_ids')).to eq([]) # but association is
1323
- end
1324
-
1325
- it "filters has_and_belongs_to_many records by indexed attribute values" do
1326
- create_example(:id => '9', :name => 'James Smith',
1327
- :email => 'jsmith@example.com', :active => false)
1328
- create_example(:id => '10', :name => 'Alpha Beta',
1329
- :email => 'abc@example.com', :active => true)
1330
-
1331
- example = Zermelo::RedisExample.find_by_id('8')
1332
- example_2 = Zermelo::RedisExample.find_by_id('9')
1333
- example_3 = Zermelo::RedisExample.find_by_id('10')
1334
- template = Zermelo::Template.find_by_id('2')
1335
-
1336
- example.templates << template
1337
- example_2.templates << template
1338
- example_3.templates << template
1339
-
1340
- examples = template.examples.intersect(:active => true).all
1341
- expect(examples).not_to be_nil
1342
- expect(examples).to be_an(Array)
1343
- expect(examples.size).to eq(2)
1344
- expect(examples.map(&:id)).to match_array(['8', '10'])
1345
- end
1346
-
1347
- it "checks whether a record id exists through a has_and_belongs_to_many filter" do
1348
- create_example(:id => '9', :name => 'James Smith',
1349
- :email => 'jsmith@example.com', :active => false)
1350
-
1351
- example = Zermelo::RedisExample.find_by_id('8')
1352
- example_2 = Zermelo::RedisExample.find_by_id('9')
1353
- template = Zermelo::Template.find_by_id('2')
1354
-
1355
- example.templates << template
1356
- example_2.templates << template
1357
-
1358
- expect(template.examples.intersect(:active => false).exists?('9')).to be_truthy
1359
- expect(template.examples.intersect(:active => false).exists?('8')).to be_falsey
1360
- end
1361
-
1362
- it "finds a record through a has_and_belongs_to_many filter" do
1363
- create_example(:id => '9', :name => 'James Smith',
1364
- :email => 'jsmith@example.com', :active => false)
1365
-
1366
- example = Zermelo::RedisExample.find_by_id('8')
1367
- example_2 = Zermelo::RedisExample.find_by_id('9')
1368
- template = Zermelo::Template.find_by_id('2')
1369
-
1370
- example.templates << template
1371
- example_2.templates << template
1372
-
1373
- james = template.examples.intersect(:active => false).find_by_id('9')
1374
- expect(james).not_to be_nil
1375
- expect(james).to be_a(Zermelo::RedisExample)
1376
- expect(james.id).to eq(example_2.id)
1377
- end
1378
-
1379
- it 'clears a has_and_belongs_to_many association when a record is deleted'
1380
-
1381
- it 'returns associated ids for multiple parent ids' do
1382
- create_example(:id => '9', :name => 'Jane Johnson',
1383
- :email => 'jjohnson@example.com', :active => 'true')
1384
- example_9 = Zermelo::RedisExample.find_by_id('9')
1385
-
1386
- create_example(:id => '10', :name => 'Jim Smith',
1387
- :email => 'jsmith@example.com', :active => 'true')
1388
- example_10 = Zermelo::RedisExample.find_by_id('10')
1389
-
1390
- create_template(:id => '3', :name => 'Template 3')
1391
- create_template(:id => '4', :name => 'Template 4')
1392
-
1393
- template_2 = Zermelo::Template.find_by_id('2')
1394
- template_3 = Zermelo::Template.find_by_id('3')
1395
- template_4 = Zermelo::Template.find_by_id('4')
1396
-
1397
- example_9.templates.add(template_2)
1398
- example_10.templates.add(template_3, template_4)
1399
-
1400
- assoc_ids = Zermelo::RedisExample.intersect(:id => ['8', '9', '10']).
1401
- associated_ids_for(:templates)
1402
- expect(assoc_ids).to eq('8' => Set.new([]),
1403
- '9' => Set.new(['2']),
1404
- '10' => Set.new(['3', '4']))
1405
- end
1406
-
1407
- end
1408
-
1409
- context 'bad parameters' do
1410
-
1411
- let(:example) { Zermelo::RedisExample.find_by_id('8') }
1412
-
1413
- before(:each) do
1414
- create_example(:id => '8', :name => 'John Jones',
1415
- :email => 'jjones@example.com', :active => true)
1416
- end
1417
-
1418
- it 'raises an error when calling add on has_many without an argument' do
1419
- expect {
1420
- example.children.add
1421
- }.to raise_error
1422
- end
1423
-
1424
- it 'raises an error when calling delete on has_many without an argument' do
1425
- expect {
1426
- example.children.delete
1427
- }.to raise_error
1428
- end
1429
-
1430
- it 'raises an error when calling add on has_sorted_set without an argument' do
1431
- expect {
1432
- example.data.add
1433
- }.to raise_error
1434
- end
1435
-
1436
- it 'raises an error when calling delete on has_sorted_set without an argument' do
1437
- expect {
1438
- example.data.delete
1439
- }.to raise_error
1440
- end
1441
-
1442
- it 'raises an error when calling add on has_and_belongs_to_many without an argument' do
1443
- expect {
1444
- example.templates.add
1445
- }.to raise_error
1446
- end
1447
-
1448
- it 'raises an error when calling delete on has_and_belongs_to_many without an argument' do
1449
- expect {
1450
- example.templates.delete
1451
- }.to raise_error
1452
- end
1453
-
1454
- it 'raises an error when trying to filter on a non-indexed value' do
1455
- expect {
1456
- Zermelo::RedisExample.intersect(:email => 'jjones@example.com').all
1457
- }.to raise_error
1458
- end
1459
- end
1460
-
1461
- end