redis-objects 0.2.3 → 0.2.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,26 +1,31 @@
1
1
  = Redis::Objects - Map Redis types directly to Ruby objects
2
2
 
3
- This is *not* an ORM. People that are wrapping ORM's around Redis are missing
4
- the point.
3
+ This is *not* an ORM. People that are wrapping ORMs around Redis are missing the point.
5
4
 
6
- The killer feature of Redis that it allows you to perform atomic operations
7
- on _individual_ data structures, like counters, lists, and sets. You can then use
8
- these *with* your existing ActiveRecord/DataMapper/etc models, or in classes that have
9
- nothing to do with an ORM or even a database. This gem maps {Redis types}[http://code.google.com/p/redis/wiki/CommandReference]
10
- to Ruby objects, via a thin layer over Ezra's +redis+ gem.
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 +include+ in a model class,
18
- or by using +new+ with the type of data structure you want to create.
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
- You can use Redis::Objects with any framework. There are *no* dependencies on Rails.
44
- I use it from Sinatra and rake tasks all the time.
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
- Include Redis::Objects in any type of class:
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 ORM objects (kinda cool):
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 object that maps to each Redis type.
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
- Again, Redis::Objects needs a handle to the +redis+ server. For standalone use, you
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
- @value = Redis::Value.new('myvalue')
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
- @value = Redis::Value.new('myvalue', redis)
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
- === Values
262
+ === Sorted Sets
223
263
 
224
- Simple values are easy as well:
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/value'
227
- @value = Redis::Value.new('value_name')
228
- @value.value = 'a'
229
- @value.delete
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
- Of course complex data is no problem:
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
- @account = Account.create!(params[:account])
234
- @newest = Redis::Value.new('newest_account')
235
- @newest.value = @account
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
- {ATOMICITY}[http://github.com/nateware/redis-objects/blob/master/ATOMICITY.rdoc].
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
 
@@ -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, redis=$redis, options={})
14
+ def initialize(key, *args)
15
15
  @key = key
16
- @redis = redis
17
- @options = options
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.incr(key, by).to_i
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.decr(key, by).to_i
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
@@ -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, redis=$redis, options={})
15
+ def initialize(key, *args)
16
16
  @key = key
17
- @redis = redis
18
- @options = options
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'
@@ -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, redis=$redis, options={})
13
+ def initialize(key, *args)
14
14
  @key = key
15
- @redis = redis
16
- @options = options
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
 
@@ -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+ and +lock+
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.incr(field_key(name, id), by).to_i
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.decr(field_key(name, id), by).to_i
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
@@ -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
@@ -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, redis=$redis, options={})
16
+ def initialize(key, *args)
17
17
  @key = key
18
- @redis = redis
19
- @options = options
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
@@ -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, redis=$redis, options={})
12
+ def initialize(key, *args)
13
13
  @key = key
14
- @redis = redis
15
- @options = options
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::Counter do
424
+ describe Redis::SortedSet do
293
425
  before :all do
294
- @counter = Redis::Counter.new('spec/counter')
295
- @counter2 = Redis::Counter.new('spec/counter')
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
- @counter.reset
433
+ @set.clear
434
+ @set_1.clear
435
+ @set_2.clear
436
+ @set_3.clear
300
437
  end
301
438
 
302
- it "should support increment/decrement of counters" do
303
- @counter.key.should == 'spec/counter'
304
- @counter.incr(10)
305
- @counter.should == 10
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
- # math proxy ops
308
- (@counter == 10).should be_true
309
- (@counter <= 10).should be_true
310
- (@counter < 11).should be_true
311
- (@counter > 9).should be_true
312
- (@counter >= 10).should be_true
313
- "#{@counter}".should == "10"
314
-
315
- @counter.increment.should == 11
316
- @counter.increment.should == 12
317
- @counter2.increment.should == 13
318
- @counter2.increment(2).should == 15
319
- @counter.decrement.should == 14
320
- @counter2.decrement.should == 13
321
- @counter.decrement.should == 12
322
- @counter2.decrement(4).should == 8
323
- @counter.should == 8
324
- @counter.reset.should be_true
325
- @counter.should == 0
326
- @counter.reset(15).should be_true
327
- @counter.should == 15
328
- end
329
-
330
- after :all do
331
- @counter.delete
332
- end
333
- end
334
-
335
- describe Redis::Lock do
336
-
337
- before :each do
338
- $redis.flushall
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
- it "should set value to 1 when no expiration is set" do
358
- lock = Redis::Lock.new(:test_lock, $redis, :init => false)
359
- lock.lock do
360
- $redis.get('test_lock').should == '1'
361
- end
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
- # key should have been cleaned up
364
- $redis.get("test_lock").should be_nil
365
- end
510
+ @set_2['a'] = 10
511
+ @set_2['b'] = 15
512
+ @set_2['c'] = 15
366
513
 
367
- it "should let lock be gettable when lock is expired" do
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
- # create a fake lock in the past
372
- $redis.set("test_lock", Time.now-(expiry + 60))
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
- gotit = false
375
- lock.lock do
376
- gotit = true
377
- end
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
- # should get the lock because it has expired
380
- gotit.should be_true
381
- $redis.get("test_lock").should be_nil
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 not let non-expired locks be gettable" do
385
- expiry = 15
386
- lock = Redis::Lock.new(:test_lock, $redis, :expiration => expiry, :timeout => 0.1, :init => false)
387
-
388
- # create a fake lock
389
- $redis.set("test_lock", (Time.now + expiry).to_f)
390
-
391
- gotit = false
392
- error = nil
393
- begin
394
- lock.lock do
395
- gotit = true
396
- end
397
- rescue => error
398
- end
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
- it "should not remove the key if lock is held past expiration" do
410
- lock = Redis::Lock.new(:test_lock, $redis, :expiration => 0.0, :init => false)
411
-
412
- lock.lock do
413
- sleep 1.1
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
@@ -58,7 +58,7 @@ describe Redis::Objects do
58
58
 
59
59
  it "should provide a connection method" do
60
60
  Roster.redis.should == Redis::Objects.redis
61
- Roster.redis.should be_kind_of(Redis)
61
+ # Roster.redis.should be_kind_of(Redis)
62
62
  end
63
63
 
64
64
  it "should create counter accessors" do
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
- version: 0.2.3
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-02-18 00:00:00 -08:00
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
- type: :runtime
18
- version_requirement:
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
- version: "0.1"
24
- version:
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, v0.1 or greater
91
+ - redis, v1.0.3 or greater
79
92
  rubyforge_project: redis-objects
80
- rubygems_version: 1.3.5
93
+ rubygems_version: 1.3.6
81
94
  signing_key:
82
95
  specification_version: 3
83
96
  summary: Maps Redis types to Ruby objects