redis-objects 0.2.2 → 0.3.0

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