redis-objects 0.2.2 → 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 +192 -105
- 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 +42 -5
- data/lib/redis/objects/counters.rb +4 -3
- data/lib/redis/objects/locks.rb +1 -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 +300 -36
- data/spec/redis_objects_model_spec.rb +18 -4
- metadata +26 -13
- data/ChangeLog +0 -15
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,44 +1,136 @@
|
|
|
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
|
-
|
|
33
|
-
either set the $redis global variable
|
|
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
|
-
@
|
|
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
|
-
@
|
|
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
|
+
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
|
+
|
|
61
181
|
=== Lists
|
|
62
182
|
|
|
63
183
|
Lists work just like Ruby arrays:
|
|
@@ -78,14 +198,15 @@ Lists work just like Ruby arrays:
|
|
|
78
198
|
@list.clear
|
|
79
199
|
# etc
|
|
80
200
|
|
|
81
|
-
Complex data types are no problem:
|
|
201
|
+
Complex data types are no problem with :marshal => true:
|
|
82
202
|
|
|
203
|
+
@list = Redis::List.new('list_name', :marshal => true)
|
|
83
204
|
@list << {:name => "Nate", :city => "San Diego"}
|
|
84
205
|
@list << {:name => "Peter", :city => "Oceanside"}
|
|
85
206
|
@list.each do |el|
|
|
86
207
|
puts "#{el[:name]} lives in #{el[:city]}"
|
|
87
208
|
end
|
|
88
|
-
|
|
209
|
+
|
|
89
210
|
=== Sets
|
|
90
211
|
|
|
91
212
|
Sets work like the Ruby {Set}[http://ruby-doc.org/core/classes/Set.html] class:
|
|
@@ -111,8 +232,8 @@ You can perform Redis intersections/unions/diffs easily:
|
|
|
111
232
|
members = @set1 & @set2 # intersection
|
|
112
233
|
members = @set1 | @set2 # union
|
|
113
234
|
members = @set1 + @set2 # union
|
|
114
|
-
members = @set1 ^ @set2 #
|
|
115
|
-
members = @set1 - @set2 #
|
|
235
|
+
members = @set1 ^ @set2 # difference
|
|
236
|
+
members = @set1 - @set2 # difference
|
|
116
237
|
members = @set1.intersection(@set2, @set3) # multiple
|
|
117
238
|
members = @set1.union(@set2, @set3) # multiple
|
|
118
239
|
members = @set1.difference(@set2, @set3) # multiple
|
|
@@ -126,8 +247,10 @@ Or store them in Redis:
|
|
|
126
247
|
@set1.diffstore('diffname', @set2, @set3)
|
|
127
248
|
members = @set1.redis.get('diffname')
|
|
128
249
|
|
|
129
|
-
And use complex data types too:
|
|
250
|
+
And use complex data types too, with :marshal => true:
|
|
130
251
|
|
|
252
|
+
@set1 = Redis::Set.new('set1', :marshal => true)
|
|
253
|
+
@set2 = Redis::Set.new('set2', :marshal => true)
|
|
131
254
|
@set1 << {:name => "Nate", :city => "San Diego"}
|
|
132
255
|
@set1 << {:name => "Peter", :city => "Oceanside"}
|
|
133
256
|
@set2 << {:name => "Nate", :city => "San Diego"}
|
|
@@ -137,100 +260,50 @@ And use complex data types too:
|
|
|
137
260
|
@set1 - @set2 # Peter
|
|
138
261
|
@set1 | @set2 # all 3 people
|
|
139
262
|
|
|
140
|
-
===
|
|
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
|
|
156
|
-
|
|
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
|
|
172
|
-
|
|
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
|
|
263
|
+
=== Sorted Sets
|
|
186
264
|
|
|
187
|
-
|
|
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:
|
|
188
267
|
|
|
189
|
-
|
|
190
|
-
@
|
|
191
|
-
@
|
|
192
|
-
@
|
|
193
|
-
@
|
|
194
|
-
@team.on_base.pop
|
|
195
|
-
@team.on_base.shift
|
|
196
|
-
@team.on_base.length # 1
|
|
197
|
-
@team.on_base.delete('player3')
|
|
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
|
|
198
273
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
@
|
|
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'}
|
|
274
|
+
# Array access to get sorted order
|
|
275
|
+
@sorted_set[0..2] # => ["Nate", "Jeff", "Peter"]
|
|
276
|
+
@sorted_set[0,2] # => ["Nate", "Jeff"]
|
|
208
277
|
|
|
209
|
-
|
|
278
|
+
@sorted_set['Peter'] # => 75
|
|
279
|
+
@sorted_set['Jeff'] # => 24
|
|
280
|
+
@sorted_set.score('Jeff') # same thing (24)
|
|
210
281
|
|
|
211
|
-
@
|
|
212
|
-
@
|
|
282
|
+
@sorted_set.rank('Peter') # => 2
|
|
283
|
+
@sorted_set.rank('Jeff') # => 1
|
|
213
284
|
|
|
214
|
-
|
|
285
|
+
@sorted_set.first # => "Nate"
|
|
286
|
+
@sorted_set.last # => "Peter"
|
|
287
|
+
@sorted_set.revrange(0,2) # => ["Peter", "Jeff", "Nate"]
|
|
215
288
|
|
|
216
|
-
@
|
|
217
|
-
@
|
|
218
|
-
@team.hits.incr(3) # add 3
|
|
219
|
-
@team.runs = 4 # exception
|
|
289
|
+
@sorted_set['Newbie'] = 1
|
|
290
|
+
@sorted_set.members # => ["Newbie", "Nate", "Jeff", "Peter"]
|
|
220
291
|
|
|
221
|
-
|
|
222
|
-
|
|
292
|
+
@sorted_set.rangebyscore(10, 100, :limit => 2) # => ["Nate", "Jeff"]
|
|
293
|
+
@sorted_set.members(:withscores => true) # => [["Newbie", 1], ["Nate", 16], ["Jeff", 28], ["Peter", 76]]
|
|
223
294
|
|
|
224
|
-
|
|
225
|
-
@
|
|
295
|
+
# atomic increment
|
|
296
|
+
@sorted_set.increment('Nate')
|
|
297
|
+
@sorted_set.incr('Peter') # shorthand
|
|
298
|
+
@sorted_set.incr('Jeff', 4)
|
|
226
299
|
|
|
227
|
-
|
|
300
|
+
The other Redis Sorted Set commands are supported as well; see {Sorted Sets API}[http://code.google.com/p/redis/wiki/SortedSets].
|
|
228
301
|
|
|
229
302
|
== Atomic Counters and Locks
|
|
230
303
|
|
|
231
304
|
You are probably not handling atomicity correctly in your app. For a fun rant
|
|
232
305
|
on the topic, see
|
|
233
|
-
{
|
|
306
|
+
{An Atomic Rant}[http://nateware.com/2010/02/18/an-atomic-rant].
|
|
234
307
|
|
|
235
308
|
Atomic counters are a good way to handle concurrency:
|
|
236
309
|
|
|
@@ -277,6 +350,10 @@ Class-level atomic block (may save a DB fetch depending on your app):
|
|
|
277
350
|
|
|
278
351
|
Locks with Redis. On completion or exception the lock is released:
|
|
279
352
|
|
|
353
|
+
class Team < ActiveRecord::Base
|
|
354
|
+
lock :reorder # declare a lock
|
|
355
|
+
end
|
|
356
|
+
|
|
280
357
|
@team.reorder_lock.lock do
|
|
281
358
|
@team.reorder_all_players
|
|
282
359
|
end
|
|
@@ -287,9 +364,19 @@ Class-level lock (same concept)
|
|
|
287
364
|
Team.reorder_all_players(team_id)
|
|
288
365
|
end
|
|
289
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
|
+
|
|
290
377
|
|
|
291
378
|
== Author
|
|
292
379
|
|
|
293
|
-
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.
|
|
294
381
|
Released under the {Artistic License}[http://www.opensource.org/licenses/artistic-license-2.0.php].
|
|
295
382
|
|
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
|
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
@@ -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(
|
|
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
|