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