redis-objects 1.7.0 → 2.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.
data/README.md CHANGED
@@ -1,22 +1,82 @@
1
- Redis::Objects - Map Redis types directly to Ruby objects
2
- =========================================================
1
+ # Redis::Objects - Map Redis types directly to Ruby objects
3
2
 
4
3
  [![Build Status](https://app.travis-ci.com/nateware/redis-objects.svg?branch=master)](https://travis-ci.com/github/nateware/redis-objects)
5
4
  [![Code Coverage](https://codecov.io/gh/nateware/redis-objects/branch/master/graph/badge.svg)](https://codecov.io/gh/nateware/redis-objects)
6
5
  [![Donate](https://www.paypalobjects.com/en_US/i/btn/btn_donate_SM.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=MJF7JU5M7F8VL)
7
6
 
8
- This is **not** an ORM. People that are wrapping ORM’s around Redis are missing the point.
7
+ ## Important 2.0 changes
8
+
9
+ Several longstanding bugs have been addressed in this release. However, this may require some
10
+ code changes as part of the upgrade.
11
+
12
+ ### Renaming of `lock` Method
13
+
14
+ The `lock` method that collided with `ActiveRecord::Base` has been renamed `redis_lock`.
15
+ This means your classes need to be updated to call `redis_lock` instead:
16
+
17
+ ```ruby
18
+ class YouClassNameHere < ActiveRecord::Base
19
+ include Redis::Objects
20
+ redis_lock :mylock # formerly just "lock"
21
+ end
22
+ ```
23
+
24
+ For more details on the issue and fix refer to [#196](https://github.com/nateware/redis-objects/issues/196).
25
+
26
+ ### New key naming method
27
+
28
+ A new method to determine the internal key naming scheme has been added to fix a
29
+ longstanding bug. (Refer to [#213](https://github.com/nateware/redis-objects/issues/231))
30
+ By default, backwards compatibility is maintained, but this means that in some cases,
31
+ Nested classes (for example `Dog::Behavior` and `Cat::Behavior`) would have key names
32
+ that collide (they would both start with `Behavior` as anything before `::` is stripped).
33
+
34
+ To ensure names are unique, you can set `Redis::Objects.prefix_style = :modern` after
35
+ you load the module. By default, it is set to `Redis::Objects.prefix_style = :legacy`.
36
+
37
+ ### Migrating old keys
38
+
39
+ If your Redis::Object subclasses are nested such as `Dog::Behavior` and `Cat::Behavior`,
40
+ then you should upgrade them using the below proceedure. (only needed once)
41
+
42
+ Create a script like this:
43
+
44
+ ```ruby
45
+ class Dog::Behavior < ActiveRecord::Base
46
+ include Redis::Objects
47
+ # ... your relevant redis_object definitions here (counters/sets) ...
48
+ end
49
+
50
+ Dog::Behavior.migrate_redis_legacy_keys
51
+ ```
52
+
53
+ You need to find a time when you can temporarily pause writes to your redis server
54
+ so that you can run that script. It uses `redis.scan` internally so it should be able to
55
+ handle a high number of keys. For large data sets, it could take a while.
56
+
57
+ After migrating all of your redis keys, update `Redis::Objects.prefix_style = :modern` to
58
+ start using the new keys.
59
+
60
+ **Note: Your existing data in Redis will not be accessible after running `migrate_redis_legacy_keys`
61
+ until the `prefix_style` has been changed.**
62
+
63
+ ---
64
+
65
+ # Overview
66
+
67
+ This is **not** an ORM ([Object-Relational Mapping](https://en.wikipedia.org/wiki/Object%E2%80%93relational_mapping)).
68
+ People that are wrapping ORM’s around Redis are missing the point.
9
69
 
10
70
  The killer feature of Redis is that it allows you to perform _atomic_ operations
11
- on _individual_ data structures, like counters, lists, and sets. The **atomic** part is HUGE.
71
+ on _individual_ data structures, like counters, lists, and sets. The **atomic** part is HUGE.
12
72
  Using an ORM wrapper that retrieves a "record", updates values, then sends those values back,
13
- _removes_ the atomicity, and thus the major advantage of Redis. Just use MySQL, k?
73
+ _removes_ the atomicity, and thus the major advantage of Redis. Just use MySQL, k?
14
74
 
15
75
  This gem provides a Rubyish interface to Redis, by mapping [Redis data types](http://redis.io/commands)
16
- to Ruby objects, via a thin layer over the `redis` gem. It offers several advantages
76
+ to Ruby objects, via a thin layer over the `redis` gem. It offers several advantages
17
77
  over the lower-level redis-rb API:
18
78
 
19
- 1. Easy to integrate directly with existing ORMs - ActiveRecord, DataMapper, etc. Add counters to your model!
79
+ 1. Easy to integrate directly with existing ORMs - ActiveRecord, DataMapper, etc. Add counters to your model!
20
80
  2. Complex data structures are automatically Marshaled (if you set :marshal => true)
21
81
  3. Integers are returned as integers, rather than '17'
22
82
  4. Higher-level types are provided, such as Locks, that wrap multiple calls
@@ -25,46 +85,50 @@ This gem originally arose out of a need for high-concurrency atomic operations;
25
85
  for a fun rant on the topic, see [An Atomic Rant](http://nateware.com/2010/02/18/an-atomic-rant),
26
86
  or scroll down to [Atomic Counters and Locks](#atomicity) in this README.
27
87
 
28
- There are two ways to use Redis::Objects, either as an include in a model class (to
29
- tightly integrate with ORMs or other classes), or standalone by using classes such
88
+ There are two ways to use Redis::Objects, either as an include within a [model class](#option-1-model-class-include) (to
89
+ tightly integrate with ORMs or other classes), or [standalone](#option-2-standalone-usage) by using classes such
30
90
  as `Redis::List` and `Redis::SortedSet`.
31
91
 
32
- Installation and Setup
33
- ----------------------
92
+ # Installation and Setup
93
+
34
94
  Add it to your Gemfile as:
35
95
 
36
- ~~~ruby
96
+ ```ruby
37
97
  gem 'redis-objects'
38
- ~~~
98
+ ```
39
99
 
40
- Redis::Objects needs a handle created by `Redis.new` or a [ConnectionPool](https://github.com/mperham/connection_pool):
100
+ Redis::Objects needs a handle created by `Redis.new` or a [ConnectionPool](https://github.com/mperham/connection_pool).
101
+
102
+ If you're using Rails, `config/initializers/redis.rb` is a good place for this.
103
+ However, there are **no** dependencies on Rails. Redis::Objects can be used in any Ruby code; Sinatra,
104
+ Resque, or Standalone - no problem.
41
105
 
42
106
  The recommended approach is to use a `ConnectionPool` since this guarantees that most timeouts in the `redis` client
43
- do not pollute your existing connection. However, you need to make sure that both `:timeout` and `:size` are set appropriately
44
- in a multithreaded environment.
45
- ~~~ruby
107
+ do not pollute your existing connection.
108
+
109
+ ```ruby
46
110
  require 'connection_pool'
47
111
  Redis::Objects.redis = ConnectionPool.new(size: 5, timeout: 5) { Redis.new(:host => '127.0.0.1', :port => 6379) }
48
- ~~~
112
+ ```
49
113
 
50
- Redis::Objects can also default to `Redis.current` if `Redis::Objects.redis` is not set.
51
- ~~~ruby
52
- Redis.current = Redis.new(:host => '127.0.0.1', :port => 6379)
53
- ~~~
54
-
55
- (If you're on Rails, `config/initializers/redis.rb` is a good place for this.)
56
- Remember you can use Redis::Objects in any Ruby code. There are **no** dependencies
57
- on Rails. Standalone, Sinatra, Resque - no problem.
114
+ However, you need to make sure that both `:timeout` and `:size` are set appropriately
115
+ in a multithreaded environment.
58
116
 
59
117
  Alternatively, you can set the `redis` handle directly:
60
118
 
61
- ~~~ruby
119
+ ```ruby
62
120
  Redis::Objects.redis = Redis.new(...)
63
- ~~~
121
+ ```
122
+
123
+ Redis::Objects will also default to `Redis.current` if `Redis::Objects.redis` is not set.
124
+
125
+ ```ruby
126
+ Redis.current = Redis.new(:host => '127.0.0.1', :port => 6379)
127
+ ```
64
128
 
65
129
  Finally, you can even set different handles for different classes:
66
130
 
67
- ~~~ruby
131
+ ```ruby
68
132
  class User
69
133
  include Redis::Objects
70
134
  end
@@ -75,16 +139,16 @@ end
75
139
  # you can also use a ConnectionPool here as well
76
140
  User.redis = Redis.new(:host => '1.2.3.4')
77
141
  Post.redis = Redis.new(:host => '5.6.7.8')
78
- ~~~
142
+ ```
79
143
 
80
144
  As of `0.7.0`, `redis-objects` now autoloads the appropriate `Redis::Whatever`
81
- classes on demand. Previous strategies of individually requiring `redis/list`
145
+ classes on demand. Previous strategies of individually requiring `redis/list`
82
146
  or `redis/set` are no longer required.
83
147
 
84
- Option 1: Model Class Include
85
- =============================
148
+ # Option 1: Model Class Include
149
+
86
150
  Including Redis::Objects in a model class makes it trivial to integrate Redis types
87
- with an existing ActiveRecord, DataMapper, Mongoid, or similar class. **Redis::Objects
151
+ with an existing ActiveRecord, DataMapper, Mongoid, or similar class. **Redis::Objects
88
152
  will work with _any_ class that provides an `id` method that returns a unique value.**
89
153
  Redis::Objects automatically creates keys that are unique to each object, in the format:
90
154
 
@@ -92,7 +156,7 @@ Redis::Objects automatically creates keys that are unique to each object, in the
92
156
 
93
157
  For illustration purposes, consider this stub class:
94
158
 
95
- ~~~ruby
159
+ ```ruby
96
160
  class User
97
161
  include Redis::Objects
98
162
  counter :my_posts
@@ -111,15 +175,15 @@ user.my_posts.reset
111
175
  puts user.my_posts.value # 0
112
176
  user.my_posts.reset 5
113
177
  puts user.my_posts.value # 5
114
- ~~~
178
+ ```
115
179
 
116
180
  Here's an example that integrates several data types with an ActiveRecord model:
117
181
 
118
- ~~~ruby
182
+ ```ruby
119
183
  class Team < ActiveRecord::Base
120
184
  include Redis::Objects
121
185
 
122
- lock :trade_players, :expiration => 15 # sec
186
+ redis_lock :trade_players, :expiration => 15 # sec
123
187
  value :at_bat
124
188
  counter :hits
125
189
  counter :runs
@@ -129,13 +193,21 @@ class Team < ActiveRecord::Base
129
193
  list :coaches, :marshal => true
130
194
  set :outfielders
131
195
  hash_key :pitchers_faced # "hash" is taken by Ruby
132
- sorted_set :rank, :global => true
196
+
197
+ # Customized keys
198
+ counter :player_totals, :key => 'players/#{username}/total'
199
+ list :all_player_stats, :key => 'players:all_stats', :global => true
200
+ set :total_wins, :key => 'players:#{id}:all_stats'
201
+ value :my_rank, :key => 'players:my_rank:#{username}'
202
+
203
+ def id; @id; end
204
+ def username; "user#{id}"; end
133
205
  end
134
- ~~~
206
+ ```
135
207
 
136
- Familiar Ruby array operations Just Work (TM):
208
+ Familiar Ruby array operations Just Work™:
137
209
 
138
- ~~~ruby
210
+ ```ruby
139
211
  @team = Team.find_by_name('New York Yankees')
140
212
  @team.on_base << 'player1'
141
213
  @team.on_base << 'player2'
@@ -146,11 +218,11 @@ Familiar Ruby array operations Just Work (TM):
146
218
  @team.on_base.length # 1
147
219
  @team.on_base.delete('player2')
148
220
  @team.on_base = ['player1', 'player2'] # ['player1', 'player2']
149
- ~~~
221
+ ```
150
222
 
151
223
  Sets work too:
152
224
 
153
- ~~~ruby
225
+ ```ruby
154
226
  @team.outfielders << 'outfielder1'
155
227
  @team.outfielders << 'outfielder2'
156
228
  @team.outfielders << 'outfielder1' # dup ignored
@@ -160,35 +232,35 @@ Sets work too:
160
232
  end
161
233
  player = @team.outfielders.detect{|of| of == 'outfielder2'}
162
234
  @team.outfielders = ['outfielder1', 'outfielder3'] # ['outfielder1', 'outfielder3']
163
- ~~~
235
+ ```
164
236
 
165
237
  Hashes work too:
166
238
 
167
- ~~~ruby
239
+ ```ruby
168
240
  @team.pitchers_faced['player1'] = 'pitcher2'
169
241
  @team.pitchers_faced['player2'] = 'pitcher1'
170
242
  @team.pitchers_faced = { 'player1' => 'pitcher2', 'player2' => 'pitcher1' }
171
- ~~~
243
+ ```
172
244
 
173
245
  And you can do unions and intersections between objects (kinda cool):
174
246
 
175
- ~~~ruby
247
+ ```ruby
176
248
  @team1.outfielders | @team2.outfielders # outfielders on both teams
177
249
  @team1.outfielders & @team2.outfielders # in baseball, should be empty :-)
178
- ~~~
250
+ ```
179
251
 
180
252
  Counters can be atomically incremented/decremented (but not assigned):
181
253
 
182
- ~~~ruby
254
+ ```ruby
183
255
  @team.hits.increment # or incr
184
256
  @team.hits.decrement # or decr
185
257
  @team.hits.incr(3) # add 3
186
258
  @team.runs = 4 # exception
187
- ~~~
259
+ ```
188
260
 
189
261
  Defining a different method as the `id` field is easy
190
262
 
191
- ~~~ruby
263
+ ```ruby
192
264
  class User
193
265
  include Redis::Objects
194
266
  redis_id_field :uid
@@ -197,84 +269,100 @@ end
197
269
 
198
270
  user.uid # 195137a1bdea4473
199
271
  user.my_posts.increment # 1
200
- ~~~
272
+ ```
273
+
274
+ You can also define globals redis attributes that are accessed through the class itself.
275
+ No id needed/used for these.
276
+
277
+ ```ruby
278
+ class Team < ActiveRecord::Base
279
+ include Redis::Objects
280
+
281
+ sorted_set :rank, :global => true
282
+ end
283
+
284
+ Team.rank['Yankees'] = 12
285
+ Team.rank['Red Socks'] = 5
286
+ Team.rank['Mariners'] = 7
287
+ Team.rank.members(:with_scores => true) # => [["Red Socks", 5], ["Mariners", 7], ["Yankees", 12]]
288
+ ```
201
289
 
202
290
  Finally, for free, you get a `redis` method that points directly to a Redis connection:
203
291
 
204
- ~~~ruby
292
+ ```ruby
205
293
  Team.redis.get('somekey')
206
294
  @team = Team.new
207
295
  @team.redis.get('somekey')
208
296
  @team.redis.smembers('someset')
209
- ~~~
297
+ ```
210
298
 
211
299
  You can use the `redis` handle to directly call any [Redis API command](http://redis.io/commands).
212
300
 
213
- Option 2: Standalone Usage
214
- ===========================
301
+ # Option 2: Standalone Usage
302
+
215
303
  There is a Ruby class that maps to each Redis type, with methods for each
216
304
  [Redis API command](http://redis.io/commands).
217
305
  Note that calling `new` does not imply it's actually a "new" value - it just
218
306
  creates a mapping between that Ruby object and the corresponding Redis data
219
307
  structure, which may already exist on the `redis-server`.
220
308
 
221
- Counters
222
- --------
309
+ ## Counters
310
+
223
311
  The `counter_name` is the key stored in Redis.
224
312
 
225
- ~~~ruby
313
+ ```ruby
226
314
  @counter = Redis::Counter.new('counter_name')
227
315
  @counter.increment # or incr
228
316
  @counter.decrement # or decr
229
317
  @counter.increment(3)
230
318
  puts @counter.value
231
- ~~~
319
+ ```
232
320
 
233
321
  This gem provides a clean way to do atomic blocks as well:
234
322
 
235
- ~~~ruby
323
+ ```ruby
236
324
  @counter.increment do |val|
237
325
  raise "Full" if val > MAX_VAL # rewind counter
238
326
  end
239
- ~~~
327
+ ```
240
328
 
241
329
  See the section on [Atomic Counters and Locks](#atomicity) for cool uses of atomic counter blocks.
242
330
 
243
- Locks
244
- -----
331
+ ## Locks
332
+
245
333
  A convenience class that wraps the pattern of [using setnx to perform locking](http://redis.io/commands/setnx).
246
334
 
247
- ~~~ruby
335
+ ```ruby
248
336
  @lock = Redis::Lock.new('serialize_stuff', :expiration => 15, :timeout => 0.1)
249
337
  @lock.lock do
250
338
  # do work
251
339
  end
252
- ~~~
340
+ ```
253
341
 
254
342
  This can be especially useful if you're running batch jobs spread across multiple hosts.
255
343
 
256
- Values
257
- ------
344
+ ## Values
345
+
258
346
  Simple values are easy as well:
259
347
 
260
- ~~~ruby
348
+ ```ruby
261
349
  @value = Redis::Value.new('value_name')
262
350
  @value.value = 'a'
263
351
  @value.delete
264
- ~~~
352
+ ```
265
353
 
266
354
  Complex data is no problem with :marshal => true:
267
355
 
268
- ~~~ruby
356
+ ```ruby
269
357
  @account = Account.create!(params[:account])
270
358
  @newest = Redis::Value.new('newest_account', :marshal => true)
271
359
  @newest.value = @account.attributes
272
360
  puts @newest.value['username']
273
- ~~~
361
+ ```
274
362
 
275
363
  Compress data to save memory usage on Redis with :compress => true:
276
364
 
277
- ~~~ruby
365
+ ```ruby
278
366
  @account = Account.create!(params[:account])
279
367
  @marshaled_value = Redis::Value.new('marshaled', :marshal => true, :compress => true)
280
368
  @marshaled_value.value = @account.attributes
@@ -282,13 +370,13 @@ Compress data to save memory usage on Redis with :compress => true:
282
370
  @unmarshaled_value = 'Really Long String'
283
371
  puts @marshaled_value.value['username']
284
372
  puts @unmarshaled_value.value
285
- ~~~
373
+ ```
374
+
375
+ ## Lists
286
376
 
287
- Lists
288
- -----
289
377
  Lists work just like Ruby arrays:
290
378
 
291
- ~~~ruby
379
+ ```ruby
292
380
  @list = Redis::List.new('list_name')
293
381
  @list << 'a'
294
382
  @list << 'b'
@@ -303,35 +391,35 @@ Lists work just like Ruby arrays:
303
391
  @list.pop
304
392
  @list.clear
305
393
  # etc
306
- ~~~
394
+ ```
307
395
 
308
396
  You can bound the size of the list to only hold N elements like so:
309
397
 
310
- ~~~ruby
398
+ ```ruby
311
399
  # Only holds 10 elements, throws out old ones when you reach :maxlength.
312
400
  @list = Redis::List.new('list_name', :maxlength => 10)
313
- ~~~
401
+ ```
314
402
 
315
403
  Complex data types are serialized with :marshal => true:
316
404
 
317
- ~~~ruby
405
+ ```ruby
318
406
  @list = Redis::List.new('list_name', :marshal => true)
319
407
  @list << {:name => "Nate", :city => "San Diego"}
320
408
  @list << {:name => "Peter", :city => "Oceanside"}
321
409
  @list.each do |el|
322
410
  puts "#{el[:name]} lives in #{el[:city]}"
323
411
  end
324
- ~~~
412
+ ```
325
413
 
326
414
  Note: If you run into issues, with Marshal errors, refer to the fix in [Issue #176](https://github.com/nateware/redis-objects/issues/176).
327
415
 
328
- Hashes
329
- ------
416
+ ## Hashes
417
+
330
418
  Hashes work like a Ruby [Hash](http://ruby-doc.org/core/classes/Hash.html), with
331
- a few Redis-specific additions. (The class name is "HashKey" not just "Hash", due to
419
+ a few Redis-specific additions. (The class name is "HashKey" not just "Hash", due to
332
420
  conflicts with the Ruby core Hash class in other gems.)
333
421
 
334
- ~~~ruby
422
+ ```ruby
335
423
  @hash = Redis::HashKey.new('hash_name')
336
424
  @hash['a'] = 1
337
425
  @hash['b'] = 2
@@ -341,26 +429,26 @@ end
341
429
  @hash['c'] = 3
342
430
  puts @hash.all # {"a"=>"1","b"=>"2","c"=>"3"}
343
431
  @hash.clear
344
- ~~~
432
+ ```
345
433
 
346
434
  Redis also adds incrementing and bulk operations:
347
435
 
348
- ~~~ruby
436
+ ```ruby
349
437
  @hash.incr('c', 6) # 9
350
438
  @hash.bulk_set('d' => 5, 'e' => 6)
351
439
  @hash.bulk_get('d','e') # "5", "6"
352
- ~~~
440
+ ```
353
441
 
354
- Remember that numbers become strings in Redis. Unlike with other Redis data types,
442
+ Remember that numbers become strings in Redis. Unlike with other Redis data types,
355
443
  `redis-objects` can't guess at your data type in this situation, since you may
356
444
  actually mean to store "1.5".
357
445
 
358
- Sets
359
- ----
446
+ ## Sets
447
+
360
448
  Sets work like the Ruby [Set](http://ruby-doc.org/core/classes/Set.html) class.
361
449
  They are unordered, but guarantee uniqueness of members.
362
450
 
363
- ~~~ruby
451
+ ```ruby
364
452
  @set = Redis::Set.new('set_name')
365
453
  @set << 'a'
366
454
  @set << 'b'
@@ -373,11 +461,11 @@ They are unordered, but guarantee uniqueness of members.
373
461
  end
374
462
  @set.clear
375
463
  # etc
376
- ~~~
464
+ ```
377
465
 
378
466
  You can perform Redis intersections/unions/diffs easily:
379
467
 
380
- ~~~ruby
468
+ ```ruby
381
469
  @set1 = Redis::Set.new('set1')
382
470
  @set2 = Redis::Set.new('set2')
383
471
  @set3 = Redis::Set.new('set3')
@@ -389,22 +477,22 @@ members = @set1 - @set2 # difference
389
477
  members = @set1.intersection(@set2, @set3) # multiple
390
478
  members = @set1.union(@set2, @set3) # multiple
391
479
  members = @set1.difference(@set2, @set3) # multiple
392
- ~~~
480
+ ```
393
481
 
394
482
  Or store them in Redis:
395
483
 
396
- ~~~ruby
484
+ ```ruby
397
485
  @set1.interstore('intername', @set2, @set3)
398
486
  members = @set1.redis.get('intername')
399
487
  @set1.unionstore('unionname', @set2, @set3)
400
488
  members = @set1.redis.get('unionname')
401
489
  @set1.diffstore('diffname', @set2, @set3)
402
490
  members = @set1.redis.get('diffname')
403
- ~~~
491
+ ```
404
492
 
405
493
  And use complex data types too, with :marshal => true:
406
494
 
407
- ~~~ruby
495
+ ```ruby
408
496
  @set1 = Redis::Set.new('set1', :marshal => true)
409
497
  @set2 = Redis::Set.new('set2', :marshal => true)
410
498
  @set1 << {:name => "Nate", :city => "San Diego"}
@@ -415,14 +503,14 @@ And use complex data types too, with :marshal => true:
415
503
  @set1 & @set2 # Nate
416
504
  @set1 - @set2 # Peter
417
505
  @set1 | @set2 # all 3 people
418
- ~~~
506
+ ```
507
+
508
+ ## Sorted Sets
419
509
 
420
- Sorted Sets
421
- -----------
422
510
  Due to their unique properties, Sorted Sets work like a hybrid between
423
- a Hash and an Array. You assign like a Hash, but retrieve like an Array:
511
+ a Hash and an Array. You assign like a Hash, but retrieve like an Array:
424
512
 
425
- ~~~ruby
513
+ ```ruby
426
514
  @sorted_set = Redis::SortedSet.new('number_of_posts')
427
515
  @sorted_set['Nate'] = 15
428
516
  @sorted_set['Peter'] = 75
@@ -454,19 +542,21 @@ a Hash and an Array. You assign like a Hash, but retrieve like an Array:
454
542
  @sorted_set.increment('Nate')
455
543
  @sorted_set.incr('Peter') # shorthand
456
544
  @sorted_set.incr('Jeff', 4)
457
- ~~~
545
+ ```
458
546
 
459
547
  The other Redis Sorted Set commands are supported as well; see [Sorted Sets API](http://redis.io/commands#sorted_set).
460
548
 
461
549
  <a name="atomicity"></a>
462
550
  Atomic Counters and Locks
463
- -------------------------
464
- You are probably not handling atomicity correctly in your app. For a fun rant
551
+
552
+ ---
553
+
554
+ You are probably not handling atomicity correctly in your app. For a fun rant
465
555
  on the topic, see [An Atomic Rant](http://nateware.com/an-atomic-rant.html).
466
556
 
467
557
  Atomic counters are a good way to handle concurrency:
468
558
 
469
- ~~~ruby
559
+ ```ruby
470
560
  @team = Team.find(1)
471
561
  if @team.drafted_players.increment <= @team.max_players
472
562
  # do stuff
@@ -476,101 +566,101 @@ else
476
566
  # reset counter state
477
567
  @team.drafted_players.decrement
478
568
  end
479
- ~~~
569
+ ```
480
570
 
481
571
  An _atomic block_ gives you a cleaner way to do the above. Exceptions or returning nil
482
572
  will rewind the counter back to its previous state:
483
573
 
484
- ~~~ruby
574
+ ```ruby
485
575
  @team.drafted_players.increment do |val|
486
576
  raise Team::TeamFullError if val > @team.max_players # rewind
487
577
  @team.team_players.create!(:player_id => 221)
488
578
  @team.active_players.increment
489
579
  end
490
- ~~~
580
+ ```
491
581
 
492
582
  Here's a similar approach, using an if block (failure rewinds counter):
493
583
 
494
- ~~~ruby
584
+ ```ruby
495
585
  @team.drafted_players.increment do |val|
496
586
  if val <= @team.max_players
497
587
  @team.team_players.create!(:player_id => 221)
498
588
  @team.active_players.increment
499
589
  end
500
590
  end
501
- ~~~
591
+ ```
502
592
 
503
593
  Class methods work too, using the familiar ActiveRecord counter syntax:
504
594
 
505
- ~~~ruby
595
+ ```ruby
506
596
  Team.increment_counter :drafted_players, team_id
507
597
  Team.decrement_counter :drafted_players, team_id, 2
508
598
  Team.increment_counter :total_online_players # no ID on global counter
509
- ~~~
599
+ ```
510
600
 
511
- Class-level atomic blocks can also be used. This may save a DB fetch, if you have
601
+ Class-level atomic blocks can also be used. This may save a DB fetch, if you have
512
602
  a record ID and don't need any other attributes from the DB table:
513
603
 
514
- ~~~ruby
604
+ ```ruby
515
605
  Team.increment_counter(:drafted_players, team_id) do |val|
516
606
  TeamPitcher.create!(:team_id => team_id, :pitcher_id => 181)
517
607
  Team.increment_counter(:active_players, team_id)
518
608
  end
519
- ~~~
609
+ ```
520
610
 
521
- ### Locks ###
611
+ ### Locks
522
612
 
523
613
  Locks work similarly. On completion or exception the lock is released:
524
614
 
525
- ~~~ruby
615
+ ```ruby
526
616
  class Team < ActiveRecord::Base
527
- lock :reorder # declare a lock
617
+ redis_lock :reorder # declare a lock
528
618
  end
529
619
 
530
620
  @team.reorder_lock.lock do
531
621
  @team.reorder_all_players
532
622
  end
533
- ~~~
623
+ ```
534
624
 
535
625
  Class-level lock (same concept)
536
626
 
537
- ~~~ruby
627
+ ```ruby
538
628
  Team.obtain_lock(:reorder, team_id) do
539
629
  Team.reorder_all_players(team_id)
540
630
  end
541
- ~~~
631
+ ```
542
632
 
543
- Lock expiration. Sometimes you want to make sure your locks are cleaned up should
544
- the unthinkable happen (server failure). You can set lock expirations to handle
545
- this. Expired locks are released by the next process to attempt lock. Just
633
+ Lock expiration. Sometimes you want to make sure your locks are cleaned up should
634
+ the unthinkable happen (server failure). You can set lock expirations to handle
635
+ this. Expired locks are released by the next process to attempt lock. Just
546
636
  make sure you expiration value is sufficiently large compared to your expected
547
637
  lock time.
548
638
 
549
- ~~~ruby
639
+ ```ruby
550
640
  class Team < ActiveRecord::Base
551
- lock :reorder, :expiration => 15.minutes
641
+ redis_lock :reorder, :expiration => 15.minutes
552
642
  end
553
- ~~~
643
+ ```
554
644
 
555
- Keep in mind that true locks serialize your entire application at that point. As
645
+ Keep in mind that true locks serialize your entire application at that point. As
556
646
  such, atomic counters are strongly preferred.
557
647
 
558
- ### Expiration ###
648
+ ### Expiration
559
649
 
560
650
  Use :expiration and :expireat options to set default expiration.
561
651
 
562
- ~~~ruby
652
+ ```ruby
563
653
  value :value_with_expiration, :expiration => 1.hour
564
654
  value :value_with_expireat, :expireat => lambda { Time.now + 1.hour }
565
- ~~~
655
+ ```
566
656
 
567
657
  :warning: In the above example, `expiration` is evaluated at class load time.
568
658
  In this example, it will be one hour after loading the class, not after one hour
569
659
  after setting a value. If you want to expire one hour after setting the value,
570
660
  please use `:expireat` with `lambda`.
571
661
 
572
- Custom serialization
573
- --------------------
662
+ ## Custom serialization
663
+
574
664
  You can customize how values are serialized by setting `serializer: CustomSerializer`.
575
665
  The default is `Marshal` from the standard lib, but it can be anything that responds to `dump` and
576
666
  `load`. `JSON` and `YAML` are popular options.
@@ -578,7 +668,7 @@ The default is `Marshal` from the standard lib, but it can be anything that resp
578
668
  If you need to pass extra arguments to `dump` or `load`, you can set
579
669
  `marshal_dump_args: { foo: 'bar' }` and `marshal_load_args: { foo: 'bar' }` respectively.
580
670
 
581
- ~~~ruby
671
+ ```ruby
582
672
  class CustomSerializer
583
673
  def self.dump(value)
584
674
  # custom code for serializing
@@ -592,9 +682,22 @@ end
592
682
  @account = Account.create!(params[:account])
593
683
  @newest = Redis::Value.new('custom_serializer', marshal: true, serializer: CustomSerializer)
594
684
  @newest.value = @account.attributes
595
- ~~~
685
+ ```
686
+
687
+ ---
688
+
689
+ ## Under the Hood
690
+
691
+ Redis keys are prefixed to namespace keys with the same names.
692
+ By default the prefix is generated from the embedded class's name.
693
+ But you can also set a custom prefix using `redis_prefix=`.
694
+
695
+ If needed the redis key for a specific attribute of a class can be obtained by:
696
+ `MyClass.redis_field_key(attr_name, primary_id)`
697
+ or for globals
698
+ `MyClass.redis_field_key(attr_name)`
699
+
700
+ ## Author
596
701
 
597
- Author
598
- =======
599
- Copyright (c) 2009-2019 [Nate Wiger](http://nateware.com). All Rights Reserved.
702
+ Copyright (c) 2009-2026 [Nate Wiger](http://nateware.com). All Rights Reserved.
600
703
  Released under the [Artistic License](http://www.opensource.org/licenses/artistic-license-2.0.php).