redis-objects 0.2.1 → 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 +201 -97
- data/lib/redis/counter.rb +10 -12
- data/lib/redis/helpers/core_commands.rb +54 -0
- data/lib/redis/helpers/serialize.rb +27 -0
- data/lib/redis/list.rb +9 -11
- data/lib/redis/lock.rb +42 -5
- data/lib/redis/objects/counters.rb +41 -23
- data/lib/redis/objects/lists.rb +19 -9
- data/lib/redis/objects/locks.rb +21 -11
- data/lib/redis/objects/sets.rb +20 -9
- data/lib/redis/objects/sorted_sets.rb +45 -0
- data/lib/redis/objects/values.rb +29 -12
- data/lib/redis/objects.rb +10 -8
- data/lib/redis/set.rb +33 -11
- data/lib/redis/sorted_set.rb +275 -0
- data/lib/redis/value.rb +10 -12
- data/spec/redis_objects_instance_spec.rb +363 -37
- data/spec/redis_objects_model_spec.rb +206 -5
- data/spec/spec_helper.rb +1 -0
- metadata +28 -13
- data/lib/redis/objects/core.rb +0 -0
- data/lib/redis/serialize.rb +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,48 +1,142 @@
|
|
|
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
|
-
== Example 1:
|
|
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:
|
|
38
|
+
|
|
39
|
+
model_name:id:field_name
|
|
40
|
+
|
|
41
|
+
=== Initialization
|
|
42
|
+
|
|
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
|
|
27
115
|
|
|
28
|
-
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.
|
|
29
121
|
|
|
30
122
|
=== Initialization
|
|
31
123
|
|
|
32
|
-
|
|
124
|
+
Redis::Objects needs a handle to the +redis+ server. For standalone use, you
|
|
125
|
+
can either set the $redis global variable:
|
|
33
126
|
|
|
34
127
|
$redis = Redis.new(:host => 'localhost', :port => 6379)
|
|
35
|
-
@
|
|
128
|
+
@list = Redis::List.new('mylist')
|
|
36
129
|
|
|
37
|
-
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:
|
|
38
131
|
|
|
39
132
|
redis = Redis.new(:host => 'localhost', :port => 6379)
|
|
40
|
-
@
|
|
133
|
+
@list = Redis::List.new('mylist', redis)
|
|
41
134
|
|
|
42
135
|
=== Counters
|
|
43
136
|
|
|
44
137
|
Create a new counter. The +counter_name+ is the key stored in Redis.
|
|
45
138
|
|
|
139
|
+
require 'redis/counter'
|
|
46
140
|
@counter = Redis::Counter.new('counter_name')
|
|
47
141
|
@counter.increment
|
|
48
142
|
@counter.decrement
|
|
@@ -56,10 +150,39 @@ This gem provides a clean way to do atomic blocks as well:
|
|
|
56
150
|
|
|
57
151
|
See the section on "Atomicity" for cool uses of atomic counter blocks.
|
|
58
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
|
+
|
|
59
181
|
=== Lists
|
|
60
182
|
|
|
61
183
|
Lists work just like Ruby arrays:
|
|
62
184
|
|
|
185
|
+
require 'redis/list'
|
|
63
186
|
@list = Redis::List.new('list_name')
|
|
64
187
|
@list << 'a'
|
|
65
188
|
@list << 'b'
|
|
@@ -75,18 +198,20 @@ Lists work just like Ruby arrays:
|
|
|
75
198
|
@list.clear
|
|
76
199
|
# etc
|
|
77
200
|
|
|
78
|
-
Complex data types are no problem:
|
|
201
|
+
Complex data types are no problem with :marshal => true:
|
|
79
202
|
|
|
203
|
+
@list = Redis::List.new('list_name', :marshal => true)
|
|
80
204
|
@list << {:name => "Nate", :city => "San Diego"}
|
|
81
205
|
@list << {:name => "Peter", :city => "Oceanside"}
|
|
82
206
|
@list.each do |el|
|
|
83
207
|
puts "#{el[:name]} lives in #{el[:city]}"
|
|
84
208
|
end
|
|
85
|
-
|
|
209
|
+
|
|
86
210
|
=== Sets
|
|
87
211
|
|
|
88
212
|
Sets work like the Ruby {Set}[http://ruby-doc.org/core/classes/Set.html] class:
|
|
89
213
|
|
|
214
|
+
require 'redis/set'
|
|
90
215
|
@set = Redis::Set.new('set_name')
|
|
91
216
|
@set << 'a'
|
|
92
217
|
@set << 'b'
|
|
@@ -99,7 +224,7 @@ Sets work like the Ruby {Set}[http://ruby-doc.org/core/classes/Set.html] class:
|
|
|
99
224
|
@set.clear
|
|
100
225
|
# etc
|
|
101
226
|
|
|
102
|
-
You can perform Redis intersections/unions easily:
|
|
227
|
+
You can perform Redis intersections/unions/diffs easily:
|
|
103
228
|
|
|
104
229
|
@set1 = Redis::Set.new('set1')
|
|
105
230
|
@set2 = Redis::Set.new('set2')
|
|
@@ -107,8 +232,11 @@ You can perform Redis intersections/unions easily:
|
|
|
107
232
|
members = @set1 & @set2 # intersection
|
|
108
233
|
members = @set1 | @set2 # union
|
|
109
234
|
members = @set1 + @set2 # union
|
|
235
|
+
members = @set1 ^ @set2 # difference
|
|
236
|
+
members = @set1 - @set2 # difference
|
|
110
237
|
members = @set1.intersection(@set2, @set3) # multiple
|
|
111
238
|
members = @set1.union(@set2, @set3) # multiple
|
|
239
|
+
members = @set1.difference(@set2, @set3) # multiple
|
|
112
240
|
|
|
113
241
|
Or store them in Redis:
|
|
114
242
|
|
|
@@ -116,104 +244,66 @@ Or store them in Redis:
|
|
|
116
244
|
members = @set1.redis.get('intername')
|
|
117
245
|
@set1.unionstore('unionname', @set2, @set3)
|
|
118
246
|
members = @set1.redis.get('unionname')
|
|
247
|
+
@set1.diffstore('diffname', @set2, @set3)
|
|
248
|
+
members = @set1.redis.get('diffname')
|
|
119
249
|
|
|
120
|
-
And use complex data types too:
|
|
250
|
+
And use complex data types too, with :marshal => true:
|
|
121
251
|
|
|
252
|
+
@set1 = Redis::Set.new('set1', :marshal => true)
|
|
253
|
+
@set2 = Redis::Set.new('set2', :marshal => true)
|
|
122
254
|
@set1 << {:name => "Nate", :city => "San Diego"}
|
|
123
255
|
@set1 << {:name => "Peter", :city => "Oceanside"}
|
|
124
256
|
@set2 << {:name => "Nate", :city => "San Diego"}
|
|
125
257
|
@set2 << {:name => "Jeff", :city => "Del Mar"}
|
|
126
258
|
|
|
127
259
|
@set1 & @set2 # Nate
|
|
260
|
+
@set1 - @set2 # Peter
|
|
128
261
|
@set1 | @set2 # all 3 people
|
|
129
262
|
|
|
130
|
-
===
|
|
131
|
-
|
|
132
|
-
Simple values are easy as well:
|
|
133
|
-
|
|
134
|
-
@value = Redis::Value.new('value_name')
|
|
135
|
-
@value.value = 'a'
|
|
136
|
-
@value.delete
|
|
137
|
-
|
|
138
|
-
Of course complex data is no problem:
|
|
139
|
-
|
|
140
|
-
@account = Account.create!(params[:account])
|
|
141
|
-
@newest = Redis::Value.new('newest_account')
|
|
142
|
-
@newest.value = @account
|
|
143
|
-
|
|
144
|
-
== Example 2: Class Usage
|
|
145
|
-
|
|
146
|
-
Using Redis::Objects this way makes it trivial to integrate Redis types with an
|
|
147
|
-
existing ActiveRecord model, DataMapper resource, or other class. Redis::Objects
|
|
148
|
-
will work with any class that provides an +id+ method that returns a unique
|
|
149
|
-
value. Redis::Objects will automatically create keys that are unique to
|
|
150
|
-
each object.
|
|
151
|
-
|
|
152
|
-
=== Initialization
|
|
153
|
-
|
|
154
|
-
If on Rails, config/initializers/redis.rb is a good place for this:
|
|
263
|
+
=== Sorted Sets
|
|
155
264
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
Redis::Objects.redis = Redis.new(:host => 127.0.0.1, :port => 6379)
|
|
159
|
-
|
|
160
|
-
=== Model Class
|
|
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:
|
|
161
267
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
counter :hits
|
|
168
|
-
counter :runs
|
|
169
|
-
counter :outs
|
|
170
|
-
counter :inning, :start => 1
|
|
171
|
-
list :on_base
|
|
172
|
-
set :outfielders
|
|
173
|
-
value :at_bat
|
|
174
|
-
end
|
|
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
|
|
175
273
|
|
|
176
|
-
|
|
274
|
+
# Array access to get sorted order
|
|
275
|
+
@sorted_set[0..2] # => ["Nate", "Jeff", "Peter"]
|
|
276
|
+
@sorted_set[0,2] # => ["Nate", "Jeff"]
|
|
177
277
|
|
|
178
|
-
@
|
|
179
|
-
@
|
|
180
|
-
@
|
|
181
|
-
@team.on_base << 'player3'
|
|
182
|
-
@team.on_base # ['player1', 'player2']
|
|
183
|
-
@team.on_base.pop
|
|
184
|
-
@team.on_base.shift
|
|
185
|
-
@team.on_base.length # 1
|
|
186
|
-
@team.on_base.delete('player3')
|
|
278
|
+
@sorted_set['Peter'] # => 75
|
|
279
|
+
@sorted_set['Jeff'] # => 24
|
|
280
|
+
@sorted_set.score('Jeff') # same thing (24)
|
|
187
281
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
@team.outfielders << 'outfielder1' << 'outfielder1'
|
|
191
|
-
@team.outfielders << 'outfielder2'
|
|
192
|
-
@team.outfielders # ['outfielder1', 'outfielder2']
|
|
193
|
-
@team.outfielders.each do |player|
|
|
194
|
-
puts player
|
|
195
|
-
end
|
|
196
|
-
player = @team.outfielders.detect{|of| of == 'outfielder2'}
|
|
282
|
+
@sorted_set.rank('Peter') # => 2
|
|
283
|
+
@sorted_set.rank('Jeff') # => 1
|
|
197
284
|
|
|
198
|
-
|
|
285
|
+
@sorted_set.first # => "Nate"
|
|
286
|
+
@sorted_set.last # => "Peter"
|
|
287
|
+
@sorted_set.revrange(0,2) # => ["Peter", "Jeff", "Nate"]
|
|
199
288
|
|
|
200
|
-
@
|
|
201
|
-
@
|
|
202
|
-
@team.hits.incr(3) # add 3
|
|
203
|
-
@team.runs = 4 # exception
|
|
289
|
+
@sorted_set['Newbie'] = 1
|
|
290
|
+
@sorted_set.members # => ["Newbie", "Nate", "Jeff", "Peter"]
|
|
204
291
|
|
|
205
|
-
|
|
292
|
+
@sorted_set.rangebyscore(10, 100, :limit => 2) # => ["Nate", "Jeff"]
|
|
293
|
+
@sorted_set.members(:withscores => true) # => [["Newbie", 1], ["Nate", 16], ["Jeff", 28], ["Peter", 76]]
|
|
206
294
|
|
|
207
|
-
|
|
208
|
-
@
|
|
295
|
+
# atomic increment
|
|
296
|
+
@sorted_set.increment('Nate')
|
|
297
|
+
@sorted_set.incr('Peter') # shorthand
|
|
298
|
+
@sorted_set.incr('Jeff', 4)
|
|
209
299
|
|
|
210
|
-
|
|
300
|
+
The other Redis Sorted Set commands are supported as well; see {Sorted Sets API}[http://code.google.com/p/redis/wiki/SortedSets].
|
|
211
301
|
|
|
212
|
-
==
|
|
302
|
+
== Atomic Counters and Locks
|
|
213
303
|
|
|
214
304
|
You are probably not handling atomicity correctly in your app. For a fun rant
|
|
215
305
|
on the topic, see
|
|
216
|
-
{
|
|
306
|
+
{An Atomic Rant}[http://nateware.com/2010/02/18/an-atomic-rant].
|
|
217
307
|
|
|
218
308
|
Atomic counters are a good way to handle concurrency:
|
|
219
309
|
|
|
@@ -260,6 +350,10 @@ Class-level atomic block (may save a DB fetch depending on your app):
|
|
|
260
350
|
|
|
261
351
|
Locks with Redis. On completion or exception the lock is released:
|
|
262
352
|
|
|
353
|
+
class Team < ActiveRecord::Base
|
|
354
|
+
lock :reorder # declare a lock
|
|
355
|
+
end
|
|
356
|
+
|
|
263
357
|
@team.reorder_lock.lock do
|
|
264
358
|
@team.reorder_all_players
|
|
265
359
|
end
|
|
@@ -270,9 +364,19 @@ Class-level lock (same concept)
|
|
|
270
364
|
Team.reorder_all_players(team_id)
|
|
271
365
|
end
|
|
272
366
|
|
|
367
|
+
Lock expiration. Sometimes you want to make sure your locks are cleaned up should
|
|
368
|
+
the unthinkable happen (server failure). You can set lock expirations to handle
|
|
369
|
+
this. Expired locks are released by the next process to attempt lock. Just
|
|
370
|
+
make sure you expiration value is sufficiently large compared to your expected
|
|
371
|
+
lock time.
|
|
372
|
+
|
|
373
|
+
class Team < ActiveRecord::Base
|
|
374
|
+
lock :reorder, :expiration => 15.minutes
|
|
375
|
+
end
|
|
376
|
+
|
|
273
377
|
|
|
274
378
|
== Author
|
|
275
379
|
|
|
276
|
-
Copyright (c) 2009 {Nate Wiger}[http://nate.wiger.org]. All Rights Reserved.
|
|
380
|
+
Copyright (c) 2009-2010 {Nate Wiger}[http://nate.wiger.org]. All Rights Reserved.
|
|
277
381
|
Released under the {Artistic License}[http://www.opensource.org/licenses/artistic-license-2.0.php].
|
|
278
382
|
|
data/lib/redis/counter.rb
CHANGED
|
@@ -7,11 +7,14 @@ class Redis
|
|
|
7
7
|
# class to define a counter.
|
|
8
8
|
#
|
|
9
9
|
class Counter
|
|
10
|
+
require 'redis/helpers/core_commands'
|
|
11
|
+
include Redis::Helpers::CoreCommands
|
|
12
|
+
|
|
10
13
|
attr_reader :key, :options, :redis
|
|
11
|
-
def initialize(key,
|
|
14
|
+
def initialize(key, *args)
|
|
12
15
|
@key = key
|
|
13
|
-
@
|
|
14
|
-
@
|
|
16
|
+
@options = args.last.is_a?(Hash) ? args.pop : {}
|
|
17
|
+
@redis = args.first || $redis
|
|
15
18
|
@options[:start] ||= 0
|
|
16
19
|
@redis.setnx(key, @options[:start]) unless @options[:start] == 0 || @options[:init] === false
|
|
17
20
|
end
|
|
@@ -21,7 +24,8 @@ class Redis
|
|
|
21
24
|
# with a parent and starting over (for example, restarting a game and
|
|
22
25
|
# disconnecting all players).
|
|
23
26
|
def reset(to=options[:start])
|
|
24
|
-
redis.set
|
|
27
|
+
redis.set key, to.to_i
|
|
28
|
+
true # hack for redis-rb regression
|
|
25
29
|
end
|
|
26
30
|
|
|
27
31
|
# Returns the current value of the counter. Normally just calling the
|
|
@@ -33,19 +37,13 @@ class Redis
|
|
|
33
37
|
end
|
|
34
38
|
alias_method :get, :value
|
|
35
39
|
|
|
36
|
-
# Delete a counter. Usage discouraged. Consider +reset+ instead.
|
|
37
|
-
def delete
|
|
38
|
-
redis.del(key)
|
|
39
|
-
end
|
|
40
|
-
alias_method :del, :delete
|
|
41
|
-
|
|
42
40
|
# Increment the counter atomically and return the new value. If passed
|
|
43
41
|
# a block, that block will be evaluated with the new value of the counter
|
|
44
42
|
# as an argument. If the block returns nil or throws an exception, the
|
|
45
43
|
# counter will automatically be decremented to its previous value. This
|
|
46
44
|
# method is aliased as incr() for brevity.
|
|
47
45
|
def increment(by=1, &block)
|
|
48
|
-
val = redis.
|
|
46
|
+
val = redis.incrby(key, by).to_i
|
|
49
47
|
block_given? ? rewindable_block(:decrement, val, &block) : val
|
|
50
48
|
end
|
|
51
49
|
alias_method :incr, :increment
|
|
@@ -56,7 +54,7 @@ class Redis
|
|
|
56
54
|
# counter will automatically be incremented to its previous value. This
|
|
57
55
|
# method is aliased as incr() for brevity.
|
|
58
56
|
def decrement(by=1, &block)
|
|
59
|
-
val = redis.
|
|
57
|
+
val = redis.decrby(key, by).to_i
|
|
60
58
|
block_given? ? rewindable_block(:increment, val, &block) : val
|
|
61
59
|
end
|
|
62
60
|
alias_method :decr, :decrement
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
class Redis
|
|
2
|
+
module Helpers
|
|
3
|
+
# These are core commands that all types share (rename, etc)
|
|
4
|
+
module CoreCommands
|
|
5
|
+
def exists?
|
|
6
|
+
redis.exists key
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def delete
|
|
10
|
+
redis.del key
|
|
11
|
+
end
|
|
12
|
+
alias_method :del, :delete
|
|
13
|
+
alias_method :clear, :delete
|
|
14
|
+
|
|
15
|
+
def type
|
|
16
|
+
redis.type key
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def rename(name, setkey=true)
|
|
20
|
+
dest = name.is_a?(self.class) ? name.key : name
|
|
21
|
+
ret = redis.rename key, dest
|
|
22
|
+
@key = dest if ret && setkey
|
|
23
|
+
ret
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def renamenx(name, setkey=true)
|
|
27
|
+
dest = name.is_a?(self.class) ? name.key : name
|
|
28
|
+
ret = redis.renamenx key, dest
|
|
29
|
+
@key = dest if ret && setkey
|
|
30
|
+
ret
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def expire(seconds)
|
|
34
|
+
redis.expire key, seconds
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def expireat(unixtime)
|
|
38
|
+
redis.expire key, unixtime
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def move(dbindex)
|
|
42
|
+
redis.move key, dbindex
|
|
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
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
class Redis
|
|
2
|
+
module Helpers
|
|
3
|
+
module Serialize
|
|
4
|
+
include Marshal
|
|
5
|
+
|
|
6
|
+
def to_redis(value)
|
|
7
|
+
return value unless options[:marshal]
|
|
8
|
+
case value
|
|
9
|
+
when String, Fixnum, Bignum, Float
|
|
10
|
+
value
|
|
11
|
+
else
|
|
12
|
+
dump(value)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def from_redis(value)
|
|
17
|
+
return value unless options[:marshal]
|
|
18
|
+
case value
|
|
19
|
+
when Array
|
|
20
|
+
value.collect{|v| from_redis(v)}
|
|
21
|
+
else
|
|
22
|
+
restore(value) rescue value
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
data/lib/redis/list.rb
CHANGED
|
@@ -6,14 +6,16 @@ class Redis
|
|
|
6
6
|
class List
|
|
7
7
|
require 'enumerator'
|
|
8
8
|
include Enumerable
|
|
9
|
-
require 'redis/
|
|
10
|
-
include Redis::
|
|
9
|
+
require 'redis/helpers/core_commands'
|
|
10
|
+
include Redis::Helpers::CoreCommands
|
|
11
|
+
require 'redis/helpers/serialize'
|
|
12
|
+
include Redis::Helpers::Serialize
|
|
11
13
|
|
|
12
14
|
attr_reader :key, :options, :redis
|
|
13
|
-
def initialize(key,
|
|
15
|
+
def initialize(key, *args)
|
|
14
16
|
@key = key
|
|
15
|
-
@
|
|
16
|
-
@
|
|
17
|
+
@options = args.last.is_a?(Hash) ? args.pop : {}
|
|
18
|
+
@redis = args.first || $redis
|
|
17
19
|
end
|
|
18
20
|
|
|
19
21
|
# Works like push. Can chain together: list << 'a' << 'b'
|
|
@@ -60,9 +62,10 @@ class Redis
|
|
|
60
62
|
at(index)
|
|
61
63
|
end
|
|
62
64
|
end
|
|
63
|
-
|
|
65
|
+
|
|
64
66
|
# Delete the element(s) from the list that match name. If count is specified,
|
|
65
67
|
# only the first-N (if positive) or last-N (if negative) will be removed.
|
|
68
|
+
# Use .del to completely delete the entire key.
|
|
66
69
|
# Redis: LREM
|
|
67
70
|
def delete(name, count=0)
|
|
68
71
|
redis.lrem(key, count, name) # weird api
|
|
@@ -96,11 +99,6 @@ class Redis
|
|
|
96
99
|
at(-1)
|
|
97
100
|
end
|
|
98
101
|
|
|
99
|
-
# Clear the list entirely. Redis: DEL
|
|
100
|
-
def clear
|
|
101
|
-
redis.del(key)
|
|
102
|
-
end
|
|
103
|
-
|
|
104
102
|
# Return the length of the list. Aliased as size. Redis: LLEN
|
|
105
103
|
def length
|
|
106
104
|
redis.llen(key)
|