redis-objects 0.9.1 → 1.0.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.
- 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
|
=======
|