zermelo 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +10 -0
  3. data/README.md +76 -52
  4. data/lib/zermelo/associations/association_data.rb +4 -3
  5. data/lib/zermelo/associations/class_methods.rb +37 -50
  6. data/lib/zermelo/associations/index.rb +3 -1
  7. data/lib/zermelo/associations/multiple.rb +247 -0
  8. data/lib/zermelo/associations/range_index.rb +44 -0
  9. data/lib/zermelo/associations/singular.rb +193 -0
  10. data/lib/zermelo/associations/unique_index.rb +4 -3
  11. data/lib/zermelo/backend.rb +120 -0
  12. data/lib/zermelo/backends/{influxdb_backend.rb → influxdb.rb} +87 -31
  13. data/lib/zermelo/backends/{redis_backend.rb → redis.rb} +53 -58
  14. data/lib/zermelo/backends/stub.rb +43 -0
  15. data/lib/zermelo/filter.rb +194 -0
  16. data/lib/zermelo/filters/index_range.rb +22 -0
  17. data/lib/zermelo/filters/{influxdb_filter.rb → influxdb.rb} +12 -11
  18. data/lib/zermelo/filters/redis.rb +173 -0
  19. data/lib/zermelo/filters/steps/list_step.rb +48 -30
  20. data/lib/zermelo/filters/steps/set_step.rb +148 -89
  21. data/lib/zermelo/filters/steps/sort_step.rb +2 -2
  22. data/lib/zermelo/record.rb +53 -0
  23. data/lib/zermelo/records/attributes.rb +32 -0
  24. data/lib/zermelo/records/class_methods.rb +12 -25
  25. data/lib/zermelo/records/{influxdb_record.rb → influxdb.rb} +3 -4
  26. data/lib/zermelo/records/instance_methods.rb +9 -8
  27. data/lib/zermelo/records/key.rb +3 -1
  28. data/lib/zermelo/records/redis.rb +17 -0
  29. data/lib/zermelo/records/stub.rb +17 -0
  30. data/lib/zermelo/version.rb +1 -1
  31. data/spec/lib/zermelo/associations/index_spec.rb +70 -1
  32. data/spec/lib/zermelo/associations/multiple_spec.rb +1084 -0
  33. data/spec/lib/zermelo/associations/range_index_spec.rb +77 -0
  34. data/spec/lib/zermelo/associations/singular_spec.rb +149 -0
  35. data/spec/lib/zermelo/associations/unique_index_spec.rb +58 -2
  36. data/spec/lib/zermelo/filter_spec.rb +363 -0
  37. data/spec/lib/zermelo/locks/redis_lock_spec.rb +3 -3
  38. data/spec/lib/zermelo/records/instance_methods_spec.rb +206 -0
  39. data/spec/spec_helper.rb +9 -1
  40. data/spec/support/mock_logger.rb +48 -0
  41. metadata +31 -46
  42. data/lib/zermelo/associations/belongs_to.rb +0 -115
  43. data/lib/zermelo/associations/has_and_belongs_to_many.rb +0 -128
  44. data/lib/zermelo/associations/has_many.rb +0 -120
  45. data/lib/zermelo/associations/has_one.rb +0 -109
  46. data/lib/zermelo/associations/has_sorted_set.rb +0 -124
  47. data/lib/zermelo/backends/base.rb +0 -115
  48. data/lib/zermelo/filters/base.rb +0 -212
  49. data/lib/zermelo/filters/redis_filter.rb +0 -111
  50. data/lib/zermelo/filters/steps/sorted_set_step.rb +0 -156
  51. data/lib/zermelo/records/base.rb +0 -62
  52. data/lib/zermelo/records/redis_record.rb +0 -27
  53. data/spec/lib/zermelo/associations/belongs_to_spec.rb +0 -6
  54. data/spec/lib/zermelo/associations/has_many_spec.rb +0 -6
  55. data/spec/lib/zermelo/associations/has_one_spec.rb +0 -6
  56. data/spec/lib/zermelo/associations/has_sorted_set.spec.rb +0 -6
  57. data/spec/lib/zermelo/backends/influxdb_backend_spec.rb +0 -0
  58. data/spec/lib/zermelo/backends/moneta_backend_spec.rb +0 -0
  59. data/spec/lib/zermelo/filters/influxdb_filter_spec.rb +0 -0
  60. data/spec/lib/zermelo/filters/redis_filter_spec.rb +0 -0
  61. data/spec/lib/zermelo/records/influxdb_record_spec.rb +0 -434
  62. data/spec/lib/zermelo/records/key_spec.rb +0 -6
  63. data/spec/lib/zermelo/records/redis_record_spec.rb +0 -1461
  64. data/spec/lib/zermelo/records/type_validator_spec.rb +0 -6
  65. data/spec/lib/zermelo/version_spec.rb +0 -6
  66. data/spec/lib/zermelo_spec.rb +0 -6
@@ -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