zermelo 1.1.0 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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,77 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'zermelo/records/redis'
|
3
|
+
require 'zermelo/associations/range_index'
|
4
|
+
|
5
|
+
describe Zermelo::Associations::RangeIndex do
|
6
|
+
|
7
|
+
context 'redis', :redis => true do
|
8
|
+
|
9
|
+
let(:redis) { Zermelo.redis }
|
10
|
+
|
11
|
+
let(:time) { Time.now }
|
12
|
+
|
13
|
+
module ZermeloExamples
|
14
|
+
class RedisRangeIndex
|
15
|
+
include Zermelo::Records::Redis
|
16
|
+
define_attributes :created_at => :timestamp
|
17
|
+
validates :created_at, :presence => true
|
18
|
+
range_index_by :created_at
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'adds an entry to a sorted set indexing an attribute' do
|
23
|
+
example = ZermeloExamples::RedisRangeIndex.new(:id => '1',
|
24
|
+
:created_at => time)
|
25
|
+
expect(example).to be_valid
|
26
|
+
expect(example.save).to be true
|
27
|
+
|
28
|
+
expect(redis.exists('redis_range_index::indices:by_created_at')).to be true
|
29
|
+
expect(redis.zrange('redis_range_index::indices:by_created_at', 0, -1, :withscores => true)).to eq([
|
30
|
+
['1', time.to_f]
|
31
|
+
])
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'removes an entry from a sorted set indexing an attribute' do
|
35
|
+
time_2 = time + 100
|
36
|
+
|
37
|
+
example = ZermeloExamples::RedisRangeIndex.new(:id => '1',
|
38
|
+
:created_at => time)
|
39
|
+
example.save
|
40
|
+
|
41
|
+
example_2 = ZermeloExamples::RedisRangeIndex.new(:id => '2',
|
42
|
+
:created_at => time_2)
|
43
|
+
example_2.save
|
44
|
+
|
45
|
+
expect(redis.zrange('redis_range_index::indices:by_created_at', 0, -1, :withscores => true)).to eq([
|
46
|
+
['1', time.to_f],
|
47
|
+
['2', time_2.to_f]
|
48
|
+
])
|
49
|
+
|
50
|
+
example.destroy
|
51
|
+
|
52
|
+
expect(redis.zrange('redis_range_index::indices:by_created_at', 0, -1, :withscores => true)).to eq([
|
53
|
+
['2', time_2.to_f]
|
54
|
+
])
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'changes an entry in a sorted set indexing an attribute' do
|
58
|
+
example = ZermeloExamples::RedisRangeIndex.new(:id => '1',
|
59
|
+
:created_at => time)
|
60
|
+
example.save
|
61
|
+
|
62
|
+
expect(redis.zrange('redis_range_index::indices:by_created_at', 0, -1, :withscores => true)).to eq([
|
63
|
+
['1', time.to_f]
|
64
|
+
])
|
65
|
+
|
66
|
+
time_2 = time + 100
|
67
|
+
example.created_at = time_2
|
68
|
+
example.save
|
69
|
+
|
70
|
+
expect(redis.zrange('redis_range_index::indices:by_created_at', 0, -1, :withscores => true)).to eq([
|
71
|
+
['1', time_2.to_f]
|
72
|
+
])
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
@@ -0,0 +1,149 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'zermelo/records/redis'
|
3
|
+
require 'zermelo/associations/singular'
|
4
|
+
|
5
|
+
describe Zermelo::Associations::Singular do
|
6
|
+
|
7
|
+
context 'has_one' do
|
8
|
+
|
9
|
+
shared_examples "has_one functions work", :has_one => true do
|
10
|
+
|
11
|
+
it 'returns associated ids for multiple parent ids' do
|
12
|
+
create_parent(:id => '8')
|
13
|
+
create_parent(:id => '9')
|
14
|
+
create_parent(:id => '10')
|
15
|
+
|
16
|
+
time = Time.now.to_i
|
17
|
+
|
18
|
+
create_child(parent_class.find_by_id('9'), :id => '3')
|
19
|
+
create_child(parent_class.find_by_id('10'), :id => '4')
|
20
|
+
|
21
|
+
assoc_ids = parent_class.intersect(:id => ['8', '9', '10']).
|
22
|
+
associated_ids_for(:child)
|
23
|
+
expect(assoc_ids).to eq('8' => nil,
|
24
|
+
'9' => '3',
|
25
|
+
'10' => '4')
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'calls before/after_read callbacks when the value is read' # do
|
29
|
+
# create_parent(:id => '8')
|
30
|
+
# parent = parent_class.find_by_id('8')
|
31
|
+
|
32
|
+
# expect(parent.read).to be_nil
|
33
|
+
# expect(parent.child).to be_nil
|
34
|
+
# expect(parent.read).to eq([:pre, :post])
|
35
|
+
# end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
context 'redis', :redis => true, :has_one => true do
|
40
|
+
|
41
|
+
let(:redis) { Zermelo.redis }
|
42
|
+
|
43
|
+
module ZermeloExamples
|
44
|
+
class AssociationsHasOneParentRedis
|
45
|
+
include Zermelo::Records::Redis
|
46
|
+
has_one :child, :class_name => 'ZermeloExamples::AssociationsHasOneChildRedis',
|
47
|
+
:inverse_of => :parent
|
48
|
+
end
|
49
|
+
|
50
|
+
class AssociationsHasOneChildRedis
|
51
|
+
include Zermelo::Records::Redis
|
52
|
+
belongs_to :parent, :class_name => 'ZermeloExamples::AssociationsHasOneParentRedis',
|
53
|
+
:inverse_of => :child
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
let(:parent_class) { ZermeloExamples::AssociationsHasOneParentRedis }
|
58
|
+
let(:child_class) { ZermeloExamples::AssociationsHasOneChildRedis }
|
59
|
+
|
60
|
+
# parent and child keys
|
61
|
+
let(:pk) { 'associations_has_one_parent_redis' }
|
62
|
+
let(:ck) { 'associations_has_one_child_redis' }
|
63
|
+
|
64
|
+
def create_parent(attrs = {})
|
65
|
+
redis.sadd("#{pk}::attrs:ids", attrs[:id])
|
66
|
+
end
|
67
|
+
|
68
|
+
def create_child(parent, attrs = {})
|
69
|
+
redis.hset("#{ck}:#{attrs[:id]}:assocs:belongs_to", 'parent_id', parent.id)
|
70
|
+
redis.hset("#{pk}:#{parent.id}:assocs:has_one", 'child_id', attrs[:id])
|
71
|
+
redis.sadd("#{ck}::attrs:ids", attrs[:id])
|
72
|
+
end
|
73
|
+
|
74
|
+
it "sets and retrieves a record via a has_one association" do
|
75
|
+
create_parent(:id => '8')
|
76
|
+
|
77
|
+
child = child_class.new(:id => '22')
|
78
|
+
expect(child.save).to be true
|
79
|
+
|
80
|
+
parent = parent_class.find_by_id('8')
|
81
|
+
parent.child = child
|
82
|
+
|
83
|
+
expect(redis.keys('*')).to match_array([
|
84
|
+
"#{pk}::attrs:ids",
|
85
|
+
"#{pk}:8:assocs:has_one",
|
86
|
+
"#{ck}::attrs:ids",
|
87
|
+
"#{ck}:22:assocs:belongs_to"
|
88
|
+
])
|
89
|
+
|
90
|
+
expect(redis.hgetall("#{pk}:8:assocs:has_one")).to eq("child_id" => "22")
|
91
|
+
|
92
|
+
expect(redis.smembers("#{ck}::attrs:ids")).to eq(["22"])
|
93
|
+
|
94
|
+
expect(redis.hgetall("#{ck}:22:assocs:belongs_to")).to eq(
|
95
|
+
'parent_id' => '8'
|
96
|
+
)
|
97
|
+
|
98
|
+
parent2 = parent_class.find_by_id('8')
|
99
|
+
child2 = parent2.child
|
100
|
+
expect(child2).not_to be_nil
|
101
|
+
|
102
|
+
expect(child2.id).to eq('22')
|
103
|
+
expect(child2.parent.id).to eq('8')
|
104
|
+
end
|
105
|
+
|
106
|
+
it 'clears the belongs_to association when the child record is deleted' do
|
107
|
+
create_parent(:id => '8')
|
108
|
+
parent = parent_class.find_by_id('8')
|
109
|
+
create_child(parent, :id => '3')
|
110
|
+
child = child_class.find_by_id('3')
|
111
|
+
|
112
|
+
expect(redis.keys).to match_array([
|
113
|
+
"#{pk}::attrs:ids",
|
114
|
+
"#{pk}:8:assocs:has_one",
|
115
|
+
"#{ck}::attrs:ids",
|
116
|
+
"#{ck}:3:assocs:belongs_to"
|
117
|
+
])
|
118
|
+
|
119
|
+
child.destroy
|
120
|
+
|
121
|
+
expect(redis.keys).to match_array([
|
122
|
+
"#{pk}::attrs:ids",
|
123
|
+
])
|
124
|
+
end
|
125
|
+
|
126
|
+
it "clears the belongs_to association when the parent record is deleted" do
|
127
|
+
create_parent(:id => '8')
|
128
|
+
parent = parent_class.find_by_id('8')
|
129
|
+
create_child(parent, :id => '3')
|
130
|
+
|
131
|
+
expect(redis.keys).to match_array([
|
132
|
+
"#{pk}::attrs:ids",
|
133
|
+
"#{pk}:8:assocs:has_one",
|
134
|
+
"#{ck}::attrs:ids",
|
135
|
+
"#{ck}:3:assocs:belongs_to"
|
136
|
+
])
|
137
|
+
|
138
|
+
parent.destroy
|
139
|
+
|
140
|
+
expect(redis.keys).to match_array([
|
141
|
+
"#{ck}::attrs:ids"
|
142
|
+
])
|
143
|
+
end
|
144
|
+
|
145
|
+
end
|
146
|
+
|
147
|
+
end
|
148
|
+
|
149
|
+
end
|
@@ -1,6 +1,62 @@
|
|
1
1
|
require 'spec_helper'
|
2
|
+
require 'zermelo/records/redis'
|
2
3
|
require 'zermelo/associations/unique_index'
|
3
4
|
|
4
|
-
describe Zermelo::Associations::UniqueIndex
|
5
|
+
describe Zermelo::Associations::UniqueIndex do
|
5
6
|
|
6
|
-
|
7
|
+
context 'redis', :redis => true do
|
8
|
+
|
9
|
+
let(:redis) { Zermelo.redis }
|
10
|
+
|
11
|
+
module ZermeloExamples
|
12
|
+
class RedisUniqueIndex
|
13
|
+
include Zermelo::Records::Redis
|
14
|
+
define_attributes :name => :string
|
15
|
+
validates :name, :presence => true
|
16
|
+
unique_index_by :name
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'adds an entry to a hash indexing an attribute' do
|
21
|
+
example = ZermeloExamples::RedisUniqueIndex.new(:id => '1',
|
22
|
+
:name => 'John Smith')
|
23
|
+
expect(example).to be_valid
|
24
|
+
expect(example.save).to be true
|
25
|
+
|
26
|
+
expect(redis.exists('redis_unique_index::indices:by_name')).to be true
|
27
|
+
expect(redis.hgetall('redis_unique_index::indices:by_name')).to eq('string:John%20Smith' => '1')
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'removes an entry from a hash indexing an attribute' do
|
31
|
+
example = ZermeloExamples::RedisUniqueIndex.new(:id => '1',
|
32
|
+
:name => 'John Smith')
|
33
|
+
example.save
|
34
|
+
|
35
|
+
example_2 = ZermeloExamples::RedisUniqueIndex.new(:id => '2',
|
36
|
+
:name => 'Roger Wilco')
|
37
|
+
example_2.save
|
38
|
+
|
39
|
+
expect(redis.hgetall('redis_unique_index::indices:by_name')).to eq('string:John%20Smith' => '1',
|
40
|
+
'string:Roger%20Wilco' => '2')
|
41
|
+
|
42
|
+
example.destroy
|
43
|
+
|
44
|
+
expect(redis.hgetall('redis_unique_index::indices:by_name')).to eq({'string:Roger%20Wilco' => '2'})
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'changes an entry in a hash indexing an attribute' do
|
48
|
+
example = ZermeloExamples::RedisUniqueIndex.new(:id => '1',
|
49
|
+
:name => 'John Smith')
|
50
|
+
example.save
|
51
|
+
|
52
|
+
expect(redis.hgetall('redis_unique_index::indices:by_name')).to eq('string:John%20Smith' => '1')
|
53
|
+
|
54
|
+
example.name = 'Jane Jones'
|
55
|
+
example.save
|
56
|
+
|
57
|
+
expect(redis.hgetall('redis_unique_index::indices:by_name')).to eq('string:Jane%20Jones' => '1')
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
@@ -0,0 +1,363 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'zermelo/filter'
|
3
|
+
require 'zermelo/records/redis'
|
4
|
+
require 'zermelo/records/influxdb'
|
5
|
+
require 'zermelo/associations/range_index'
|
6
|
+
|
7
|
+
describe Zermelo::Filter do
|
8
|
+
|
9
|
+
shared_examples 'filter functions work', :filter => true do
|
10
|
+
|
11
|
+
let(:time) { Time.now }
|
12
|
+
|
13
|
+
let(:active) {
|
14
|
+
create_example(:id => '8', :name => 'John Jones', :active => true,
|
15
|
+
:created_at => (time - 200).to_f)
|
16
|
+
}
|
17
|
+
|
18
|
+
let(:inactive) {
|
19
|
+
create_example(:id => '9', :name => 'James Brown', :active => false,
|
20
|
+
:created_at => (time - 100).to_f)
|
21
|
+
}
|
22
|
+
|
23
|
+
before do
|
24
|
+
active; inactive
|
25
|
+
end
|
26
|
+
|
27
|
+
it "returns all record ids" do
|
28
|
+
examples = example_class.ids
|
29
|
+
expect(examples).not_to be_nil
|
30
|
+
expect(examples).to be_an(Array)
|
31
|
+
expect(examples.size).to eq(2)
|
32
|
+
expect(examples).to contain_exactly('8', '9')
|
33
|
+
end
|
34
|
+
|
35
|
+
it "returns a count of records" do
|
36
|
+
example_count = example_class.count
|
37
|
+
expect(example_count).not_to be_nil
|
38
|
+
expect(example_count).to be_an(Integer)
|
39
|
+
expect(example_count).to eq(2)
|
40
|
+
end
|
41
|
+
|
42
|
+
it "returns all records" do
|
43
|
+
examples = example_class.all
|
44
|
+
expect(examples).not_to be_nil
|
45
|
+
expect(examples).to be_an(Array)
|
46
|
+
expect(examples.size).to eq(2)
|
47
|
+
expect(examples.map(&:id)).to contain_exactly('9', '8')
|
48
|
+
end
|
49
|
+
|
50
|
+
it "finds a record by id" do
|
51
|
+
example = example_class.find_by_id('8')
|
52
|
+
expect(example).not_to be_nil
|
53
|
+
expect(example.id).to eq('8')
|
54
|
+
expect(example.name).to eq('John Jones')
|
55
|
+
end
|
56
|
+
|
57
|
+
it "finds records by a uniquely indexed value" do
|
58
|
+
examples = example_class.intersect(:name => 'John Jones').all
|
59
|
+
expect(examples).not_to be_nil
|
60
|
+
expect(examples).to be_an(Array)
|
61
|
+
expect(examples.size).to eq(1)
|
62
|
+
example = examples.first
|
63
|
+
expect(example.id).to eq('8')
|
64
|
+
expect(example.name).to eq('John Jones')
|
65
|
+
end
|
66
|
+
|
67
|
+
it "filters all class records by indexed attribute values" do
|
68
|
+
example = example_class.intersect(:active => true).all
|
69
|
+
expect(example).not_to be_nil
|
70
|
+
expect(example).to be_an(Array)
|
71
|
+
expect(example.size).to eq(1)
|
72
|
+
expect(example.map(&:id)).to eq(['8'])
|
73
|
+
end
|
74
|
+
|
75
|
+
it 'filters by id attribute values' do
|
76
|
+
example = example_class.intersect(:id => '9').all
|
77
|
+
expect(example).not_to be_nil
|
78
|
+
expect(example).to be_an(Array)
|
79
|
+
expect(example.size).to eq(1)
|
80
|
+
expect(example.map(&:id)).to eq(['9'])
|
81
|
+
end
|
82
|
+
|
83
|
+
it 'filters by multiple id attribute values' do
|
84
|
+
create_example(:id => '10', :name => 'Jay Johns', :active => true,
|
85
|
+
:created_at => (time - 50).to_f)
|
86
|
+
|
87
|
+
examples = example_class.intersect(:id => ['8', '10']).all
|
88
|
+
expect(examples).not_to be_nil
|
89
|
+
expect(examples).to be_an(Array)
|
90
|
+
expect(examples.size).to eq(2)
|
91
|
+
expect(examples.map(&:id)).to match_array(['8', '10'])
|
92
|
+
end
|
93
|
+
|
94
|
+
it 'supports sequential intersection and union operations' do
|
95
|
+
examples = example_class.intersect(:active => true).
|
96
|
+
union(:active => false).all
|
97
|
+
expect(examples).not_to be_nil
|
98
|
+
expect(examples).to be_an(Array)
|
99
|
+
expect(examples.size).to eq(2)
|
100
|
+
expect(examples.map(&:id)).to match_array(['8', '9'])
|
101
|
+
end
|
102
|
+
|
103
|
+
it "chains two intersect filters together" do
|
104
|
+
example = example_class.intersect(:active => true).
|
105
|
+
intersect(:name => 'John Jones').all
|
106
|
+
expect(example).not_to be_nil
|
107
|
+
expect(example).to be_an(Array)
|
108
|
+
expect(example.size).to eq(1)
|
109
|
+
expect(example.map(&:id)).to eq(['8'])
|
110
|
+
end
|
111
|
+
|
112
|
+
it "allows multiple attributes in an intersect filter" do
|
113
|
+
example = example_class.intersect(:active => true,
|
114
|
+
:name => 'John Jones').all
|
115
|
+
expect(example).not_to be_nil
|
116
|
+
expect(example).to be_an(Array)
|
117
|
+
expect(example.size).to eq(1)
|
118
|
+
expect(example.map(&:id)).to eq(['8'])
|
119
|
+
end
|
120
|
+
|
121
|
+
it "chains an intersect and a diff filter together" do
|
122
|
+
create_example(:id => '3', :name => 'Fred Bloggs',
|
123
|
+
:active => 'true')
|
124
|
+
|
125
|
+
example = example_class.intersect(:active => true).diff(:name => 'Fred Bloggs').all
|
126
|
+
expect(example).not_to be_nil
|
127
|
+
expect(example).to be_an(Array)
|
128
|
+
expect(example.size).to eq(1)
|
129
|
+
expect(example.map(&:id)).to eq(['8'])
|
130
|
+
end
|
131
|
+
|
132
|
+
it "does not return a spurious record count when records don't exist" do
|
133
|
+
scope = example_class.intersect(:id => ['3000', '5000'])
|
134
|
+
expect(scope.all).to be_empty
|
135
|
+
expect(scope.count).to eq 0
|
136
|
+
end
|
137
|
+
|
138
|
+
it 'finds records by regex match against a uniquely indexed value' do
|
139
|
+
examples = example_class.intersect(:name => /hn Jones/).all
|
140
|
+
expect(examples).not_to be_nil
|
141
|
+
expect(examples).to be_an(Array)
|
142
|
+
expect(examples.size).to eq(1)
|
143
|
+
example = examples.first
|
144
|
+
expect(example.id).to eq('8')
|
145
|
+
expect(example.name).to eq('John Jones')
|
146
|
+
end
|
147
|
+
|
148
|
+
it 'cannot find records by regex match against non-string values' do
|
149
|
+
expect {
|
150
|
+
example_class.intersect(:active => /alse/).all
|
151
|
+
}.to raise_error("Can't query non-string values via regexp")
|
152
|
+
end
|
153
|
+
|
154
|
+
it 'can append to a filter chain fragment more than once' do
|
155
|
+
inter = example_class.intersect(:active => true)
|
156
|
+
expect(inter.ids).to eq(['8'])
|
157
|
+
|
158
|
+
union = inter.union(:name => 'James Brown')
|
159
|
+
expect(union.ids).to match_array(['8', '9'])
|
160
|
+
|
161
|
+
diff = inter.diff(:id => ['8'])
|
162
|
+
expect(diff.ids).to eq([])
|
163
|
+
end
|
164
|
+
|
165
|
+
it "ANDs multiple union arguments, not ORs them" do
|
166
|
+
create_example(:id => '10', :name => 'Jay Johns', :active => true)
|
167
|
+
examples = example_class.intersect(:id => ['8']).
|
168
|
+
union(:id => ['9', '10'], :active => true).all
|
169
|
+
expect(examples).not_to be_nil
|
170
|
+
expect(examples).to be_an(Array)
|
171
|
+
expect(examples.size).to eq(2)
|
172
|
+
expect(examples.map(&:id)).to match_array(['8', '10'])
|
173
|
+
end
|
174
|
+
|
175
|
+
it "ANDs multiple diff arguments, not ORs them" do
|
176
|
+
create_example(:id => '10', :name => 'Jay Johns', :active => true)
|
177
|
+
examples = example_class.intersect(:id => ['8', '9', '10']).
|
178
|
+
diff(:id => ['9', '10'], :active => false).all
|
179
|
+
expect(examples).not_to be_nil
|
180
|
+
expect(examples).to be_an(Array)
|
181
|
+
expect(examples.size).to eq(2)
|
182
|
+
expect(examples.map(&:id)).to match_array(['8', '10'])
|
183
|
+
end
|
184
|
+
|
185
|
+
it 'supports a regex as argument in union after intersect' do
|
186
|
+
create_example(:id => '10', :name => 'Jay Johns', :active => true)
|
187
|
+
examples = example_class.intersect(:id => ['8']).
|
188
|
+
union(:id => ['9', '10'], :name => [nil, /^Jam/]).all
|
189
|
+
expect(examples).not_to be_nil
|
190
|
+
expect(examples).to be_an(Array)
|
191
|
+
expect(examples.size).to eq(2)
|
192
|
+
expect(examples.map(&:id)).to match_array(['8', '9'])
|
193
|
+
end
|
194
|
+
|
195
|
+
it 'allows intersection operations across multiple values for an attribute' do
|
196
|
+
create_example(:id => '10', :name => 'Jay Johns', :active => true)
|
197
|
+
|
198
|
+
examples = example_class.intersect(:name => ['Jay Johns', 'James Brown']).all
|
199
|
+
expect(examples).not_to be_nil
|
200
|
+
expect(examples).to be_an(Array)
|
201
|
+
expect(examples.size).to eq(2)
|
202
|
+
expect(examples.map(&:id)).to match_array(['9', '10'])
|
203
|
+
end
|
204
|
+
|
205
|
+
it 'allows union operations across multiple values for an attribute' do
|
206
|
+
create_example(:id => '10', :name => 'Jay Johns', :active => true)
|
207
|
+
|
208
|
+
examples = example_class.intersect(:active => false).
|
209
|
+
union(:name => ['Jay Johns', 'James Brown']).all
|
210
|
+
expect(examples).not_to be_nil
|
211
|
+
expect(examples).to be_an(Array)
|
212
|
+
expect(examples.size).to eq(2)
|
213
|
+
expect(examples.map(&:id)).to match_array(['9', '10'])
|
214
|
+
end
|
215
|
+
|
216
|
+
it 'excludes particular records' do
|
217
|
+
example = example_class.diff(:active => true).all
|
218
|
+
expect(example).not_to be_nil
|
219
|
+
expect(example).to be_an(Array)
|
220
|
+
expect(example.size).to eq(1)
|
221
|
+
expect(example.map(&:id)).to eq(['9'])
|
222
|
+
end
|
223
|
+
|
224
|
+
end
|
225
|
+
|
226
|
+
context 'redis', :redis => true, :filter => true do
|
227
|
+
|
228
|
+
let(:redis) { Zermelo.redis }
|
229
|
+
|
230
|
+
module ZermeloExamples
|
231
|
+
class FilterRedis
|
232
|
+
include Zermelo::Records::Redis
|
233
|
+
define_attributes :name => :string,
|
234
|
+
:active => :boolean,
|
235
|
+
:created_at => :timestamp
|
236
|
+
validates :name, :presence => true
|
237
|
+
validates :active, :inclusion => {:in => [true, false]}
|
238
|
+
index_by :active
|
239
|
+
range_index_by :created_at
|
240
|
+
unique_index_by :name
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
let(:example_class) { ZermeloExamples::FilterRedis }
|
245
|
+
|
246
|
+
# parent and child keys
|
247
|
+
let(:ek) { 'filter_redis' }
|
248
|
+
|
249
|
+
def create_example(attrs = {})
|
250
|
+
redis.hmset("#{ek}:#{attrs[:id]}:attrs",
|
251
|
+
{'name' => attrs[:name], 'active' => attrs[:active]}.to_a.flatten)
|
252
|
+
redis.sadd("#{ek}::indices:by_active:boolean:#{!!attrs[:active]}", attrs[:id])
|
253
|
+
name = attrs[:name].gsub(/%/, '%%').gsub(/ /, '%20').gsub(/:/, '%3A')
|
254
|
+
redis.hset("#{ek}::indices:by_name", "string:#{name}", attrs[:id])
|
255
|
+
redis.zadd("#{ek}::indices:by_created_at", attrs[:created_at].to_f, attrs[:id])
|
256
|
+
redis.sadd("#{ek}::attrs:ids", attrs[:id])
|
257
|
+
end
|
258
|
+
|
259
|
+
# following only working in Redis for now
|
260
|
+
it 'sorts records by an attribute' do
|
261
|
+
example = example_class.sort(:name, :order => 'alpha').all
|
262
|
+
expect(example).not_to be_nil
|
263
|
+
expect(example).to be_an(Array)
|
264
|
+
expect(example.size).to eq(2)
|
265
|
+
expect(example.map(&:id)).to eq(['9', '8'])
|
266
|
+
end
|
267
|
+
|
268
|
+
it 'sorts by multiple fields' do
|
269
|
+
data = {:active => true, :created_at => Time.now}
|
270
|
+
create_example(data.merge(:id => '1', :name => 'abc'))
|
271
|
+
create_example(data.merge(:id => '2', :name => 'def'))
|
272
|
+
create_example(data.merge(:id => '3', :name => 'abc'))
|
273
|
+
create_example(data.merge(:id => '4', :name => 'def'))
|
274
|
+
|
275
|
+
expect(example_class.sort(:name => :asc, :id => :desc).map(&:id)).to eq(
|
276
|
+
['9', '8', '3', '1', '4', '2']
|
277
|
+
)
|
278
|
+
end
|
279
|
+
|
280
|
+
# NB sort is case-sensitive, may want a non-case sensitive version
|
281
|
+
it "returns paginated query responses" do
|
282
|
+
data = {:active => true, :created_at => Time.now}
|
283
|
+
create_example(data.merge(:id => '1', :name => 'mno'))
|
284
|
+
create_example(data.merge(:id => '2', :name => 'abc'))
|
285
|
+
create_example(data.merge(:id => '3', :name => 'jkl'))
|
286
|
+
create_example(data.merge(:id => '4', :name => 'ghi'))
|
287
|
+
create_example(data.merge(:id => '5', :name => 'def'))
|
288
|
+
|
289
|
+
expect(example_class.sort(:id).page(1, :per_page => 3).map(&:id)).to eq(['1', '2', '3'])
|
290
|
+
expect(example_class.sort(:id).page(2, :per_page => 2).map(&:id)).to eq(['3', '4'])
|
291
|
+
expect(example_class.sort(:id).page(3, :per_page => 2).map(&:id)).to eq(['5', '8'])
|
292
|
+
expect(example_class.sort(:id).page(3, :per_page => 3).map(&:id)).to eq(['9'])
|
293
|
+
|
294
|
+
expect(example_class.sort(:name).page(1, :per_page => 3).map(&:id)).to eq(['9', '8', '2'])
|
295
|
+
expect(example_class.sort(:name).page(2, :per_page => 3).map(&:id)).to eq(['5', '4', '3'])
|
296
|
+
expect(example_class.sort(:name).page(3, :per_page => 3).map(&:id)).to eq(['1'])
|
297
|
+
expect(example_class.sort(:name).page(4, :per_page => 3).map(&:id)).to eq([])
|
298
|
+
end
|
299
|
+
|
300
|
+
it 'filters by records created before a certain time' do
|
301
|
+
examples = example_class.intersect(:created_at => Zermelo::Filters::IndexRange.new(nil, time - 150, :by_score => true))
|
302
|
+
expect(examples.count).to eq(1)
|
303
|
+
expect(examples.map(&:id)).to eq(['8'])
|
304
|
+
end
|
305
|
+
|
306
|
+
it 'filters by records created after a certain time' do
|
307
|
+
examples = example_class.intersect(:created_at => Zermelo::Filters::IndexRange.new(time - 150, nil, :by_score => true))
|
308
|
+
expect(examples.count).to eq(1)
|
309
|
+
expect(examples.map(&:id)).to eq(['9'])
|
310
|
+
end
|
311
|
+
|
312
|
+
it 'raises an error when trying to filter on a non-indexed value' do
|
313
|
+
expect {
|
314
|
+
example_class.intersect(:email => 'jjones@example.com').all
|
315
|
+
}.to raise_error("'email' property is not indexed")
|
316
|
+
end
|
317
|
+
|
318
|
+
end
|
319
|
+
|
320
|
+
context 'influxdb', :influxdb => true, :filter => true do
|
321
|
+
|
322
|
+
let(:influxdb) { Zermelo.influxdb }
|
323
|
+
|
324
|
+
module ZermeloExamples
|
325
|
+
class FilterInfluxDB
|
326
|
+
include Zermelo::Records::InfluxDB
|
327
|
+
define_attributes :name => :string,
|
328
|
+
:active => :boolean,
|
329
|
+
:created_at => :timestamp
|
330
|
+
validates :name, :presence => true
|
331
|
+
validates :active, :inclusion => {:in => [true, false]}
|
332
|
+
index_by :active
|
333
|
+
range_index_by :created_at
|
334
|
+
unique_index_by :name
|
335
|
+
end
|
336
|
+
end
|
337
|
+
|
338
|
+
let(:example_class) { ZermeloExamples::FilterInfluxDB }
|
339
|
+
|
340
|
+
# parent and child keys
|
341
|
+
let(:ek) { 'filter_influx_db' }
|
342
|
+
|
343
|
+
def create_example(attrs = {})
|
344
|
+
attrs[:active] = attrs[:active].to_s unless attrs[:active].nil?
|
345
|
+
attrs[:time] = attrs[:created_at].to_i
|
346
|
+
Zermelo.influxdb.write_point("#{ek}/#{attrs[:id]}", attrs)
|
347
|
+
end
|
348
|
+
|
349
|
+
# need to fix the influxdb driver to work with these (see Redis examples above)
|
350
|
+
it 'sorts records by an attribute'
|
351
|
+
it 'sorts by multiple fields'
|
352
|
+
it "returns paginated query responses"
|
353
|
+
it 'filters by records created before a certain time'
|
354
|
+
it 'filters by records created after a certain time'
|
355
|
+
|
356
|
+
it 'raises an error when trying to filter on a non-indexed value' do
|
357
|
+
expect {
|
358
|
+
example_class.intersect(:email => 'jjones@example.com').all
|
359
|
+
}.to raise_error("Field email doesn't exist in series filter_influx_db/10")
|
360
|
+
end
|
361
|
+
|
362
|
+
end
|
363
|
+
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
require 'zermelo/locks/redis_lock'
|
3
|
-
require 'zermelo/records/
|
3
|
+
require 'zermelo/records/redis'
|
4
4
|
|
5
5
|
describe Zermelo::Locks::RedisLock, :redis => true do
|
6
6
|
|
@@ -8,12 +8,12 @@ describe Zermelo::Locks::RedisLock, :redis => true do
|
|
8
8
|
|
9
9
|
module Zermelo
|
10
10
|
class RedisLockExample
|
11
|
-
include Zermelo::Records::
|
11
|
+
include Zermelo::Records::Redis
|
12
12
|
define_attributes :name => :string
|
13
13
|
end
|
14
14
|
|
15
15
|
class AnotherExample
|
16
|
-
include Zermelo::Records::
|
16
|
+
include Zermelo::Records::Redis
|
17
17
|
define_attributes :age => :integer
|
18
18
|
end
|
19
19
|
end
|