redis-objects 0.9.1 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.rdoc +13 -1
- data/README.md +306 -234
- data/lib/redis/base_object.rb +4 -13
- data/lib/redis/counter.rb +4 -2
- data/lib/redis/hash_key.rb +4 -2
- data/lib/redis/list.rb +2 -3
- data/lib/redis/objects/version.rb +1 -1
- data/lib/redis/sorted_set.rb +12 -5
- data/lib/redis/value.rb +1 -1
- data/spec/redis_objects_instance_spec.rb +240 -64
- metadata +3 -4
- data/VERSION +0 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c4689abbe0340e8c683371bac1b94b29893a7c5b
|
4
|
+
data.tar.gz: 41678b7dd50984a9b7c0c974e2a9eadf6d8a712d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 917dad1c9f8edd6eb355dbdec952d7ade90b45b6fa7b1dd81266dff885ff09bf7d5fbf2e01a3d0ffec05e6c7dccff46d6f14b52fae790e1030a7c30d0c9da84f
|
7
|
+
data.tar.gz: e64a57836c26bcc882517f2de31fb1c9b3a95394c9794f10646fd77f244e3db454bec529e92376fb7ff802dec048ec355b3f7c254e3140022a52b32bbc7fda7b
|
data/CHANGELOG.rdoc
CHANGED
@@ -1,6 +1,18 @@
|
|
1
1
|
= Changelog for Redis::Objects
|
2
2
|
|
3
|
-
== 0.
|
3
|
+
== 1.0.0 (25 Jul 2014)
|
4
|
+
|
5
|
+
* Fix expiration filter to handle atomic blocks, remove method aliasing [nateware]
|
6
|
+
|
7
|
+
* Fix incrbyfloat to actually return a float [james-lawrence]
|
8
|
+
|
9
|
+
* Allow false as default: value (bugfix) [james-lawrence]
|
10
|
+
|
11
|
+
* Allow unionstore and interstore between sorted sets and sets [jrdi]
|
12
|
+
|
13
|
+
* Add syntax highlighting to README.md [bartolsthoorn]
|
14
|
+
|
15
|
+
== 0.9.1 (25 Mar 2014)
|
4
16
|
|
5
17
|
* Fix bad marshal calls in SortedSet [Fleurer, nateware]
|
6
18
|
|
data/README.md
CHANGED
@@ -32,13 +32,17 @@ Installation and Setup
|
|
32
32
|
----------------------
|
33
33
|
Add it to your Gemfile as:
|
34
34
|
|
35
|
-
|
35
|
+
~~~ruby
|
36
|
+
gem 'redis-objects'
|
37
|
+
~~~
|
36
38
|
|
37
39
|
Redis::Objects needs a handle created by `Redis.new`. The recommended approach
|
38
40
|
is to set `Redis.current` to point to your server, which Redis::Objects will
|
39
41
|
pick up automatically.
|
40
42
|
|
41
|
-
|
43
|
+
~~~ruby
|
44
|
+
Redis.current = Redis.new(:host => '127.0.0.1', :port => 6379)
|
45
|
+
~~~
|
42
46
|
|
43
47
|
(If you're on Rails, `config/initializers/redis.rb` is a good place for this.)
|
44
48
|
Remember you can use Redis::Objects in any Ruby code. There are **no** dependencies
|
@@ -46,19 +50,23 @@ on Rails. Standalone, Sinatra, Resque - no problem.
|
|
46
50
|
|
47
51
|
Alternatively, you can set the `redis` handle directly:
|
48
52
|
|
49
|
-
|
53
|
+
~~~ruby
|
54
|
+
Redis::Objects.redis = Redis.new(...)
|
55
|
+
~~~
|
50
56
|
|
51
57
|
Finally, you can even set different handles for different classes:
|
52
58
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
+
~~~ruby
|
60
|
+
class User
|
61
|
+
include Redis::Objects
|
62
|
+
end
|
63
|
+
class Post
|
64
|
+
include Redis::Objects
|
65
|
+
end
|
59
66
|
|
60
|
-
|
61
|
-
|
67
|
+
User.redis = Redis.new(:host => '1.2.3.4')
|
68
|
+
Post.redis = Redis.new(:host => '5.6.7.8')
|
69
|
+
~~~
|
62
70
|
|
63
71
|
As of `0.7.0`, `redis-objects` now autoloads the appropriate `Redis::Whatever`
|
64
72
|
classes on demand. Previous strategies of individually requiring `redis/list`
|
@@ -75,95 +83,111 @@ Redis::Objects automatically creates keys that are unique to each object, in the
|
|
75
83
|
|
76
84
|
For illustration purposes, consider this stub class:
|
77
85
|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
86
|
+
~~~ruby
|
87
|
+
class User
|
88
|
+
include Redis::Objects
|
89
|
+
counter :my_posts
|
90
|
+
def id
|
91
|
+
1
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
user = User.new
|
96
|
+
user.id # 1
|
97
|
+
user.my_posts.increment
|
98
|
+
user.my_posts.increment
|
99
|
+
user.my_posts.increment
|
100
|
+
puts user.my_posts # 3
|
101
|
+
user.my_posts.reset
|
102
|
+
puts user.my_posts.value # 0
|
103
|
+
user.my_posts.reset 5
|
104
|
+
puts user.my_posts.value # 5
|
105
|
+
~~~
|
96
106
|
|
97
107
|
Here's an example that integrates several data types with an ActiveRecord model:
|
98
108
|
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
109
|
+
~~~ruby
|
110
|
+
class Team < ActiveRecord::Base
|
111
|
+
include Redis::Objects
|
112
|
+
|
113
|
+
lock :trade_players, :expiration => 15 # sec
|
114
|
+
value :at_bat
|
115
|
+
counter :hits
|
116
|
+
counter :runs
|
117
|
+
counter :outs
|
118
|
+
counter :inning, :start => 1
|
119
|
+
list :on_base
|
120
|
+
list :coaches, :marshal => true
|
121
|
+
set :outfielders
|
122
|
+
hash_key :pitchers_faced # "hash" is taken by Ruby
|
123
|
+
sorted_set :rank, :global => true
|
124
|
+
end
|
125
|
+
~~~
|
114
126
|
|
115
127
|
Familiar Ruby array operations Just Work (TM):
|
116
128
|
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
129
|
+
~~~ruby
|
130
|
+
@team = Team.find_by_name('New York Yankees')
|
131
|
+
@team.on_base << 'player1'
|
132
|
+
@team.on_base << 'player2'
|
133
|
+
@team.on_base << 'player3'
|
134
|
+
@team.on_base # ['player1', 'player2', 'player3']
|
135
|
+
@team.on_base.pop
|
136
|
+
@team.on_base.shift
|
137
|
+
@team.on_base.length # 1
|
138
|
+
@team.on_base.delete('player2')
|
139
|
+
~~~
|
126
140
|
|
127
141
|
Sets work too:
|
128
142
|
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
143
|
+
~~~ruby
|
144
|
+
@team.outfielders << 'outfielder1'
|
145
|
+
@team.outfielders << 'outfielder2'
|
146
|
+
@team.outfielders << 'outfielder1' # dup ignored
|
147
|
+
@team.outfielders # ['outfielder1', 'outfielder2']
|
148
|
+
@team.outfielders.each do |player|
|
149
|
+
puts player
|
150
|
+
end
|
151
|
+
player = @team.outfielders.detect{|of| of == 'outfielder2'}
|
152
|
+
~~~
|
137
153
|
|
138
154
|
And you can do unions and intersections between objects (kinda cool):
|
139
155
|
|
140
|
-
|
141
|
-
|
156
|
+
~~~ruby
|
157
|
+
@team1.outfielders | @team2.outfielders # outfielders on both teams
|
158
|
+
@team1.outfielders & @team2.outfielders # in baseball, should be empty :-)
|
159
|
+
~~~
|
142
160
|
|
143
161
|
Counters can be atomically incremented/decremented (but not assigned):
|
144
162
|
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
163
|
+
~~~ruby
|
164
|
+
@team.hits.increment # or incr
|
165
|
+
@team.hits.decrement # or decr
|
166
|
+
@team.hits.incr(3) # add 3
|
167
|
+
@team.runs = 4 # exception
|
168
|
+
~~~
|
149
169
|
|
150
170
|
Defining a different method as the `id` field is easy
|
151
171
|
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
172
|
+
~~~ruby
|
173
|
+
class User
|
174
|
+
include Redis::Objects
|
175
|
+
redis_id_field :uid
|
176
|
+
counter :my_posts
|
177
|
+
end
|
157
178
|
|
158
|
-
|
159
|
-
|
179
|
+
user.uid # 195137a1bdea4473
|
180
|
+
user.my_posts.increment # 1
|
181
|
+
~~~
|
160
182
|
|
161
183
|
Finally, for free, you get a `redis` method that points directly to a Redis connection:
|
162
184
|
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
185
|
+
~~~ruby
|
186
|
+
Team.redis.get('somekey')
|
187
|
+
@team = Team.new
|
188
|
+
@team.redis.get('somekey')
|
189
|
+
@team.redis.smembers('someset')
|
190
|
+
~~~
|
167
191
|
|
168
192
|
You can use the `redis` handle to directly call any [Redis API command](http://redis.io/commands).
|
169
193
|
|
@@ -179,17 +203,21 @@ Counters
|
|
179
203
|
--------
|
180
204
|
The `counter_name` is the key stored in Redis.
|
181
205
|
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
206
|
+
~~~ruby
|
207
|
+
@counter = Redis::Counter.new('counter_name')
|
208
|
+
@counter.increment # or incr
|
209
|
+
@counter.decrement # or decr
|
210
|
+
@counter.increment(3)
|
211
|
+
puts @counter.value
|
212
|
+
~~~
|
187
213
|
|
188
214
|
This gem provides a clean way to do atomic blocks as well:
|
189
215
|
|
190
|
-
|
191
|
-
|
192
|
-
|
216
|
+
~~~ruby
|
217
|
+
@counter.increment do |val|
|
218
|
+
raise "Full" if val > MAX_VAL # rewind counter
|
219
|
+
end
|
220
|
+
~~~
|
193
221
|
|
194
222
|
See the section on [Atomic Counters and Locks](#atomicity) for cool uses of atomic counter blocks.
|
195
223
|
|
@@ -197,10 +225,12 @@ Locks
|
|
197
225
|
-----
|
198
226
|
A convenience class that wraps the pattern of [using setnx to perform locking](http://redis.io/commands/setnx).
|
199
227
|
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
228
|
+
~~~ruby
|
229
|
+
@lock = Redis::Lock.new('serialize_stuff', :expiration => 15, :timeout => 0.1)
|
230
|
+
@lock.lock do
|
231
|
+
# do work
|
232
|
+
end
|
233
|
+
~~~
|
204
234
|
|
205
235
|
This can be especially useful if you're running batch jobs spread across multiple hosts.
|
206
236
|
|
@@ -208,49 +238,59 @@ Values
|
|
208
238
|
------
|
209
239
|
Simple values are easy as well:
|
210
240
|
|
211
|
-
|
212
|
-
|
213
|
-
|
241
|
+
~~~ruby
|
242
|
+
@value = Redis::Value.new('value_name')
|
243
|
+
@value.value = 'a'
|
244
|
+
@value.delete
|
245
|
+
~~~
|
214
246
|
|
215
247
|
Complex data is no problem with :marshal => true:
|
216
248
|
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
249
|
+
~~~ruby
|
250
|
+
@account = Account.create!(params[:account])
|
251
|
+
@newest = Redis::Value.new('newest_account', :marshal => true)
|
252
|
+
@newest.value = @account.attributes
|
253
|
+
puts @newest.value['username']
|
254
|
+
~~~
|
221
255
|
|
222
256
|
Lists
|
223
257
|
-----
|
224
258
|
Lists work just like Ruby arrays:
|
225
259
|
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
260
|
+
~~~ruby
|
261
|
+
@list = Redis::List.new('list_name')
|
262
|
+
@list << 'a'
|
263
|
+
@list << 'b'
|
264
|
+
@list.include? 'c' # false
|
265
|
+
@list.values # ['a','b']
|
266
|
+
@list << 'c'
|
267
|
+
@list.delete('c')
|
268
|
+
@list[0]
|
269
|
+
@list[0,1]
|
270
|
+
@list[0..1]
|
271
|
+
@list.shift
|
272
|
+
@list.pop
|
273
|
+
@list.clear
|
274
|
+
# etc
|
275
|
+
~~~
|
240
276
|
|
241
277
|
You can bound the size of the list to only hold N elements like so:
|
242
278
|
|
243
|
-
|
244
|
-
|
279
|
+
~~~ruby
|
280
|
+
# Only holds 10 elements, throws out old ones when you reach :maxlength.
|
281
|
+
@list = Redis::List.new('list_name', :maxlength => 10)
|
282
|
+
~~~
|
245
283
|
|
246
284
|
Complex data types are now handled with :marshal => true:
|
247
285
|
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
286
|
+
~~~ruby
|
287
|
+
@list = Redis::List.new('list_name', :marshal => true)
|
288
|
+
@list << {:name => "Nate", :city => "San Diego"}
|
289
|
+
@list << {:name => "Peter", :city => "Oceanside"}
|
290
|
+
@list.each do |el|
|
291
|
+
puts "#{el[:name]} lives in #{el[:city]}"
|
292
|
+
end
|
293
|
+
~~~
|
254
294
|
|
255
295
|
Hashes
|
256
296
|
------
|
@@ -258,21 +298,25 @@ Hashes work like a Ruby [Hash](http://ruby-doc.org/core/classes/Hash.html), with
|
|
258
298
|
a few Redis-specific additions. (The class name is "HashKey" not just "Hash", due to
|
259
299
|
conflicts with the Ruby core Hash class in other gems.)
|
260
300
|
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
301
|
+
~~~ruby
|
302
|
+
@hash = Redis::HashKey.new('hash_name')
|
303
|
+
@hash['a'] = 1
|
304
|
+
@hash['b'] = 2
|
305
|
+
@hash.each do |k,v|
|
306
|
+
puts "#{k} = #{v}"
|
307
|
+
end
|
308
|
+
@hash['c'] = 3
|
309
|
+
puts @hash.all # {"a"=>"1","b"=>"2","c"=>"3"}
|
310
|
+
@hash.clear
|
311
|
+
~~~
|
270
312
|
|
271
313
|
Redis also adds incrementing and bulk operations:
|
272
314
|
|
273
|
-
|
274
|
-
|
275
|
-
|
315
|
+
~~~ruby
|
316
|
+
@hash.incr('c', 6) # 9
|
317
|
+
@hash.bulk_set('d' => 5, 'e' => 6)
|
318
|
+
@hash.bulk_get('d','e') # "5", "6"
|
319
|
+
~~~
|
276
320
|
|
277
321
|
Remember that numbers become strings in Redis. Unlike with other Redis data types,
|
278
322
|
`redis-objects` can't guess at your data type in this situation, since you may
|
@@ -283,91 +327,101 @@ Sets
|
|
283
327
|
Sets work like the Ruby [Set](http://ruby-doc.org/core/classes/Set.html) class.
|
284
328
|
They are unordered, but guarantee uniqueness of members.
|
285
329
|
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
330
|
+
~~~ruby
|
331
|
+
@set = Redis::Set.new('set_name')
|
332
|
+
@set << 'a'
|
333
|
+
@set << 'b'
|
334
|
+
@set << 'a' # dup ignored
|
335
|
+
@set.member? 'c' # false
|
336
|
+
@set.members # ['a','b']
|
337
|
+
@set.members.reverse # ['b','a']
|
338
|
+
@set.each do |member|
|
339
|
+
puts member
|
340
|
+
end
|
341
|
+
@set.clear
|
342
|
+
# etc
|
343
|
+
~~~
|
298
344
|
|
299
345
|
You can perform Redis intersections/unions/diffs easily:
|
300
346
|
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
347
|
+
~~~ruby
|
348
|
+
@set1 = Redis::Set.new('set1')
|
349
|
+
@set2 = Redis::Set.new('set2')
|
350
|
+
@set3 = Redis::Set.new('set3')
|
351
|
+
members = @set1 & @set2 # intersection
|
352
|
+
members = @set1 | @set2 # union
|
353
|
+
members = @set1 + @set2 # union
|
354
|
+
members = @set1 ^ @set2 # difference
|
355
|
+
members = @set1 - @set2 # difference
|
356
|
+
members = @set1.intersection(@set2, @set3) # multiple
|
357
|
+
members = @set1.union(@set2, @set3) # multiple
|
358
|
+
members = @set1.difference(@set2, @set3) # multiple
|
359
|
+
~~~
|
312
360
|
|
313
361
|
Or store them in Redis:
|
314
362
|
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
363
|
+
~~~ruby
|
364
|
+
@set1.interstore('intername', @set2, @set3)
|
365
|
+
members = @set1.redis.get('intername')
|
366
|
+
@set1.unionstore('unionname', @set2, @set3)
|
367
|
+
members = @set1.redis.get('unionname')
|
368
|
+
@set1.diffstore('diffname', @set2, @set3)
|
369
|
+
members = @set1.redis.get('diffname')
|
370
|
+
~~~
|
321
371
|
|
322
372
|
And use complex data types too, with :marshal => true:
|
323
373
|
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
374
|
+
~~~ruby
|
375
|
+
@set1 = Redis::Set.new('set1', :marshal => true)
|
376
|
+
@set2 = Redis::Set.new('set2', :marshal => true)
|
377
|
+
@set1 << {:name => "Nate", :city => "San Diego"}
|
378
|
+
@set1 << {:name => "Peter", :city => "Oceanside"}
|
379
|
+
@set2 << {:name => "Nate", :city => "San Diego"}
|
380
|
+
@set2 << {:name => "Jeff", :city => "Del Mar"}
|
330
381
|
|
331
|
-
|
332
|
-
|
333
|
-
|
382
|
+
@set1 & @set2 # Nate
|
383
|
+
@set1 - @set2 # Peter
|
384
|
+
@set1 | @set2 # all 3 people
|
385
|
+
~~~
|
334
386
|
|
335
387
|
Sorted Sets
|
336
388
|
-----------
|
337
389
|
Due to their unique properties, Sorted Sets work like a hybrid between
|
338
390
|
a Hash and an Array. You assign like a Hash, but retrieve like an Array:
|
339
391
|
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
392
|
+
~~~ruby
|
393
|
+
@sorted_set = Redis::SortedSet.new('number_of_posts')
|
394
|
+
@sorted_set['Nate'] = 15
|
395
|
+
@sorted_set['Peter'] = 75
|
396
|
+
@sorted_set['Jeff'] = 24
|
344
397
|
|
345
|
-
|
346
|
-
|
347
|
-
|
398
|
+
# Array access to get sorted order
|
399
|
+
@sorted_set[0..2] # => ["Nate", "Jeff", "Peter"]
|
400
|
+
@sorted_set[0,2] # => ["Nate", "Jeff"]
|
348
401
|
|
349
|
-
|
350
|
-
|
351
|
-
|
402
|
+
@sorted_set['Peter'] # => 75
|
403
|
+
@sorted_set['Jeff'] # => 24
|
404
|
+
@sorted_set.score('Jeff') # same thing (24)
|
352
405
|
|
353
|
-
|
354
|
-
|
406
|
+
@sorted_set.rank('Peter') # => 2
|
407
|
+
@sorted_set.rank('Jeff') # => 1
|
355
408
|
|
356
|
-
|
357
|
-
|
358
|
-
|
409
|
+
@sorted_set.first # => "Nate"
|
410
|
+
@sorted_set.last # => "Peter"
|
411
|
+
@sorted_set.revrange(0,2) # => ["Peter", "Jeff", "Nate"]
|
359
412
|
|
360
|
-
|
361
|
-
|
362
|
-
|
413
|
+
@sorted_set['Newbie'] = 1
|
414
|
+
@sorted_set.members # => ["Newbie", "Nate", "Jeff", "Peter"]
|
415
|
+
@sorted_set.members.reverse # => ["Peter", "Jeff", "Nate", "Newbie"]
|
363
416
|
|
364
|
-
|
365
|
-
|
417
|
+
@sorted_set.rangebyscore(10, 100, :limit => 2) # => ["Nate", "Jeff"]
|
418
|
+
@sorted_set.members(:with_scores => true) # => [["Newbie", 1], ["Nate", 16], ["Jeff", 28], ["Peter", 76]]
|
366
419
|
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
420
|
+
# atomic increment
|
421
|
+
@sorted_set.increment('Nate')
|
422
|
+
@sorted_set.incr('Peter') # shorthand
|
423
|
+
@sorted_set.incr('Jeff', 4)
|
424
|
+
~~~
|
371
425
|
|
372
426
|
The other Redis Sorted Set commands are supported as well; see [Sorted Sets API](http://redis.io/commands#sorted_set).
|
373
427
|
|
@@ -379,65 +433,79 @@ on the topic, see [An Atomic Rant](http://nateware.com/an-atomic-rant.html).
|
|
379
433
|
|
380
434
|
Atomic counters are a good way to handle concurrency:
|
381
435
|
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
436
|
+
~~~ruby
|
437
|
+
@team = Team.find(1)
|
438
|
+
if @team.drafted_players.increment <= @team.max_players
|
439
|
+
# do stuff
|
440
|
+
@team.team_players.create!(:player_id => 221)
|
441
|
+
@team.active_players.increment
|
442
|
+
else
|
443
|
+
# reset counter state
|
444
|
+
@team.drafted_players.decrement
|
445
|
+
end
|
446
|
+
~~~
|
391
447
|
|
392
448
|
An _atomic block_ gives you a cleaner way to do the above. Exceptions or returning nil
|
393
449
|
will rewind the counter back to its previous state:
|
394
450
|
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
451
|
+
~~~ruby
|
452
|
+
@team.drafted_players.increment do |val|
|
453
|
+
raise Team::TeamFullError if val > @team.max_players # rewind
|
454
|
+
@team.team_players.create!(:player_id => 221)
|
455
|
+
@team.active_players.increment
|
456
|
+
end
|
457
|
+
~~~
|
400
458
|
|
401
459
|
Here's a similar approach, using an if block (failure rewinds counter):
|
402
460
|
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
461
|
+
~~~ruby
|
462
|
+
@team.drafted_players.increment do |val|
|
463
|
+
if val <= @team.max_players
|
464
|
+
@team.team_players.create!(:player_id => 221)
|
465
|
+
@team.active_players.increment
|
466
|
+
end
|
467
|
+
end
|
468
|
+
~~~
|
409
469
|
|
410
470
|
Class methods work too, using the familiar ActiveRecord counter syntax:
|
411
471
|
|
412
|
-
|
413
|
-
|
414
|
-
|
472
|
+
~~~ruby
|
473
|
+
Team.increment_counter :drafted_players, team_id
|
474
|
+
Team.decrement_counter :drafted_players, team_id, 2
|
475
|
+
Team.increment_counter :total_online_players # no ID on global counter
|
476
|
+
~~~
|
415
477
|
|
416
478
|
Class-level atomic blocks can also be used. This may save a DB fetch, if you have
|
417
479
|
a record ID and don't need any other attributes from the DB table:
|
418
480
|
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
481
|
+
~~~ruby
|
482
|
+
Team.increment_counter(:drafted_players, team_id) do |val|
|
483
|
+
TeamPitcher.create!(:team_id => team_id, :pitcher_id => 181)
|
484
|
+
Team.increment_counter(:active_players, team_id)
|
485
|
+
end
|
486
|
+
~~~
|
423
487
|
|
424
488
|
### Locks ###
|
425
489
|
|
426
490
|
Locks work similarly. On completion or exception the lock is released:
|
427
491
|
|
428
|
-
|
429
|
-
|
430
|
-
|
492
|
+
~~~ruby
|
493
|
+
class Team < ActiveRecord::Base
|
494
|
+
lock :reorder # declare a lock
|
495
|
+
end
|
431
496
|
|
432
|
-
|
433
|
-
|
434
|
-
|
497
|
+
@team.reorder_lock.lock do
|
498
|
+
@team.reorder_all_players
|
499
|
+
end
|
500
|
+
~~~
|
435
501
|
|
436
502
|
Class-level lock (same concept)
|
437
503
|
|
438
|
-
|
439
|
-
|
440
|
-
|
504
|
+
~~~ruby
|
505
|
+
Team.obtain_lock(:reorder, team_id) do
|
506
|
+
Team.reorder_all_players(team_id)
|
507
|
+
end
|
508
|
+
~~~
|
441
509
|
|
442
510
|
Lock expiration. Sometimes you want to make sure your locks are cleaned up should
|
443
511
|
the unthinkable happen (server failure). You can set lock expirations to handle
|
@@ -445,9 +513,11 @@ this. Expired locks are released by the next process to attempt lock. Just
|
|
445
513
|
make sure you expiration value is sufficiently large compared to your expected
|
446
514
|
lock time.
|
447
515
|
|
448
|
-
|
449
|
-
|
450
|
-
|
516
|
+
~~~ruby
|
517
|
+
class Team < ActiveRecord::Base
|
518
|
+
lock :reorder, :expiration => 15.minutes
|
519
|
+
end
|
520
|
+
~~~
|
451
521
|
|
452
522
|
Keep in mind that true locks serialize your entire application at that point. As
|
453
523
|
such, atomic counters are strongly preferred.
|
@@ -456,8 +526,10 @@ such, atomic counters are strongly preferred.
|
|
456
526
|
|
457
527
|
Use :expiration and :expireat options to set default expiration.
|
458
528
|
|
459
|
-
|
460
|
-
|
529
|
+
~~~ruby
|
530
|
+
value :value_with_expiration, :expiration => 1.hour
|
531
|
+
value :value_with_expireat, :expireat => Time.now + 1.hour
|
532
|
+
~~~
|
461
533
|
|
462
534
|
Author
|
463
535
|
=======
|