redis-objects-legacy 1.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,199 @@
1
+ # Redis::Objects - Lightweight object layer around redis-rb
2
+ # See README.rdoc for usage and approach.
3
+ require 'redis'
4
+ require 'redis/objects/connection_pool_proxy'
5
+
6
+ class Redis
7
+ autoload :Counter, 'redis/counter'
8
+ autoload :List, 'redis/list'
9
+ autoload :Lock, 'redis/lock'
10
+ autoload :Set, 'redis/set'
11
+ autoload :SortedSet, 'redis/sorted_set'
12
+ autoload :Value, 'redis/value'
13
+ autoload :HashKey, 'redis/hash_key'
14
+
15
+ #
16
+ # Redis::Objects enables high-performance atomic operations in your app
17
+ # by leveraging the atomic features of the Redis server. To use Redis::Objects,
18
+ # first include it in any class you want. (This example uses an ActiveRecord
19
+ # subclass, but that is *not* required.) Then, use +counter+, +lock+, +set+, etc
20
+ # to define your primitives:
21
+ #
22
+ # class Game < ActiveRecord::Base
23
+ # include Redis::Objects
24
+ #
25
+ # counter :joined_players
26
+ # counter :active_players, :key => 'game:#{id}:act_plyr'
27
+ # lock :archive_game
28
+ # set :player_ids
29
+ # end
30
+ #
31
+ # The, you can use these counters both for bookkeeping and as atomic actions:
32
+ #
33
+ # @game = Game.find(id)
34
+ # @game_user = @game.joined_players.increment do |val|
35
+ # break if val > @game.max_players
36
+ # gu = @game.game_users.create!(:user_id => @user.id)
37
+ # @game.active_players.increment
38
+ # gu
39
+ # end
40
+ # if @game_user.nil?
41
+ # # game is full - error screen
42
+ # else
43
+ # # success
44
+ # end
45
+ #
46
+ #
47
+ #
48
+ module Objects
49
+ autoload :Counters, 'redis/objects/counters'
50
+ autoload :Lists, 'redis/objects/lists'
51
+ autoload :Locks, 'redis/objects/locks'
52
+ autoload :Sets, 'redis/objects/sets'
53
+ autoload :SortedSets, 'redis/objects/sorted_sets'
54
+ autoload :Values, 'redis/objects/values'
55
+ autoload :Hashes, 'redis/objects/hashes'
56
+
57
+ class NotConnected < StandardError; end
58
+ class NilObjectId < StandardError; end
59
+
60
+ class << self
61
+ def redis=(conn)
62
+ @redis = Objects::ConnectionPoolProxy.proxy_if_needed(conn)
63
+ end
64
+ def redis
65
+ @redis || $redis || Redis.current ||
66
+ raise(NotConnected, "Redis::Objects.redis not set to a Redis.new connection")
67
+ end
68
+
69
+ def included(klass)
70
+ # Core (this file)
71
+ klass.instance_variable_set(:@redis, nil)
72
+ klass.instance_variable_set(:@redis_objects, {})
73
+ klass.send :include, InstanceMethods
74
+ klass.extend ClassMethods
75
+
76
+ # Pull in each object type
77
+ klass.send :include, Redis::Objects::Counters
78
+ klass.send :include, Redis::Objects::Lists
79
+ klass.send :include, Redis::Objects::Locks
80
+ klass.send :include, Redis::Objects::Sets
81
+ klass.send :include, Redis::Objects::SortedSets
82
+ klass.send :include, Redis::Objects::Values
83
+ klass.send :include, Redis::Objects::Hashes
84
+ end
85
+ end
86
+
87
+ # Class methods that appear in your class when you include Redis::Objects.
88
+ module ClassMethods
89
+ # Enable per-class connections (eg, User and Post can use diff redis-server)
90
+ def redis=(conn)
91
+ @redis = Objects::ConnectionPoolProxy.proxy_if_needed(conn)
92
+ end
93
+
94
+ def redis
95
+ @redis || Objects.redis
96
+ end
97
+
98
+ # Internal list of objects
99
+ attr_writer :redis_objects
100
+ def redis_objects
101
+ @redis_objects ||= {}
102
+ end
103
+
104
+ # Set the Redis redis_prefix to use. Defaults to model_name
105
+ def redis_prefix=(redis_prefix) @redis_prefix = redis_prefix end
106
+ def redis_prefix(klass = self) #:nodoc:
107
+ @redis_prefix ||= klass.name.to_s.
108
+ sub(%r{(.*::)}, '').
109
+ gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
110
+ gsub(/([a-z\d])([A-Z])/,'\1_\2').
111
+ downcase
112
+ end
113
+
114
+ def redis_options(name)
115
+ klass = first_ancestor_with(name)
116
+ return klass.redis_objects[name.to_sym] || {}
117
+ end
118
+
119
+ def redis_field_redis(name) #:nodoc:
120
+ klass = first_ancestor_with(name)
121
+ override_redis = klass.redis_objects[name.to_sym][:redis]
122
+ if override_redis
123
+ Objects::ConnectionPoolProxy.proxy_if_needed(override_redis)
124
+ else
125
+ self.redis
126
+ end
127
+ end
128
+
129
+ def redis_field_key(name, id=nil, context=self) #:nodoc:
130
+ klass = first_ancestor_with(name)
131
+ # READ THIS: This can never ever ever ever change or upgrades will corrupt all data
132
+ # I don't think people were using Proc as keys before (that would create a weird key). Should be ok
133
+ if key = klass.redis_objects[name.to_sym][:key]
134
+ if key.respond_to?(:call)
135
+ key = key.call context
136
+ else
137
+ context.instance_eval "%(#{key})"
138
+ end
139
+ else
140
+ if id.nil? and !klass.redis_objects[name.to_sym][:global]
141
+ raise NilObjectId,
142
+ "[#{klass.redis_objects[name.to_sym]}] Attempt to address redis-object " +
143
+ ":#{name} on class #{klass.name} with nil id (unsaved record?) [object_id=#{object_id}]"
144
+ end
145
+ "#{redis_prefix(klass)}:#{id}:#{name}"
146
+ end
147
+ end
148
+
149
+ def first_ancestor_with(name)
150
+ if redis_objects && redis_objects.key?(name.to_sym)
151
+ self
152
+ elsif superclass && superclass.respond_to?(:redis_objects)
153
+ superclass.first_ancestor_with(name)
154
+ end
155
+ end
156
+
157
+ def redis_id_field(id=nil)
158
+ @redis_id_field = id || @redis_id_field
159
+
160
+ if superclass && superclass.respond_to?(:redis_id_field)
161
+ @redis_id_field ||= superclass.redis_id_field
162
+ end
163
+
164
+ @redis_id_field ||= :id
165
+ end
166
+ end
167
+
168
+ # Instance methods that appear in your class when you include Redis::Objects.
169
+ module InstanceMethods
170
+ # Map up one level to make modular extend/include approach sane
171
+ def redis() self.class.redis end
172
+ def redis_objects() self.class.redis_objects end
173
+
174
+ def redis_delete_objects
175
+ redis.del(redis_instance_keys)
176
+ end
177
+
178
+ def redis_instance_keys
179
+ redis_objects
180
+ .reject { |_, value| value[:global] }
181
+ .keys
182
+ .collect { |name| redis_field_key(name) }
183
+ end
184
+
185
+ def redis_options(name) #:nodoc:
186
+ return self.class.redis_options(name)
187
+ end
188
+
189
+ def redis_field_redis(name) #:nodoc:
190
+ return self.class.redis_field_redis(name)
191
+ end
192
+
193
+ def redis_field_key(name) #:nodoc:
194
+ id = send(self.class.redis_id_field)
195
+ self.class.redis_field_key(name, id, self)
196
+ end
197
+ end
198
+ end
199
+ end
data/lib/redis/set.rb ADDED
@@ -0,0 +1,182 @@
1
+ require File.dirname(__FILE__) + '/enumerable_object'
2
+
3
+ class Redis
4
+ #
5
+ # Class representing a set.
6
+ #
7
+ class Set < EnumerableObject
8
+ # Works like add. Can chain together: list << 'a' << 'b'
9
+ def <<(value)
10
+ add(value)
11
+ self # for << 'a' << 'b'
12
+ end
13
+
14
+ # Add the specified value to the set only if it does not exist already.
15
+ # Redis: SADD
16
+ def add(value)
17
+ allow_expiration do
18
+ redis.sadd(key, marshal(value)) if value.nil? || !Array(value).empty?
19
+ end
20
+ end
21
+
22
+ # Remove and return a random member. Redis: SPOP
23
+ def pop(count = nil)
24
+ unmarshal redis.spop(key, count)
25
+ end
26
+
27
+ # return a random member. Redis: SRANDMEMBER
28
+ def randmember(count = nil)
29
+ unmarshal redis.srandmember(key, count)
30
+ end
31
+
32
+ # Adds the specified values to the set. Only works on redis > 2.4
33
+ # Redis: SADD
34
+ def merge(*values)
35
+ allow_expiration do
36
+ redis.sadd(key, values.flatten.map{|v| marshal(v)})
37
+ end
38
+ end
39
+
40
+ # Return all members in the set. Redis: SMEMBERS
41
+ def members
42
+ vals = redis.smembers(key)
43
+ vals.nil? ? [] : vals.map{|v| unmarshal(v) }
44
+ end
45
+ alias_method :get, :members
46
+ alias_method :value, :members
47
+
48
+ # Returns true if the specified value is in the set. Redis: SISMEMBER
49
+ def member?(value)
50
+ redis.sismember(key, marshal(value))
51
+ end
52
+ alias_method :include?, :member?
53
+
54
+ # Delete the value from the set. Redis: SREM
55
+ def delete(value)
56
+ redis.srem(key, marshal(value))
57
+ end
58
+
59
+ # Delete if matches block
60
+ def delete_if(&block)
61
+ res = false
62
+ redis.smembers(key).each do |m|
63
+ if block.call(unmarshal(m))
64
+ res = redis.srem(key, m)
65
+ end
66
+ end
67
+ res
68
+ end
69
+
70
+ # Return the intersection with another set. Can pass it either another set
71
+ # object or set name. Also available as & which is a bit cleaner:
72
+ #
73
+ # members_in_both = set1 & set2
74
+ #
75
+ # If you want to specify multiple sets, you must use +intersection+:
76
+ #
77
+ # members_in_all = set1.intersection(set2, set3, set4)
78
+ # members_in_all = set1.inter(set2, set3, set4) # alias
79
+ #
80
+ # Redis: SINTER
81
+ def intersection(*sets)
82
+ redis.sinter(key, *keys_from_objects(sets)).map{|v| unmarshal(v)}
83
+ end
84
+ alias_method :intersect, :intersection
85
+ alias_method :inter, :intersection
86
+ alias_method :&, :intersection
87
+
88
+ # Calculate the intersection and store it in Redis as +name+. Returns the number
89
+ # of elements in the stored intersection. Redis: SUNIONSTORE
90
+ def interstore(name, *sets)
91
+ redis.sinterstore(name, key, *keys_from_objects(sets))
92
+ end
93
+
94
+ # Return the union with another set. Can pass it either another set
95
+ # object or set name. Also available as | and + which are a bit cleaner:
96
+ #
97
+ # members_in_either = set1 | set2
98
+ # members_in_either = set1 + set2
99
+ #
100
+ # If you want to specify multiple sets, you must use +union+:
101
+ #
102
+ # members_in_all = set1.union(set2, set3, set4)
103
+ #
104
+ # Redis: SUNION
105
+ def union(*sets)
106
+ redis.sunion(key, *keys_from_objects(sets)).map{|v| unmarshal(v)}
107
+ end
108
+ alias_method :|, :union
109
+ alias_method :+, :union
110
+
111
+ # Calculate the union and store it in Redis as +name+. Returns the number
112
+ # of elements in the stored union. Redis: SUNIONSTORE
113
+ def unionstore(name, *sets)
114
+ redis.sunionstore(name, key, *keys_from_objects(sets))
115
+ end
116
+
117
+ # Return the difference vs another set. Can pass it either another set
118
+ # object or set name. Also available as ^ or - which is a bit cleaner:
119
+ #
120
+ # members_difference = set1 ^ set2
121
+ # members_difference = set1 - set2
122
+ #
123
+ # If you want to specify multiple sets, you must use +difference+:
124
+ #
125
+ # members_difference = set1.difference(set2, set3, set4)
126
+ # members_difference = set1.diff(set2, set3, set4)
127
+ #
128
+ # Redis: SDIFF
129
+ def difference(*sets)
130
+ redis.sdiff(key, *keys_from_objects(sets)).map{|v| unmarshal(v)}
131
+ end
132
+ alias_method :diff, :difference
133
+ alias_method :^, :difference
134
+ alias_method :-, :difference
135
+
136
+ # Calculate the diff and store it in Redis as +name+. Returns the number
137
+ # of elements in the stored union. Redis: SDIFFSTORE
138
+ def diffstore(name, *sets)
139
+ redis.sdiffstore(name, key, *keys_from_objects(sets))
140
+ end
141
+
142
+ # Moves value from one set to another. Destination can be a String
143
+ # or Redis::Set.
144
+ #
145
+ # set.move(value, "name_of_key_in_redis")
146
+ # set.move(value, set2)
147
+ #
148
+ # Returns true if moved successfully.
149
+ #
150
+ # Redis: SMOVE
151
+ def move(value, destination)
152
+ redis.smove(key, destination.is_a?(Redis::Set) ? destination.key : destination.to_s, value)
153
+ end
154
+
155
+ # The number of members in the set. Aliased as size or count. Redis: SCARD
156
+ def length
157
+ redis.scard(key)
158
+ end
159
+ alias_method :size, :length
160
+ alias_method :count, :length
161
+
162
+ # Returns true if the set has no members. Redis: SCARD == 0
163
+ def empty?
164
+ length == 0
165
+ end
166
+
167
+ def ==(x)
168
+ members == x
169
+ end
170
+
171
+ def to_s
172
+ members.join(', ')
173
+ end
174
+
175
+ private
176
+
177
+ def keys_from_objects(sets)
178
+ raise ArgumentError, "Must pass in one or more set names" if sets.empty?
179
+ sets.collect{|set| set.is_a?(Redis::Set) ? set.key : set}
180
+ end
181
+ end
182
+ end
@@ -0,0 +1,325 @@
1
+ require File.dirname(__FILE__) + '/enumerable_object'
2
+
3
+ class Redis
4
+ #
5
+ # Class representing a sorted set.
6
+ #
7
+ class SortedSet < EnumerableObject
8
+ # How to add values using a sorted set. The key is the member, eg,
9
+ # "Peter", and the value is the score, eg, 163. So:
10
+ # num_posts['Peter'] = 163
11
+ def []=(member, score)
12
+ add(member, score)
13
+ end
14
+
15
+ # Add a member and its corresponding value to Redis. Note that the
16
+ # arguments to this are flipped; the member comes first rather than
17
+ # the score, since the member is the unique item (not the score).
18
+ def add(member, score)
19
+ allow_expiration do
20
+ redis.zadd(key, score, marshal(member))
21
+ end
22
+ end
23
+
24
+ # Add a list of members and their corresponding value (or a hash mapping
25
+ # values to scores) to Redis. Note that the arguments to this are flipped;
26
+ # the member comes first rather than the score, since the member is the unique
27
+ # item (not the score).
28
+ def merge(values)
29
+ allow_expiration do
30
+ vals = values.map{|v,s| [s, marshal(v)] }
31
+ redis.zadd(key, vals)
32
+ end
33
+ end
34
+ alias_method :add_all, :merge
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.max)
42
+ elsif length
43
+ case length <=> 0
44
+ when 1 then range(index, index + length - 1)
45
+ when 0 then []
46
+ when -1 then nil # Ruby does this (a bit weird)
47
+ end
48
+ else
49
+ result = score(index) || 0 # handles a nil score
50
+ end
51
+ end
52
+ alias_method :slice, :[]
53
+
54
+ # Return the score of the specified element of the sorted set at key. If the
55
+ # specified element does not exist in the sorted set, or the key does not exist
56
+ # at all, nil is returned. Redis: ZSCORE.
57
+ def score(member)
58
+ result = redis.zscore(key, marshal(member))
59
+
60
+ result.to_f unless result.nil?
61
+ end
62
+
63
+ # Return the rank of the member in the sorted set, with scores ordered from
64
+ # low to high. +revrank+ returns the rank with scores ordered from high to low.
65
+ # When the given member does not exist in the sorted set, nil is returned.
66
+ # The returned rank (or index) of the member is 0-based for both commands
67
+ def rank(member)
68
+ if n = redis.zrank(key, marshal(member))
69
+ n.to_i
70
+ else
71
+ nil
72
+ end
73
+ end
74
+
75
+ def revrank(member)
76
+ if n = redis.zrevrank(key, marshal(member))
77
+ n.to_i
78
+ else
79
+ nil
80
+ end
81
+ end
82
+
83
+ # Return all members of the sorted set with their scores. Extremely CPU-intensive.
84
+ # Better to use a range instead.
85
+ def members(options={})
86
+ range(0, -1, options) || []
87
+ end
88
+ alias_method :value, :members
89
+
90
+ # Return a range of values from +start_index+ to +end_index+. Can also use
91
+ # the familiar list[start,end] Ruby syntax. Redis: ZRANGE
92
+ def range(start_index, end_index, options={})
93
+ if options[:withscores] || options[:with_scores]
94
+ redis.zrange(key, start_index, end_index, :with_scores => true).map{|v,s| [unmarshal(v), s] }
95
+ else
96
+ redis.zrange(key, start_index, end_index).map{|v| unmarshal(v) }
97
+ end
98
+ end
99
+
100
+ # Return a range of values from +start_index+ to +end_index+ in reverse order. Redis: ZREVRANGE
101
+ def revrange(start_index, end_index, options={})
102
+ if options[:withscores] || options[:with_scores]
103
+ redis.zrevrange(key, start_index, end_index, :with_scores => true).map{|v,s| [unmarshal(v), s] }
104
+ else
105
+ redis.zrevrange(key, start_index, end_index).map{|v| unmarshal(v) }
106
+ end
107
+ end
108
+
109
+ # Return the all the elements in the sorted set at key with a score between min and max
110
+ # (including elements with score equal to min or max). Options:
111
+ # :count, :offset - passed to LIMIT
112
+ # :withscores - if true, scores are returned as well
113
+ # Redis: ZRANGEBYSCORE
114
+ def rangebyscore(min, max, options={})
115
+ args = {}
116
+ args[:limit] = [options[:offset] || 0, options[:limit] || options[:count]] if
117
+ options[:offset] || options[:limit] || options[:count]
118
+ args[:with_scores] = true if options[:withscores] || options[:with_scores]
119
+
120
+ redis.zrangebyscore(key, min, max, **args).map{|v| unmarshal(v) }
121
+ end
122
+
123
+ # Returns all the elements in the sorted set at key with a score between max and min
124
+ # (including elements with score equal to max or min). In contrary to the default ordering of sorted sets,
125
+ # for this command the elements are considered to be ordered from high to low scores.
126
+ # Options:
127
+ # :count, :offset - passed to LIMIT
128
+ # :withscores - if true, scores are returned as well
129
+ # Redis: ZREVRANGEBYSCORE
130
+ def revrangebyscore(max, min, options={})
131
+ args = {}
132
+ args[:limit] = [options[:offset] || 0, options[:limit] || options[:count]] if
133
+ options[:offset] || options[:limit] || options[:count]
134
+ args[:with_scores] = true if options[:withscores] || options[:with_scores]
135
+
136
+ redis.zrevrangebyscore(key, max, min, **args).map{|v| unmarshal(v) }
137
+ end
138
+
139
+ # Remove all elements in the sorted set at key with rank between start and end. Start and end are
140
+ # 0-based with rank 0 being the element with the lowest score. Both start and end can be negative
141
+ # numbers, where they indicate offsets starting at the element with the highest rank. For example:
142
+ # -1 is the element with the highest score, -2 the element with the second highest score and so forth.
143
+ # Redis: ZREMRANGEBYRANK
144
+ def remrangebyrank(min, max)
145
+ redis.zremrangebyrank(key, min, max)
146
+ end
147
+
148
+ # Remove all the elements in the sorted set at key with a score between min and max (including
149
+ # elements with score equal to min or max). Redis: ZREMRANGEBYSCORE
150
+ def remrangebyscore(min, max)
151
+ redis.zremrangebyscore(key, min, max)
152
+ end
153
+
154
+ # Delete the value from the set. Redis: ZREM
155
+ def delete(value)
156
+ allow_expiration do
157
+ redis.zrem(key, marshal(value))
158
+ end
159
+ end
160
+
161
+ # Delete element if it matches block
162
+ def delete_if(&block)
163
+ raise ArgumentError, "Missing block to SortedSet#delete_if" unless block_given?
164
+ res = false
165
+ redis.zrange(key, 0, -1).each do |m|
166
+ if block.call(unmarshal(m))
167
+ res = redis.zrem(key, m)
168
+ end
169
+ end
170
+ res
171
+ end
172
+
173
+ # Increment the rank of that member atomically and return the new value. This
174
+ # method is aliased as incr() for brevity. Redis: ZINCRBY
175
+ def increment(member, by=1)
176
+ allow_expiration do
177
+ zincrby(member, by)
178
+ end
179
+ end
180
+ alias_method :incr, :increment
181
+ alias_method :incrby, :increment
182
+
183
+ # Convenience to calling increment() with a negative number.
184
+ def decrement(member, by=1)
185
+ allow_expiration do
186
+ zincrby(member, -by)
187
+ end
188
+ end
189
+ alias_method :decr, :decrement
190
+ alias_method :decrby, :decrement
191
+
192
+ # Return the intersection with another set. Can pass it either another set
193
+ # object or set name. Also available as & which is a bit cleaner:
194
+ #
195
+ # members_in_both = set1 & set2
196
+ #
197
+ # If you want to specify multiple sets, you must use +intersection+:
198
+ #
199
+ # members_in_all = set1.intersection(set2, set3, set4)
200
+ # members_in_all = set1.inter(set2, set3, set4) # alias
201
+ #
202
+ # Redis: SINTER
203
+ def intersection(*sets)
204
+ result = nil
205
+ temp_key = :"#{key}:intersection:#{Time.current.to_i + rand}"
206
+
207
+ redis.multi do
208
+ interstore(temp_key, *sets)
209
+ redis.expire(temp_key, 1)
210
+
211
+ result = redis.zrange(temp_key, 0, -1)
212
+ end
213
+
214
+ result.value
215
+ end
216
+ alias_method :intersect, :intersection
217
+ alias_method :inter, :intersection
218
+ alias_method :&, :intersection
219
+
220
+ # Calculate the intersection and store it in Redis as +name+. Returns the number
221
+ # of elements in the stored intersection. Redis: SUNIONSTORE
222
+ def interstore(name, *sets)
223
+ allow_expiration do
224
+ opts = sets.last.is_a?(Hash) ? sets.pop : {}
225
+ redis.zinterstore(key_from_object(name), keys_from_objects([self] + sets), **opts)
226
+ end
227
+ end
228
+
229
+ # Return the union with another set. Can pass it either another set
230
+ # object or set name. Also available as | and + which are a bit cleaner:
231
+ #
232
+ # members_in_either = set1 | set2
233
+ # members_in_either = set1 + set2
234
+ #
235
+ # If you want to specify multiple sets, you must use +union+:
236
+ #
237
+ # members_in_all = set1.union(set2, set3, set4)
238
+ #
239
+ # Redis: SUNION
240
+ def union(*sets)
241
+ result = nil
242
+ temp_key = :"#{key}:union:#{Time.current.to_i + rand}"
243
+
244
+ redis.multi do
245
+ unionstore(temp_key, *sets)
246
+ redis.expire(temp_key, 1)
247
+
248
+ result = redis.zrange(temp_key, 0, -1)
249
+ end
250
+
251
+ result.value
252
+ end
253
+ alias_method :|, :union
254
+ alias_method :+, :union
255
+
256
+ # Calculate the union and store it in Redis as +name+. Returns the number
257
+ # of elements in the stored union. Redis: SUNIONSTORE
258
+ def unionstore(name, *sets)
259
+ allow_expiration do
260
+ opts = sets.last.is_a?(Hash) ? sets.pop : {}
261
+ redis.zunionstore(key_from_object(name), keys_from_objects([self] + sets), **opts)
262
+ end
263
+ end
264
+
265
+ # Returns true if the set has no members. Redis: SCARD == 0
266
+ def empty?
267
+ length == 0
268
+ end
269
+
270
+ def ==(x)
271
+ members == x
272
+ end
273
+
274
+ def to_s
275
+ members.join(', ')
276
+ end
277
+
278
+ # Return the value at the given index. Can also use familiar list[index] syntax.
279
+ # Redis: ZRANGE
280
+ def at(index)
281
+ range(index, index).first
282
+ end
283
+
284
+ # Return the first element in the list. Redis: ZRANGE(0)
285
+ def first
286
+ at(0)
287
+ end
288
+
289
+ # Return the last element in the list. Redis: ZRANGE(-1)
290
+ def last
291
+ at(-1)
292
+ end
293
+
294
+ # The number of members in the set. Aliased as size or count. Redis: ZCARD
295
+ def length
296
+ redis.zcard(key)
297
+ end
298
+ alias_method :size, :length
299
+ alias_method :count, :length
300
+
301
+ # The number of members within a range of scores. Redis: ZCOUNT
302
+ def range_size(min, max)
303
+ redis.zcount(key, min, max)
304
+ end
305
+
306
+ # Return a boolean indicating whether +value+ is a member.
307
+ def member?(value)
308
+ !redis.zscore(key, marshal(value)).nil?
309
+ end
310
+
311
+ private
312
+ def key_from_object(set)
313
+ set.is_a?(Redis::SortedSet) ? set.key : set
314
+ end
315
+
316
+ def keys_from_objects(sets)
317
+ raise ArgumentError, "Must pass in one or more set names" if sets.empty?
318
+ sets.collect{|set| set.is_a?(Redis::SortedSet) || set.is_a?(Redis::Set) ? set.key : set}
319
+ end
320
+
321
+ def zincrby(member, by)
322
+ redis.zincrby(key, by, marshal(member)).to_i
323
+ end
324
+ end
325
+ end