redis-objects 0.2.3 → 0.2.4
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/README.rdoc +92 -38
- data/lib/redis/counter.rb +6 -5
- data/lib/redis/helpers/core_commands.rb +9 -1
- data/lib/redis/list.rb +3 -3
- data/lib/redis/lock.rb +4 -3
- data/lib/redis/objects.rb +8 -6
- data/lib/redis/objects/counters.rb +4 -3
- data/lib/redis/objects/locks.rb +0 -4
- data/lib/redis/objects/sorted_sets.rb +45 -0
- 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 +268 -111
- data/spec/redis_objects_model_spec.rb +1 -1
- metadata +24 -11
data/README.rdoc
CHANGED
@@ -1,26 +1,31 @@
|
|
1
1
|
= Redis::Objects - Map Redis types directly to Ruby objects
|
2
2
|
|
3
|
-
This is *not* an ORM.
|
4
|
-
the point.
|
3
|
+
This is *not* an ORM. People that are wrapping ORM’s around Redis are missing the point.
|
5
4
|
|
6
|
-
The killer feature of Redis that it allows you to perform
|
7
|
-
on _individual_ data structures, like counters, lists, and sets.
|
8
|
-
|
9
|
-
|
10
|
-
|
5
|
+
The killer feature of Redis is that it allows you to perform _atomic_ operations
|
6
|
+
on _individual_ data structures, like counters, lists, and sets. The *atomic* part is HUGE.
|
7
|
+
Using an ORM wrapper that retrieves a "record", updates values, then sends those values back,
|
8
|
+
_removes_ the atomicity, cutting the nuts off the major advantage of Redis. Just use MySQL, k?
|
9
|
+
|
10
|
+
This gem provides a Rubyish interface to Redis, by mapping {Redis types}[http://code.google.com/p/redis/wiki/CommandReference]
|
11
|
+
to Ruby objects, via a thin layer over Ezra's +redis+ gem. It offers several advantages
|
12
|
+
over the lower-level redis-rb API:
|
13
|
+
|
14
|
+
1. Easy to integrate directly with existing ORMs - ActiveRecord, DataMapper, etc. Add counters to your model!
|
15
|
+
2. Complex data structures are automatically Marshaled
|
16
|
+
3. Integers are returned as integers, rather than '17'
|
17
|
+
4. Higher-level types are provided, such as Locks, that wrap multiple calls
|
11
18
|
|
12
19
|
This gem originally arose out of a need for high-concurrency atomic operations;
|
13
|
-
for a fun rant on the topic, see
|
14
|
-
{ATOMICITY}[http://github.com/nateware/redis-objects/blob/master/ATOMICITY.rdoc],
|
20
|
+
for a fun rant on the topic, see {An Atomic Rant}[http://nateware.com/2010/02/18/an-atomic-rant],
|
15
21
|
or scroll down to "Atomicity" in this README.
|
16
22
|
|
17
|
-
There are two ways to use Redis::Objects, either as an
|
18
|
-
or by using
|
23
|
+
There are two ways to use Redis::Objects, either as an include in a model class (to
|
24
|
+
integrate with ORMs or other classes), or by using new with the type of data structure
|
25
|
+
you want to create.
|
19
26
|
|
20
27
|
== Installation
|
21
28
|
|
22
|
-
gem install gemcutter
|
23
|
-
gem tumble
|
24
29
|
gem install redis-objects
|
25
30
|
|
26
31
|
== Example 1: Model Class Usage
|
@@ -29,27 +34,30 @@ Using Redis::Objects this way makes it trivial to integrate Redis types with an
|
|
29
34
|
existing ActiveRecord model, DataMapper resource, or other class. Redis::Objects
|
30
35
|
will work with _any_ class that provides an +id+ method that returns a unique
|
31
36
|
value. Redis::Objects will automatically create keys that are unique to
|
32
|
-
each object
|
37
|
+
each object, in the format:
|
38
|
+
|
39
|
+
model_name:id:field_name
|
33
40
|
|
34
41
|
=== Initialization
|
35
42
|
|
36
|
-
Redis::Objects needs a handle created by Redis.new. If you're on Rails,
|
37
|
-
config/initializers/redis.rb is a good place for this
|
43
|
+
Redis::Objects needs a handle created by Redis.new. (If you're on Rails,
|
44
|
+
config/initializers/redis.rb is a good place for this.)
|
38
45
|
|
39
46
|
require 'redis'
|
40
47
|
require 'redis/objects'
|
41
48
|
Redis::Objects.redis = Redis.new(:host => 127.0.0.1, :port => 6379)
|
42
49
|
|
43
|
-
|
44
|
-
|
50
|
+
Remember you can use Redis::Objects in any Ruby code. There are *no* dependencies
|
51
|
+
on Rails. Standalone, Sinatra, Resque - no problem.
|
45
52
|
|
46
53
|
=== Model Class
|
47
54
|
|
48
|
-
|
55
|
+
You can include Redis::Objects in any type of class:
|
49
56
|
|
50
57
|
class Team < ActiveRecord::Base
|
51
58
|
include Redis::Objects
|
52
59
|
|
60
|
+
lock :trade_players, :expiration => 15 # sec
|
53
61
|
counter :hits
|
54
62
|
counter :runs
|
55
63
|
counter :outs
|
@@ -82,7 +90,7 @@ Sets work too:
|
|
82
90
|
end
|
83
91
|
player = @team.outfielders.detect{|of| of == 'outfielder2'}
|
84
92
|
|
85
|
-
And you can do intersections between
|
93
|
+
And you can do intersections between objects (kinda cool):
|
86
94
|
|
87
95
|
@team1.outfielders | @team2.outfielders # outfielders on both teams
|
88
96
|
@team1.outfielders & @team2.outfielders # in baseball, should be empty :-)
|
@@ -101,26 +109,28 @@ Finally, for free, you get a +redis+ method that points directly to a Redis conn
|
|
101
109
|
@team.redis.get('somekey')
|
102
110
|
@team.redis.smembers('someset')
|
103
111
|
|
104
|
-
You can use the +redis+ handle to directly call any {Redis command}[http://code.google.com/p/redis/wiki/CommandReference]
|
112
|
+
You can use the +redis+ handle to directly call any {Redis API command}[http://code.google.com/p/redis/wiki/CommandReference].
|
105
113
|
|
106
114
|
== Example 2: Standalone Usage
|
107
115
|
|
108
|
-
There is a Ruby
|
116
|
+
There is a Ruby class that maps to each Redis type, with methods for each
|
117
|
+
{Redis API command}[http://code.google.com/p/redis/wiki/CommandReference].
|
118
|
+
Note that calling +new+ does not imply it's actually a "new" value - it just
|
119
|
+
creates a mapping between that object and the corresponding Redis data structure,
|
120
|
+
which may already exist on the redis-server.
|
109
121
|
|
110
122
|
=== Initialization
|
111
123
|
|
112
|
-
|
124
|
+
Redis::Objects needs a handle to the +redis+ server. For standalone use, you
|
113
125
|
can either set the $redis global variable:
|
114
126
|
|
115
127
|
$redis = Redis.new(:host => 'localhost', :port => 6379)
|
116
|
-
@
|
128
|
+
@list = Redis::List.new('mylist')
|
117
129
|
|
118
130
|
Or you can pass the Redis handle into the new method for each type:
|
119
131
|
|
120
132
|
redis = Redis.new(:host => 'localhost', :port => 6379)
|
121
|
-
@
|
122
|
-
|
123
|
-
Your choice.
|
133
|
+
@list = Redis::List.new('mylist', redis)
|
124
134
|
|
125
135
|
=== Counters
|
126
136
|
|
@@ -140,6 +150,34 @@ This gem provides a clean way to do atomic blocks as well:
|
|
140
150
|
|
141
151
|
See the section on "Atomicity" for cool uses of atomic counter blocks.
|
142
152
|
|
153
|
+
=== Locks
|
154
|
+
|
155
|
+
A convenience class that wraps the pattern of {using +setnx+ to perform locking}[http://code.google.com/p/redis/wiki/SetnxCommand].
|
156
|
+
|
157
|
+
require 'redis/lock'
|
158
|
+
@lock = Redis::Lock.new('image_resizing', :expiration => 15, :timeout => 0.1)
|
159
|
+
@lock.lock do
|
160
|
+
# do work
|
161
|
+
end
|
162
|
+
|
163
|
+
This can be especially useful if you're running batch jobs spread across multiple hosts.
|
164
|
+
|
165
|
+
=== Values
|
166
|
+
|
167
|
+
Simple values are easy as well:
|
168
|
+
|
169
|
+
require 'redis/value'
|
170
|
+
@value = Redis::Value.new('value_name')
|
171
|
+
@value.value = 'a'
|
172
|
+
@value.delete
|
173
|
+
|
174
|
+
Of course complex data is no problem:
|
175
|
+
|
176
|
+
@account = Account.create!(params[:account])
|
177
|
+
@newest = Redis::Value.new('newest_account')
|
178
|
+
@newest.value = @account.attributes
|
179
|
+
puts @newest.value['username']
|
180
|
+
|
143
181
|
=== Lists
|
144
182
|
|
145
183
|
Lists work just like Ruby arrays:
|
@@ -167,6 +205,8 @@ Complex data types are no problem:
|
|
167
205
|
@list.each do |el|
|
168
206
|
puts "#{el[:name]} lives in #{el[:city]}"
|
169
207
|
end
|
208
|
+
|
209
|
+
This gem will automatically marshal complex data, similar to how session stores work.
|
170
210
|
|
171
211
|
=== Sets
|
172
212
|
|
@@ -219,26 +259,40 @@ And use complex data types too:
|
|
219
259
|
@set1 - @set2 # Peter
|
220
260
|
@set1 | @set2 # all 3 people
|
221
261
|
|
222
|
-
===
|
262
|
+
=== Sorted Sets
|
223
263
|
|
224
|
-
|
264
|
+
Due to their unique properties, Sorted Sets work like a hybrid between
|
265
|
+
a Hash and an Array. You assign like a Hash, but retrieve like an Array:
|
225
266
|
|
226
|
-
require 'redis/
|
227
|
-
@
|
228
|
-
@
|
229
|
-
@
|
267
|
+
require 'redis/sorted_set'
|
268
|
+
@sorted_set = Redis::SortedSet.new('number_of_posts')
|
269
|
+
@sorted_set['Nate'] = 15
|
270
|
+
@sorted_set['Peter'] = 75
|
271
|
+
@sorted_set['Jeff'] = 24
|
272
|
+
|
273
|
+
# atomic increment
|
274
|
+
@sorted_set.increment('Nate')
|
275
|
+
@sorted_set.incr('Peter') # shorthand
|
276
|
+
@sorted_set.incr('Jeff', 4)
|
230
277
|
|
231
|
-
|
278
|
+
@sorted_set[0,2] # => ["Nate", "Jeff", "Peter"]
|
279
|
+
@sorted_set.first # => "Nate"
|
280
|
+
@sorted_set.last # => "Nate"
|
281
|
+
@sorted_set.revrange(0,2) # => ["Peter", "Jeff", "Nate"]
|
232
282
|
|
233
|
-
@
|
234
|
-
@
|
235
|
-
|
283
|
+
@sorted_set['Newbie'] = 1
|
284
|
+
@sorted_set.members # => ["Newbie", "Nate", "Jeff", "Peter"]
|
285
|
+
|
286
|
+
@sorted_set.rangebyscore(10, 100, :limit => 2) # => ["Nate", "Jeff"]
|
287
|
+
@sorted_set.members(:withscores => true) # => [["Newbie", 1], ["Nate", 16], ["Jeff", 28], ["Peter", 76]]
|
288
|
+
|
289
|
+
The other Redis Sorted Set commands are supported as well; see {SortedSets API}[http://code.google.com/p/redis/wiki/SortedSets].
|
236
290
|
|
237
291
|
== Atomic Counters and Locks
|
238
292
|
|
239
293
|
You are probably not handling atomicity correctly in your app. For a fun rant
|
240
294
|
on the topic, see
|
241
|
-
{
|
295
|
+
{An Atomic Rant}[http://nateware.com/2010/02/18/an-atomic-rant].
|
242
296
|
|
243
297
|
Atomic counters are a good way to handle concurrency:
|
244
298
|
|
data/lib/redis/counter.rb
CHANGED
@@ -11,10 +11,10 @@ class Redis
|
|
11
11
|
include Redis::Helpers::CoreCommands
|
12
12
|
|
13
13
|
attr_reader :key, :options, :redis
|
14
|
-
def initialize(key,
|
14
|
+
def initialize(key, *args)
|
15
15
|
@key = key
|
16
|
-
@
|
17
|
-
@
|
16
|
+
@options = args.last.is_a?(Hash) ? args.pop : {}
|
17
|
+
@redis = args.first || $redis
|
18
18
|
@options[:start] ||= 0
|
19
19
|
@redis.setnx(key, @options[:start]) unless @options[:start] == 0 || @options[:init] === false
|
20
20
|
end
|
@@ -25,6 +25,7 @@ class Redis
|
|
25
25
|
# disconnecting all players).
|
26
26
|
def reset(to=options[:start])
|
27
27
|
redis.set key, to.to_i
|
28
|
+
true # hack for redis-rb regression
|
28
29
|
end
|
29
30
|
|
30
31
|
# Returns the current value of the counter. Normally just calling the
|
@@ -42,7 +43,7 @@ class Redis
|
|
42
43
|
# counter will automatically be decremented to its previous value. This
|
43
44
|
# method is aliased as incr() for brevity.
|
44
45
|
def increment(by=1, &block)
|
45
|
-
val = redis.
|
46
|
+
val = redis.incrby(key, by).to_i
|
46
47
|
block_given? ? rewindable_block(:decrement, val, &block) : val
|
47
48
|
end
|
48
49
|
alias_method :incr, :increment
|
@@ -53,7 +54,7 @@ class Redis
|
|
53
54
|
# counter will automatically be incremented to its previous value. This
|
54
55
|
# method is aliased as incr() for brevity.
|
55
56
|
def decrement(by=1, &block)
|
56
|
-
val = redis.
|
57
|
+
val = redis.decrby(key, by).to_i
|
57
58
|
block_given? ? rewindable_block(:increment, val, &block) : val
|
58
59
|
end
|
59
60
|
alias_method :decr, :decrement
|
@@ -15,7 +15,7 @@ class Redis
|
|
15
15
|
def type
|
16
16
|
redis.type key
|
17
17
|
end
|
18
|
-
|
18
|
+
|
19
19
|
def rename(name, setkey=true)
|
20
20
|
dest = name.is_a?(self.class) ? name.key : name
|
21
21
|
ret = redis.rename key, dest
|
@@ -41,6 +41,14 @@ class Redis
|
|
41
41
|
def move(dbindex)
|
42
42
|
redis.move key, dbindex
|
43
43
|
end
|
44
|
+
|
45
|
+
# See the documentation for SORT: http://code.google.com/p/redis/wiki/SortCommand
|
46
|
+
# TODO
|
47
|
+
# def sort(options)
|
48
|
+
# args = []
|
49
|
+
# args += ['sort']
|
50
|
+
# from_redis redis.sort key
|
51
|
+
# end
|
44
52
|
end
|
45
53
|
end
|
46
54
|
end
|
data/lib/redis/list.rb
CHANGED
@@ -12,10 +12,10 @@ class Redis
|
|
12
12
|
include Redis::Helpers::Serialize
|
13
13
|
|
14
14
|
attr_reader :key, :options, :redis
|
15
|
-
def initialize(key,
|
15
|
+
def initialize(key, *args)
|
16
16
|
@key = key
|
17
|
-
@
|
18
|
-
@
|
17
|
+
@options = args.last.is_a?(Hash) ? args.pop : {}
|
18
|
+
@redis = args.first || $redis
|
19
19
|
end
|
20
20
|
|
21
21
|
# Works like push. Can chain together: list << 'a' << 'b'
|
data/lib/redis/lock.rb
CHANGED
@@ -10,11 +10,12 @@ class Redis
|
|
10
10
|
class LockTimeout < StandardError; end #:nodoc:
|
11
11
|
|
12
12
|
attr_reader :key, :options, :redis
|
13
|
-
def initialize(key,
|
13
|
+
def initialize(key, *args)
|
14
14
|
@key = key
|
15
|
-
@
|
16
|
-
@
|
15
|
+
@options = args.last.is_a?(Hash) ? args.pop : {}
|
16
|
+
@redis = args.first || $redis
|
17
17
|
@options[:timeout] ||= 5
|
18
|
+
@options[:init] = false if @options[:init].nil? # default :init to false
|
18
19
|
@redis.setnx(key, @options[:start]) unless @options[:start] == 0 || @options[:init] === false
|
19
20
|
end
|
20
21
|
|
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
|
|
@@ -56,7 +56,7 @@ class Redis
|
|
56
56
|
def increment_counter(name, id=nil, by=1, &block)
|
57
57
|
verify_counter_defined!(name, id)
|
58
58
|
initialize_counter!(name, id)
|
59
|
-
value = redis.
|
59
|
+
value = redis.incrby(field_key(name, id), by).to_i
|
60
60
|
block_given? ? rewindable_block(:decrement_counter, name, id, value, &block) : value
|
61
61
|
end
|
62
62
|
|
@@ -65,7 +65,7 @@ class Redis
|
|
65
65
|
def decrement_counter(name, id=nil, by=1, &block)
|
66
66
|
verify_counter_defined!(name, id)
|
67
67
|
initialize_counter!(name, id)
|
68
|
-
value = redis.
|
68
|
+
value = redis.decrby(field_key(name, id), by).to_i
|
69
69
|
block_given? ? rewindable_block(:increment_counter, name, id, value, &block) : value
|
70
70
|
end
|
71
71
|
|
@@ -73,7 +73,8 @@ class Redis
|
|
73
73
|
def reset_counter(name, id=nil, to=nil)
|
74
74
|
verify_counter_defined!(name, id)
|
75
75
|
to = @redis_objects[name][:start] if to.nil?
|
76
|
-
redis.set(field_key(name, id), to)
|
76
|
+
redis.set(field_key(name, id), to.to_i)
|
77
|
+
true
|
77
78
|
end
|
78
79
|
|
79
80
|
private
|
data/lib/redis/objects/locks.rb
CHANGED
@@ -16,7 +16,6 @@ class Redis
|
|
16
16
|
# so it can be used alongside ActiveRecord/DataMapper, etc.
|
17
17
|
def lock(name, options={})
|
18
18
|
options[:timeout] ||= 5 # seconds
|
19
|
-
options[:init] = false if options[:init].nil? # default :init to false
|
20
19
|
@redis_objects[name] = options.merge(:type => :lock)
|
21
20
|
if options[:global]
|
22
21
|
instance_eval <<-EndMethods
|
@@ -36,9 +35,6 @@ class Redis
|
|
36
35
|
end
|
37
36
|
EndMethods
|
38
37
|
end
|
39
|
-
|
40
|
-
|
41
|
-
|
42
38
|
end
|
43
39
|
|
44
40
|
# Obtain a lock, and execute the block synchronously. Any other code
|
@@ -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/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
|
+
at(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
|
|
@@ -3,9 +3,10 @@ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
|
3
3
|
|
4
4
|
require 'redis/counter'
|
5
5
|
require 'redis/list'
|
6
|
-
require 'redis/set'
|
7
6
|
require 'redis/value'
|
8
7
|
require 'redis/lock'
|
8
|
+
require 'redis/set'
|
9
|
+
require 'redis/sorted_set'
|
9
10
|
|
10
11
|
describe Redis::Value do
|
11
12
|
before :all do
|
@@ -47,6 +48,7 @@ describe Redis::Value do
|
|
47
48
|
old.should be_nil
|
48
49
|
old.value = 'Tuff'
|
49
50
|
@value.renamenx('spec/value').should be_false
|
51
|
+
@value.value.should == 'Peter Pan'
|
50
52
|
end
|
51
53
|
|
52
54
|
after :all do
|
@@ -160,9 +162,11 @@ describe Redis::List do
|
|
160
162
|
old = Redis::List.new('spec/list')
|
161
163
|
old.should be_empty
|
162
164
|
old << 'Tuff'
|
165
|
+
old.values.should == ['Tuff']
|
163
166
|
@list.renamenx('spec/list').should be_false
|
164
167
|
@list.renamenx(old).should be_false
|
165
168
|
@list.renamenx('spec/foo').should be_true
|
169
|
+
old.values.should == ['Tuff']
|
166
170
|
@list.clear
|
167
171
|
@list.redis.del('spec/list2')
|
168
172
|
end
|
@@ -172,6 +176,134 @@ describe Redis::List do
|
|
172
176
|
end
|
173
177
|
end
|
174
178
|
|
179
|
+
describe Redis::Counter do
|
180
|
+
before :all do
|
181
|
+
@counter = Redis::Counter.new('spec/counter')
|
182
|
+
@counter2 = Redis::Counter.new('spec/counter')
|
183
|
+
end
|
184
|
+
|
185
|
+
before :each do
|
186
|
+
@counter.reset
|
187
|
+
end
|
188
|
+
|
189
|
+
it "should support increment/decrement of counters" do
|
190
|
+
@counter.key.should == 'spec/counter'
|
191
|
+
@counter.incr(10)
|
192
|
+
@counter.should == 10
|
193
|
+
|
194
|
+
# math proxy ops
|
195
|
+
(@counter == 10).should be_true
|
196
|
+
(@counter <= 10).should be_true
|
197
|
+
(@counter < 11).should be_true
|
198
|
+
(@counter > 9).should be_true
|
199
|
+
(@counter >= 10).should be_true
|
200
|
+
"#{@counter}".should == "10"
|
201
|
+
|
202
|
+
@counter.increment.should == 11
|
203
|
+
@counter.increment.should == 12
|
204
|
+
@counter2.increment.should == 13
|
205
|
+
@counter2.increment(2).should == 15
|
206
|
+
@counter.decrement.should == 14
|
207
|
+
@counter2.decrement.should == 13
|
208
|
+
@counter.decrement.should == 12
|
209
|
+
@counter2.decrement(4).should == 8
|
210
|
+
@counter.should == 8
|
211
|
+
@counter.reset.should be_true
|
212
|
+
@counter.should == 0
|
213
|
+
@counter.reset(15).should be_true
|
214
|
+
@counter.should == 15
|
215
|
+
end
|
216
|
+
|
217
|
+
after :all do
|
218
|
+
@counter.delete
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
describe Redis::Lock do
|
223
|
+
before :each do
|
224
|
+
$redis.flushall
|
225
|
+
end
|
226
|
+
|
227
|
+
it "should set the value to the expiration" do
|
228
|
+
start = Time.now
|
229
|
+
expiry = 15
|
230
|
+
lock = Redis::Lock.new(:test_lock, :expiration => expiry)
|
231
|
+
lock.lock do
|
232
|
+
expiration = $redis.get("test_lock").to_f
|
233
|
+
|
234
|
+
# The expiration stored in redis should be 15 seconds from when we started
|
235
|
+
# or a little more
|
236
|
+
expiration.should be_close((start + expiry).to_f, 2.0)
|
237
|
+
end
|
238
|
+
|
239
|
+
# key should have been cleaned up
|
240
|
+
$redis.get("test_lock").should be_nil
|
241
|
+
end
|
242
|
+
|
243
|
+
it "should set value to 1 when no expiration is set" do
|
244
|
+
lock = Redis::Lock.new(:test_lock)
|
245
|
+
lock.lock do
|
246
|
+
$redis.get('test_lock').should == '1'
|
247
|
+
end
|
248
|
+
|
249
|
+
# key should have been cleaned up
|
250
|
+
$redis.get("test_lock").should be_nil
|
251
|
+
end
|
252
|
+
|
253
|
+
it "should let lock be gettable when lock is expired" do
|
254
|
+
expiry = 15
|
255
|
+
lock = Redis::Lock.new(:test_lock, :expiration => expiry, :timeout => 0.1)
|
256
|
+
|
257
|
+
# create a fake lock in the past
|
258
|
+
$redis.set("test_lock", Time.now-(expiry + 60))
|
259
|
+
|
260
|
+
gotit = false
|
261
|
+
lock.lock do
|
262
|
+
gotit = true
|
263
|
+
end
|
264
|
+
|
265
|
+
# should get the lock because it has expired
|
266
|
+
gotit.should be_true
|
267
|
+
$redis.get("test_lock").should be_nil
|
268
|
+
end
|
269
|
+
|
270
|
+
it "should not let non-expired locks be gettable" do
|
271
|
+
expiry = 15
|
272
|
+
lock = Redis::Lock.new(:test_lock, :expiration => expiry, :timeout => 0.1)
|
273
|
+
|
274
|
+
# create a fake lock
|
275
|
+
$redis.set("test_lock", (Time.now + expiry).to_f)
|
276
|
+
|
277
|
+
gotit = false
|
278
|
+
error = nil
|
279
|
+
begin
|
280
|
+
lock.lock do
|
281
|
+
gotit = true
|
282
|
+
end
|
283
|
+
rescue => error
|
284
|
+
end
|
285
|
+
|
286
|
+
error.should be_kind_of(Redis::Lock::LockTimeout)
|
287
|
+
|
288
|
+
# should not have the lock
|
289
|
+
gotit.should_not be_true
|
290
|
+
|
291
|
+
# lock value should still be set
|
292
|
+
$redis.get("test_lock").should_not be_nil
|
293
|
+
end
|
294
|
+
|
295
|
+
it "should not remove the key if lock is held past expiration" do
|
296
|
+
lock = Redis::Lock.new(:test_lock, :expiration => 0.0)
|
297
|
+
|
298
|
+
lock.lock do
|
299
|
+
sleep 1.1
|
300
|
+
end
|
301
|
+
|
302
|
+
# lock value should still be set since the lock was held for more than the expiry
|
303
|
+
$redis.get("test_lock").should_not be_nil
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
175
307
|
describe Redis::Set do
|
176
308
|
before :all do
|
177
309
|
@set = Redis::Set.new('spec/set')
|
@@ -289,132 +421,157 @@ describe Redis::Set do
|
|
289
421
|
end
|
290
422
|
end
|
291
423
|
|
292
|
-
describe Redis::
|
424
|
+
describe Redis::SortedSet do
|
293
425
|
before :all do
|
294
|
-
@
|
295
|
-
@
|
426
|
+
@set = Redis::SortedSet.new('spec/zset')
|
427
|
+
@set_1 = Redis::SortedSet.new('spec/zset_1')
|
428
|
+
@set_2 = Redis::SortedSet.new('spec/zset_2')
|
429
|
+
@set_3 = Redis::SortedSet.new('spec/zset_3')
|
296
430
|
end
|
297
431
|
|
298
432
|
before :each do
|
299
|
-
@
|
433
|
+
@set.clear
|
434
|
+
@set_1.clear
|
435
|
+
@set_2.clear
|
436
|
+
@set_3.clear
|
300
437
|
end
|
301
438
|
|
302
|
-
it "should
|
303
|
-
@
|
304
|
-
@
|
305
|
-
@
|
439
|
+
it "should handle sets of simple values" do
|
440
|
+
@set.should be_empty
|
441
|
+
@set['a'] = 11
|
442
|
+
@set['a'] = 21
|
443
|
+
@set.add('a', 5)
|
444
|
+
@set.score('a').should == 5
|
445
|
+
@set['a'] = 3
|
446
|
+
@set['b'] = 5
|
447
|
+
@set['c'] = 4
|
448
|
+
|
449
|
+
@set[0,-1].should == ['a','c','b']
|
450
|
+
@set.range(0,-1).should == ['a','c','b']
|
451
|
+
@set.revrange(0,-1).should == ['b','c','a']
|
452
|
+
@set[0..1].should == ['a','c']
|
453
|
+
@set[1].should == 'c'
|
454
|
+
@set.at(1).should == 'c'
|
455
|
+
@set.first.should == 'a'
|
456
|
+
@set.last.should == 'b'
|
457
|
+
|
458
|
+
@set.members.should == ['a','c','b']
|
459
|
+
@set.members(:withscores => true).should == [['a',3],['c',4],['b',5]]
|
460
|
+
|
461
|
+
@set['b'] = 5
|
462
|
+
@set['b'] = 6
|
463
|
+
@set.score('b').should == 6
|
464
|
+
@set.delete('c')
|
465
|
+
@set.to_s.should == 'a, b'
|
466
|
+
@set.should == ['a','b']
|
467
|
+
@set.members.should == ['a','b']
|
468
|
+
@set['d'] = 0
|
306
469
|
|
307
|
-
|
308
|
-
(
|
309
|
-
(
|
310
|
-
(
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
@
|
316
|
-
@
|
317
|
-
@
|
318
|
-
|
319
|
-
@
|
320
|
-
@
|
321
|
-
@
|
322
|
-
@
|
323
|
-
@
|
324
|
-
@
|
325
|
-
@
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
@
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
end
|
340
|
-
|
341
|
-
it "should set the value to the expiration" do
|
342
|
-
start = Time.now
|
343
|
-
expiry = 15
|
344
|
-
lock = Redis::Lock.new(:test_lock, $redis, :expiration => expiry, :init => false)
|
345
|
-
lock.lock do
|
346
|
-
expiration = $redis.get("test_lock").to_f
|
347
|
-
|
348
|
-
# The expiration stored in redis should be 15 seconds from when we started
|
349
|
-
# or a little more
|
350
|
-
expiration.should be_close((start + expiry).to_f, 2.0)
|
351
|
-
end
|
352
|
-
|
353
|
-
# key should have been cleaned up
|
354
|
-
$redis.get("test_lock").should be_nil
|
470
|
+
@set.rangebyscore(0, 4).should == ['d','a']
|
471
|
+
@set.rangebyscore(0, 4, :count => 1).should == ['d']
|
472
|
+
@set.rangebyscore(0, 4, :count => 2).should == ['d','a']
|
473
|
+
@set.rangebyscore(0, 4, :limit => 2).should == ['d','a']
|
474
|
+
|
475
|
+
# Redis 1.3.5
|
476
|
+
# @set.rangebyscore(0,4, :withscores => true).should == [['d',4],['a',3]]
|
477
|
+
# @set.revrangebyscore(0,4).should == ['d','a']
|
478
|
+
# @set.revrangebyscore(0,4, :count => 2).should == ['a','d']
|
479
|
+
# @set.rank('b').should == 2
|
480
|
+
# @set.revrank('b').should == 3
|
481
|
+
|
482
|
+
@set['f'] = 100
|
483
|
+
@set['g'] = 110
|
484
|
+
@set['h'] = 120
|
485
|
+
@set['j'] = 130
|
486
|
+
@set.incr('h', 20)
|
487
|
+
@set.remrangebyscore(100, 120)
|
488
|
+
@set.members.should == ['d','a','b','j','h']
|
489
|
+
|
490
|
+
# Redis 1.3.5
|
491
|
+
# @set['h'] = 12
|
492
|
+
# @set['j'] = 13
|
493
|
+
# @set.remrangebyrank(4,-1)
|
494
|
+
# @set.members.should == ['d','a','b']
|
495
|
+
|
496
|
+
@set.delete('d')
|
497
|
+
@set['c'] = 200
|
498
|
+
@set.members.should == ['a','b','j','h','c']
|
499
|
+
@set.delete('c')
|
500
|
+
@set.length.should == 4
|
501
|
+
@set.size.should == 4
|
355
502
|
end
|
356
503
|
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
504
|
+
# Not until Redis 1.3.5 with hashes
|
505
|
+
xit "Redis 1.3.5: should handle set intersections, unions, and diffs" do
|
506
|
+
@set_1['a'] = 5
|
507
|
+
@set_2['b'] = 18
|
508
|
+
@set_2['c'] = 12
|
362
509
|
|
363
|
-
|
364
|
-
|
365
|
-
|
510
|
+
@set_2['a'] = 10
|
511
|
+
@set_2['b'] = 15
|
512
|
+
@set_2['c'] = 15
|
366
513
|
|
367
|
-
|
368
|
-
expiry = 15
|
369
|
-
lock = Redis::Lock.new(:test_lock, $redis, :expiration => expiry, :timeout => 0.1, :init => false)
|
514
|
+
(@set_1 & @set_2).sort.should == ['c','d','e']
|
370
515
|
|
371
|
-
|
372
|
-
|
516
|
+
@set_1 << 'a' << 'b' << 'c' << 'd' << 'e'
|
517
|
+
@set_2 << 'c' << 'd' << 'e' << 'f' << 'g'
|
518
|
+
@set_3 << 'a' << 'd' << 'g' << 'l' << 'm'
|
519
|
+
@set_1.sort.should == %w(a b c d e)
|
520
|
+
@set_2.sort.should == %w(c d e f g)
|
521
|
+
@set_3.sort.should == %w(a d g l m)
|
522
|
+
(@set_1 & @set_2).sort.should == ['c','d','e']
|
523
|
+
@set_1.intersection(@set_2).sort.should == ['c','d','e']
|
524
|
+
@set_1.intersection(@set_2, @set_3).sort.should == ['d']
|
525
|
+
@set_1.intersect(@set_2).sort.should == ['c','d','e']
|
526
|
+
@set_1.inter(@set_2, @set_3).sort.should == ['d']
|
527
|
+
@set_1.interstore(INTERSTORE_KEY, @set_2).should == 3
|
528
|
+
@set_1.redis.smembers(INTERSTORE_KEY).sort.should == ['c','d','e']
|
529
|
+
@set_1.interstore(INTERSTORE_KEY, @set_2, @set_3).should == 1
|
530
|
+
@set_1.redis.smembers(INTERSTORE_KEY).sort.should == ['d']
|
373
531
|
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
532
|
+
(@set_1 | @set_2).sort.should == ['a','b','c','d','e','f','g']
|
533
|
+
(@set_1 + @set_2).sort.should == ['a','b','c','d','e','f','g']
|
534
|
+
@set_1.union(@set_2).sort.should == ['a','b','c','d','e','f','g']
|
535
|
+
@set_1.union(@set_2, @set_3).sort.should == ['a','b','c','d','e','f','g','l','m']
|
536
|
+
@set_1.unionstore(UNIONSTORE_KEY, @set_2).should == 7
|
537
|
+
@set_1.redis.smembers(UNIONSTORE_KEY).sort.should == ['a','b','c','d','e','f','g']
|
538
|
+
@set_1.unionstore(UNIONSTORE_KEY, @set_2, @set_3).should == 9
|
539
|
+
@set_1.redis.smembers(UNIONSTORE_KEY).sort.should == ['a','b','c','d','e','f','g','l','m']
|
378
540
|
|
379
|
-
|
380
|
-
|
381
|
-
|
541
|
+
(@set_1 ^ @set_2).sort.should == ["a", "b"]
|
542
|
+
(@set_1 - @set_2).sort.should == ["a", "b"]
|
543
|
+
(@set_2 - @set_1).sort.should == ["f", "g"]
|
544
|
+
@set_1.difference(@set_2).sort.should == ["a", "b"]
|
545
|
+
@set_1.diff(@set_2).sort.should == ["a", "b"]
|
546
|
+
@set_1.difference(@set_2, @set_3).sort.should == ['b']
|
547
|
+
@set_1.diffstore(DIFFSTORE_KEY, @set_2).should == 2
|
548
|
+
@set_1.redis.smembers(DIFFSTORE_KEY).sort.should == ['a','b']
|
549
|
+
@set_1.diffstore(DIFFSTORE_KEY, @set_2, @set_3).should == 1
|
550
|
+
@set_1.redis.smembers(DIFFSTORE_KEY).sort.should == ['b']
|
382
551
|
end
|
383
552
|
|
384
|
-
it "should
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
error.should be_kind_of(Redis::Lock::LockTimeout)
|
401
|
-
|
402
|
-
# should not have the lock
|
403
|
-
gotit.should_not be_true
|
404
|
-
|
405
|
-
# lock value should still be set
|
406
|
-
$redis.get("test_lock").should_not be_nil
|
553
|
+
it "should support renaming sets" do
|
554
|
+
@set.should be_empty
|
555
|
+
@set['zynga'] = 151
|
556
|
+
@set['playfish'] = 202
|
557
|
+
@set.members.should == ['zynga','playfish']
|
558
|
+
@set.key.should == 'spec/zset'
|
559
|
+
@set.rename('spec/zset2').should be_true
|
560
|
+
@set.key.should == 'spec/zset2'
|
561
|
+
old = Redis::SortedSet.new('spec/zset')
|
562
|
+
old.should be_empty
|
563
|
+
old['tuff'] = 54
|
564
|
+
@set.renamenx('spec/zset').should be_false
|
565
|
+
@set.renamenx(old).should be_false
|
566
|
+
@set.renamenx('spec/zfoo').should be_true
|
567
|
+
@set.clear
|
568
|
+
@set.redis.del('spec/zset2')
|
407
569
|
end
|
408
570
|
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
end
|
415
|
-
|
416
|
-
# lock value should still be set since the lock was held for more than the expiry
|
417
|
-
$redis.get("test_lock").should_not be_nil
|
571
|
+
after :all do
|
572
|
+
@set.clear
|
573
|
+
@set_1.clear
|
574
|
+
@set_2.clear
|
575
|
+
@set_3.clear
|
418
576
|
end
|
419
|
-
|
420
|
-
end
|
577
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,12 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: redis-objects
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 2
|
8
|
+
- 4
|
9
|
+
version: 0.2.4
|
5
10
|
platform: ruby
|
6
11
|
authors:
|
7
12
|
- Nate Wiger
|
@@ -9,19 +14,23 @@ autorequire:
|
|
9
14
|
bindir: bin
|
10
15
|
cert_chain: []
|
11
16
|
|
12
|
-
date: 2010-
|
17
|
+
date: 2010-04-09 00:00:00 -07:00
|
13
18
|
default_executable:
|
14
19
|
dependencies:
|
15
20
|
- !ruby/object:Gem::Dependency
|
16
21
|
name: redis
|
17
|
-
|
18
|
-
|
19
|
-
version_requirements: !ruby/object:Gem::Requirement
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
20
24
|
requirements:
|
21
25
|
- - ">="
|
22
26
|
- !ruby/object:Gem::Version
|
23
|
-
|
24
|
-
|
27
|
+
segments:
|
28
|
+
- 1
|
29
|
+
- 0
|
30
|
+
- 3
|
31
|
+
version: 1.0.3
|
32
|
+
type: :runtime
|
33
|
+
version_requirements: *id001
|
25
34
|
description: Map Redis types directly to Ruby objects. Works with any class or ORM.
|
26
35
|
email: nate@wiger.org
|
27
36
|
executables: []
|
@@ -42,9 +51,11 @@ files:
|
|
42
51
|
- lib/redis/objects/lists.rb
|
43
52
|
- lib/redis/objects/locks.rb
|
44
53
|
- lib/redis/objects/sets.rb
|
54
|
+
- lib/redis/objects/sorted_sets.rb
|
45
55
|
- lib/redis/objects/values.rb
|
46
56
|
- lib/redis/objects.rb
|
47
57
|
- lib/redis/set.rb
|
58
|
+
- lib/redis/sorted_set.rb
|
48
59
|
- lib/redis/value.rb
|
49
60
|
- spec/redis_objects_instance_spec.rb
|
50
61
|
- spec/redis_objects_model_spec.rb
|
@@ -66,18 +77,20 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
66
77
|
requirements:
|
67
78
|
- - ">="
|
68
79
|
- !ruby/object:Gem::Version
|
80
|
+
segments:
|
81
|
+
- 0
|
69
82
|
version: "0"
|
70
|
-
version:
|
71
83
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
72
84
|
requirements:
|
73
85
|
- - ">="
|
74
86
|
- !ruby/object:Gem::Version
|
87
|
+
segments:
|
88
|
+
- 0
|
75
89
|
version: "0"
|
76
|
-
version:
|
77
90
|
requirements:
|
78
|
-
- redis,
|
91
|
+
- redis, v1.0.3 or greater
|
79
92
|
rubyforge_project: redis-objects
|
80
|
-
rubygems_version: 1.3.
|
93
|
+
rubygems_version: 1.3.6
|
81
94
|
signing_key:
|
82
95
|
specification_version: 3
|
83
96
|
summary: Maps Redis types to Ruby objects
|