redis-objects 0.2.2 → 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.
data/ChangeLog CHANGED
@@ -1,4 +1,12 @@
1
1
 
2
+ *0.2.3 [Final] (18 February 2010)*
3
+
4
+ * Added lock expiration to Redis::Lock [Ben VandenBos]
5
+
6
+ * Fixed some bugs [Ben VandenBos]
7
+
8
+ * Added lock tests and test helpers [Ben VandenBos]
9
+
2
10
  *0.2.2 [Final] (14 December 2009)*
3
11
 
4
12
  * Added @set.diff(@set2) with "^" and "-" synonyms (oversight). [Nate Wiger]
data/README.rdoc CHANGED
@@ -1,44 +1,136 @@
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
- == Example 1: Standalone Usage
31
+ == Example 1: Model Class Usage
32
+
33
+ Using Redis::Objects this way makes it trivial to integrate Redis types with an
34
+ existing ActiveRecord model, DataMapper resource, or other class. Redis::Objects
35
+ will work with _any_ class that provides an +id+ method that returns a unique
36
+ value. Redis::Objects will automatically create keys that are unique to
37
+ each object, in the format:
27
38
 
28
- There is a Ruby object that maps to each Redis type.
39
+ model_name:id:field_name
29
40
 
30
41
  === Initialization
31
42
 
32
- This gem needs a handle to the +redis+ server. For standalone use, you can
33
- either set the $redis global variable to your Redis.new handle:
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.)
45
+
46
+ require 'redis'
47
+ require 'redis/objects'
48
+ Redis::Objects.redis = Redis.new(:host => 127.0.0.1, :port => 6379)
49
+
50
+ Remember you can use Redis::Objects in any Ruby code. There are *no* dependencies
51
+ on Rails. Standalone, Sinatra, Resque - no problem.
52
+
53
+ === Model Class
54
+
55
+ You can include Redis::Objects in any type of class:
56
+
57
+ class Team < ActiveRecord::Base
58
+ include Redis::Objects
59
+
60
+ lock :trade_players, :expiration => 15 # sec
61
+ counter :hits
62
+ counter :runs
63
+ counter :outs
64
+ counter :inning, :start => 1
65
+ list :on_base
66
+ set :outfielders
67
+ value :at_bat
68
+ end
69
+
70
+ Familiar Ruby array operations Just Work (TM):
71
+
72
+ @team = Team.find_by_name('New York Yankees')
73
+ @team.on_base << 'player1'
74
+ @team.on_base << 'player2'
75
+ @team.on_base << 'player3'
76
+ @team.on_base # ['player1', 'player2', 'player3']
77
+ @team.on_base.pop
78
+ @team.on_base.shift
79
+ @team.on_base.length # 1
80
+ @team.on_base.delete('player2')
81
+
82
+ Sets work too:
83
+
84
+ @team.outfielders << 'outfielder1'
85
+ @team.outfielders << 'outfielder2'
86
+ @team.outfielders << 'outfielder1' # dup ignored
87
+ @team.outfielders # ['outfielder1', 'outfielder2']
88
+ @team.outfielders.each do |player|
89
+ puts player
90
+ end
91
+ player = @team.outfielders.detect{|of| of == 'outfielder2'}
92
+
93
+ And you can do intersections between objects (kinda cool):
94
+
95
+ @team1.outfielders | @team2.outfielders # outfielders on both teams
96
+ @team1.outfielders & @team2.outfielders # in baseball, should be empty :-)
97
+
98
+ Counters can be atomically incremented/decremented (but not assigned):
99
+
100
+ @team.hits.increment # or incr
101
+ @team.hits.decrement # or decr
102
+ @team.hits.incr(3) # add 3
103
+ @team.runs = 4 # exception
104
+
105
+ Finally, for free, you get a +redis+ method that points directly to a Redis connection:
106
+
107
+ Team.redis.get('somekey')
108
+ @team = Team.new
109
+ @team.redis.get('somekey')
110
+ @team.redis.smembers('someset')
111
+
112
+ You can use the +redis+ handle to directly call any {Redis API command}[http://code.google.com/p/redis/wiki/CommandReference].
113
+
114
+ == Example 2: Standalone Usage
115
+
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.
121
+
122
+ === Initialization
123
+
124
+ Redis::Objects needs a handle to the +redis+ server. For standalone use, you
125
+ can either set the $redis global variable:
34
126
 
35
127
  $redis = Redis.new(:host => 'localhost', :port => 6379)
36
- @value = Redis::Value.new('myvalue')
128
+ @list = Redis::List.new('mylist')
37
129
 
38
- Or you can pass the Redis handle into the new method:
130
+ Or you can pass the Redis handle into the new method for each type:
39
131
 
40
132
  redis = Redis.new(:host => 'localhost', :port => 6379)
41
- @value = Redis::Value.new('myvalue', redis)
133
+ @list = Redis::List.new('mylist', redis)
42
134
 
43
135
  === Counters
44
136
 
@@ -58,6 +150,34 @@ This gem provides a clean way to do atomic blocks as well:
58
150
 
59
151
  See the section on "Atomicity" for cool uses of atomic counter blocks.
60
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
+
61
181
  === Lists
62
182
 
63
183
  Lists work just like Ruby arrays:
@@ -85,6 +205,8 @@ Complex data types are no problem:
85
205
  @list.each do |el|
86
206
  puts "#{el[:name]} lives in #{el[:city]}"
87
207
  end
208
+
209
+ This gem will automatically marshal complex data, similar to how session stores work.
88
210
 
89
211
  === Sets
90
212
 
@@ -111,8 +233,8 @@ You can perform Redis intersections/unions/diffs easily:
111
233
  members = @set1 & @set2 # intersection
112
234
  members = @set1 | @set2 # union
113
235
  members = @set1 + @set2 # union
114
- members = @set1 ^ @set2 # union
115
- members = @set1 - @set2 # union
236
+ members = @set1 ^ @set2 # difference
237
+ members = @set1 - @set2 # difference
116
238
  members = @set1.intersection(@set2, @set3) # multiple
117
239
  members = @set1.union(@set2, @set3) # multiple
118
240
  members = @set1.difference(@set2, @set3) # multiple
@@ -137,100 +259,40 @@ And use complex data types too:
137
259
  @set1 - @set2 # Peter
138
260
  @set1 | @set2 # all 3 people
139
261
 
140
- === Values
141
-
142
- Simple values are easy as well:
143
-
144
- require 'redis/value'
145
- @value = Redis::Value.new('value_name')
146
- @value.value = 'a'
147
- @value.delete
148
-
149
- Of course complex data is no problem:
150
-
151
- @account = Account.create!(params[:account])
152
- @newest = Redis::Value.new('newest_account')
153
- @newest.value = @account
154
-
155
- == Example 2: Model Class Usage
262
+ === Sorted Sets
156
263
 
157
- Using Redis::Objects this way makes it trivial to integrate Redis types with an
158
- existing ActiveRecord model, DataMapper resource, or other class. Redis::Objects
159
- will work with _any_ class that provides an +id+ method that returns a unique
160
- value. Redis::Objects will automatically create keys that are unique to
161
- each object.
162
-
163
- === Initialization
164
-
165
- If on Rails, config/initializers/redis.rb is a good place for this:
166
-
167
- require 'redis'
168
- require 'redis/objects'
169
- Redis::Objects.redis = Redis.new(:host => 127.0.0.1, :port => 6379)
170
-
171
- === Model Class
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:
172
266
 
173
- Include Redis::Objects in any type of class:
174
-
175
- class Team < ActiveRecord::Base
176
- include Redis::Objects
177
-
178
- counter :hits
179
- counter :runs
180
- counter :outs
181
- counter :inning, :start => 1
182
- list :on_base
183
- set :outfielders
184
- value :at_bat
185
- end
186
-
187
- Familiar Ruby array operations Just Work (TM):
188
-
189
- @team = Team.find_by_name('New York Yankees')
190
- @team.on_base << 'player1'
191
- @team.on_base << 'player2'
192
- @team.on_base << 'player3'
193
- @team.on_base # ['player1', 'player2']
194
- @team.on_base.pop
195
- @team.on_base.shift
196
- @team.on_base.length # 1
197
- @team.on_base.delete('player3')
198
-
199
- Sets work too:
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
200
272
 
201
- @team.outfielders << 'outfielder1' << 'outfielder1'
202
- @team.outfielders << 'outfielder2'
203
- @team.outfielders # ['outfielder1', 'outfielder2']
204
- @team.outfielders.each do |player|
205
- puts player
206
- end
207
- player = @team.outfielders.detect{|of| of == 'outfielder2'}
273
+ # atomic increment
274
+ @sorted_set.increment('Nate')
275
+ @sorted_set.incr('Peter') # shorthand
276
+ @sorted_set.incr('Jeff', 4)
208
277
 
209
- And you can do intersections between ORM objects (kinda cool):
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"]
210
282
 
211
- @team1.outfielders | @team2.outfielders # all outfielders
212
- @team1.outfielders & @team2.outfielders # should be empty
283
+ @sorted_set['Newbie'] = 1
284
+ @sorted_set.members # => ["Newbie", "Nate", "Jeff", "Peter"]
213
285
 
214
- Counters can be atomically incremented/decremented (but not assigned):
215
-
216
- @team.hits.increment # or incr
217
- @team.hits.decrement # or decr
218
- @team.hits.incr(3) # add 3
219
- @team.runs = 4 # exception
286
+ @sorted_set.rangebyscore(10, 100, :limit => 2) # => ["Nate", "Jeff"]
287
+ @sorted_set.members(:withscores => true) # => [["Newbie", 1], ["Nate", 16], ["Jeff", 28], ["Peter", 76]]
220
288
 
221
- Finally, for free, you get a +redis+ handle usable in your class that
222
- points directly to a Redis API object:
223
-
224
- @team.redis.get('somekey')
225
- @team.redis.smembers('someset')
226
-
227
- You can use the +redis+ handle to directly call any {Redis command}[http://code.google.com/p/redis/wiki/CommandReference]
289
+ The other Redis Sorted Set commands are supported as well; see {SortedSets API}[http://code.google.com/p/redis/wiki/SortedSets].
228
290
 
229
291
  == Atomic Counters and Locks
230
292
 
231
293
  You are probably not handling atomicity correctly in your app. For a fun rant
232
294
  on the topic, see
233
- {ATOMICITY}[http://github.com/nateware/redis-objects/blob/master/ATOMICITY.rdoc].
295
+ {An Atomic Rant}[http://nateware.com/2010/02/18/an-atomic-rant].
234
296
 
235
297
  Atomic counters are a good way to handle concurrency:
236
298
 
@@ -277,6 +339,10 @@ Class-level atomic block (may save a DB fetch depending on your app):
277
339
 
278
340
  Locks with Redis. On completion or exception the lock is released:
279
341
 
342
+ class Team < ActiveRecord::Base
343
+ lock :reorder # declare a lock
344
+ end
345
+
280
346
  @team.reorder_lock.lock do
281
347
  @team.reorder_all_players
282
348
  end
@@ -287,9 +353,19 @@ Class-level lock (same concept)
287
353
  Team.reorder_all_players(team_id)
288
354
  end
289
355
 
356
+ Lock expiration. Sometimes you want to make sure your locks are cleaned up should
357
+ the unthinkable happen (server failure). You can set lock expirations to handle
358
+ this. Expired locks are released by the next process to attempt lock. Just
359
+ make sure you expiration value is sufficiently large compared to your expected
360
+ lock time.
361
+
362
+ class Team < ActiveRecord::Base
363
+ lock :reorder, :expiration => 15.minutes
364
+ end
365
+
290
366
 
291
367
  == Author
292
368
 
293
- Copyright (c) 2009 {Nate Wiger}[http://nate.wiger.org]. All Rights Reserved.
369
+ Copyright (c) 2009-2010 {Nate Wiger}[http://nate.wiger.org]. All Rights Reserved.
294
370
  Released under the {Artistic License}[http://www.opensource.org/licenses/artistic-license-2.0.php].
295
371
 
data/lib/redis/counter.rb CHANGED
@@ -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
data/lib/redis/list.rb CHANGED
@@ -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'
data/lib/redis/lock.rb CHANGED
@@ -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
 
@@ -31,17 +32,53 @@ class Redis
31
32
  def lock(&block)
32
33
  start = Time.now
33
34
  gotit = false
35
+ expiration = nil
34
36
  while Time.now - start < @options[:timeout]
35
- gotit = redis.setnx(key, 1)
37
+ expiration = generate_expiration
38
+ # Use the expiration as the value of the lock.
39
+ gotit = redis.setnx(key, expiration)
36
40
  break if gotit
41
+
42
+ # Lock is being held. Now check to see if it's expired (if we're using
43
+ # lock expiration).
44
+ # See "Handling Deadlocks" section on http://code.google.com/p/redis/wiki/SetnxCommand
45
+ if !@options[:expiration].nil?
46
+ old_expiration = redis.get(key).to_f
47
+
48
+ if old_expiration < Time.now.to_f
49
+ # If it's expired, use GETSET to update it.
50
+ expiration = generate_expiration
51
+ old_expiration = redis.getset(key, expiration).to_f
52
+
53
+ # Since GETSET returns the old value of the lock, if the old expiration
54
+ # is still in the past, we know no one else has expired the locked
55
+ # and we now have it.
56
+ if old_expiration < Time.now.to_f
57
+ gotit = true
58
+ break
59
+ end
60
+ end
61
+ end
62
+
37
63
  sleep 0.1
38
64
  end
39
65
  raise LockTimeout, "Timeout on lock #{key} exceeded #{@options[:timeout]} sec" unless gotit
40
66
  begin
41
67
  yield
42
68
  ensure
43
- redis.del(key)
69
+ # We need to be careful when cleaning up the lock key. If we took a really long
70
+ # time for some reason, and the lock expired, someone else may have it, and
71
+ # it's not safe for us to remove it. Check how much time has passed since we
72
+ # wrote the lock key and only delete it if it hasn't expired (or we're not using
73
+ # lock expiration)
74
+ if @options[:expiration].nil? || expiration > Time.now.to_f
75
+ redis.del(key)
76
+ end
44
77
  end
45
78
  end
79
+
80
+ def generate_expiration
81
+ @options[:expiration].nil? ? 1 : (Time.now + @options[:expiration].to_f + 1).to_f
82
+ end
46
83
  end
47
84
  end
@@ -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
@@ -35,9 +35,6 @@ class Redis
35
35
  end
36
36
  EndMethods
37
37
  end
38
-
39
-
40
-
41
38
  end
42
39
 
43
40
  # Obtain a lock, and execute the block synchronously. Any other code
@@ -47,7 +44,7 @@ class Redis
47
44
  verify_lock_defined!(name)
48
45
  raise ArgumentError, "Missing block to #{self.name}.obtain_lock" unless block_given?
49
46
  lock_name = field_key("#{name}_lock", id)
50
- Redis::Lock.new(redis, lock_name, self.class.redis_objects[name]).lock(&block)
47
+ Redis::Lock.new(lock_name, redis, self.redis_objects[name]).lock(&block)
51
48
  end
52
49
 
53
50
  # Clear the lock. Use with care - usually only in an Admin page to clear
@@ -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