redis-objects 0.2.2 → 0.3.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.
- data/CHANGELOG.rdoc +37 -0
- data/README.rdoc +192 -105
- data/lib/redis/counter.rb +6 -5
- data/lib/redis/helpers/core_commands.rb +9 -1
- data/lib/redis/helpers/serialize.rb +2 -0
- data/lib/redis/list.rb +4 -4
- data/lib/redis/lock.rb +42 -5
- data/lib/redis/objects/counters.rb +4 -3
- data/lib/redis/objects/locks.rb +1 -4
- data/lib/redis/objects/sorted_sets.rb +45 -0
- data/lib/redis/objects.rb +8 -6
- data/lib/redis/set.rb +3 -3
- data/lib/redis/sorted_set.rb +275 -0
- data/lib/redis/value.rb +3 -3
- data/spec/redis_objects_instance_spec.rb +300 -36
- data/spec/redis_objects_model_spec.rb +18 -4
- metadata +26 -13
- data/ChangeLog +0 -15
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# This is the class loader, for use as "include Redis::Objects::Sets"
|
|
2
|
+
# For the object itself, see "Redis::Set"
|
|
3
|
+
require 'redis/sorted_set'
|
|
4
|
+
class Redis
|
|
5
|
+
module Objects
|
|
6
|
+
module SortedSets
|
|
7
|
+
def self.included(klass)
|
|
8
|
+
klass.send :include, InstanceMethods
|
|
9
|
+
klass.extend ClassMethods
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# Class methods that appear in your class when you include Redis::Objects.
|
|
13
|
+
module ClassMethods
|
|
14
|
+
# Define a new list. It will function like a regular instance
|
|
15
|
+
# method, so it can be used alongside ActiveRecord, DataMapper, etc.
|
|
16
|
+
def sorted_set(name, options={})
|
|
17
|
+
@redis_objects[name] = options.merge(:type => :sorted_set)
|
|
18
|
+
if options[:global]
|
|
19
|
+
instance_eval <<-EndMethods
|
|
20
|
+
def #{name}
|
|
21
|
+
@#{name} ||= Redis::SortedSet.new(field_key(:#{name}, ''), redis, @redis_objects[:#{name}])
|
|
22
|
+
end
|
|
23
|
+
EndMethods
|
|
24
|
+
class_eval <<-EndMethods
|
|
25
|
+
def #{name}
|
|
26
|
+
self.class.#{name}
|
|
27
|
+
end
|
|
28
|
+
EndMethods
|
|
29
|
+
else
|
|
30
|
+
class_eval <<-EndMethods
|
|
31
|
+
def #{name}
|
|
32
|
+
@#{name} ||= Redis::SortedSet.new(field_key(:#{name}), redis, self.class.redis_objects[:#{name}])
|
|
33
|
+
end
|
|
34
|
+
EndMethods
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Instance methods that appear in your class when you include Redis::Objects.
|
|
41
|
+
module InstanceMethods
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
data/lib/redis/objects.rb
CHANGED
|
@@ -6,7 +6,7 @@ class Redis
|
|
|
6
6
|
# Redis::Objects enables high-performance atomic operations in your app
|
|
7
7
|
# by leveraging the atomic features of the Redis server. To use Redis::Objects,
|
|
8
8
|
# first include it in any class you want. (This example uses an ActiveRecord
|
|
9
|
-
# subclass, but that is *not* required.) Then, use +counter
|
|
9
|
+
# subclass, but that is *not* required.) Then, use +counter+, +lock+, +set+, etc
|
|
10
10
|
# to define your primitives:
|
|
11
11
|
#
|
|
12
12
|
# class Game < ActiveRecord::Base
|
|
@@ -14,8 +14,8 @@ class Redis
|
|
|
14
14
|
#
|
|
15
15
|
# counter :joined_players
|
|
16
16
|
# counter :active_players
|
|
17
|
-
# set :player_ids
|
|
18
17
|
# lock :archive_game
|
|
18
|
+
# set :player_ids
|
|
19
19
|
# end
|
|
20
20
|
#
|
|
21
21
|
# The, you can use these counters both for bookeeping and as atomic actions:
|
|
@@ -39,10 +39,11 @@ class Redis
|
|
|
39
39
|
dir = File.expand_path(__FILE__.sub(/\.rb$/,''))
|
|
40
40
|
|
|
41
41
|
autoload :Counters, File.join(dir, 'counters')
|
|
42
|
-
autoload :Values, File.join(dir, 'values')
|
|
43
42
|
autoload :Lists, File.join(dir, 'lists')
|
|
44
|
-
autoload :Sets, File.join(dir, 'sets')
|
|
45
43
|
autoload :Locks, File.join(dir, 'locks')
|
|
44
|
+
autoload :Sets, File.join(dir, 'sets')
|
|
45
|
+
autoload :SortedSets, File.join(dir, 'sorted_sets')
|
|
46
|
+
autoload :Values, File.join(dir, 'values')
|
|
46
47
|
|
|
47
48
|
class NotConnected < StandardError; end
|
|
48
49
|
|
|
@@ -61,10 +62,11 @@ class Redis
|
|
|
61
62
|
|
|
62
63
|
# Pull in each object type
|
|
63
64
|
klass.send :include, Redis::Objects::Counters
|
|
64
|
-
klass.send :include, Redis::Objects::Values
|
|
65
65
|
klass.send :include, Redis::Objects::Lists
|
|
66
|
-
klass.send :include, Redis::Objects::Sets
|
|
67
66
|
klass.send :include, Redis::Objects::Locks
|
|
67
|
+
klass.send :include, Redis::Objects::Sets
|
|
68
|
+
klass.send :include, Redis::Objects::SortedSets
|
|
69
|
+
klass.send :include, Redis::Objects::Values
|
|
68
70
|
end
|
|
69
71
|
end
|
|
70
72
|
|
data/lib/redis/set.rb
CHANGED
|
@@ -13,10 +13,10 @@ class Redis
|
|
|
13
13
|
attr_reader :key, :options, :redis
|
|
14
14
|
|
|
15
15
|
# Create a new Set.
|
|
16
|
-
def initialize(key,
|
|
16
|
+
def initialize(key, *args)
|
|
17
17
|
@key = key
|
|
18
|
-
@
|
|
19
|
-
@
|
|
18
|
+
@options = args.last.is_a?(Hash) ? args.pop : {}
|
|
19
|
+
@redis = args.first || $redis
|
|
20
20
|
end
|
|
21
21
|
|
|
22
22
|
# Works like add. Can chain together: list << 'a' << 'b'
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
class Redis
|
|
2
|
+
#
|
|
3
|
+
# Class representing a sorted set.
|
|
4
|
+
#
|
|
5
|
+
class SortedSet
|
|
6
|
+
# require 'enumerator'
|
|
7
|
+
# include Enumerable
|
|
8
|
+
require 'redis/helpers/core_commands'
|
|
9
|
+
include Redis::Helpers::CoreCommands
|
|
10
|
+
require 'redis/helpers/serialize'
|
|
11
|
+
include Redis::Helpers::Serialize
|
|
12
|
+
|
|
13
|
+
attr_reader :key, :options, :redis
|
|
14
|
+
|
|
15
|
+
# Create a new SortedSet.
|
|
16
|
+
def initialize(key, *args)
|
|
17
|
+
@key = key
|
|
18
|
+
@options = args.last.is_a?(Hash) ? args.pop : {}
|
|
19
|
+
@redis = args.first || $redis
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# How to add values using a sorted set. The key is the member, eg,
|
|
23
|
+
# "Peter", and the value is the score, eg, 163. So:
|
|
24
|
+
# num_posts['Peter'] = 163
|
|
25
|
+
def []=(member, score)
|
|
26
|
+
add(member, score)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Add a member and its corresponding value to Redis. Note that the
|
|
30
|
+
# arguments to this are flipped; the member comes first rather than
|
|
31
|
+
# the score, since the member is the unique item (not the score).
|
|
32
|
+
def add(member, score)
|
|
33
|
+
redis.zadd(key, score, to_redis(member))
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Same functionality as Ruby arrays. If a single number is given, return
|
|
37
|
+
# just the element at that index using Redis: ZRANGE. Otherwise, return
|
|
38
|
+
# a range of values using Redis: ZRANGE.
|
|
39
|
+
def [](index, length=nil)
|
|
40
|
+
if index.is_a? Range
|
|
41
|
+
range(index.first, index.last)
|
|
42
|
+
elsif length
|
|
43
|
+
range(index, length)
|
|
44
|
+
else
|
|
45
|
+
score(index)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Return the score of the specified element of the sorted set at key. If the
|
|
50
|
+
# specified element does not exist in the sorted set, or the key does not exist
|
|
51
|
+
# at all, nil is returned. Redis: ZSCORE.
|
|
52
|
+
def score(member)
|
|
53
|
+
redis.zscore(key, to_redis(member)).to_i
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Return the rank of the member in the sorted set, with scores ordered from
|
|
57
|
+
# low to high. +revrank+ returns the rank with scores ordered from high to low.
|
|
58
|
+
# When the given member does not exist in the sorted set, nil is returned.
|
|
59
|
+
# The returned rank (or index) of the member is 0-based for both commands
|
|
60
|
+
def rank(member)
|
|
61
|
+
redis.zrank(key, to_redis(member)).to_i
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def revrank(member)
|
|
65
|
+
redis.zrevrank(key, to_redis(member)).to_i
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Return all members of the sorted set with their scores. Extremely CPU-intensive.
|
|
69
|
+
# Better to use a range instead.
|
|
70
|
+
def members(options={})
|
|
71
|
+
range(0, -1, options)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Return a range of values from +start_index+ to +end_index+. Can also use
|
|
75
|
+
# the familiar list[start,end] Ruby syntax. Redis: ZRANGE
|
|
76
|
+
def range(start_index, end_index, options={})
|
|
77
|
+
if options[:withscores]
|
|
78
|
+
val = from_redis redis.zrange(key, start_index, end_index, 'withscores')
|
|
79
|
+
ret = []
|
|
80
|
+
while k = val.shift and v = val.shift
|
|
81
|
+
ret << [k, v.to_i]
|
|
82
|
+
end
|
|
83
|
+
ret
|
|
84
|
+
else
|
|
85
|
+
from_redis redis.zrange(key, start_index, end_index)
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Return a range of values from +start_index+ to +end_index+ in reverse order. Redis: ZREVRANGE
|
|
90
|
+
def revrange(start_index, end_index, options={})
|
|
91
|
+
if options[:withscores]
|
|
92
|
+
val = from_redis redis.zrevrange(key, start_index, end_index, 'withscores')
|
|
93
|
+
ret = []
|
|
94
|
+
while k = val.shift and v = val.shift
|
|
95
|
+
ret << [k, v.to_i]
|
|
96
|
+
end
|
|
97
|
+
ret
|
|
98
|
+
else
|
|
99
|
+
from_redis redis.zrevrange(key, start_index, end_index)
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Return the all the elements in the sorted set at key with a score between min and max
|
|
104
|
+
# (including elements with score equal to min or max). Options:
|
|
105
|
+
# :count, :offset - passed to LIMIT
|
|
106
|
+
# :withscores - if true, scores are returned as well
|
|
107
|
+
# Redis: ZRANGEBYSCORE
|
|
108
|
+
def rangebyscore(min, max, options={})
|
|
109
|
+
args = []
|
|
110
|
+
args += ['limit', options[:offset] || 0, options[:limit] || options[:count]] if
|
|
111
|
+
options[:offset] || options[:limit] || options[:count]
|
|
112
|
+
args += ['withscores'] if options[:withscores]
|
|
113
|
+
from_redis redis.zrangebyscore(key, min, max, *args)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Forwards compat (not yet implemented in Redis)
|
|
117
|
+
def revrangebyscore(min, max, options={})
|
|
118
|
+
args = []
|
|
119
|
+
args += ['limit', options[:offset] || 0, options[:limit] || options[:count]] if
|
|
120
|
+
options[:offset] || options[:limit] || options[:count]
|
|
121
|
+
args += ['withscores'] if options[:withscores]
|
|
122
|
+
from_redis redis.zrevrangebyscore(key, min, max, *args)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Remove all elements in the sorted set at key with rank between start and end. Start and end are
|
|
126
|
+
# 0-based with rank 0 being the element with the lowest score. Both start and end can be negative
|
|
127
|
+
# numbers, where they indicate offsets starting at the element with the highest rank. For example:
|
|
128
|
+
# -1 is the element with the highest score, -2 the element with the second highest score and so forth.
|
|
129
|
+
# Redis: ZREMRANGEBYRANK
|
|
130
|
+
def remrangebyrank(min, max)
|
|
131
|
+
redis.zremrangebyrank(key, min, max)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Remove all the elements in the sorted set at key with a score between min and max (including
|
|
135
|
+
# elements with score equal to min or max). Redis: ZREMRANGEBYSCORE
|
|
136
|
+
def remrangebyscore(min, max)
|
|
137
|
+
redis.zremrangebyscore(key, min, max)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# Delete the value from the set. Redis: ZREM
|
|
141
|
+
def delete(value)
|
|
142
|
+
redis.zrem(key, value)
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# Increment the rank of that member atomically and return the new value. This
|
|
146
|
+
# method is aliased as incr() for brevity. Redis: ZINCRBY
|
|
147
|
+
def increment(member, by=1)
|
|
148
|
+
redis.zincrby(key, by, member).to_i
|
|
149
|
+
end
|
|
150
|
+
alias_method :incr, :increment
|
|
151
|
+
alias_method :incrby, :increment
|
|
152
|
+
|
|
153
|
+
# Convenience to calling increment() with a negative number.
|
|
154
|
+
def decrement(by=1)
|
|
155
|
+
redis.zincrby(key, -by).to_i
|
|
156
|
+
end
|
|
157
|
+
alias_method :decr, :decrement
|
|
158
|
+
alias_method :decrby, :decrement
|
|
159
|
+
|
|
160
|
+
# Return the intersection with another set. Can pass it either another set
|
|
161
|
+
# object or set name. Also available as & which is a bit cleaner:
|
|
162
|
+
#
|
|
163
|
+
# members_in_both = set1 & set2
|
|
164
|
+
#
|
|
165
|
+
# If you want to specify multiple sets, you must use +intersection+:
|
|
166
|
+
#
|
|
167
|
+
# members_in_all = set1.intersection(set2, set3, set4)
|
|
168
|
+
# members_in_all = set1.inter(set2, set3, set4) # alias
|
|
169
|
+
#
|
|
170
|
+
# Redis: SINTER
|
|
171
|
+
def intersection(*sets)
|
|
172
|
+
from_redis redis.zinter(key, *keys_from_objects(sets))
|
|
173
|
+
end
|
|
174
|
+
alias_method :intersect, :intersection
|
|
175
|
+
alias_method :inter, :intersection
|
|
176
|
+
alias_method :&, :intersection
|
|
177
|
+
|
|
178
|
+
# Calculate the intersection and store it in Redis as +name+. Returns the number
|
|
179
|
+
# of elements in the stored intersection. Redis: SUNIONSTORE
|
|
180
|
+
def interstore(name, *sets)
|
|
181
|
+
redis.zinterstore(name, key, *keys_from_objects(sets))
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# Return the union with another set. Can pass it either another set
|
|
185
|
+
# object or set name. Also available as | and + which are a bit cleaner:
|
|
186
|
+
#
|
|
187
|
+
# members_in_either = set1 | set2
|
|
188
|
+
# members_in_either = set1 + set2
|
|
189
|
+
#
|
|
190
|
+
# If you want to specify multiple sets, you must use +union+:
|
|
191
|
+
#
|
|
192
|
+
# members_in_all = set1.union(set2, set3, set4)
|
|
193
|
+
#
|
|
194
|
+
# Redis: SUNION
|
|
195
|
+
def union(*sets)
|
|
196
|
+
from_redis redis.zunion(key, *keys_from_objects(sets))
|
|
197
|
+
end
|
|
198
|
+
alias_method :|, :union
|
|
199
|
+
alias_method :+, :union
|
|
200
|
+
|
|
201
|
+
# Calculate the union and store it in Redis as +name+. Returns the number
|
|
202
|
+
# of elements in the stored union. Redis: SUNIONSTORE
|
|
203
|
+
def unionstore(name, *sets)
|
|
204
|
+
redis.zunionstore(name, key, *keys_from_objects(sets))
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
# Return the difference vs another set. Can pass it either another set
|
|
208
|
+
# object or set name. Also available as ^ or - which is a bit cleaner:
|
|
209
|
+
#
|
|
210
|
+
# members_difference = set1 ^ set2
|
|
211
|
+
# members_difference = set1 - set2
|
|
212
|
+
#
|
|
213
|
+
# If you want to specify multiple sets, you must use +difference+:
|
|
214
|
+
#
|
|
215
|
+
# members_difference = set1.difference(set2, set3, set4)
|
|
216
|
+
# members_difference = set1.diff(set2, set3, set4)
|
|
217
|
+
#
|
|
218
|
+
# Redis: SDIFF
|
|
219
|
+
def difference(*sets)
|
|
220
|
+
from_redis redis.zdiff(key, *keys_from_objects(sets))
|
|
221
|
+
end
|
|
222
|
+
alias_method :diff, :difference
|
|
223
|
+
alias_method :^, :difference
|
|
224
|
+
alias_method :-, :difference
|
|
225
|
+
|
|
226
|
+
# Calculate the diff and store it in Redis as +name+. Returns the number
|
|
227
|
+
# of elements in the stored union. Redis: SDIFFSTORE
|
|
228
|
+
def diffstore(name, *sets)
|
|
229
|
+
redis.zdiffstore(name, key, *keys_from_objects(sets))
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
# Returns true if the set has no members. Redis: SCARD == 0
|
|
233
|
+
def empty?
|
|
234
|
+
length == 0
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
def ==(x)
|
|
238
|
+
members == x
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
def to_s
|
|
242
|
+
members.join(', ')
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
# Return the value at the given index. Can also use familiar list[index] syntax.
|
|
246
|
+
# Redis: ZRANGE
|
|
247
|
+
def at(index)
|
|
248
|
+
range(index, index).first
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
# Return the first element in the list. Redis: ZRANGE(0)
|
|
252
|
+
def first
|
|
253
|
+
at(0)
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
# Return the last element in the list. Redis: ZRANGE(-1)
|
|
257
|
+
def last
|
|
258
|
+
at(-1)
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
# The number of members in the set. Aliased as size. Redis: ZCARD
|
|
262
|
+
def length
|
|
263
|
+
redis.zcard(key)
|
|
264
|
+
end
|
|
265
|
+
alias_method :size, :length
|
|
266
|
+
|
|
267
|
+
private
|
|
268
|
+
|
|
269
|
+
def keys_from_objects(sets)
|
|
270
|
+
raise ArgumentError, "Must pass in one or more set names" if sets.empty?
|
|
271
|
+
sets.collect{|set| set.is_a?(Redis::SortedSet) ? set.key : set}
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
end
|
|
275
|
+
end
|
data/lib/redis/value.rb
CHANGED
|
@@ -9,10 +9,10 @@ class Redis
|
|
|
9
9
|
include Redis::Helpers::Serialize
|
|
10
10
|
|
|
11
11
|
attr_reader :key, :options, :redis
|
|
12
|
-
def initialize(key,
|
|
12
|
+
def initialize(key, *args)
|
|
13
13
|
@key = key
|
|
14
|
-
@
|
|
15
|
-
@
|
|
14
|
+
@options = args.last.is_a?(Hash) ? args.pop : {}
|
|
15
|
+
@redis = args.first || $redis
|
|
16
16
|
@redis.setnx(key, @options[:default]) if @options[:default]
|
|
17
17
|
end
|
|
18
18
|
|