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