redis-objects-legacy 1.6.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 ADDED
@@ -0,0 +1,600 @@
1
+ Redis::Objects - Map Redis types directly to Ruby objects
2
+ =========================================================
3
+
4
+ [![Build Status](https://app.travis-ci.com/nateware/redis-objects.svg?branch=master)](https://travis-ci.com/nateware/redis-objects)
5
+ [![Code Coverage](https://codecov.io/gh/nateware/redis-objects/branch/master/graph/badge.svg)](https://codecov.io/gh/nateware/redis-objects)
6
+ [![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
+
8
+ This is **not** an ORM. People that are wrapping ORM’s around Redis are missing the point.
9
+
10
+ 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.
12
+ 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?
14
+
15
+ 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
17
+ over the lower-level redis-rb API:
18
+
19
+ 1. Easy to integrate directly with existing ORMs - ActiveRecord, DataMapper, etc. Add counters to your model!
20
+ 2. Complex data structures are automatically Marshaled (if you set :marshal => true)
21
+ 3. Integers are returned as integers, rather than '17'
22
+ 4. Higher-level types are provided, such as Locks, that wrap multiple calls
23
+
24
+ This gem originally arose out of a need for high-concurrency atomic operations;
25
+ for a fun rant on the topic, see [An Atomic Rant](http://nateware.com/2010/02/18/an-atomic-rant),
26
+ or scroll down to [Atomic Counters and Locks](#atomicity) in this README.
27
+
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
30
+ as `Redis::List` and `Redis::SortedSet`.
31
+
32
+ Installation and Setup
33
+ ----------------------
34
+ Add it to your Gemfile as:
35
+
36
+ ~~~ruby
37
+ gem 'redis-objects'
38
+ ~~~
39
+
40
+ Redis::Objects needs a handle created by `Redis.new` or a [ConnectionPool](https://github.com/mperham/connection_pool):
41
+
42
+ 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
46
+ require 'connection_pool'
47
+ Redis::Objects.redis = ConnectionPool.new(size: 5, timeout: 5) { Redis.new(:host => '127.0.0.1', :port => 6379) }
48
+ ~~~
49
+
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.
58
+
59
+ Alternatively, you can set the `redis` handle directly:
60
+
61
+ ~~~ruby
62
+ Redis::Objects.redis = Redis.new(...)
63
+ ~~~
64
+
65
+ Finally, you can even set different handles for different classes:
66
+
67
+ ~~~ruby
68
+ class User
69
+ include Redis::Objects
70
+ end
71
+ class Post
72
+ include Redis::Objects
73
+ end
74
+
75
+ # you can also use a ConnectionPool here as well
76
+ User.redis = Redis.new(:host => '1.2.3.4')
77
+ Post.redis = Redis.new(:host => '5.6.7.8')
78
+ ~~~
79
+
80
+ As of `0.7.0`, `redis-objects` now autoloads the appropriate `Redis::Whatever`
81
+ classes on demand. Previous strategies of individually requiring `redis/list`
82
+ or `redis/set` are no longer required.
83
+
84
+ Option 1: Model Class Include
85
+ =============================
86
+ 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
88
+ will work with _any_ class that provides an `id` method that returns a unique value.**
89
+ Redis::Objects automatically creates keys that are unique to each object, in the format:
90
+
91
+ model_name:id:field_name
92
+
93
+ For illustration purposes, consider this stub class:
94
+
95
+ ~~~ruby
96
+ class User
97
+ include Redis::Objects
98
+ counter :my_posts
99
+ def id
100
+ 1
101
+ end
102
+ end
103
+
104
+ user = User.new
105
+ user.id # 1
106
+ user.my_posts.increment
107
+ user.my_posts.increment
108
+ user.my_posts.increment
109
+ puts user.my_posts.value # 3
110
+ user.my_posts.reset
111
+ puts user.my_posts.value # 0
112
+ user.my_posts.reset 5
113
+ puts user.my_posts.value # 5
114
+ ~~~
115
+
116
+ Here's an example that integrates several data types with an ActiveRecord model:
117
+
118
+ ~~~ruby
119
+ class Team < ActiveRecord::Base
120
+ include Redis::Objects
121
+
122
+ lock :trade_players, :expiration => 15 # sec
123
+ value :at_bat
124
+ counter :hits
125
+ counter :runs
126
+ counter :outs
127
+ counter :inning, :start => 1
128
+ list :on_base
129
+ list :coaches, :marshal => true
130
+ set :outfielders
131
+ hash_key :pitchers_faced # "hash" is taken by Ruby
132
+ sorted_set :rank, :global => true
133
+ end
134
+ ~~~
135
+
136
+ Familiar Ruby array operations Just Work (TM):
137
+
138
+ ~~~ruby
139
+ @team = Team.find_by_name('New York Yankees')
140
+ @team.on_base << 'player1'
141
+ @team.on_base << 'player2'
142
+ @team.on_base << 'player3'
143
+ @team.on_base # ['player1', 'player2', 'player3']
144
+ @team.on_base.pop
145
+ @team.on_base.shift
146
+ @team.on_base.length # 1
147
+ @team.on_base.delete('player2')
148
+ @team.on_base = ['player1', 'player2'] # ['player1', 'player2']
149
+ ~~~
150
+
151
+ Sets work too:
152
+
153
+ ~~~ruby
154
+ @team.outfielders << 'outfielder1'
155
+ @team.outfielders << 'outfielder2'
156
+ @team.outfielders << 'outfielder1' # dup ignored
157
+ @team.outfielders # ['outfielder1', 'outfielder2']
158
+ @team.outfielders.each do |player|
159
+ puts player
160
+ end
161
+ player = @team.outfielders.detect{|of| of == 'outfielder2'}
162
+ @team.outfielders = ['outfielder1', 'outfielder3'] # ['outfielder1', 'outfielder3']
163
+ ~~~
164
+
165
+ Hashes work too:
166
+
167
+ ~~~ruby
168
+ @team.pitchers_faced['player1'] = 'pitcher2'
169
+ @team.pitchers_faced['player2'] = 'pitcher1'
170
+ @team.pitchers_faced = { 'player1' => 'pitcher2', 'player2' => 'pitcher1' }
171
+ ~~~
172
+
173
+ And you can do unions and intersections between objects (kinda cool):
174
+
175
+ ~~~ruby
176
+ @team1.outfielders | @team2.outfielders # outfielders on both teams
177
+ @team1.outfielders & @team2.outfielders # in baseball, should be empty :-)
178
+ ~~~
179
+
180
+ Counters can be atomically incremented/decremented (but not assigned):
181
+
182
+ ~~~ruby
183
+ @team.hits.increment # or incr
184
+ @team.hits.decrement # or decr
185
+ @team.hits.incr(3) # add 3
186
+ @team.runs = 4 # exception
187
+ ~~~
188
+
189
+ Defining a different method as the `id` field is easy
190
+
191
+ ~~~ruby
192
+ class User
193
+ include Redis::Objects
194
+ redis_id_field :uid
195
+ counter :my_posts
196
+ end
197
+
198
+ user.uid # 195137a1bdea4473
199
+ user.my_posts.increment # 1
200
+ ~~~
201
+
202
+ Finally, for free, you get a `redis` method that points directly to a Redis connection:
203
+
204
+ ~~~ruby
205
+ Team.redis.get('somekey')
206
+ @team = Team.new
207
+ @team.redis.get('somekey')
208
+ @team.redis.smembers('someset')
209
+ ~~~
210
+
211
+ You can use the `redis` handle to directly call any [Redis API command](http://redis.io/commands).
212
+
213
+ Option 2: Standalone Usage
214
+ ===========================
215
+ There is a Ruby class that maps to each Redis type, with methods for each
216
+ [Redis API command](http://redis.io/commands).
217
+ Note that calling `new` does not imply it's actually a "new" value - it just
218
+ creates a mapping between that Ruby object and the corresponding Redis data
219
+ structure, which may already exist on the `redis-server`.
220
+
221
+ Counters
222
+ --------
223
+ The `counter_name` is the key stored in Redis.
224
+
225
+ ~~~ruby
226
+ @counter = Redis::Counter.new('counter_name')
227
+ @counter.increment # or incr
228
+ @counter.decrement # or decr
229
+ @counter.increment(3)
230
+ puts @counter.value
231
+ ~~~
232
+
233
+ This gem provides a clean way to do atomic blocks as well:
234
+
235
+ ~~~ruby
236
+ @counter.increment do |val|
237
+ raise "Full" if val > MAX_VAL # rewind counter
238
+ end
239
+ ~~~
240
+
241
+ See the section on [Atomic Counters and Locks](#atomicity) for cool uses of atomic counter blocks.
242
+
243
+ Locks
244
+ -----
245
+ A convenience class that wraps the pattern of [using setnx to perform locking](http://redis.io/commands/setnx).
246
+
247
+ ~~~ruby
248
+ @lock = Redis::Lock.new('serialize_stuff', :expiration => 15, :timeout => 0.1)
249
+ @lock.lock do
250
+ # do work
251
+ end
252
+ ~~~
253
+
254
+ This can be especially useful if you're running batch jobs spread across multiple hosts.
255
+
256
+ Values
257
+ ------
258
+ Simple values are easy as well:
259
+
260
+ ~~~ruby
261
+ @value = Redis::Value.new('value_name')
262
+ @value.value = 'a'
263
+ @value.delete
264
+ ~~~
265
+
266
+ Complex data is no problem with :marshal => true:
267
+
268
+ ~~~ruby
269
+ @account = Account.create!(params[:account])
270
+ @newest = Redis::Value.new('newest_account', :marshal => true)
271
+ @newest.value = @account.attributes
272
+ puts @newest.value['username']
273
+ ~~~
274
+
275
+ Compress data to save memory usage on Redis with :compress => true:
276
+
277
+ ~~~ruby
278
+ @account = Account.create!(params[:account])
279
+ @marshaled_value = Redis::Value.new('marshaled', :marshal => true, :compress => true)
280
+ @marshaled_value.value = @account.attributes
281
+ @unmarshaled_value = Redis::Value.new('unmarshaled', :compress => true)
282
+ @unmarshaled_value = 'Really Long String'
283
+ puts @marshaled_value.value['username']
284
+ puts @unmarshaled_value.value
285
+ ~~~
286
+
287
+ Lists
288
+ -----
289
+ Lists work just like Ruby arrays:
290
+
291
+ ~~~ruby
292
+ @list = Redis::List.new('list_name')
293
+ @list << 'a'
294
+ @list << 'b'
295
+ @list.include? 'c' # false
296
+ @list.values # ['a','b']
297
+ @list << 'c'
298
+ @list.delete('c')
299
+ @list[0]
300
+ @list[0,1]
301
+ @list[0..1]
302
+ @list.shift
303
+ @list.pop
304
+ @list.clear
305
+ # etc
306
+ ~~~
307
+
308
+ You can bound the size of the list to only hold N elements like so:
309
+
310
+ ~~~ruby
311
+ # Only holds 10 elements, throws out old ones when you reach :maxlength.
312
+ @list = Redis::List.new('list_name', :maxlength => 10)
313
+ ~~~
314
+
315
+ Complex data types are serialized with :marshal => true:
316
+
317
+ ~~~ruby
318
+ @list = Redis::List.new('list_name', :marshal => true)
319
+ @list << {:name => "Nate", :city => "San Diego"}
320
+ @list << {:name => "Peter", :city => "Oceanside"}
321
+ @list.each do |el|
322
+ puts "#{el[:name]} lives in #{el[:city]}"
323
+ end
324
+ ~~~
325
+
326
+ 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
+
328
+ Hashes
329
+ ------
330
+ 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
332
+ conflicts with the Ruby core Hash class in other gems.)
333
+
334
+ ~~~ruby
335
+ @hash = Redis::HashKey.new('hash_name')
336
+ @hash['a'] = 1
337
+ @hash['b'] = 2
338
+ @hash.each do |k,v|
339
+ puts "#{k} = #{v}"
340
+ end
341
+ @hash['c'] = 3
342
+ puts @hash.all # {"a"=>"1","b"=>"2","c"=>"3"}
343
+ @hash.clear
344
+ ~~~
345
+
346
+ Redis also adds incrementing and bulk operations:
347
+
348
+ ~~~ruby
349
+ @hash.incr('c', 6) # 9
350
+ @hash.bulk_set('d' => 5, 'e' => 6)
351
+ @hash.bulk_get('d','e') # "5", "6"
352
+ ~~~
353
+
354
+ Remember that numbers become strings in Redis. Unlike with other Redis data types,
355
+ `redis-objects` can't guess at your data type in this situation, since you may
356
+ actually mean to store "1.5".
357
+
358
+ Sets
359
+ ----
360
+ Sets work like the Ruby [Set](http://ruby-doc.org/core/classes/Set.html) class.
361
+ They are unordered, but guarantee uniqueness of members.
362
+
363
+ ~~~ruby
364
+ @set = Redis::Set.new('set_name')
365
+ @set << 'a'
366
+ @set << 'b'
367
+ @set << 'a' # dup ignored
368
+ @set.member? 'c' # false
369
+ @set.members # ['a','b']
370
+ @set.members.reverse # ['b','a']
371
+ @set.each do |member|
372
+ puts member
373
+ end
374
+ @set.clear
375
+ # etc
376
+ ~~~
377
+
378
+ You can perform Redis intersections/unions/diffs easily:
379
+
380
+ ~~~ruby
381
+ @set1 = Redis::Set.new('set1')
382
+ @set2 = Redis::Set.new('set2')
383
+ @set3 = Redis::Set.new('set3')
384
+ members = @set1 & @set2 # intersection
385
+ members = @set1 | @set2 # union
386
+ members = @set1 + @set2 # union
387
+ members = @set1 ^ @set2 # difference
388
+ members = @set1 - @set2 # difference
389
+ members = @set1.intersection(@set2, @set3) # multiple
390
+ members = @set1.union(@set2, @set3) # multiple
391
+ members = @set1.difference(@set2, @set3) # multiple
392
+ ~~~
393
+
394
+ Or store them in Redis:
395
+
396
+ ~~~ruby
397
+ @set1.interstore('intername', @set2, @set3)
398
+ members = @set1.redis.get('intername')
399
+ @set1.unionstore('unionname', @set2, @set3)
400
+ members = @set1.redis.get('unionname')
401
+ @set1.diffstore('diffname', @set2, @set3)
402
+ members = @set1.redis.get('diffname')
403
+ ~~~
404
+
405
+ And use complex data types too, with :marshal => true:
406
+
407
+ ~~~ruby
408
+ @set1 = Redis::Set.new('set1', :marshal => true)
409
+ @set2 = Redis::Set.new('set2', :marshal => true)
410
+ @set1 << {:name => "Nate", :city => "San Diego"}
411
+ @set1 << {:name => "Peter", :city => "Oceanside"}
412
+ @set2 << {:name => "Nate", :city => "San Diego"}
413
+ @set2 << {:name => "Jeff", :city => "Del Mar"}
414
+
415
+ @set1 & @set2 # Nate
416
+ @set1 - @set2 # Peter
417
+ @set1 | @set2 # all 3 people
418
+ ~~~
419
+
420
+ Sorted Sets
421
+ -----------
422
+ 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:
424
+
425
+ ~~~ruby
426
+ @sorted_set = Redis::SortedSet.new('number_of_posts')
427
+ @sorted_set['Nate'] = 15
428
+ @sorted_set['Peter'] = 75
429
+ @sorted_set['Jeff'] = 24
430
+
431
+ # Array access to get sorted order
432
+ @sorted_set[0..2] # => ["Nate", "Jeff", "Peter"]
433
+ @sorted_set[0,2] # => ["Nate", "Jeff"]
434
+
435
+ @sorted_set['Peter'] # => 75
436
+ @sorted_set['Jeff'] # => 24
437
+ @sorted_set.score('Jeff') # same thing (24)
438
+
439
+ @sorted_set.rank('Peter') # => 2
440
+ @sorted_set.rank('Jeff') # => 1
441
+
442
+ @sorted_set.first # => "Nate"
443
+ @sorted_set.last # => "Peter"
444
+ @sorted_set.revrange(0,2) # => ["Peter", "Jeff", "Nate"]
445
+
446
+ @sorted_set['Newbie'] = 1
447
+ @sorted_set.members # => ["Newbie", "Nate", "Jeff", "Peter"]
448
+ @sorted_set.members.reverse # => ["Peter", "Jeff", "Nate", "Newbie"]
449
+
450
+ @sorted_set.rangebyscore(10, 100, :limit => 2) # => ["Nate", "Jeff"]
451
+ @sorted_set.members(:with_scores => true) # => [["Newbie", 1], ["Nate", 16], ["Jeff", 28], ["Peter", 76]]
452
+
453
+ # atomic increment
454
+ @sorted_set.increment('Nate')
455
+ @sorted_set.incr('Peter') # shorthand
456
+ @sorted_set.incr('Jeff', 4)
457
+ ~~~
458
+
459
+ The other Redis Sorted Set commands are supported as well; see [Sorted Sets API](http://redis.io/commands#sorted_set).
460
+
461
+ <a name="atomicity"></a>
462
+ Atomic Counters and Locks
463
+ -------------------------
464
+ You are probably not handling atomicity correctly in your app. For a fun rant
465
+ on the topic, see [An Atomic Rant](http://nateware.com/an-atomic-rant.html).
466
+
467
+ Atomic counters are a good way to handle concurrency:
468
+
469
+ ~~~ruby
470
+ @team = Team.find(1)
471
+ if @team.drafted_players.increment <= @team.max_players
472
+ # do stuff
473
+ @team.team_players.create!(:player_id => 221)
474
+ @team.active_players.increment
475
+ else
476
+ # reset counter state
477
+ @team.drafted_players.decrement
478
+ end
479
+ ~~~
480
+
481
+ An _atomic block_ gives you a cleaner way to do the above. Exceptions or returning nil
482
+ will rewind the counter back to its previous state:
483
+
484
+ ~~~ruby
485
+ @team.drafted_players.increment do |val|
486
+ raise Team::TeamFullError if val > @team.max_players # rewind
487
+ @team.team_players.create!(:player_id => 221)
488
+ @team.active_players.increment
489
+ end
490
+ ~~~
491
+
492
+ Here's a similar approach, using an if block (failure rewinds counter):
493
+
494
+ ~~~ruby
495
+ @team.drafted_players.increment do |val|
496
+ if val <= @team.max_players
497
+ @team.team_players.create!(:player_id => 221)
498
+ @team.active_players.increment
499
+ end
500
+ end
501
+ ~~~
502
+
503
+ Class methods work too, using the familiar ActiveRecord counter syntax:
504
+
505
+ ~~~ruby
506
+ Team.increment_counter :drafted_players, team_id
507
+ Team.decrement_counter :drafted_players, team_id, 2
508
+ Team.increment_counter :total_online_players # no ID on global counter
509
+ ~~~
510
+
511
+ Class-level atomic blocks can also be used. This may save a DB fetch, if you have
512
+ a record ID and don't need any other attributes from the DB table:
513
+
514
+ ~~~ruby
515
+ Team.increment_counter(:drafted_players, team_id) do |val|
516
+ TeamPitcher.create!(:team_id => team_id, :pitcher_id => 181)
517
+ Team.increment_counter(:active_players, team_id)
518
+ end
519
+ ~~~
520
+
521
+ ### Locks ###
522
+
523
+ Locks work similarly. On completion or exception the lock is released:
524
+
525
+ ~~~ruby
526
+ class Team < ActiveRecord::Base
527
+ lock :reorder # declare a lock
528
+ end
529
+
530
+ @team.reorder_lock.lock do
531
+ @team.reorder_all_players
532
+ end
533
+ ~~~
534
+
535
+ Class-level lock (same concept)
536
+
537
+ ~~~ruby
538
+ Team.obtain_lock(:reorder, team_id) do
539
+ Team.reorder_all_players(team_id)
540
+ end
541
+ ~~~
542
+
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
546
+ make sure you expiration value is sufficiently large compared to your expected
547
+ lock time.
548
+
549
+ ~~~ruby
550
+ class Team < ActiveRecord::Base
551
+ lock :reorder, :expiration => 15.minutes
552
+ end
553
+ ~~~
554
+
555
+ Keep in mind that true locks serialize your entire application at that point. As
556
+ such, atomic counters are strongly preferred.
557
+
558
+ ### Expiration ###
559
+
560
+ Use :expiration and :expireat options to set default expiration.
561
+
562
+ ~~~ruby
563
+ value :value_with_expiration, :expiration => 1.hour
564
+ value :value_with_expireat, :expireat => lambda { Time.now + 1.hour }
565
+ ~~~
566
+
567
+ :warning: In the above example, `expiration` is evaluated at class load time.
568
+ In this example, it will be one hour after loading the class, not after one hour
569
+ after setting a value. If you want to expire one hour after setting the value,
570
+ please use `:expireat` with `lambda`.
571
+
572
+ Custom serialization
573
+ --------------------
574
+ You can customize how values are serialized by setting `serializer: CustomSerializer`.
575
+ The default is `Marshal` from the standard lib, but it can be anything that responds to `dump` and
576
+ `load`. `JSON` and `YAML` are popular options.
577
+
578
+ If you need to pass extra arguments to `dump` or `load`, you can set
579
+ `marshal_dump_args: { foo: 'bar' }` and `marshal_load_args: { foo: 'bar' }` respectively.
580
+
581
+ ~~~ruby
582
+ class CustomSerializer
583
+ def self.dump(value)
584
+ # custom code for serializing
585
+ end
586
+
587
+ def self.load(value)
588
+ # custom code for deserializing
589
+ end
590
+ end
591
+
592
+ @account = Account.create!(params[:account])
593
+ @newest = Redis::Value.new('custom_serializer', marshal: true, serializer: CustomSerializer)
594
+ @newest.value = @account.attributes
595
+ ~~~
596
+
597
+ Author
598
+ =======
599
+ Copyright (c) 2009-2019 [Nate Wiger](http://nateware.com). All Rights Reserved.
600
+ Released under the [Artistic License](http://www.opensource.org/licenses/artistic-license-2.0.php).
data/Rakefile ADDED
@@ -0,0 +1,14 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ desc "run all the specs"
4
+ task :test do
5
+ sh "bacon spec/*_spec.rb"
6
+ end
7
+ task :default => :test
8
+ task :spec => :test
9
+
10
+ desc "show changelog"
11
+ task :changelog do
12
+ latest = `git tag |tail -1`.chomp
13
+ sh "git log --pretty=format:'* %s %b [%an]' #{latest}..HEAD"
14
+ end
@@ -0,0 +1,62 @@
1
+ require 'redis/helpers/core_commands'
2
+
3
+ class Redis
4
+ # Defines base functionality for all redis-objects.
5
+ class BaseObject
6
+ include Redis::Helpers::CoreCommands
7
+
8
+ attr_reader :key, :options
9
+
10
+ def initialize(key, *args)
11
+ @key = key.is_a?(Array) ? key.flatten.join(':') : key
12
+ @options = args.last.is_a?(Hash) ? args.pop : {}
13
+ @myredis = Objects::ConnectionPoolProxy.proxy_if_needed(args.first)
14
+ end
15
+
16
+ # Dynamically query the handle to enable resetting midstream
17
+ def redis
18
+ @myredis || ::Redis::Objects.redis
19
+ end
20
+
21
+ alias :inspect :to_s # Ruby 1.9.2
22
+
23
+ def set_expiration
24
+ if !@options[:expiration].nil?
25
+ redis.expire(@key, @options[:expiration]) if redis.ttl(@key) < 0
26
+ elsif !@options[:expireat].nil?
27
+ expireat = @options[:expireat]
28
+ at = expireat.respond_to?(:call) ? expireat.call.to_i : expireat.to_i
29
+ redis.expireat(@key, at) if redis.ttl(@key) < 0
30
+ end
31
+ end
32
+
33
+ def allow_expiration(&block)
34
+ result = block.call
35
+ set_expiration
36
+ result
37
+ end
38
+
39
+ def to_json(*args)
40
+ to_hash.to_json(*args)
41
+ rescue NoMethodError => e
42
+ raise e.class, "The current runtime does not provide a `to_json` implementation. Require 'json' or another JSON library and try again."
43
+ end
44
+
45
+ def as_json(*)
46
+ to_hash
47
+ end
48
+
49
+ def to_hash
50
+ { "key" => @key, "options" => @options, "value" => value }
51
+ end
52
+
53
+ # Math ops - delegate to value method
54
+ %w(== < > <= >=).each do |m|
55
+ class_eval <<-EndOverload
56
+ def #{m}(what)
57
+ value #{m} what
58
+ end
59
+ EndOverload
60
+ end
61
+ end
62
+ end