redis-objects 0.6.1 → 0.7.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 +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
|
|