redis-objects 2.0.0.beta → 2.0.0.beta2

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