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.
@@ -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