redis-objects 0.2.3 → 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.
- data/CHANGELOG.rdoc +37 -0
- data/README.rdoc +106 -41
- data/lib/redis/counter.rb +6 -5
- data/lib/redis/helpers/core_commands.rb +9 -1
- data/lib/redis/helpers/serialize.rb +2 -0
- data/lib/redis/list.rb +4 -4
- data/lib/redis/lock.rb +4 -3
- data/lib/redis/objects/counters.rb +4 -3
- data/lib/redis/objects/locks.rb +0 -4
- data/lib/redis/objects/sorted_sets.rb +45 -0
- data/lib/redis/objects.rb +8 -6
- data/lib/redis/set.rb +3 -3
- data/lib/redis/sorted_set.rb +275 -0
- data/lib/redis/value.rb +3 -3
- data/spec/redis_objects_instance_spec.rb +292 -116
- data/spec/redis_objects_model_spec.rb +4 -4
- metadata +26 -13
- data/ChangeLog +0 -23
data/CHANGELOG.rdoc
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
= Changelog for Redis::Objects
|
|
2
|
+
|
|
3
|
+
== 0.3.0 [Final] (14 April 2010)
|
|
4
|
+
|
|
5
|
+
* Due to Ruby 1.9 bugs and performance considerations, marshaling of
|
|
6
|
+
data types is now OFF by default. You must say :marshal => true for
|
|
7
|
+
any objects that you want serialization enabled on. [Nate Wiger]
|
|
8
|
+
|
|
9
|
+
* Sorted Set class changed slightly due to feedback. You can now get
|
|
10
|
+
an individual element back via @set['item'] since it acts like a Hash.
|
|
11
|
+
|
|
12
|
+
== 0.2.4 [Final] (9 April 2010)*
|
|
13
|
+
|
|
14
|
+
* Added sorted set support via Redis::SortedSet [Nate Wiger]
|
|
15
|
+
|
|
16
|
+
== 0.2.3 [Final] (18 February 2010)*
|
|
17
|
+
|
|
18
|
+
* Added lock expiration to Redis::Lock [Ben VandenBos]
|
|
19
|
+
|
|
20
|
+
* Fixed some bugs [Ben VandenBos]
|
|
21
|
+
|
|
22
|
+
* Added lock tests and test helpers [Ben VandenBos]
|
|
23
|
+
|
|
24
|
+
== 0.2.2 [Final] (14 December 2009)*
|
|
25
|
+
|
|
26
|
+
* Added @set.diff(@set2) with "^" and "-" synonyms (oversight). [Nate Wiger]
|
|
27
|
+
|
|
28
|
+
* Implemented Redis core commands in all data types, such as rename. [Nate Wiger]
|
|
29
|
+
|
|
30
|
+
* Renamed Redis::Serialize to Redis::Helpers::Serialize to keep Redis:: cleaner. [Nate Wiger]
|
|
31
|
+
|
|
32
|
+
* More spec coverage. [Nate Wiger]
|
|
33
|
+
|
|
34
|
+
== 0.2.1 [Final] (27 November 2009)*
|
|
35
|
+
|
|
36
|
+
* First worthwhile public release, with good spec coverage and functionality. [Nate Wiger]
|
|
37
|
+
|
data/README.rdoc
CHANGED
|
@@ -1,26 +1,31 @@
|
|
|
1
1
|
= Redis::Objects - Map Redis types directly to Ruby objects
|
|
2
2
|
|
|
3
|
-
This is *not* an ORM.
|
|
4
|
-
the point.
|
|
3
|
+
This is *not* an ORM. People that are wrapping ORM’s around Redis are missing the point.
|
|
5
4
|
|
|
6
|
-
The killer feature of Redis that it allows you to perform
|
|
7
|
-
on _individual_ data structures, like counters, lists, and sets.
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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 (if you set :marshal => true)
|
|
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
|
|
18
|
-
or by using
|
|
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
|
-
|
|
44
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
@
|
|
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
|
-
@
|
|
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
|
+
Complex data is no problem with :marshal => true:
|
|
175
|
+
|
|
176
|
+
@account = Account.create!(params[:account])
|
|
177
|
+
@newest = Redis::Value.new('newest_account', :marshal => true)
|
|
178
|
+
@newest.value = @account.attributes
|
|
179
|
+
puts @newest.value['username']
|
|
180
|
+
|
|
143
181
|
=== Lists
|
|
144
182
|
|
|
145
183
|
Lists work just like Ruby arrays:
|
|
@@ -160,14 +198,15 @@ Lists work just like Ruby arrays:
|
|
|
160
198
|
@list.clear
|
|
161
199
|
# etc
|
|
162
200
|
|
|
163
|
-
Complex data types are no problem:
|
|
201
|
+
Complex data types are no problem with :marshal => true:
|
|
164
202
|
|
|
203
|
+
@list = Redis::List.new('list_name', :marshal => true)
|
|
165
204
|
@list << {:name => "Nate", :city => "San Diego"}
|
|
166
205
|
@list << {:name => "Peter", :city => "Oceanside"}
|
|
167
206
|
@list.each do |el|
|
|
168
207
|
puts "#{el[:name]} lives in #{el[:city]}"
|
|
169
208
|
end
|
|
170
|
-
|
|
209
|
+
|
|
171
210
|
=== Sets
|
|
172
211
|
|
|
173
212
|
Sets work like the Ruby {Set}[http://ruby-doc.org/core/classes/Set.html] class:
|
|
@@ -208,8 +247,10 @@ Or store them in Redis:
|
|
|
208
247
|
@set1.diffstore('diffname', @set2, @set3)
|
|
209
248
|
members = @set1.redis.get('diffname')
|
|
210
249
|
|
|
211
|
-
And use complex data types too:
|
|
250
|
+
And use complex data types too, with :marshal => true:
|
|
212
251
|
|
|
252
|
+
@set1 = Redis::Set.new('set1', :marshal => true)
|
|
253
|
+
@set2 = Redis::Set.new('set2', :marshal => true)
|
|
213
254
|
@set1 << {:name => "Nate", :city => "San Diego"}
|
|
214
255
|
@set1 << {:name => "Peter", :city => "Oceanside"}
|
|
215
256
|
@set2 << {:name => "Nate", :city => "San Diego"}
|
|
@@ -219,26 +260,50 @@ And use complex data types too:
|
|
|
219
260
|
@set1 - @set2 # Peter
|
|
220
261
|
@set1 | @set2 # all 3 people
|
|
221
262
|
|
|
222
|
-
===
|
|
263
|
+
=== Sorted Sets
|
|
223
264
|
|
|
224
|
-
|
|
265
|
+
Due to their unique properties, Sorted Sets work like a hybrid between
|
|
266
|
+
a Hash and an Array. You assign like a Hash, but retrieve like an Array:
|
|
225
267
|
|
|
226
|
-
require 'redis/
|
|
227
|
-
@
|
|
228
|
-
@
|
|
229
|
-
@
|
|
268
|
+
require 'redis/sorted_set'
|
|
269
|
+
@sorted_set = Redis::SortedSet.new('number_of_posts')
|
|
270
|
+
@sorted_set['Nate'] = 15
|
|
271
|
+
@sorted_set['Peter'] = 75
|
|
272
|
+
@sorted_set['Jeff'] = 24
|
|
230
273
|
|
|
231
|
-
|
|
274
|
+
# Array access to get sorted order
|
|
275
|
+
@sorted_set[0..2] # => ["Nate", "Jeff", "Peter"]
|
|
276
|
+
@sorted_set[0,2] # => ["Nate", "Jeff"]
|
|
232
277
|
|
|
233
|
-
@
|
|
234
|
-
@
|
|
235
|
-
@
|
|
278
|
+
@sorted_set['Peter'] # => 75
|
|
279
|
+
@sorted_set['Jeff'] # => 24
|
|
280
|
+
@sorted_set.score('Jeff') # same thing (24)
|
|
281
|
+
|
|
282
|
+
@sorted_set.rank('Peter') # => 2
|
|
283
|
+
@sorted_set.rank('Jeff') # => 1
|
|
284
|
+
|
|
285
|
+
@sorted_set.first # => "Nate"
|
|
286
|
+
@sorted_set.last # => "Peter"
|
|
287
|
+
@sorted_set.revrange(0,2) # => ["Peter", "Jeff", "Nate"]
|
|
288
|
+
|
|
289
|
+
@sorted_set['Newbie'] = 1
|
|
290
|
+
@sorted_set.members # => ["Newbie", "Nate", "Jeff", "Peter"]
|
|
291
|
+
|
|
292
|
+
@sorted_set.rangebyscore(10, 100, :limit => 2) # => ["Nate", "Jeff"]
|
|
293
|
+
@sorted_set.members(:withscores => true) # => [["Newbie", 1], ["Nate", 16], ["Jeff", 28], ["Peter", 76]]
|
|
294
|
+
|
|
295
|
+
# atomic increment
|
|
296
|
+
@sorted_set.increment('Nate')
|
|
297
|
+
@sorted_set.incr('Peter') # shorthand
|
|
298
|
+
@sorted_set.incr('Jeff', 4)
|
|
299
|
+
|
|
300
|
+
The other Redis Sorted Set commands are supported as well; see {Sorted Sets API}[http://code.google.com/p/redis/wiki/SortedSets].
|
|
236
301
|
|
|
237
302
|
== Atomic Counters and Locks
|
|
238
303
|
|
|
239
304
|
You are probably not handling atomicity correctly in your app. For a fun rant
|
|
240
305
|
on the topic, see
|
|
241
|
-
{
|
|
306
|
+
{An Atomic Rant}[http://nateware.com/2010/02/18/an-atomic-rant].
|
|
242
307
|
|
|
243
308
|
Atomic counters are a good way to handle concurrency:
|
|
244
309
|
|
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,
|
|
14
|
+
def initialize(key, *args)
|
|
15
15
|
@key = key
|
|
16
|
-
@
|
|
17
|
-
@
|
|
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.
|
|
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.
|
|
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
|
|
@@ -4,6 +4,7 @@ class Redis
|
|
|
4
4
|
include Marshal
|
|
5
5
|
|
|
6
6
|
def to_redis(value)
|
|
7
|
+
return value unless options[:marshal]
|
|
7
8
|
case value
|
|
8
9
|
when String, Fixnum, Bignum, Float
|
|
9
10
|
value
|
|
@@ -13,6 +14,7 @@ class Redis
|
|
|
13
14
|
end
|
|
14
15
|
|
|
15
16
|
def from_redis(value)
|
|
17
|
+
return value unless options[:marshal]
|
|
16
18
|
case value
|
|
17
19
|
when Array
|
|
18
20
|
value.collect{|v| from_redis(v)}
|
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,
|
|
15
|
+
def initialize(key, *args)
|
|
16
16
|
@key = key
|
|
17
|
-
@
|
|
18
|
-
@
|
|
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'
|
|
@@ -62,7 +62,7 @@ class Redis
|
|
|
62
62
|
at(index)
|
|
63
63
|
end
|
|
64
64
|
end
|
|
65
|
-
|
|
65
|
+
|
|
66
66
|
# Delete the element(s) from the list that match name. If count is specified,
|
|
67
67
|
# only the first-N (if positive) or last-N (if negative) will be removed.
|
|
68
68
|
# Use .del to completely delete the entire key.
|
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,
|
|
13
|
+
def initialize(key, *args)
|
|
14
14
|
@key = key
|
|
15
|
-
@
|
|
16
|
-
@
|
|
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
|
|
|
@@ -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.
|
|
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.
|
|
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
|
data/lib/redis/objects/locks.rb
CHANGED
|
@@ -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
|
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
|
|
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,
|
|
16
|
+
def initialize(key, *args)
|
|
17
17
|
@key = key
|
|
18
|
-
@
|
|
19
|
-
@
|
|
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'
|