redis-objects-legacy 1.6.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,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