ohm-zset 0.2
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.
- data/.gems +15 -0
- data/.gitignore +2 -0
- data/.rvmrc +1 -0
- data/CHANGELOG +24 -0
- data/LICENSE +20 -0
- data/README.md +172 -0
- data/Rakefile +2 -0
- data/lib/ohm-zset.rb +406 -0
- data/lib/suppress-warnings.rb +16 -0
- data/test/helper.rb +4 -0
- data/test/unit/ohm-zset.rb +663 -0
- metadata +79 -0
data/.gems
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
# .gems generated gem export file. Note that any env variable settings will be missing. Append these after using a ';' field separator
|
2
|
+
|
3
|
+
file-tail -v1.0.10
|
4
|
+
minitest -v3.2.0
|
5
|
+
nest -v1.1.1
|
6
|
+
ohm -v1.0.2
|
7
|
+
redis -v3.0.1
|
8
|
+
redis -v2.2.2
|
9
|
+
ruby2ruby -v1.3.1
|
10
|
+
ruby_parser -v2.3.1
|
11
|
+
scrivener -v0.0.3
|
12
|
+
sexp_processor -v3.2.0
|
13
|
+
sourcify -v0.5.0
|
14
|
+
tins -v0.4.3
|
15
|
+
uuidtools -v2.1.3
|
data/.gitignore
ADDED
data/.rvmrc
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
rvm --create use 1.9.3@ohm-zset
|
data/CHANGELOG
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
v 0.2
|
2
|
+
|
3
|
+
- refactored code
|
4
|
+
|
5
|
+
v 0.1.4
|
6
|
+
|
7
|
+
- included option to intersect and union multiple sets with specified name
|
8
|
+
|
9
|
+
v 0.1.3
|
10
|
+
|
11
|
+
- included save_set, and load_set, and cleaned code
|
12
|
+
|
13
|
+
v 0.1.2
|
14
|
+
|
15
|
+
- included duplicate and ZSet.generate_uuid
|
16
|
+
|
17
|
+
v 0.1.1
|
18
|
+
|
19
|
+
- included UUID generation for keys of new ZSets for union and intersection
|
20
|
+
|
21
|
+
v 0.1.0
|
22
|
+
|
23
|
+
- stable
|
24
|
+
- Ohm basic support for Redis sorted sets
|
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2012 Joshua Arvin Lat
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,172 @@
|
|
1
|
+
# ohm-zset
|
2
|
+
|
3
|
+
Ohm Sorted Set (ZSET) support for Redis.
|
4
|
+
|
5
|
+
## Basic Usage
|
6
|
+
|
7
|
+
```ruby
|
8
|
+
class Big < Ohm::Model
|
9
|
+
set :smalls, :Small
|
10
|
+
zset :zsmalls, :Small, :size
|
11
|
+
end
|
12
|
+
|
13
|
+
class Small < Ohm::Model
|
14
|
+
attribute :name
|
15
|
+
attribute :size
|
16
|
+
end
|
17
|
+
|
18
|
+
b = Big.create
|
19
|
+
s1 = Small.create(name: 'S1', size: 5)
|
20
|
+
s2 = Small.create(name: 'S2', size: 4)
|
21
|
+
s3 = Small.create(name: 'S3', size: 3)
|
22
|
+
s4 = Small.create(name: 'S4', size: 2)
|
23
|
+
s5 = Small.create(name: 'S5', size: 1)
|
24
|
+
|
25
|
+
b.zsmalls.add_list(s1, s2, s3, s4, s5)
|
26
|
+
|
27
|
+
b.zsmalls.size
|
28
|
+
# => 5
|
29
|
+
|
30
|
+
b.zsmalls.to_a.map(&:name)
|
31
|
+
# => ['S5', 'S4', 'S3', 'S2', 'S1']
|
32
|
+
|
33
|
+
b.zsmalls.to_a.map(&:size)
|
34
|
+
# => ['1', '2', '3', '4', '5']
|
35
|
+
```
|
36
|
+
|
37
|
+
## Interacting with the elements of the set
|
38
|
+
**Ohm-ZSET** includes *get*, *rank*, *revrank*, *score*, *range*, *revrange*, *rangebyscore*, *revrangebyscore*
|
39
|
+
|
40
|
+
```ruby
|
41
|
+
|
42
|
+
b.zsmalls.get(0).name
|
43
|
+
# => 'S5'
|
44
|
+
|
45
|
+
b.zsmalls.range(0, 3).to_a.map(&:name)
|
46
|
+
# => ['S5', 'S4', 'S3', 'S2']
|
47
|
+
|
48
|
+
b.zsmalls.revrange(0, 3).each do |small|
|
49
|
+
puts "#{small.name} - #{small.size}"
|
50
|
+
end
|
51
|
+
# => S1 - 5
|
52
|
+
# => S2 - 4
|
53
|
+
# => S3 - 3
|
54
|
+
# => S4 - 2
|
55
|
+
|
56
|
+
s6 = Small.create(name:'S6', size:3.5)
|
57
|
+
b.zsmalls.add(s6)
|
58
|
+
b.zsmalls.to_a.map(&:name)
|
59
|
+
# => ['S5', 'S4', 'S3', 'S6', 'S2', 'S1']
|
60
|
+
```
|
61
|
+
|
62
|
+
You can update the score of an element by using *update*. There is also a *count* function that returns the number of elements with scores inside a specified range.
|
63
|
+
|
64
|
+
## Deleting Elements
|
65
|
+
**Ohm-ZSET** includes *delete* for deleting a single element, *clear* for deleting all elements, and *remrangebyrank* and *remrangebyscore* for deleting selected elements.
|
66
|
+
|
67
|
+
```ruby
|
68
|
+
b.zsmalls.delete(s6)
|
69
|
+
b.zsmalls.to_a.map(&:name)
|
70
|
+
# => ['S5', 'S4', 'S3', 'S2', 'S1']
|
71
|
+
|
72
|
+
b.zsmalls.include? s6
|
73
|
+
# => false
|
74
|
+
|
75
|
+
b.zsmalls.clear
|
76
|
+
b.zsmalls.to_a.map(&:name)
|
77
|
+
# => []
|
78
|
+
```
|
79
|
+
|
80
|
+
It also has *destroy!* to delete the key of the sorted set.
|
81
|
+
|
82
|
+
## Set Intersection and Union
|
83
|
+
Set intersection between sorted sets and sets are allowed. You can use *intersect*, *intersect_multiple*, *union*, and *union_multiple* between sets and sorted sets.
|
84
|
+
|
85
|
+
```ruby
|
86
|
+
b.smalls.add(s1)
|
87
|
+
b.smalls.add(s2)
|
88
|
+
b.smalls.add(s4)
|
89
|
+
|
90
|
+
# Intersect ['S5', 'S4', 'S3', 'S2', 'S1'] with ['S4', 'S2', 'S1']
|
91
|
+
b.zsmalls.intersect(b.smalls).to_a.map(&:name)
|
92
|
+
# => ['S4', 'S2', 'S1']
|
93
|
+
```
|
94
|
+
|
95
|
+
The sorted set allows union and intersection of multiple sets and sorted sets with weights.
|
96
|
+
Result of intersection and union is another ZSET.
|
97
|
+
|
98
|
+
It also allows intersection and union to a new set with the specified name.
|
99
|
+
|
100
|
+
```ruby
|
101
|
+
zunion = Ohm::ZSet.union_multiple('zunion', [zlittles, slittles, zlittles2, zlittles3])
|
102
|
+
|
103
|
+
zunion.key
|
104
|
+
# => 'zunion'
|
105
|
+
```
|
106
|
+
|
107
|
+
## Scoring Functions
|
108
|
+
**Ohm-ZSET** also supports custom scoring functions. The default scoring function is ZScore::Integer.
|
109
|
+
There are also available built-in scoring functions in the module.
|
110
|
+
|
111
|
+
```ruby
|
112
|
+
class Bigger < Ohm::Model
|
113
|
+
include Ohm::ZScores
|
114
|
+
|
115
|
+
zset :zlittles, :Little, :score
|
116
|
+
|
117
|
+
# Custom scoring function
|
118
|
+
zset :zsmalls, :Small, :value, lambda{ |x| Integer(x) + 1 }
|
119
|
+
|
120
|
+
# Built-in scoring functions
|
121
|
+
zset :zlittles, :Little, :score, ZScore::Float
|
122
|
+
zset :zbools, :Bool, :is_valid, ZScore::Boolean
|
123
|
+
zset :zdts, :DT, :last_login, ZScore::DateTime
|
124
|
+
|
125
|
+
# Built-in string sorting functions
|
126
|
+
zset :zbools2, :Bool, :name, ZScore::String
|
127
|
+
zset :zbools3, :Bool, :name, ZScore::StringInsensitive
|
128
|
+
zset :zbools4, :Bool, :name, ZScore::StringInsensitiveHigh
|
129
|
+
end
|
130
|
+
|
131
|
+
class Bool < Ohm::Model
|
132
|
+
attribute :name
|
133
|
+
attribute :is_valid
|
134
|
+
end
|
135
|
+
|
136
|
+
class DT < Ohm::Model
|
137
|
+
attribute :name
|
138
|
+
attribute :last_login
|
139
|
+
end
|
140
|
+
|
141
|
+
class Small < Ohm::Model
|
142
|
+
attribute :name
|
143
|
+
attribute :value
|
144
|
+
end
|
145
|
+
|
146
|
+
class Little < Ohm::Model
|
147
|
+
attribute :name
|
148
|
+
attribute :score
|
149
|
+
end
|
150
|
+
|
151
|
+
```
|
152
|
+
|
153
|
+
Redis allows only numerical scores so DateTime and strings objects are first converted to numbers and then stored as scores.
|
154
|
+
|
155
|
+
**Note**: The string scoring algorithm is limited only to the first 9 characters because of the floating point accuracy limit.
|
156
|
+
You can use the *starts_with* function when dealing with string sorted sets. It returns all the elements of the set with a score field value that starts with the specified string.
|
157
|
+
|
158
|
+
## Saving and Loading ZSet instances by name / key
|
159
|
+
|
160
|
+
```ruby
|
161
|
+
|
162
|
+
sorted_set = b.zlittles
|
163
|
+
sorted_set.save_set
|
164
|
+
|
165
|
+
sorted_set_2 = Ohm::ZSet.load_set(sorted_set.key)
|
166
|
+
sorted_set_2.add(Little.create(name: 'X1',score: 29))
|
167
|
+
|
168
|
+
# sorted_set and sorted_set_2 are pointing to same ZSet instance
|
169
|
+
```
|
170
|
+
|
171
|
+
## Copyright
|
172
|
+
Copyright (c) 2012 [Joshua Arvin Lat](http://www.joshualat.com). See LICENSE for more details.
|
data/Rakefile
ADDED
data/lib/ohm-zset.rb
ADDED
@@ -0,0 +1,406 @@
|
|
1
|
+
require 'ohm'
|
2
|
+
require 'time'
|
3
|
+
require 'uuidtools'
|
4
|
+
require 'sourcify'
|
5
|
+
|
6
|
+
# http://whytheluckystiff.net/articles/seeingMetaclassesClearly.html
|
7
|
+
class Object
|
8
|
+
def meta_def(name, &blk)
|
9
|
+
(class << self; self; end).instance_eval { define_method(name, &blk) }
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
module Ohm
|
14
|
+
class Model
|
15
|
+
def self.zset(name, model, *score_field)
|
16
|
+
|
17
|
+
define_method name do
|
18
|
+
model = Utils.const(self.class, model)
|
19
|
+
Ohm::ZSet.new(key[name], model.key, model, score_field)
|
20
|
+
end
|
21
|
+
|
22
|
+
meta_def name do
|
23
|
+
model = Utils.const(self, model)
|
24
|
+
Ohm::ZSet.new(key[name], model.key, model, score_field)
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
module Utils
|
31
|
+
class << self
|
32
|
+
def proc_to_string(function)
|
33
|
+
function.to_source
|
34
|
+
end
|
35
|
+
|
36
|
+
def string_to_proc(function)
|
37
|
+
eval function
|
38
|
+
end
|
39
|
+
|
40
|
+
def score_field_to_string(score_field)
|
41
|
+
string_list = []
|
42
|
+
|
43
|
+
lambda_function = score_field.each do |field|
|
44
|
+
break field if field.is_a? Proc
|
45
|
+
|
46
|
+
string_list.push(field.to_s)
|
47
|
+
|
48
|
+
break lambda{ |x| x.to_i } if field == score_field.last
|
49
|
+
end
|
50
|
+
|
51
|
+
string_list.push Ohm::Utils.proc_to_string(lambda_function)
|
52
|
+
string_list.join(":")
|
53
|
+
end
|
54
|
+
|
55
|
+
def string_to_score_field(score_field)
|
56
|
+
string_list = score_field.split(":")
|
57
|
+
return_list = []
|
58
|
+
|
59
|
+
string_list.each do |field|
|
60
|
+
if field.include? 'proc'
|
61
|
+
return_list.push Ohm::Utils.string_to_proc field
|
62
|
+
else
|
63
|
+
return_list.push field.to_sym
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
return_list
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
module ZScores
|
73
|
+
module ZScore
|
74
|
+
Integer = lambda{ |x| Integer(x) }
|
75
|
+
Float = lambda{ |x| Float(x) }
|
76
|
+
Boolean = lambda{ |x| Ohm::ZScores.boolean(x) }
|
77
|
+
DateTime = lambda{ |x| Ohm::ZScores.datetime(x) }
|
78
|
+
String = lambda{ |x| Ohm::ZScores.string(x) }
|
79
|
+
StringInsensitive = lambda{ |x| Ohm::ZScores.string_insensitive(x) }
|
80
|
+
StringInsensitiveHigh = lambda{ |x| Ohm::ZScores.string_insensitive_high(x) }
|
81
|
+
end
|
82
|
+
|
83
|
+
class << self
|
84
|
+
def boolean(val)
|
85
|
+
case val
|
86
|
+
when "true", "1", true
|
87
|
+
1
|
88
|
+
when "false", "0", false, nil
|
89
|
+
0
|
90
|
+
else
|
91
|
+
1
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def datetime(val)
|
96
|
+
::DateTime.parse(val.to_s).to_time.to_i
|
97
|
+
end
|
98
|
+
|
99
|
+
# scoring accurate until 9th character
|
100
|
+
def string(val)
|
101
|
+
total_score = 0
|
102
|
+
|
103
|
+
val.each_char.with_index do |c, i|
|
104
|
+
total_score += (c.ord-31) * ((126-32) ** (10 - i))
|
105
|
+
break if i == 9
|
106
|
+
end
|
107
|
+
|
108
|
+
total_score.to_f
|
109
|
+
end
|
110
|
+
|
111
|
+
# scoring accurate until 9th character
|
112
|
+
def string_insensitive(val)
|
113
|
+
total_score = 0
|
114
|
+
|
115
|
+
val.each_char.with_index do |c, i|
|
116
|
+
char_ord = c.ord-31
|
117
|
+
if ('a'..'z').include? c
|
118
|
+
char_ord -= 32
|
119
|
+
end
|
120
|
+
total_score += (char_ord) * ((126-32) ** (10 - i))
|
121
|
+
break if i == 9
|
122
|
+
end
|
123
|
+
|
124
|
+
total_score.to_f
|
125
|
+
end
|
126
|
+
|
127
|
+
# scoring accurate until 9th character
|
128
|
+
def string_insensitive_high(val)
|
129
|
+
total_score = 0
|
130
|
+
|
131
|
+
val.each_char.with_index do |c, i|
|
132
|
+
char_ord = c.ord-31
|
133
|
+
if ('a'..'z').include? c
|
134
|
+
char_ord -= 31.5
|
135
|
+
end
|
136
|
+
total_score += (char_ord) * ((126-32) ** (10 - i))
|
137
|
+
break if i == 9
|
138
|
+
end
|
139
|
+
|
140
|
+
total_score.to_f
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
class ZSet < Struct.new(:key, :namespace, :model, :score_field)
|
146
|
+
include PipelinedFetch
|
147
|
+
include Enumerable
|
148
|
+
|
149
|
+
class << self
|
150
|
+
def intersect_multiple(new_key, sets, weights = [])
|
151
|
+
base_set = sets[0]
|
152
|
+
weights = [1.0] * sets.length if weights = []
|
153
|
+
|
154
|
+
new_set = Ohm::ZSet.new(new_key, base_set.model.key, base_set.model, base_set.score_field)
|
155
|
+
sets = sets.map(&:key)
|
156
|
+
|
157
|
+
Ohm.redis.zinterstore(new_key, sets, :weights => weights)
|
158
|
+
new_set
|
159
|
+
end
|
160
|
+
|
161
|
+
def load_set(name)
|
162
|
+
new_model, new_score_field = Ohm.redis.hmget("ZSet:" + name, "model", "score_field")
|
163
|
+
return nil if new_model == nil && new_score_field == nil
|
164
|
+
|
165
|
+
new_model = Ohm::Utils.const(self.class, new_model.to_sym)
|
166
|
+
new_score_field = Ohm::Utils.string_to_score_field new_score_field
|
167
|
+
return_instance = Ohm::ZSet.new(name, new_model.key, new_model, new_score_field)
|
168
|
+
end
|
169
|
+
|
170
|
+
def union_multiple(new_key, sets, weights = [])
|
171
|
+
base_set = sets[0]
|
172
|
+
weights = [1.0] * sets.length if weights = []
|
173
|
+
|
174
|
+
new_set = Ohm::ZSet.new(new_key, base_set.model.key, base_set.model, base_set.score_field)
|
175
|
+
sets = sets.map(&:key)
|
176
|
+
|
177
|
+
Ohm.redis.zunionstore(new_key, sets, :weights => weights)
|
178
|
+
|
179
|
+
new_set
|
180
|
+
end
|
181
|
+
|
182
|
+
def generate_uuid
|
183
|
+
"ZSet:" + UUIDTools::UUID.random_create.to_s
|
184
|
+
end
|
185
|
+
|
186
|
+
def random_instance(model, score_field)
|
187
|
+
self.new_instance(Ohm::ZSet.generate_uuid, model, score_field)
|
188
|
+
end
|
189
|
+
|
190
|
+
def new_instance(name, model, score_field)
|
191
|
+
self.new(name, model.key, model, score_field)
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
def size
|
196
|
+
db.zcard(key)
|
197
|
+
end
|
198
|
+
|
199
|
+
def count(a = "-inf", b = "+inf")
|
200
|
+
return size if a == "-inf" && b == "+inf"
|
201
|
+
|
202
|
+
db.zcount(key, a, b)
|
203
|
+
end
|
204
|
+
|
205
|
+
def ids(a = 0, b = -1)
|
206
|
+
execute { |key| db.zrange(key, a, b) }
|
207
|
+
end
|
208
|
+
|
209
|
+
def range(a = 0, b = -1)
|
210
|
+
fetch(ids(a, b))
|
211
|
+
end
|
212
|
+
|
213
|
+
def revrange(a = 0, b = -1)
|
214
|
+
fetch(execute { |key| db.zrevrange(key, a, b) })
|
215
|
+
end
|
216
|
+
|
217
|
+
# Fetch data from Redis
|
218
|
+
def to_a
|
219
|
+
fetch(ids)
|
220
|
+
end
|
221
|
+
|
222
|
+
def include?(model)
|
223
|
+
!rank(model).nil?
|
224
|
+
end
|
225
|
+
|
226
|
+
def get(i)
|
227
|
+
range(i, i)[0] rescue nil
|
228
|
+
end
|
229
|
+
|
230
|
+
def each
|
231
|
+
to_a.each { |element| yield element }
|
232
|
+
end
|
233
|
+
|
234
|
+
def empty?
|
235
|
+
size == 0
|
236
|
+
end
|
237
|
+
|
238
|
+
def add(model)
|
239
|
+
score_value = model
|
240
|
+
|
241
|
+
lambda_function = score_field.each do |field|
|
242
|
+
break field if field.is_a? Proc
|
243
|
+
|
244
|
+
score_value = model.send(field)
|
245
|
+
|
246
|
+
break lambda{ |x| x.to_i } if field == score_field.last
|
247
|
+
end
|
248
|
+
|
249
|
+
db.zadd(key, lambda_function.call(score_value), model.id)
|
250
|
+
end
|
251
|
+
|
252
|
+
def add_list(*models)
|
253
|
+
models.each do |model|
|
254
|
+
add(model)
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
def update(model)
|
259
|
+
add (model)
|
260
|
+
end
|
261
|
+
|
262
|
+
def delete(model)
|
263
|
+
db.zrem(key, model.id)
|
264
|
+
|
265
|
+
model
|
266
|
+
end
|
267
|
+
|
268
|
+
def remrangebyrank(a, b)
|
269
|
+
db.zremrangebyrank(key, a, b)
|
270
|
+
end
|
271
|
+
|
272
|
+
def remrangebyscore(a, b)
|
273
|
+
db.zremrangebyscore(key, a, b)
|
274
|
+
end
|
275
|
+
|
276
|
+
def intersect(set, w1=1.0, w2=1.0)
|
277
|
+
new_key = generate_uuid
|
278
|
+
new_set = Ohm::ZSet.new(new_key, model.key, model, score_field)
|
279
|
+
|
280
|
+
db.zinterstore(new_set.key, [key, set.key], :weights => [w1, w2])
|
281
|
+
new_set
|
282
|
+
end
|
283
|
+
|
284
|
+
def intersect_multiple(sets, weights = [])
|
285
|
+
sets = sets.map(&:key)
|
286
|
+
sets.push(key)
|
287
|
+
weights = [1.0] * sets.length if weights = []
|
288
|
+
|
289
|
+
new_key = generate_uuid
|
290
|
+
new_set = Ohm::ZSet.new(new_key, model.key, model, score_field)
|
291
|
+
|
292
|
+
db.zinterstore(new_set.key, sets, :weights => weights)
|
293
|
+
|
294
|
+
new_set
|
295
|
+
end
|
296
|
+
|
297
|
+
def intersect_multiple!(sets, weights = [])
|
298
|
+
weights = [1.0] * sets.length if weights = []
|
299
|
+
db.zinterstore(key, sets.map(&:key), :weights => weights)
|
300
|
+
self
|
301
|
+
end
|
302
|
+
|
303
|
+
def union(set, w1=1.0, w2=1.0)
|
304
|
+
new_key = generate_uuid
|
305
|
+
new_set = Ohm::ZSet.new(new_key, model.key, model, score_field)
|
306
|
+
|
307
|
+
db.zunionstore(new_set.key, [key, set.key], :weights => [w1, w2])
|
308
|
+
|
309
|
+
new_set
|
310
|
+
end
|
311
|
+
|
312
|
+
def union_multiple(sets, weights = [])
|
313
|
+
sets = sets.map(&:key)
|
314
|
+
sets.push(key)
|
315
|
+
|
316
|
+
weights = [1.0] * sets.length if weights = []
|
317
|
+
new_key = generate_uuid
|
318
|
+
new_set = Ohm::ZSet.new(new_key, model.key, model, score_field)
|
319
|
+
|
320
|
+
db.zunionstore(new_set.key, sets, :weights => weights)
|
321
|
+
|
322
|
+
new_set
|
323
|
+
end
|
324
|
+
|
325
|
+
def rank(model)
|
326
|
+
db.zrank(key, model.id)
|
327
|
+
end
|
328
|
+
|
329
|
+
def revrank(model)
|
330
|
+
db.zrevrank(key, model.id)
|
331
|
+
end
|
332
|
+
|
333
|
+
def score(model)
|
334
|
+
db.zscore(key, model.id).to_i
|
335
|
+
end
|
336
|
+
|
337
|
+
def rangebyscore(a = "-inf", b = "+inf", limit = {})
|
338
|
+
limit[:offset] ||= 0
|
339
|
+
limit[:count] ||= -1
|
340
|
+
|
341
|
+
fetch(execute { |key| db.zrangebyscore(key, a, b, :limit => [limit[:offset], limit[:count]]) })
|
342
|
+
end
|
343
|
+
|
344
|
+
def revrangebyscore(a = "+inf", b = "-inf", limit = {})
|
345
|
+
limit[:offset] ||= 0
|
346
|
+
limit[:count] ||= -1
|
347
|
+
|
348
|
+
fetch(execute { |key| db.zrevrangebyscore(key, a, b, :limit => [limit[:offset], limit[:count]]) })
|
349
|
+
end
|
350
|
+
|
351
|
+
def starts_with(query, limit = {})
|
352
|
+
start_query = ZScores.string(query)
|
353
|
+
end_query = "(" + ZScores.string(query.succ).to_s
|
354
|
+
|
355
|
+
rangebyscore(start_query, end_query, limit)
|
356
|
+
end
|
357
|
+
|
358
|
+
def first
|
359
|
+
range(0, 1)[0] rescue nil
|
360
|
+
end
|
361
|
+
|
362
|
+
def last
|
363
|
+
revrange(0, 1)[0] rescue nil
|
364
|
+
end
|
365
|
+
|
366
|
+
def destroy!
|
367
|
+
db.del(key)
|
368
|
+
end
|
369
|
+
|
370
|
+
def clear
|
371
|
+
remrangebyrank(0, -1)
|
372
|
+
end
|
373
|
+
|
374
|
+
def generate_uuid
|
375
|
+
ZSet.generate_uuid
|
376
|
+
end
|
377
|
+
|
378
|
+
def save_set
|
379
|
+
db.hmset("ZSet:" + key, *get_hmset_attrs)
|
380
|
+
end
|
381
|
+
|
382
|
+
def duplicate
|
383
|
+
intersect(self, 1.0, 0.0)
|
384
|
+
end
|
385
|
+
|
386
|
+
def get_hmset_attrs
|
387
|
+
return_list = []
|
388
|
+
return_list << "model" << model.to_s
|
389
|
+
return_list << "score_field" << Utils.score_field_to_string(score_field)
|
390
|
+
return_list
|
391
|
+
end
|
392
|
+
|
393
|
+
def expire(seconds)
|
394
|
+
db.expire(self.key, seconds)
|
395
|
+
end
|
396
|
+
|
397
|
+
private
|
398
|
+
def db
|
399
|
+
model.db
|
400
|
+
end
|
401
|
+
|
402
|
+
def execute
|
403
|
+
yield key
|
404
|
+
end
|
405
|
+
end
|
406
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Kernel
|
2
|
+
def suppress_warnings
|
3
|
+
original_verbosity = $VERBOSE
|
4
|
+
$VERBOSE = nil
|
5
|
+
result = yield
|
6
|
+
$VERBOSE = original_verbosity
|
7
|
+
return result
|
8
|
+
end
|
9
|
+
|
10
|
+
def require_suppress(library)
|
11
|
+
suppress_warnings{
|
12
|
+
require library
|
13
|
+
}
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
data/test/helper.rb
ADDED
@@ -0,0 +1,663 @@
|
|
1
|
+
require_relative "../helper"
|
2
|
+
|
3
|
+
class Big < Ohm::Model
|
4
|
+
include Ohm::ZScores
|
5
|
+
|
6
|
+
collection :smalls, :Small
|
7
|
+
zset :zlittles, :Little, :score, ZScore::Float
|
8
|
+
zset :zsmalls, :Small, :value
|
9
|
+
zset :zlittles2, :Little, :score
|
10
|
+
zset :zlittles3, :Little, :score
|
11
|
+
zset :zbools, :Bool, :is_valid, ZScore::Boolean
|
12
|
+
set :slittles, :Little
|
13
|
+
zset :zdts, :DT, :last_login, ZScore::DateTime
|
14
|
+
zset :zbools2, :Bool, :name, ZScore::String
|
15
|
+
zset :zbools3, :Bool, :name, ZScore::StringInsensitive
|
16
|
+
zset :zbools4, :Bool, :name, ZScore::StringInsensitiveHigh
|
17
|
+
end
|
18
|
+
|
19
|
+
class Bool < Ohm::Model
|
20
|
+
attribute :name
|
21
|
+
attribute :is_valid
|
22
|
+
end
|
23
|
+
|
24
|
+
class DT < Ohm::Model
|
25
|
+
attribute :name
|
26
|
+
#attribute :date_updated
|
27
|
+
attribute :last_login
|
28
|
+
end
|
29
|
+
|
30
|
+
class Small < Ohm::Model
|
31
|
+
attribute :name
|
32
|
+
attribute :value
|
33
|
+
end
|
34
|
+
|
35
|
+
class Little < Ohm::Model
|
36
|
+
attribute :name
|
37
|
+
attribute :score
|
38
|
+
end
|
39
|
+
|
40
|
+
def add_to_zset(model, zset_name, items, item_class)
|
41
|
+
z = model.send(zset_name)
|
42
|
+
|
43
|
+
items.each do |i|
|
44
|
+
z.send(:add, item_class.create(i))
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def setup_big_1
|
49
|
+
b = Big.create
|
50
|
+
|
51
|
+
l1 = Little.create(name: 'L1', score: 1)
|
52
|
+
l2 = Little.create(name: 'L2', score: 2)
|
53
|
+
l3 = Little.create(name: 'L3', score: 3)
|
54
|
+
l4 = Little.create(name: 'L4', score: 4)
|
55
|
+
|
56
|
+
Big.zlittles.clear
|
57
|
+
Big.zlittles.add(l1)
|
58
|
+
Big.zlittles.add(l2)
|
59
|
+
Big.zlittles.add(l3)
|
60
|
+
Big.zlittles.add(l4)
|
61
|
+
|
62
|
+
b.slittles.add(l1)
|
63
|
+
b.slittles.add(l2)
|
64
|
+
b.slittles.add(Little.create(name: 'E3', score: 24))
|
65
|
+
b.slittles.add(l4)
|
66
|
+
|
67
|
+
Big.zlittles2.clear
|
68
|
+
Big.zlittles2.add(l2)
|
69
|
+
Big.zlittles2.add(l3)
|
70
|
+
Big.zlittles2.add(l4)
|
71
|
+
|
72
|
+
Big.zlittles3.clear
|
73
|
+
Big.zlittles3.add(l1)
|
74
|
+
Big.zlittles3.add(l4)
|
75
|
+
Big.zlittles3.add(Little.create(name: 'E1', score: 21))
|
76
|
+
Big.zlittles3.add(Little.create(name: 'E2', score: 22))
|
77
|
+
|
78
|
+
Big.zsmalls.clear
|
79
|
+
Big.zsmalls.add(Small.create(name: 'S1', value: 8))
|
80
|
+
Big.zsmalls.add(Small.create(name: 'S2', value: 7))
|
81
|
+
Big.zsmalls.add(Small.create(name: 'S3', value: 6))
|
82
|
+
Big.zsmalls.add(Small.create(name: 'S4', value: 5))
|
83
|
+
|
84
|
+
b
|
85
|
+
end
|
86
|
+
|
87
|
+
def setup_1
|
88
|
+
b = Big.create
|
89
|
+
|
90
|
+
l1 = Little.create(name: 'L1', score: 1)
|
91
|
+
l2 = Little.create(name: 'L2', score: 2)
|
92
|
+
l3 = Little.create(name: 'L3', score: 3)
|
93
|
+
l4 = Little.create(name: 'L4', score: 4)
|
94
|
+
|
95
|
+
b.slittles.add(l1)
|
96
|
+
b.slittles.add(l2)
|
97
|
+
b.slittles.add(Little.create(name: 'E3',score: 24))
|
98
|
+
b.slittles.add(l4)
|
99
|
+
|
100
|
+
b.zlittles.add(l1)
|
101
|
+
b.zlittles.add(l2)
|
102
|
+
b.zlittles.add(l3)
|
103
|
+
b.zlittles.add(l4)
|
104
|
+
|
105
|
+
b.zlittles2.add(l2)
|
106
|
+
b.zlittles2.add(l3)
|
107
|
+
b.zlittles2.add(l4)
|
108
|
+
|
109
|
+
b.zlittles3.add(l1)
|
110
|
+
b.zlittles3.add(l4)
|
111
|
+
b.zlittles3.add(Little.create(name: 'E1', score: 21))
|
112
|
+
b.zlittles3.add(Little.create(name: 'E2', score: 22))
|
113
|
+
|
114
|
+
b.zsmalls.add(Small.create(name: 'S1', value: 8))
|
115
|
+
b.zsmalls.add(Small.create(name: 'S2', value: 7))
|
116
|
+
b.zsmalls.add(Small.create(name: 'S3', value: 6))
|
117
|
+
b.zsmalls.add(Small.create(name: 'S4', value: 5))
|
118
|
+
|
119
|
+
|
120
|
+
b.zlittles.add(l1)
|
121
|
+
b.zlittles.add(l2)
|
122
|
+
b.zlittles.add(l3)
|
123
|
+
b.zlittles.add(l4)
|
124
|
+
|
125
|
+
b
|
126
|
+
end
|
127
|
+
|
128
|
+
def setup_2
|
129
|
+
b = Big.create
|
130
|
+
|
131
|
+
l1 = Little.create(name: 'L1', score: 1)
|
132
|
+
l2 = Little.create(name: 'L2', score: 2)
|
133
|
+
l3 = Little.create(name: 'L3', score: 3)
|
134
|
+
l4 = Little.create(name: 'L4', score: 4)
|
135
|
+
|
136
|
+
b.zlittles.add(l1)
|
137
|
+
b.zlittles.add(l2)
|
138
|
+
b.zlittles.add(l3)
|
139
|
+
b.zlittles.add(l4)
|
140
|
+
|
141
|
+
[b, l1, l2, l3, l4]
|
142
|
+
end
|
143
|
+
|
144
|
+
def setup_3
|
145
|
+
b = Big.create
|
146
|
+
|
147
|
+
b1 = Bool.create(name: 'B1', is_valid: "false")
|
148
|
+
b2 = Bool.create(name: 'B2', is_valid: "true")
|
149
|
+
b3 = Bool.create(name: 'B3', is_valid: "false")
|
150
|
+
b4 = Bool.create(name: 'B4', is_valid: "true")
|
151
|
+
|
152
|
+
b.zbools.add(b1)
|
153
|
+
b.zbools.add(b2)
|
154
|
+
b.zbools.add(b3)
|
155
|
+
b.zbools.add(b4)
|
156
|
+
|
157
|
+
b
|
158
|
+
end
|
159
|
+
|
160
|
+
def setup_4
|
161
|
+
b = Big.create
|
162
|
+
|
163
|
+
d1 = DT.create(name: 'D1', last_login: "2012-07-29 06:24:20 +0800")
|
164
|
+
d2 = DT.create(name: 'D2', last_login: "2012-07-29 05:24:20 +0800")
|
165
|
+
d3 = DT.create(name: 'D3', last_login: "2012-07-29 04:24:20 +0800")
|
166
|
+
d4 = DT.create(name: 'D4', last_login: "2012-08-29")
|
167
|
+
b.zdts.add_list(d1, d2, d3, d4)
|
168
|
+
b
|
169
|
+
end
|
170
|
+
|
171
|
+
def setup_5
|
172
|
+
b = Big.create
|
173
|
+
|
174
|
+
items = [
|
175
|
+
{name: 'Apple', is_valid: 'false'},
|
176
|
+
{name: 'Coconut', is_valid: 'true'},
|
177
|
+
{name: 'Dragonfruit', is_valid: 'false'},
|
178
|
+
{name: 'Banana', is_valid: 'true'},
|
179
|
+
{name: 'apples', is_valid: 'true'},
|
180
|
+
{name: 'coconuts', is_valid: 'true'},
|
181
|
+
{name: 'durians', is_valid: 'false'},
|
182
|
+
{name: 'bananas', is_valid: 'true'},
|
183
|
+
{name: 'apple', is_valid: 'true'},
|
184
|
+
{name: 'coconut', is_valid: 'true'},
|
185
|
+
{name: 'banana', is_valid: 'false'},
|
186
|
+
{name: 'durian', is_valid: 'true'},
|
187
|
+
]
|
188
|
+
|
189
|
+
add_to_zset(b, 'zbools2', items, Bool)
|
190
|
+
|
191
|
+
b
|
192
|
+
end
|
193
|
+
|
194
|
+
def setup_6
|
195
|
+
b = Big.create
|
196
|
+
|
197
|
+
items = [
|
198
|
+
{name: 'apple', is_valid: 'false'},
|
199
|
+
{name: 'Coconut', is_valid: 'true'},
|
200
|
+
{name: 'duria', is_valid: 'false'},
|
201
|
+
{name: 'Banana', is_valid: 'true'},
|
202
|
+
{name: 'Apples', is_valid: 'true'},
|
203
|
+
{name: 'coconuts', is_valid: 'true'},
|
204
|
+
{name: 'durians', is_valid: 'false'},
|
205
|
+
{name: 'bananas', is_valid: 'true'},
|
206
|
+
{name: 'Apple', is_valid: 'true'},
|
207
|
+
{name: 'coconut', is_valid: 'true'},
|
208
|
+
{name: 'banana', is_valid: 'false'},
|
209
|
+
{name: 'Durian', is_valid: 'true'},
|
210
|
+
]
|
211
|
+
|
212
|
+
add_to_zset(b, 'zbools3', items, Bool)
|
213
|
+
|
214
|
+
b
|
215
|
+
end
|
216
|
+
|
217
|
+
def setup_7
|
218
|
+
b = Big.create
|
219
|
+
|
220
|
+
items = [
|
221
|
+
{name: 'apple', is_valid: 'false'},
|
222
|
+
{name: 'Coconut', is_valid: 'true'},
|
223
|
+
{name: 'duria', is_valid: 'false'},
|
224
|
+
{name: 'Banana', is_valid: 'true'},
|
225
|
+
{name: 'Apples', is_valid: 'true'},
|
226
|
+
{name: 'coconuts', is_valid: 'true'},
|
227
|
+
{name: 'durians', is_valid: 'false'},
|
228
|
+
{name: 'bananas', is_valid: 'true'},
|
229
|
+
{name: 'Apple', is_valid: 'true'},
|
230
|
+
{name: 'coconut', is_valid: 'true'},
|
231
|
+
{name: 'banana', is_valid: 'false'},
|
232
|
+
{name: 'Durian', is_valid: 'true'},
|
233
|
+
]
|
234
|
+
|
235
|
+
add_to_zset(b, 'zbools4', items, Bool)
|
236
|
+
|
237
|
+
b
|
238
|
+
end
|
239
|
+
|
240
|
+
# Tests for the class methods
|
241
|
+
# TODO: figure out how to DRY up the class and instance method tests. Probably move the method body to a common method and test that common method instead
|
242
|
+
describe Ohm do
|
243
|
+
it "can add objects and get the size for the class" do
|
244
|
+
setup_big_1
|
245
|
+
|
246
|
+
assert_equal 4, Big.zlittles.size
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
# Tests for the instance methods
|
251
|
+
describe Ohm do
|
252
|
+
it "can add objects and get the size" do
|
253
|
+
b = setup_1
|
254
|
+
|
255
|
+
assert_equal 4, b.zlittles.size
|
256
|
+
end
|
257
|
+
|
258
|
+
it "can add objects with scores and automatically sorts them by score" do
|
259
|
+
b = setup_1
|
260
|
+
|
261
|
+
assert_equal ["L1", "L2", "L3", "L4"], b.zlittles.to_a.map(&:name)
|
262
|
+
end
|
263
|
+
|
264
|
+
it "can return a range of sorted elements" do
|
265
|
+
b = setup_1
|
266
|
+
|
267
|
+
assert_equal ["L2", "L3"], b.zlittles.range(1, 2).map(&:name)
|
268
|
+
assert_equal ["L1", "L2", "L3"], b.zlittles.range(0, 2).map(&:name)
|
269
|
+
end
|
270
|
+
|
271
|
+
it "can return a range of sorted elements in reverse" do
|
272
|
+
b = setup_1
|
273
|
+
|
274
|
+
assert_equal ["L3", "L2"], b.zlittles.revrange(1, 2).map(&:name)
|
275
|
+
assert_equal ["L4", "L3", "L2"], b.zlittles.revrange(0, 2).map(&:name)
|
276
|
+
end
|
277
|
+
|
278
|
+
it "can iterate over the elements of a specified range of the set" do
|
279
|
+
b = setup_1
|
280
|
+
|
281
|
+
expected_items = ["L2", "L3", "L4"]
|
282
|
+
|
283
|
+
b.zlittles.range(1, 3).each_with_index do |e, i|
|
284
|
+
assert_equal expected_items[i], e.name
|
285
|
+
end
|
286
|
+
end
|
287
|
+
|
288
|
+
it "can iterate over the elements of a specified range of the reverse sorted set" do
|
289
|
+
b = setup_1
|
290
|
+
expected_items = ["L3", "L2", "L1"]
|
291
|
+
|
292
|
+
b.zlittles.revrange(1, 3).each_with_index do |e, i|
|
293
|
+
assert_equal expected_items[i], e.name
|
294
|
+
end
|
295
|
+
end
|
296
|
+
|
297
|
+
it "can get an element with a specified index" do
|
298
|
+
b = setup_1
|
299
|
+
|
300
|
+
assert_equal "L3", b.zlittles.get(2).name
|
301
|
+
assert_nil b.zlittles.get(5)
|
302
|
+
end
|
303
|
+
|
304
|
+
it "can delete an element" do
|
305
|
+
b = setup_1
|
306
|
+
|
307
|
+
x = b.zlittles.get(2)
|
308
|
+
deleted_element = b.zlittles.delete(x)
|
309
|
+
|
310
|
+
assert_equal x, deleted_element
|
311
|
+
assert_equal 3, b.zlittles.size
|
312
|
+
end
|
313
|
+
|
314
|
+
it "knows if it includes a specified element" do
|
315
|
+
b, l1, l2, l3, l4 = setup_2
|
316
|
+
|
317
|
+
assert b.zlittles.include?(l1)
|
318
|
+
|
319
|
+
b.zlittles.delete(l2)
|
320
|
+
|
321
|
+
refute b.zlittles.include?(l2)
|
322
|
+
assert b.zlittles.include?(l3)
|
323
|
+
assert b.zlittles.include?(l4)
|
324
|
+
assert b.zlittles.include?(l1)
|
325
|
+
|
326
|
+
b.zlittles.add(l2)
|
327
|
+
|
328
|
+
assert b.zlittles.include?(l2)
|
329
|
+
end
|
330
|
+
|
331
|
+
it "can delete a range of elements by rank" do
|
332
|
+
b = setup_1
|
333
|
+
assert_equal ["L1", "L2", "L3", "L4"], b.zlittles.to_a.map(&:name)
|
334
|
+
|
335
|
+
b.zlittles.remrangebyrank(0, 1)
|
336
|
+
assert_equal ["L3", "L4"], b.zlittles.to_a.map(&:name)
|
337
|
+
|
338
|
+
b.zlittles.remrangebyrank(0, 0)
|
339
|
+
assert_equal ["L4"], b.zlittles.to_a.map(&:name)
|
340
|
+
end
|
341
|
+
|
342
|
+
it "can delete a range of elements by score" do
|
343
|
+
b = setup_1
|
344
|
+
assert_equal ["L1", "L2", "L3", "L4"], b.zlittles.to_a.map(&:name)
|
345
|
+
|
346
|
+
b.zlittles.remrangebyscore(2, 3)
|
347
|
+
assert_equal ["L1", "L4"], b.zlittles.to_a.map(&:name)
|
348
|
+
end
|
349
|
+
|
350
|
+
it "can allow intersection of 2 zsets" do
|
351
|
+
b = setup_1
|
352
|
+
|
353
|
+
zlittles = b.zlittles
|
354
|
+
zlittles2 = b.zlittles2
|
355
|
+
zintersection = b.zlittles.intersect(zlittles2)
|
356
|
+
zlittles3 = b.zlittles3
|
357
|
+
zintersection2 = b.zlittles.intersect(zlittles3)
|
358
|
+
zintersection3 = zintersection.intersect(zintersection2)
|
359
|
+
zintersection4 = zintersection.intersect(zintersection)
|
360
|
+
|
361
|
+
assert_equal ["L1", "L4"], zintersection2.to_a.map(&:name)
|
362
|
+
assert_equal ["L2", "L3", "L4"], zintersection.to_a.map(&:name)
|
363
|
+
assert_equal ["L4"], zintersection3.to_a.map(&:name)
|
364
|
+
assert_equal zintersection.to_a.map(&:name), zintersection4.to_a.map(&:name)
|
365
|
+
end
|
366
|
+
|
367
|
+
it "can allow intersection of a zset and a set" do
|
368
|
+
b = setup_1
|
369
|
+
|
370
|
+
zlittles = b.zlittles
|
371
|
+
zlittles2 = b.zlittles2
|
372
|
+
slittles = b.slittles
|
373
|
+
|
374
|
+
zintersection = b.zlittles.intersect(slittles)
|
375
|
+
zintersection2 = b.zlittles2.intersect(slittles)
|
376
|
+
|
377
|
+
assert_equal ["L1","L2","L4"], zintersection.to_a.map(&:name)
|
378
|
+
assert_equal ["L2","L4"], zintersection2.to_a.map(&:name)
|
379
|
+
end
|
380
|
+
|
381
|
+
it "can allow intersection of multiple zsets and sets" do
|
382
|
+
b = setup_1
|
383
|
+
|
384
|
+
zlittles = b.zlittles
|
385
|
+
zlittles2 = b.zlittles2
|
386
|
+
zlittles3 = b.zlittles3
|
387
|
+
slittles = b.slittles
|
388
|
+
|
389
|
+
zintersection = b.zlittles.intersect_multiple([zlittles2, zlittles3])
|
390
|
+
|
391
|
+
assert_equal ["L4"], zintersection.to_a.map(&:name)
|
392
|
+
end
|
393
|
+
|
394
|
+
it "can allow intersection of multiple zsets and sets to a new named set" do
|
395
|
+
b = setup_1
|
396
|
+
|
397
|
+
zlittles = b.zlittles
|
398
|
+
zlittles2 = b.zlittles2
|
399
|
+
zlittles3 = b.zlittles3
|
400
|
+
slittles = b.slittles
|
401
|
+
zintersection = Ohm::ZSet.intersect_multiple("zintersection", [zlittles, zlittles2, zlittles3])
|
402
|
+
|
403
|
+
assert_equal ["L4"], zintersection.to_a.map(&:name)
|
404
|
+
assert_equal "zintersection", zintersection.key
|
405
|
+
end
|
406
|
+
|
407
|
+
it "can allow union of 2 zsets" do
|
408
|
+
b = setup_1
|
409
|
+
|
410
|
+
zlittles = b.zlittles
|
411
|
+
zlittles2 = b.zlittles2
|
412
|
+
zunion = b.zlittles.union(zlittles2)
|
413
|
+
|
414
|
+
assert_equal ["L1","L2","L3","L4"], zunion.to_a.map(&:name)
|
415
|
+
end
|
416
|
+
|
417
|
+
it "can allow union of a zset and a set" do
|
418
|
+
b = setup_1
|
419
|
+
|
420
|
+
zlittles = b.zlittles
|
421
|
+
zlittles2 = b.zlittles2
|
422
|
+
slittles = b.slittles
|
423
|
+
|
424
|
+
zunion = b.zlittles.union(slittles)
|
425
|
+
zunion2 = b.zlittles2.union(slittles)
|
426
|
+
|
427
|
+
assert_equal ["E3","L1","L2","L3","L4"], zunion.to_a.map(&:name)
|
428
|
+
end
|
429
|
+
|
430
|
+
it "can allow union of multiple zsets and sets" do
|
431
|
+
b = setup_1
|
432
|
+
|
433
|
+
zlittles = b.zlittles
|
434
|
+
zlittles2 = b.zlittles2
|
435
|
+
zlittles3 = b.zlittles3
|
436
|
+
slittles = b.slittles
|
437
|
+
|
438
|
+
zunion = b.zlittles.union_multiple([slittles, zlittles2, zlittles3])
|
439
|
+
|
440
|
+
assert_equal ["E3","L1","L2","L3","L4","E1","E2"], zunion.to_a.map(&:name)
|
441
|
+
end
|
442
|
+
|
443
|
+
it "can allow union of multiple zsets and sets to a newly named set" do
|
444
|
+
b = setup_1
|
445
|
+
|
446
|
+
zlittles = b.zlittles
|
447
|
+
zlittles2 = b.zlittles2
|
448
|
+
zlittles3 = b.zlittles3
|
449
|
+
slittles = b.slittles
|
450
|
+
|
451
|
+
zunion = Ohm::ZSet.union_multiple("zunion", [zlittles, slittles, zlittles2, zlittles3])
|
452
|
+
|
453
|
+
assert_equal ["E3","L1","L2","L3","L4","E1","E2"], zunion.to_a.map(&:name)
|
454
|
+
assert_equal "zunion", zunion.key
|
455
|
+
end
|
456
|
+
|
457
|
+
it "can get the rank of an element" do
|
458
|
+
b, l1, l2, l3, l4 = setup_2
|
459
|
+
|
460
|
+
assert_equal 0, b.zlittles.rank(l1)
|
461
|
+
assert_equal 1, b.zlittles.rank(l2)
|
462
|
+
assert_equal 2, b.zlittles.rank(l3)
|
463
|
+
assert_equal 3, b.zlittles.rank(l4)
|
464
|
+
end
|
465
|
+
|
466
|
+
it "can get the rank of specified element from the reverse sorted list" do
|
467
|
+
b, l1, l2, l3, l4 = setup_2
|
468
|
+
|
469
|
+
assert_equal 3, b.zlittles.revrank(l1)
|
470
|
+
assert_equal 2, b.zlittles.revrank(l2)
|
471
|
+
assert_equal 1, b.zlittles.revrank(l3)
|
472
|
+
assert_equal 0, b.zlittles.revrank(l4)
|
473
|
+
end
|
474
|
+
|
475
|
+
it "can get the score of an element" do
|
476
|
+
b, l1, l2, l3, l4 = setup_2
|
477
|
+
|
478
|
+
assert_equal 1, b.zlittles.score(l1)
|
479
|
+
assert_equal 2, b.zlittles.score(l2)
|
480
|
+
assert_equal 3, b.zlittles.score(l3)
|
481
|
+
assert_equal 4, b.zlittles.score(l4)
|
482
|
+
end
|
483
|
+
|
484
|
+
it "can get a range of elements by score" do
|
485
|
+
b, = setup_2
|
486
|
+
|
487
|
+
assert_equal ["L1", "L2", "L3", "L4"], b.zlittles.rangebyscore(0, 4).to_a.map(&:name)
|
488
|
+
assert_equal ["L1", "L2"], b.zlittles.rangebyscore(1, 2).to_a.map(&:name)
|
489
|
+
assert_equal ["L3", "L4"], b.zlittles.rangebyscore(3, 4).to_a.map(&:name)
|
490
|
+
assert_equal ["L1", "L2", "L3"], b.zlittles.rangebyscore(1, 3).to_a.map(&:name)
|
491
|
+
assert_equal ["L1", "L2"], b.zlittles.rangebyscore(0, 4, offset: 0, count: 2).to_a.map(&:name)
|
492
|
+
assert_equal ["L3", "L4"], b.zlittles.rangebyscore(0, 4, offset: 2, count: 2).to_a.map(&:name)
|
493
|
+
assert_equal ["L2", "L3", "L4"], b.zlittles.rangebyscore(0, 4, offset: 1, count: 3).to_a.map(&:name)
|
494
|
+
assert_equal ["L3"], b.zlittles.rangebyscore(1, 3, offset: 2, count: 4).to_a.map(&:name)
|
495
|
+
assert_equal ["L1", "L2", "L3", "L4"], b.zlittles.rangebyscore(0, "+inf").to_a.map(&:name)
|
496
|
+
assert_equal ["L4"], b.zlittles.rangebyscore(2, "+inf", offset: 2).to_a.map(&:name)
|
497
|
+
assert_equal ["L1", "L2"], b.zlittles.rangebyscore("-inf", 2).to_a.map(&:name)
|
498
|
+
assert_equal ["L4"], b.zlittles.rangebyscore(2, "+inf", offset: 2, count: 2).to_a.map(&:name)
|
499
|
+
end
|
500
|
+
|
501
|
+
it "can get a range of elements by score in reverse" do
|
502
|
+
b, = setup_2
|
503
|
+
|
504
|
+
assert_equal ["L4", "L3", "L2", "L1"], b.zlittles.revrangebyscore(4, 0).to_a.map(&:name)
|
505
|
+
assert_equal ["L2", "L1"], b.zlittles.revrangebyscore(2, 1).to_a.map(&:name)
|
506
|
+
assert_equal ["L4", "L3"], b.zlittles.revrangebyscore(4, 3).to_a.map(&:name)
|
507
|
+
assert_equal ["L3", "L2", "L1"], b.zlittles.revrangebyscore(3, 1).to_a.map(&:name)
|
508
|
+
assert_equal ["L4", "L3"], b.zlittles.revrangebyscore(4, 0, offset: 0, count: 2).to_a.map(&:name)
|
509
|
+
assert_equal ["L2", "L1"], b.zlittles.revrangebyscore(4, 0, offset: 2, count: 2).to_a.map(&:name)
|
510
|
+
assert_equal ["L3", "L2", "L1"], b.zlittles.revrangebyscore(4, 0, offset: 1, count: 3).to_a.map(&:name)
|
511
|
+
assert_equal ["L1"], b.zlittles.revrangebyscore(3, 1, offset: 2, count: 4).to_a.map(&:name)
|
512
|
+
assert_equal ["L4", "L3", "L2", "L1"], b.zlittles.revrangebyscore("+inf", 0).to_a.map(&:name)
|
513
|
+
assert_equal ["L2"], b.zlittles.revrangebyscore("+inf", 2, offset: 2).to_a.map(&:name)
|
514
|
+
assert_equal ["L2", "L1"], b.zlittles.revrangebyscore(2, "-inf").to_a.map(&:name)
|
515
|
+
assert_equal ["L2"], b.zlittles.revrangebyscore("+inf", 2, offset: 2, count: 2).to_a.map(&:name)
|
516
|
+
end
|
517
|
+
|
518
|
+
it "can get the number of elements between 2 given scores" do
|
519
|
+
b, = setup_2
|
520
|
+
|
521
|
+
assert_equal 2, b.zlittles.count(1, 2)
|
522
|
+
assert_equal 2, b.zlittles.count(3, 4)
|
523
|
+
assert_equal 4, b.zlittles.count(1, 4)
|
524
|
+
assert_equal 3, b.zlittles.count(2, "+inf")
|
525
|
+
assert_equal 4, b.zlittles.count
|
526
|
+
assert_equal 4, b.zlittles.count("-inf", "+inf")
|
527
|
+
assert_equal 2, b.zlittles.count("-inf", 2)
|
528
|
+
end
|
529
|
+
|
530
|
+
it "can update the sorted set properly upon updating the score of an element" do
|
531
|
+
b, l1, l2, l3, l4 = setup_2
|
532
|
+
assert_equal ["L1", "L2", "L3", "L4"], b.zlittles.to_a.map(&:name)
|
533
|
+
|
534
|
+
l1.score = 5
|
535
|
+
l1.save
|
536
|
+
b.zlittles.update(l1)
|
537
|
+
assert_equal ["L2", "L3", "L4", "L1"], b.zlittles.to_a.map(&:name)
|
538
|
+
|
539
|
+
l3.score = -3
|
540
|
+
l3.save
|
541
|
+
b.zlittles.update(l3)
|
542
|
+
assert_equal ["L3", "L2", "L4", "L1"], b.zlittles.to_a.map(&:name)
|
543
|
+
|
544
|
+
end
|
545
|
+
|
546
|
+
it "can sort elements by date" do
|
547
|
+
b = setup_4
|
548
|
+
|
549
|
+
assert_equal ["D3", "D2", "D1", "D4"], b.zdts.to_a.map(&:name)
|
550
|
+
end
|
551
|
+
|
552
|
+
it "can sort elements by name" do
|
553
|
+
b = setup_5
|
554
|
+
|
555
|
+
assert_equal ["Apple", "Banana", "Coconut", "Dragonfruit",
|
556
|
+
"apple", "apples", "banana", "bananas",
|
557
|
+
"coconut", "coconuts", "durian", "durians"],
|
558
|
+
b.zbools2.to_a.map(&:name)
|
559
|
+
end
|
560
|
+
|
561
|
+
it "can sort elements by name (insensitive)" do
|
562
|
+
b = setup_6
|
563
|
+
|
564
|
+
assert_equal ["apple", "Apple", "Apples",
|
565
|
+
"Banana", "banana", "bananas",
|
566
|
+
"Coconut", "coconut", "coconuts",
|
567
|
+
"duria", "Durian", "durians"],
|
568
|
+
b.zbools3.to_a.map(&:name)
|
569
|
+
end
|
570
|
+
|
571
|
+
it "can sort elements by name (insensitive high)" do
|
572
|
+
b = setup_7
|
573
|
+
|
574
|
+
assert_equal ["Apple", "Apples", "apple",
|
575
|
+
"Banana", "banana", "bananas",
|
576
|
+
"Coconut", "coconut", "coconuts",
|
577
|
+
"Durian", "duria", "durians"],
|
578
|
+
b.zbools4.to_a.map(&:name)
|
579
|
+
end
|
580
|
+
|
581
|
+
it "can find elements that starts with specified string" do
|
582
|
+
b = setup_5
|
583
|
+
|
584
|
+
assert_equal ["durian", "durians"], b.zbools2.starts_with("du").map(&:name)
|
585
|
+
assert_equal ["durians"], b.zbools2.starts_with("d",offset:1,limit:1).map(&:name)
|
586
|
+
assert_equal ["bananas"], b.zbools2.starts_with("bananas").map(&:name)
|
587
|
+
end
|
588
|
+
|
589
|
+
it "can return the first and last elements" do
|
590
|
+
b = setup_1
|
591
|
+
|
592
|
+
assert_equal "L1", b.zlittles.first.name
|
593
|
+
assert_equal "L4", b.zlittles.last.name
|
594
|
+
end
|
595
|
+
|
596
|
+
it "can be deleted / destroyed" do
|
597
|
+
b = setup_1
|
598
|
+
|
599
|
+
b.zlittles.destroy!
|
600
|
+
|
601
|
+
assert_equal [], b.zlittles.to_a.map(&:name)
|
602
|
+
end
|
603
|
+
|
604
|
+
it "can delete all elements at once" do
|
605
|
+
b = setup_1
|
606
|
+
|
607
|
+
b.zlittles.clear
|
608
|
+
|
609
|
+
assert_equal [], b.zlittles.to_a.map(&:name)
|
610
|
+
end
|
611
|
+
|
612
|
+
it "can clone itself to another instance" do
|
613
|
+
b, l1, l2, l3, l4 = setup_2
|
614
|
+
|
615
|
+
clone = b.zlittles.duplicate
|
616
|
+
|
617
|
+
assert_equal ["L1", "L2", "L3", "L4"], clone.to_a.map(&:name)
|
618
|
+
assert_equal 1, clone.score(l1)
|
619
|
+
assert_equal 2, clone.score(l2)
|
620
|
+
assert_equal 3, clone.score(l3)
|
621
|
+
assert_equal 4, clone.score(l4)
|
622
|
+
end
|
623
|
+
|
624
|
+
it "can convert a procedure block to string and back" do
|
625
|
+
x = lambda { |x| x + 1 }
|
626
|
+
y = Ohm::Utils.proc_to_string x
|
627
|
+
|
628
|
+
assert_equal "proc { |x| (x + 1) }", y
|
629
|
+
|
630
|
+
z = Ohm::Utils.string_to_proc y
|
631
|
+
|
632
|
+
assert_equal 3, z.call(2)
|
633
|
+
end
|
634
|
+
|
635
|
+
it "can convert a score_field list to string and back" do
|
636
|
+
score_field = [:n1, :n2, :n3, lambda { |x| x + 1 }]
|
637
|
+
score_field_string = Ohm::Utils.score_field_to_string score_field
|
638
|
+
|
639
|
+
assert_equal "n1:n2:n3:proc { |x| (x + 1) }", score_field_string
|
640
|
+
|
641
|
+
score_field_list = Ohm::Utils.string_to_score_field score_field_string
|
642
|
+
|
643
|
+
assert_equal :n1, score_field_list[0]
|
644
|
+
assert_equal :n2, score_field_list[1]
|
645
|
+
assert_equal :n3, score_field_list[2]
|
646
|
+
|
647
|
+
assert_equal 3, score_field_list[3].call(2)
|
648
|
+
end
|
649
|
+
|
650
|
+
it "can save and load sets by name" do
|
651
|
+
b = setup_1
|
652
|
+
|
653
|
+
sorted_set = b.zlittles
|
654
|
+
sorted_set.save_set
|
655
|
+
sorted_set_2 = Ohm::ZSet.load_set(sorted_set.key)
|
656
|
+
|
657
|
+
assert_equal sorted_set_2.to_a.map(&:name), sorted_set.to_a.map(&:name)
|
658
|
+
|
659
|
+
sorted_set_2.add(Little.create(name:'X1',score:29))
|
660
|
+
|
661
|
+
assert_equal sorted_set_2.to_a.map(&:name), sorted_set.to_a.map(&:name)
|
662
|
+
end
|
663
|
+
end
|
metadata
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ohm-zset
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: '0.2'
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Josh
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-10-02 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: ohm
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
description: Adds ZSet support to Ohm
|
31
|
+
email:
|
32
|
+
- akosijoshualat@gmail.com
|
33
|
+
executables: []
|
34
|
+
extensions: []
|
35
|
+
extra_rdoc_files: []
|
36
|
+
files:
|
37
|
+
- .gems
|
38
|
+
- .gitignore
|
39
|
+
- .rvmrc
|
40
|
+
- CHANGELOG
|
41
|
+
- LICENSE
|
42
|
+
- README.md
|
43
|
+
- Rakefile
|
44
|
+
- lib/.DS_Store
|
45
|
+
- lib/ohm-zset.rb
|
46
|
+
- lib/suppress-warnings.rb
|
47
|
+
- test/.DS_Store
|
48
|
+
- test/helper.rb
|
49
|
+
- test/unit/.DS_Store
|
50
|
+
- test/unit/ohm-zset.rb
|
51
|
+
homepage: ''
|
52
|
+
licenses: []
|
53
|
+
post_install_message:
|
54
|
+
rdoc_options: []
|
55
|
+
require_paths:
|
56
|
+
- lib
|
57
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
58
|
+
none: false
|
59
|
+
requirements:
|
60
|
+
- - ! '>='
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '0'
|
63
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
64
|
+
none: false
|
65
|
+
requirements:
|
66
|
+
- - ! '>='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
requirements: []
|
70
|
+
rubyforge_project:
|
71
|
+
rubygems_version: 1.8.24
|
72
|
+
signing_key:
|
73
|
+
specification_version: 3
|
74
|
+
summary: Adds ZSet support to Ohm
|
75
|
+
test_files:
|
76
|
+
- test/.DS_Store
|
77
|
+
- test/helper.rb
|
78
|
+
- test/unit/.DS_Store
|
79
|
+
- test/unit/ohm-zset.rb
|