zermelo 1.1.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (66) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +10 -0
  3. data/README.md +76 -52
  4. data/lib/zermelo/associations/association_data.rb +4 -3
  5. data/lib/zermelo/associations/class_methods.rb +37 -50
  6. data/lib/zermelo/associations/index.rb +3 -1
  7. data/lib/zermelo/associations/multiple.rb +247 -0
  8. data/lib/zermelo/associations/range_index.rb +44 -0
  9. data/lib/zermelo/associations/singular.rb +193 -0
  10. data/lib/zermelo/associations/unique_index.rb +4 -3
  11. data/lib/zermelo/backend.rb +120 -0
  12. data/lib/zermelo/backends/{influxdb_backend.rb → influxdb.rb} +87 -31
  13. data/lib/zermelo/backends/{redis_backend.rb → redis.rb} +53 -58
  14. data/lib/zermelo/backends/stub.rb +43 -0
  15. data/lib/zermelo/filter.rb +194 -0
  16. data/lib/zermelo/filters/index_range.rb +22 -0
  17. data/lib/zermelo/filters/{influxdb_filter.rb → influxdb.rb} +12 -11
  18. data/lib/zermelo/filters/redis.rb +173 -0
  19. data/lib/zermelo/filters/steps/list_step.rb +48 -30
  20. data/lib/zermelo/filters/steps/set_step.rb +148 -89
  21. data/lib/zermelo/filters/steps/sort_step.rb +2 -2
  22. data/lib/zermelo/record.rb +53 -0
  23. data/lib/zermelo/records/attributes.rb +32 -0
  24. data/lib/zermelo/records/class_methods.rb +12 -25
  25. data/lib/zermelo/records/{influxdb_record.rb → influxdb.rb} +3 -4
  26. data/lib/zermelo/records/instance_methods.rb +9 -8
  27. data/lib/zermelo/records/key.rb +3 -1
  28. data/lib/zermelo/records/redis.rb +17 -0
  29. data/lib/zermelo/records/stub.rb +17 -0
  30. data/lib/zermelo/version.rb +1 -1
  31. data/spec/lib/zermelo/associations/index_spec.rb +70 -1
  32. data/spec/lib/zermelo/associations/multiple_spec.rb +1084 -0
  33. data/spec/lib/zermelo/associations/range_index_spec.rb +77 -0
  34. data/spec/lib/zermelo/associations/singular_spec.rb +149 -0
  35. data/spec/lib/zermelo/associations/unique_index_spec.rb +58 -2
  36. data/spec/lib/zermelo/filter_spec.rb +363 -0
  37. data/spec/lib/zermelo/locks/redis_lock_spec.rb +3 -3
  38. data/spec/lib/zermelo/records/instance_methods_spec.rb +206 -0
  39. data/spec/spec_helper.rb +9 -1
  40. data/spec/support/mock_logger.rb +48 -0
  41. metadata +31 -46
  42. data/lib/zermelo/associations/belongs_to.rb +0 -115
  43. data/lib/zermelo/associations/has_and_belongs_to_many.rb +0 -128
  44. data/lib/zermelo/associations/has_many.rb +0 -120
  45. data/lib/zermelo/associations/has_one.rb +0 -109
  46. data/lib/zermelo/associations/has_sorted_set.rb +0 -124
  47. data/lib/zermelo/backends/base.rb +0 -115
  48. data/lib/zermelo/filters/base.rb +0 -212
  49. data/lib/zermelo/filters/redis_filter.rb +0 -111
  50. data/lib/zermelo/filters/steps/sorted_set_step.rb +0 -156
  51. data/lib/zermelo/records/base.rb +0 -62
  52. data/lib/zermelo/records/redis_record.rb +0 -27
  53. data/spec/lib/zermelo/associations/belongs_to_spec.rb +0 -6
  54. data/spec/lib/zermelo/associations/has_many_spec.rb +0 -6
  55. data/spec/lib/zermelo/associations/has_one_spec.rb +0 -6
  56. data/spec/lib/zermelo/associations/has_sorted_set.spec.rb +0 -6
  57. data/spec/lib/zermelo/backends/influxdb_backend_spec.rb +0 -0
  58. data/spec/lib/zermelo/backends/moneta_backend_spec.rb +0 -0
  59. data/spec/lib/zermelo/filters/influxdb_filter_spec.rb +0 -0
  60. data/spec/lib/zermelo/filters/redis_filter_spec.rb +0 -0
  61. data/spec/lib/zermelo/records/influxdb_record_spec.rb +0 -434
  62. data/spec/lib/zermelo/records/key_spec.rb +0 -6
  63. data/spec/lib/zermelo/records/redis_record_spec.rb +0 -1461
  64. data/spec/lib/zermelo/records/type_validator_spec.rb +0 -6
  65. data/spec/lib/zermelo/version_spec.rb +0 -6
  66. data/spec/lib/zermelo_spec.rb +0 -6
@@ -0,0 +1,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, :redis => true do
5
+ describe Zermelo::Associations::UniqueIndex do
5
6
 
6
- end
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/redis_record'
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::RedisRecord
11
+ include Zermelo::Records::Redis
12
12
  define_attributes :name => :string
13
13
  end
14
14
 
15
15
  class AnotherExample
16
- include Zermelo::Records::RedisRecord
16
+ include Zermelo::Records::Redis
17
17
  define_attributes :age => :integer
18
18
  end
19
19
  end