redis-objects 0.6.1 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.rdoc +28 -12
- data/{README.rdoc → README.md} +128 -100
- data/Rakefile +2 -2
- data/VERSION +1 -1
- data/lib/redis-objects.rb +1 -0
- data/lib/redis/base_object.rb +6 -1
- data/lib/redis/counter.rb +8 -8
- data/lib/redis/hash_key.rb +1 -2
- data/lib/redis/helpers/core_commands.rb +1 -1
- data/lib/redis/helpers/serialize.rb +13 -1
- data/lib/redis/list.rb +5 -5
- data/lib/redis/lock.rb +2 -2
- data/lib/redis/objects.rb +35 -15
- data/lib/redis/objects/counters.rb +11 -11
- data/lib/redis/objects/hashes.rb +1 -1
- data/lib/redis/objects/lists.rb +2 -2
- data/lib/redis/objects/locks.rb +4 -4
- data/lib/redis/objects/sets.rb +3 -3
- data/lib/redis/objects/sorted_sets.rb +2 -2
- data/lib/redis/objects/values.rb +4 -4
- data/lib/redis/set.rb +1 -1
- data/lib/redis/sorted_set.rb +1 -1
- data/lib/redis/value.rb +2 -2
- data/redis-objects.gemspec +8 -6
- data/spec/redis_autoload_objects_spec.rb +46 -0
- data/spec/redis_namespace_compat_spec.rb +2 -2
- data/spec/redis_objects_active_record_spec.rb +1 -1
- data/spec/redis_objects_conn_spec.rb +104 -0
- data/spec/redis_objects_instance_spec.rb +15 -21
- data/spec/redis_objects_model_spec.rb +103 -23
- data/spec/spec_helper.rb +26 -6
- metadata +8 -9
- data/Gemfile.lock +0 -43
data/CHANGELOG.rdoc
CHANGED
@@ -1,6 +1,22 @@
|
|
1
1
|
= Changelog for Redis::Objects
|
2
2
|
|
3
|
-
== 0.
|
3
|
+
== 0.7.0 (27 Feb 2013)
|
4
|
+
|
5
|
+
* Enable inheritance of Redis::Objects models [rossta]
|
6
|
+
|
7
|
+
* Finally fix require/autoload so "require 'redis/foo'" is not needed [nateware]
|
8
|
+
|
9
|
+
* Redis::Objects.redis= now properly sets subclass handles as expected [nateware]
|
10
|
+
|
11
|
+
* Add lib/redis-objects.rb for easier Gemfile require [notEthan]
|
12
|
+
|
13
|
+
* Fix wrong readme line, fix some indentation [giglemad]
|
14
|
+
|
15
|
+
== 0.6.1 (13 Dec 2012)
|
16
|
+
|
17
|
+
* Fixed error that incorrectly specified activerecord as a gem dep [nateware]
|
18
|
+
|
19
|
+
== 0.6.0 (13 Dec 2012)
|
4
20
|
|
5
21
|
* Add +@set.merge()+ method to add multiple members at once [hfwang]
|
6
22
|
|
@@ -24,7 +40,7 @@
|
|
24
40
|
|
25
41
|
* group_set_with_scores is no longer needed.
|
26
42
|
|
27
|
-
== 0.5.2 (13
|
43
|
+
== 0.5.2 (13 Jun 2012)
|
28
44
|
|
29
45
|
* Added Redis::SortedSet#member? method [Karl Varga]
|
30
46
|
|
@@ -40,7 +56,7 @@
|
|
40
56
|
|
41
57
|
* Updated URLs to reflect new redis.io website [Jérémy Lecour]
|
42
58
|
|
43
|
-
== 0.5.0 (8
|
59
|
+
== 0.5.0 (8 Nov 2010)
|
44
60
|
|
45
61
|
* Incompatible change: Had to rename Redis::Hash to Redis::HashKey due to internal conflicts with Redis lib and Ruby [Nate Wiger]
|
46
62
|
|
@@ -54,7 +70,7 @@
|
|
54
70
|
|
55
71
|
* Updated Redis DEL semantics per API change [Gabe da Silveira]
|
56
72
|
|
57
|
-
== 0.4.1 (23
|
73
|
+
== 0.4.1 (23 Aug 2010)
|
58
74
|
|
59
75
|
* Fixes for Ruby 1.8 failures due to missing flatten() [Gabe da Silveira]
|
60
76
|
|
@@ -64,7 +80,7 @@
|
|
64
80
|
|
65
81
|
* Fixed a typo in delete_if and added missing test coverage [Julio Capote, Nate Wiger]
|
66
82
|
|
67
|
-
== 0.4.0 (11
|
83
|
+
== 0.4.0 (11 Aug 2010)
|
68
84
|
|
69
85
|
* Full support for redis hashes via new Redis::Hash class [Julio Capote, Nate Wiger]
|
70
86
|
|
@@ -78,7 +94,7 @@
|
|
78
94
|
|
79
95
|
* Renamed :withscores option to :with_scores for consistency with redis-rb 2.0, but kept backwards compat [Tom Stuart, Nate Wiger]
|
80
96
|
|
81
|
-
== 0.3.2 (21
|
97
|
+
== 0.3.2 (21 Jul 2010)
|
82
98
|
|
83
99
|
* New "maxlength" option to Redis::List can create length-limited lists (eg, like a ring buffer) from dbalatero [David Balatero]
|
84
100
|
|
@@ -86,21 +102,21 @@
|
|
86
102
|
|
87
103
|
* Switched from rspec to bacon for tests
|
88
104
|
|
89
|
-
== 0.3.1 (1
|
105
|
+
== 0.3.1 (1 Jun 2010)
|
90
106
|
|
91
107
|
* Integrated fixes for sorted_set deletions from capotej [Julio Capote]
|
92
108
|
|
93
|
-
== 0.3.0 (14
|
109
|
+
== 0.3.0 (14 Apr 2010)
|
94
110
|
|
95
111
|
* Due to Ruby 1.9 bugs and performance considerations, marshaling of data types is now OFF by default. You must say :marshal => true for any objects that you want serialization enabled on. [Nate Wiger]
|
96
112
|
|
97
113
|
* Sorted Set class changed slightly due to feedback. You can now get an individual element back via @set['item'] since it acts like a Hash.
|
98
114
|
|
99
|
-
== 0.2.4 (9
|
115
|
+
== 0.2.4 (9 Apr 2010)
|
100
116
|
|
101
117
|
* Added sorted set support via Redis::SortedSet [Nate Wiger]
|
102
118
|
|
103
|
-
== 0.2.3 (18
|
119
|
+
== 0.2.3 (18 Feb 2010)
|
104
120
|
|
105
121
|
* Added lock expiration to Redis::Lock [Ben VandenBos]
|
106
122
|
|
@@ -108,7 +124,7 @@
|
|
108
124
|
|
109
125
|
* Added lock tests and test helpers [Ben VandenBos]
|
110
126
|
|
111
|
-
== 0.2.2 (14
|
127
|
+
== 0.2.2 (14 Dec 2009)
|
112
128
|
|
113
129
|
* Added @set.diff(@set2) with "^" and "-" synonyms (oversight). [Nate Wiger]
|
114
130
|
|
@@ -118,7 +134,7 @@
|
|
118
134
|
|
119
135
|
* More spec coverage. [Nate Wiger]
|
120
136
|
|
121
|
-
== 0.2.1 (27
|
137
|
+
== 0.2.1 (27 Nov 2009)
|
122
138
|
|
123
139
|
* First worthwhile public release, with good spec coverage and functionality. [Nate Wiger]
|
124
140
|
|
data/{README.rdoc → README.md}
RENAMED
@@ -1,14 +1,15 @@
|
|
1
|
-
|
1
|
+
Redis::Objects - Map Redis types directly to Ruby objects
|
2
|
+
=========================================================
|
2
3
|
|
3
|
-
This is
|
4
|
+
This is **not** an ORM. People that are wrapping ORM’s around Redis are missing the point.
|
4
5
|
|
5
6
|
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
|
7
|
+
on _individual_ data structures, like counters, lists, and sets. The **atomic** part is HUGE.
|
7
8
|
Using an ORM wrapper that retrieves a "record", updates values, then sends those values back,
|
8
9
|
_removes_ the atomicity, cutting the nuts off the major advantage of Redis. Just use MySQL, k?
|
9
10
|
|
10
|
-
This gem provides a Rubyish interface to Redis, by mapping
|
11
|
-
to Ruby objects, via a thin layer over
|
11
|
+
This gem provides a Rubyish interface to Redis, by mapping [Redis types](http://redis.io/commands)
|
12
|
+
to Ruby objects, via a thin layer over the `redis` gem. It offers several advantages
|
12
13
|
over the lower-level redis-rb API:
|
13
14
|
|
14
15
|
1. Easy to integrate directly with existing ORMs - ActiveRecord, DataMapper, etc. Add counters to your model!
|
@@ -17,42 +18,79 @@ over the lower-level redis-rb API:
|
|
17
18
|
4. Higher-level types are provided, such as Locks, that wrap multiple calls
|
18
19
|
|
19
20
|
This gem originally arose out of a need for high-concurrency atomic operations;
|
20
|
-
for a fun rant on the topic, see
|
21
|
-
or scroll down to
|
21
|
+
for a fun rant on the topic, see [An Atomic Rant](http://nateware.com/2010/02/18/an-atomic-rant),
|
22
|
+
or scroll down to [Atomic Counters and Locks](#atomicity) in this README.
|
22
23
|
|
23
24
|
There are two ways to use Redis::Objects, either as an include in a model class (to
|
24
25
|
integrate with ORMs or other classes), or by using new with the type of data structure
|
25
26
|
you want to create.
|
26
27
|
|
27
|
-
|
28
|
-
|
28
|
+
Installation and Setup
|
29
|
+
----------------------
|
29
30
|
Add it to your Gemfile as:
|
30
31
|
|
31
|
-
gem 'redis-objects'
|
32
|
+
gem 'redis-objects'
|
33
|
+
|
34
|
+
**Redis::Objects** needs a handle created by `Redis.new`. The recommended approach
|
35
|
+
is to set `Redis.current` to point to your server, which **Redis::Objects** will
|
36
|
+
pick up automatically.
|
37
|
+
|
38
|
+
require 'redis/objects'
|
39
|
+
Redis.current = Redis.new(:host => '127.0.0.1', :port => 6379)
|
40
|
+
|
41
|
+
(If you're on Rails, `config/initializers/redis.rb` is a good place for this.)
|
42
|
+
Remember you can use **Redis::Objects** in any Ruby code. There are **no** dependencies
|
43
|
+
on Rails. Standalone, Sinatra, Resque - no problem.
|
44
|
+
|
45
|
+
Alternatively, you can set the `redis` handle directly:
|
46
|
+
|
47
|
+
Redis::Objects.redis = Redis.new(...)
|
32
48
|
|
33
|
-
|
49
|
+
Finally, you can even setup different handles for different classes:
|
34
50
|
|
51
|
+
class User
|
52
|
+
include Redis::Objects
|
53
|
+
end
|
54
|
+
class Post
|
55
|
+
include Redis::Objects
|
56
|
+
end
|
57
|
+
|
58
|
+
User.redis = Redis.new(...)
|
59
|
+
Post.redis = Redis.new(...)
|
60
|
+
|
61
|
+
As of `0.7.0`, `redis-objects` now autoloads the appropriate `Redis::Whatever`
|
62
|
+
classes on demand. Previous strategies of individually requiring `redis/list`
|
63
|
+
or `redis/set` are no longer required.
|
64
|
+
|
65
|
+
There are two ways to use **Redis::Objects**: As part of an model class (ActiveRecord,
|
66
|
+
DataMapper, Mongoid, etc) or as standalong data type classes (`Redis::Set`, `Redis::List`, etc).
|
67
|
+
|
68
|
+
Option 1: Model Class Usage
|
69
|
+
============================
|
35
70
|
Using Redis::Objects this way makes it trivial to integrate Redis types with an
|
36
|
-
existing ActiveRecord model, DataMapper resource, or other class. Redis::Objects
|
37
|
-
will work with _any_ class that provides an
|
38
|
-
value. Redis::Objects will automatically create keys that are unique to
|
71
|
+
existing ActiveRecord model, DataMapper resource, or other class. **Redis::Objects**
|
72
|
+
will work with _any_ class that provides an `id` method that returns a unique
|
73
|
+
value. **Redis::Objects** will then automatically create keys that are unique to
|
39
74
|
each object, in the format:
|
40
75
|
|
41
76
|
model_name:id:field_name
|
42
77
|
|
43
|
-
|
44
|
-
|
45
|
-
Redis::Objects needs a handle created by Redis.new. (If you're on Rails,
|
46
|
-
config/initializers/redis.rb is a good place for this.)
|
78
|
+
For illustration purposes, consider this stub class:
|
47
79
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
80
|
+
class User
|
81
|
+
include Redis::Objects
|
82
|
+
counter :my_posts
|
83
|
+
def id
|
84
|
+
1
|
85
|
+
end
|
86
|
+
end
|
54
87
|
|
55
|
-
|
88
|
+
user = User.new
|
89
|
+
user.id # 1
|
90
|
+
user.my_posts.increment
|
91
|
+
user.my_posts.increment
|
92
|
+
user.my_posts.increment
|
93
|
+
puts user.my_posts # 3
|
56
94
|
|
57
95
|
You can include Redis::Objects in any type of class:
|
58
96
|
|
@@ -60,15 +98,16 @@ You can include Redis::Objects in any type of class:
|
|
60
98
|
include Redis::Objects
|
61
99
|
|
62
100
|
lock :trade_players, :expiration => 15 # sec
|
101
|
+
value :at_bat
|
63
102
|
counter :hits
|
64
103
|
counter :runs
|
65
104
|
counter :outs
|
66
105
|
counter :inning, :start => 1
|
67
106
|
list :on_base
|
68
|
-
|
69
|
-
|
70
|
-
sorted_set :rank, :global => true
|
107
|
+
list :coaches, :marshal => true
|
108
|
+
set :outfielders
|
71
109
|
hash_key :pitchers_faced # "hash" is taken by Ruby
|
110
|
+
sorted_set :rank, :global => true
|
72
111
|
end
|
73
112
|
|
74
113
|
Familiar Ruby array operations Just Work (TM):
|
@@ -106,44 +145,31 @@ Counters can be atomically incremented/decremented (but not assigned):
|
|
106
145
|
@team.hits.incr(3) # add 3
|
107
146
|
@team.runs = 4 # exception
|
108
147
|
|
109
|
-
Finally, for free, you get a
|
148
|
+
Finally, for free, you get a `redis` method that points directly to a Redis connection:
|
110
149
|
|
111
150
|
Team.redis.get('somekey')
|
112
151
|
@team = Team.new
|
113
152
|
@team.redis.get('somekey')
|
114
153
|
@team.redis.smembers('someset')
|
115
154
|
|
116
|
-
You can use the
|
117
|
-
|
118
|
-
== Example 2: Standalone Usage
|
155
|
+
You can use the `redis` handle to directly call any [Redis API command](http://redis.io/commands).
|
119
156
|
|
157
|
+
Option 2: Standalone Usage
|
158
|
+
===========================
|
120
159
|
There is a Ruby class that maps to each Redis type, with methods for each
|
121
|
-
|
122
|
-
Note that calling
|
123
|
-
creates a mapping between that object and the corresponding Redis data
|
124
|
-
which may already exist on the redis-server
|
125
|
-
|
126
|
-
=== Initialization
|
127
|
-
|
128
|
-
Redis::Objects needs a handle to the +redis+ server. For standalone use, you
|
129
|
-
can either set Redis.current:
|
130
|
-
|
131
|
-
Redis.current = Redis.new(:host => 'localhost', :port => 6379)
|
132
|
-
@list = Redis::List.new('mylist')
|
133
|
-
|
134
|
-
Or you can pass the Redis handle into the new method for each type:
|
160
|
+
[Redis API command](http://redis.io/commands).
|
161
|
+
Note that calling `new` does not imply it's actually a "new" value - it just
|
162
|
+
creates a mapping between that Ruby object and the corresponding Redis data
|
163
|
+
structure, which may already exist on the `redis-server`.
|
135
164
|
|
136
|
-
|
137
|
-
|
165
|
+
Counters
|
166
|
+
--------
|
167
|
+
The `counter_name` is the key stored in Redis.
|
138
168
|
|
139
|
-
=== Counters
|
140
|
-
|
141
|
-
Create a new counter. The +counter_name+ is the key stored in Redis.
|
142
|
-
|
143
|
-
require 'redis/counter'
|
144
169
|
@counter = Redis::Counter.new('counter_name')
|
145
|
-
@counter.increment
|
146
|
-
@counter.decrement
|
170
|
+
@counter.increment # or incr
|
171
|
+
@counter.decrement # or decr
|
172
|
+
@counter.increment(3)
|
147
173
|
puts @counter.value
|
148
174
|
|
149
175
|
This gem provides a clean way to do atomic blocks as well:
|
@@ -152,25 +178,23 @@ This gem provides a clean way to do atomic blocks as well:
|
|
152
178
|
raise "Full" if val > MAX_VAL # rewind counter
|
153
179
|
end
|
154
180
|
|
155
|
-
See the section on
|
156
|
-
|
157
|
-
=== Locks
|
181
|
+
See the section on [Atomic Counters and Locks](#atomicity) for cool uses of atomic counter blocks.
|
158
182
|
|
159
|
-
|
183
|
+
Locks
|
184
|
+
-----
|
185
|
+
A convenience class that wraps the pattern of [using setnx to perform locking](http://redis.io/commands/setnx).
|
160
186
|
|
161
|
-
|
162
|
-
@lock = Redis::Lock.new('image_resizing', :expiration => 15, :timeout => 0.1)
|
187
|
+
@lock = Redis::Lock.new('serialize_stuff', :expiration => 15, :timeout => 0.1)
|
163
188
|
@lock.lock do
|
164
189
|
# do work
|
165
190
|
end
|
166
191
|
|
167
192
|
This can be especially useful if you're running batch jobs spread across multiple hosts.
|
168
193
|
|
169
|
-
|
170
|
-
|
194
|
+
Values
|
195
|
+
------
|
171
196
|
Simple values are easy as well:
|
172
197
|
|
173
|
-
require 'redis/value'
|
174
198
|
@value = Redis::Value.new('value_name')
|
175
199
|
@value.value = 'a'
|
176
200
|
@value.delete
|
@@ -182,11 +206,10 @@ Complex data is no problem with :marshal => true:
|
|
182
206
|
@newest.value = @account.attributes
|
183
207
|
puts @newest.value['username']
|
184
208
|
|
185
|
-
|
186
|
-
|
209
|
+
Lists
|
210
|
+
-----
|
187
211
|
Lists work just like Ruby arrays:
|
188
212
|
|
189
|
-
require 'redis/list'
|
190
213
|
@list = Redis::List.new('list_name')
|
191
214
|
@list << 'a'
|
192
215
|
@list << 'b'
|
@@ -205,9 +228,9 @@ Lists work just like Ruby arrays:
|
|
205
228
|
You can bound the size of the list to only hold N elements like so:
|
206
229
|
|
207
230
|
# Only holds 10 elements, throws out old ones when you reach :maxlength.
|
208
|
-
@list = Redis::List.new('list_name',
|
231
|
+
@list = Redis::List.new('list_name', :maxlength => 10)
|
209
232
|
|
210
|
-
Complex data types are no
|
233
|
+
Complex data types are no handled with :marshal => true:
|
211
234
|
|
212
235
|
@list = Redis::List.new('list_name', :marshal => true)
|
213
236
|
@list << {:name => "Nate", :city => "San Diego"}
|
@@ -216,13 +239,12 @@ Complex data types are no problem with :marshal => true:
|
|
216
239
|
puts "#{el[:name]} lives in #{el[:city]}"
|
217
240
|
end
|
218
241
|
|
219
|
-
|
220
|
-
|
221
|
-
Hashes work like a Ruby
|
242
|
+
Hashes
|
243
|
+
------
|
244
|
+
Hashes work like a Ruby [Hash](http://ruby-doc.org/core/classes/Hash.html), with
|
222
245
|
a few Redis-specific additions. (The class name is "HashKey" not just "Hash", due to
|
223
246
|
conflicts with the Ruby core Hash class in other gems.)
|
224
247
|
|
225
|
-
require 'redis/hash_key'
|
226
248
|
@hash = Redis::HashKey.new('hash_name')
|
227
249
|
@hash['a'] = 1
|
228
250
|
@hash['b'] = 2
|
@@ -239,13 +261,15 @@ Redis also adds incrementing and bulk operations:
|
|
239
261
|
@hash.bulk_set('d' => 5, 'e' => 6)
|
240
262
|
@hash.bulk_get('d','e') # "5", "6"
|
241
263
|
|
242
|
-
Remember that numbers become strings in Redis. Unlike with other Redis data types,
|
264
|
+
Remember that numbers become strings in Redis. Unlike with other Redis data types,
|
265
|
+
`redis-objects` can't guess at your data type in this situation, since you may
|
266
|
+
actually mean to store "1.5".
|
243
267
|
|
244
|
-
|
268
|
+
Sets
|
269
|
+
----
|
270
|
+
Sets work like the Ruby [Set](http://ruby-doc.org/core/classes/Set.html) class.
|
271
|
+
They are unordered, but guarantee uniqueness of members.
|
245
272
|
|
246
|
-
Sets work like the Ruby {Set}[http://ruby-doc.org/core/classes/Set.html] class:
|
247
|
-
|
248
|
-
require 'redis/set'
|
249
273
|
@set = Redis::Set.new('set_name')
|
250
274
|
@set << 'a'
|
251
275
|
@set << 'b'
|
@@ -286,21 +310,20 @@ And use complex data types too, with :marshal => true:
|
|
286
310
|
|
287
311
|
@set1 = Redis::Set.new('set1', :marshal => true)
|
288
312
|
@set2 = Redis::Set.new('set2', :marshal => true)
|
289
|
-
@set1 << {:name => "Nate",
|
313
|
+
@set1 << {:name => "Nate", :city => "San Diego"}
|
290
314
|
@set1 << {:name => "Peter", :city => "Oceanside"}
|
291
|
-
@set2 << {:name => "Nate",
|
292
|
-
@set2 << {:name => "Jeff",
|
315
|
+
@set2 << {:name => "Nate", :city => "San Diego"}
|
316
|
+
@set2 << {:name => "Jeff", :city => "Del Mar"}
|
293
317
|
|
294
318
|
@set1 & @set2 # Nate
|
295
319
|
@set1 - @set2 # Peter
|
296
320
|
@set1 | @set2 # all 3 people
|
297
321
|
|
298
|
-
|
299
|
-
|
322
|
+
Sorted Sets
|
323
|
+
-----------
|
300
324
|
Due to their unique properties, Sorted Sets work like a hybrid between
|
301
325
|
a Hash and an Array. You assign like a Hash, but retrieve like an Array:
|
302
326
|
|
303
|
-
require 'redis/sorted_set'
|
304
327
|
@sorted_set = Redis::SortedSet.new('number_of_posts')
|
305
328
|
@sorted_set['Nate'] = 15
|
306
329
|
@sorted_set['Peter'] = 75
|
@@ -333,13 +356,13 @@ a Hash and an Array. You assign like a Hash, but retrieve like an Array:
|
|
333
356
|
@sorted_set.incr('Peter') # shorthand
|
334
357
|
@sorted_set.incr('Jeff', 4)
|
335
358
|
|
336
|
-
The other Redis Sorted Set commands are supported as well; see
|
337
|
-
|
338
|
-
== Atomic Counters and Locks
|
359
|
+
The other Redis Sorted Set commands are supported as well; see [Sorted Sets API](http://redis.io/commands#sorted_set).
|
339
360
|
|
361
|
+
<a name="atomicity"></a>
|
362
|
+
Atomic Counters and Locks
|
363
|
+
-------------------------
|
340
364
|
You are probably not handling atomicity correctly in your app. For a fun rant
|
341
|
-
on the topic, see
|
342
|
-
{An Atomic Rant}[http://nateware.com/2010/02/18/an-atomic-rant].
|
365
|
+
on the topic, see [An Atomic Rant](http://nateware.com/an-atomic-rant.html).
|
343
366
|
|
344
367
|
Atomic counters are a good way to handle concurrency:
|
345
368
|
|
@@ -353,16 +376,16 @@ Atomic counters are a good way to handle concurrency:
|
|
353
376
|
@team.drafted_players.decrement
|
354
377
|
end
|
355
378
|
|
356
|
-
|
357
|
-
rewind counter back to previous state
|
379
|
+
An _atomic block_ gives you a cleaner way to do the above. Exceptions or returning nil
|
380
|
+
will rewind the counter back to its previous state:
|
358
381
|
|
359
382
|
@team.drafted_players.increment do |val|
|
360
|
-
raise Team::TeamFullError if val > @team.max_players
|
383
|
+
raise Team::TeamFullError if val > @team.max_players # rewind
|
361
384
|
@team.team_players.create!(:player_id => 221)
|
362
385
|
@team.active_players.increment
|
363
386
|
end
|
364
387
|
|
365
|
-
|
388
|
+
Here's a similar approach, using an if block (failure rewinds counter):
|
366
389
|
|
367
390
|
@team.drafted_players.increment do |val|
|
368
391
|
if val <= @team.max_players
|
@@ -371,20 +394,23 @@ Similar approach, using an if block (failure rewinds counter):
|
|
371
394
|
end
|
372
395
|
end
|
373
396
|
|
374
|
-
Class methods work too
|
397
|
+
Class methods work too, using the familiar ActiveRecord counter syntax:
|
375
398
|
|
376
399
|
Team.increment_counter :drafted_players, team_id
|
377
400
|
Team.decrement_counter :drafted_players, team_id, 2
|
378
401
|
Team.increment_counter :total_online_players # no ID on global counter
|
379
402
|
|
380
|
-
Class-level atomic
|
403
|
+
Class-level atomic blocks can also be used. This may save a DB fetch, if you have
|
404
|
+
a record ID and don't need any other attributes from the DB table:
|
381
405
|
|
382
406
|
Team.increment_counter(:drafted_players, team_id) do |val|
|
383
407
|
TeamPitcher.create!(:team_id => team_id, :pitcher_id => 181)
|
384
408
|
Team.increment_counter(:active_players, team_id)
|
385
409
|
end
|
386
410
|
|
387
|
-
Locks
|
411
|
+
### Locks ###
|
412
|
+
|
413
|
+
Locks work similarly. On completion or exception the lock is released:
|
388
414
|
|
389
415
|
class Team < ActiveRecord::Base
|
390
416
|
lock :reorder # declare a lock
|
@@ -410,9 +436,11 @@ lock time.
|
|
410
436
|
lock :reorder, :expiration => 15.minutes
|
411
437
|
end
|
412
438
|
|
439
|
+
Keep in mind that true locks serialize your entire application at that point. As
|
440
|
+
such, atomic counters are strongly preferred.
|
413
441
|
|
414
|
-
|
415
|
-
|
416
|
-
Copyright (c) 2009-
|
417
|
-
Released under the
|
442
|
+
Author
|
443
|
+
=======
|
444
|
+
Copyright (c) 2009-2013 [Nate Wiger](http://nateware.com). All Rights Reserved.
|
445
|
+
Released under the [Artistic License](http://www.opensource.org/licenses/artistic-license-2.0.php).
|
418
446
|
|