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/Rakefile
CHANGED
@@ -7,7 +7,7 @@ begin
|
|
7
7
|
gem.name = "redis-objects"
|
8
8
|
gem.summary = %Q{Map Redis types directly to Ruby objects}
|
9
9
|
gem.description = %Q{Map Redis types directly to Ruby objects. Works with any class or ORM.}
|
10
|
-
gem.email = "
|
10
|
+
gem.email = "nwiger@gmail.com"
|
11
11
|
gem.homepage = "http://github.com/nateware/redis-objects"
|
12
12
|
gem.authors = ["Nate Wiger"]
|
13
13
|
gem.add_development_dependency "bacon", ">= 0"
|
@@ -30,7 +30,7 @@ end
|
|
30
30
|
|
31
31
|
desc "run all the specs"
|
32
32
|
task :test do
|
33
|
-
sh "
|
33
|
+
sh "bacon spec/*_spec.rb"
|
34
34
|
end
|
35
35
|
|
36
36
|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.7.0
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'redis/objects'
|
data/lib/redis/base_object.rb
CHANGED
@@ -4,7 +4,12 @@ class Redis
|
|
4
4
|
def initialize(key, *args)
|
5
5
|
@key = key.is_a?(Array) ? key.flatten.join(':') : key
|
6
6
|
@options = args.last.is_a?(Hash) ? args.pop : {}
|
7
|
-
@
|
7
|
+
@myredis = args.first
|
8
|
+
end
|
9
|
+
|
10
|
+
# Dynamically query the handle to enable resetting midstream
|
11
|
+
def redis
|
12
|
+
@myredis || ::Redis::Objects.redis
|
8
13
|
end
|
9
14
|
|
10
15
|
alias :inspect :to_s # Ruby 1.9.2
|
data/lib/redis/counter.rb
CHANGED
@@ -12,11 +12,11 @@ class Redis
|
|
12
12
|
require 'redis/helpers/core_commands'
|
13
13
|
include Redis::Helpers::CoreCommands
|
14
14
|
|
15
|
-
attr_reader :key, :options
|
15
|
+
attr_reader :key, :options
|
16
16
|
def initialize(key, *args)
|
17
17
|
super(key, *args)
|
18
18
|
@options[:start] ||= 0
|
19
|
-
|
19
|
+
redis.setnx(key, @options[:start]) unless @options[:start] == 0 || @options[:init] === false
|
20
20
|
end
|
21
21
|
|
22
22
|
# Reset the counter to its starting value. Not atomic, so use with care.
|
@@ -52,7 +52,7 @@ class Redis
|
|
52
52
|
# method is aliased as incr() for brevity.
|
53
53
|
def increment(by=1, &block)
|
54
54
|
val = redis.incrby(key, by).to_i
|
55
|
-
block_given? ? rewindable_block(:decrement, val, &block) : val
|
55
|
+
block_given? ? rewindable_block(:decrement, by, val, &block) : val
|
56
56
|
end
|
57
57
|
alias_method :incr, :increment
|
58
58
|
|
@@ -60,10 +60,10 @@ class Redis
|
|
60
60
|
# a block, that block will be evaluated with the new value of the counter
|
61
61
|
# as an argument. If the block returns nil or throws an exception, the
|
62
62
|
# counter will automatically be incremented to its previous value. This
|
63
|
-
# method is aliased as
|
63
|
+
# method is aliased as decr() for brevity.
|
64
64
|
def decrement(by=1, &block)
|
65
65
|
val = redis.decrby(key, by).to_i
|
66
|
-
block_given? ? rewindable_block(:increment, val, &block) : val
|
66
|
+
block_given? ? rewindable_block(:increment, by, val, &block) : val
|
67
67
|
end
|
68
68
|
alias_method :decr, :decrement
|
69
69
|
|
@@ -85,16 +85,16 @@ class Redis
|
|
85
85
|
private
|
86
86
|
|
87
87
|
# Implements atomic increment/decrement blocks
|
88
|
-
def rewindable_block(rewind, value, &block)
|
88
|
+
def rewindable_block(rewind, by, value, &block)
|
89
89
|
raise ArgumentError, "Missing block to rewindable_block somehow" unless block_given?
|
90
90
|
ret = nil
|
91
91
|
begin
|
92
92
|
ret = yield value
|
93
93
|
rescue
|
94
|
-
send(rewind)
|
94
|
+
send(rewind, by)
|
95
95
|
raise
|
96
96
|
end
|
97
|
-
send(rewind) if ret.nil?
|
97
|
+
send(rewind, by) if ret.nil?
|
98
98
|
ret
|
99
99
|
end
|
100
100
|
end
|
data/lib/redis/hash_key.rb
CHANGED
@@ -12,8 +12,20 @@ class Redis
|
|
12
12
|
dump(value)
|
13
13
|
end
|
14
14
|
end
|
15
|
-
|
15
|
+
|
16
16
|
def from_redis(value, marshal=false)
|
17
|
+
# This was removed because we can't reliably determine
|
18
|
+
# if a person said @value = "123.4" maybe for space/etc.
|
19
|
+
#begin
|
20
|
+
# case value
|
21
|
+
# when /^\d+$/
|
22
|
+
# return Integer(value)
|
23
|
+
# when /^(?:\d+\d.\d*|\d*\.\d+)$/
|
24
|
+
# return Float(value)
|
25
|
+
# end
|
26
|
+
#rescue
|
27
|
+
# # continue below
|
28
|
+
#end
|
17
29
|
return value unless options[:marshal] || marshal
|
18
30
|
case value
|
19
31
|
when Array
|
data/lib/redis/list.rb
CHANGED
@@ -13,7 +13,7 @@ class Redis
|
|
13
13
|
require 'redis/helpers/serialize'
|
14
14
|
include Redis::Helpers::Serialize
|
15
15
|
|
16
|
-
attr_reader :key, :options
|
16
|
+
attr_reader :key, :options
|
17
17
|
|
18
18
|
# Works like push. Can chain together: list << 'a' << 'b'
|
19
19
|
def <<(value)
|
@@ -21,10 +21,10 @@ class Redis
|
|
21
21
|
self # for << 'a' << 'b'
|
22
22
|
end
|
23
23
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
24
|
+
# Add a member before or after pivot in the list. Redis: LINSERT
|
25
|
+
def insert(where,pivot,value)
|
26
|
+
redis.linsert(key,where,to_redis(pivot),to_redis(value))
|
27
|
+
end
|
28
28
|
|
29
29
|
# Add a member to the end of the list. Redis: RPUSH
|
30
30
|
def push(value)
|
data/lib/redis/lock.rb
CHANGED
@@ -11,12 +11,12 @@ class Redis
|
|
11
11
|
class Lock < BaseObject
|
12
12
|
class LockTimeout < StandardError; end #:nodoc:
|
13
13
|
|
14
|
-
attr_reader :key, :options
|
14
|
+
attr_reader :key, :options
|
15
15
|
def initialize(key, *args)
|
16
16
|
super(key, *args)
|
17
17
|
@options[:timeout] ||= 5
|
18
18
|
@options[:init] = false if @options[:init].nil? # default :init to false
|
19
|
-
|
19
|
+
redis.setnx(key, @options[:start]) unless @options[:start] == 0 || @options[:init] === false
|
20
20
|
end
|
21
21
|
|
22
22
|
# Clear the lock. Should only be needed if there's a server crash
|
data/lib/redis/objects.rb
CHANGED
@@ -1,7 +1,16 @@
|
|
1
1
|
# Redis::Objects - Lightweight object layer around redis-rb
|
2
2
|
# See README.rdoc for usage and approach.
|
3
3
|
require 'redis'
|
4
|
+
|
4
5
|
class Redis
|
6
|
+
autoload :Counter, 'redis/counter'
|
7
|
+
autoload :List, 'redis/list'
|
8
|
+
autoload :Lock, 'redis/lock'
|
9
|
+
autoload :Set, 'redis/set'
|
10
|
+
autoload :SortedSet, 'redis/sorted_set'
|
11
|
+
autoload :Value, 'redis/value'
|
12
|
+
autoload :HashKey, 'redis/hash_key'
|
13
|
+
|
5
14
|
#
|
6
15
|
# Redis::Objects enables high-performance atomic operations in your app
|
7
16
|
# by leveraging the atomic features of the Redis server. To use Redis::Objects,
|
@@ -17,7 +26,7 @@ class Redis
|
|
17
26
|
# lock :archive_game
|
18
27
|
# set :player_ids
|
19
28
|
# end
|
20
|
-
#
|
29
|
+
#
|
21
30
|
# The, you can use these counters both for bookeeping and as atomic actions:
|
22
31
|
#
|
23
32
|
# @game = Game.find(id)
|
@@ -38,30 +47,33 @@ class Redis
|
|
38
47
|
module Objects
|
39
48
|
dir = File.expand_path(__FILE__.sub(/\.rb$/,''))
|
40
49
|
|
41
|
-
autoload :Counters,
|
42
|
-
autoload :Lists,
|
43
|
-
autoload :Locks,
|
44
|
-
autoload :Sets,
|
45
|
-
autoload :SortedSets,
|
46
|
-
autoload :Values,
|
47
|
-
autoload :Hashes,
|
50
|
+
autoload :Counters, 'redis/objects/counters'
|
51
|
+
autoload :Lists, 'redis/objects/lists'
|
52
|
+
autoload :Locks, 'redis/objects/locks'
|
53
|
+
autoload :Sets, 'redis/objects/sets'
|
54
|
+
autoload :SortedSets, 'redis/objects/sorted_sets'
|
55
|
+
autoload :Values, 'redis/objects/values'
|
56
|
+
autoload :Hashes, 'redis/objects/hashes'
|
48
57
|
|
49
58
|
class NotConnected < StandardError; end
|
50
59
|
class NilObjectId < StandardError; end
|
51
60
|
|
52
61
|
class << self
|
53
|
-
def redis=(conn)
|
62
|
+
def redis=(conn)
|
63
|
+
@redis = conn
|
64
|
+
end
|
54
65
|
def redis
|
55
|
-
@redis
|
66
|
+
@redis || $redis || Redis.current ||
|
67
|
+
raise(NotConnected, "Redis::Objects.redis not set to a Redis.new connection")
|
56
68
|
end
|
57
69
|
|
58
70
|
def included(klass)
|
59
71
|
# Core (this file)
|
60
|
-
klass.instance_variable_set('@redis',
|
72
|
+
klass.instance_variable_set('@redis', nil)
|
61
73
|
klass.instance_variable_set('@redis_objects', {})
|
62
74
|
klass.send :include, InstanceMethods
|
63
75
|
klass.extend ClassMethods
|
64
|
-
|
76
|
+
|
65
77
|
# Pull in each object type
|
66
78
|
klass.send :include, Redis::Objects::Counters
|
67
79
|
klass.send :include, Redis::Objects::Lists
|
@@ -75,9 +87,17 @@ class Redis
|
|
75
87
|
|
76
88
|
# Class methods that appear in your class when you include Redis::Objects.
|
77
89
|
module ClassMethods
|
78
|
-
|
79
|
-
|
80
|
-
def redis
|
90
|
+
# Enable per-class connections (eg, User and Post can use diff redis-server)
|
91
|
+
attr_writer :redis
|
92
|
+
def redis
|
93
|
+
@redis || Objects.redis
|
94
|
+
end
|
95
|
+
|
96
|
+
# Internal list of objects
|
97
|
+
attr_writer :redis_objects
|
98
|
+
def redis_objects
|
99
|
+
@redis_objects ||= {}
|
100
|
+
end
|
81
101
|
|
82
102
|
# Set the Redis redis_prefix to use. Defaults to model_name
|
83
103
|
def redis_prefix=(redis_prefix) @redis_prefix = redis_prefix end
|
@@ -22,7 +22,7 @@ class Redis
|
|
22
22
|
def counter(name, options={})
|
23
23
|
options[:start] ||= 0
|
24
24
|
options[:type] ||= options[:start] == 0 ? :increment : :decrement
|
25
|
-
|
25
|
+
redis_objects[name.to_sym] = options.merge(:type => :counter)
|
26
26
|
klass_name = '::' + self.name
|
27
27
|
if options[:global]
|
28
28
|
instance_eval <<-EndMethods
|
@@ -60,7 +60,7 @@ class Redis
|
|
60
60
|
verify_counter_defined!(name, id)
|
61
61
|
initialize_counter!(name, id)
|
62
62
|
value = redis.incrby(redis_field_key(name, id), by).to_i
|
63
|
-
block_given? ? rewindable_block(:decrement_counter, name, id, value, &block) : value
|
63
|
+
block_given? ? rewindable_block(:decrement_counter, name, id, by, value, &block) : value
|
64
64
|
end
|
65
65
|
|
66
66
|
# Decrement a counter with the specified name and id. Accepts a block
|
@@ -71,13 +71,13 @@ class Redis
|
|
71
71
|
verify_counter_defined!(name, id)
|
72
72
|
initialize_counter!(name, id)
|
73
73
|
value = redis.decrby(redis_field_key(name, id), by).to_i
|
74
|
-
block_given? ? rewindable_block(:increment_counter, name, id, value, &block) : value
|
74
|
+
block_given? ? rewindable_block(:increment_counter, name, id, by, value, &block) : value
|
75
75
|
end
|
76
76
|
|
77
77
|
# Reset a counter to its starting value.
|
78
78
|
def reset_counter(name, id=nil, to=nil)
|
79
79
|
verify_counter_defined!(name, id)
|
80
|
-
to =
|
80
|
+
to = redis_objects[name][:start] if to.nil?
|
81
81
|
redis.set(redis_field_key(name, id), to.to_i)
|
82
82
|
true
|
83
83
|
end
|
@@ -85,7 +85,7 @@ class Redis
|
|
85
85
|
# Set a counter to its starting value and return the old value.
|
86
86
|
def getset_counter(name, id=nil, to=nil)
|
87
87
|
verify_counter_defined!(name, id)
|
88
|
-
to =
|
88
|
+
to = redis_objects[name][:start] if to.nil?
|
89
89
|
redis.getset(redis_field_key(name, id), to.to_i).to_i
|
90
90
|
end
|
91
91
|
|
@@ -93,35 +93,35 @@ class Redis
|
|
93
93
|
|
94
94
|
def verify_counter_defined!(name, id) #:nodoc:
|
95
95
|
raise NoMethodError, "Undefined counter :#{name} for class #{self.name}" unless counter_defined?(name)
|
96
|
-
if id.nil? and
|
96
|
+
if id.nil? and !redis_objects[name][:global]
|
97
97
|
raise Redis::Objects::MissingID, "Missing ID for non-global counter #{self.name}##{name}"
|
98
98
|
end
|
99
99
|
end
|
100
100
|
|
101
101
|
def counter_defined?(name) #:nodoc:
|
102
|
-
|
102
|
+
redis_objects && redis_objects.has_key?(name)
|
103
103
|
end
|
104
104
|
|
105
105
|
def initialize_counter!(name, id) #:nodoc:
|
106
106
|
key = redis_field_key(name, id)
|
107
107
|
unless @initialized_counters[key]
|
108
|
-
redis.setnx(key,
|
108
|
+
redis.setnx(key, redis_objects[name][:start])
|
109
109
|
end
|
110
110
|
@initialized_counters[key] = true
|
111
111
|
end
|
112
112
|
|
113
113
|
# Implements increment/decrement blocks on a class level
|
114
|
-
def rewindable_block(rewind, name, id, value, &block) #:nodoc:
|
114
|
+
def rewindable_block(rewind, name, id, by, value, &block) #:nodoc:
|
115
115
|
# Unfortunately this is almost exactly duplicated from Redis::Counter
|
116
116
|
raise ArgumentError, "Missing block to rewindable_block somehow" unless block_given?
|
117
117
|
ret = nil
|
118
118
|
begin
|
119
119
|
ret = yield value
|
120
120
|
rescue
|
121
|
-
send(rewind, name, id)
|
121
|
+
send(rewind, name, id, by)
|
122
122
|
raise
|
123
123
|
end
|
124
|
-
send(rewind, name, id) if ret.nil?
|
124
|
+
send(rewind, name, id, by) if ret.nil?
|
125
125
|
ret
|
126
126
|
end
|
127
127
|
end
|
data/lib/redis/objects/hashes.rb
CHANGED
@@ -14,7 +14,7 @@ class Redis
|
|
14
14
|
# Define a new hash key. It will function like a regular instance
|
15
15
|
# method, so it can be used alongside ActiveRecord, DataMapper, etc.
|
16
16
|
def hash_key(name, options={})
|
17
|
-
|
17
|
+
redis_objects[name.to_sym] = options.merge(:type => :dict)
|
18
18
|
klass_name = '::' + self.name
|
19
19
|
if options[:global]
|
20
20
|
instance_eval <<-EndMethods
|
data/lib/redis/objects/lists.rb
CHANGED
@@ -14,7 +14,7 @@ class Redis
|
|
14
14
|
# Define a new list. It will function like a regular instance
|
15
15
|
# method, so it can be used alongside ActiveRecord, DataMapper, etc.
|
16
16
|
def list(name, options={})
|
17
|
-
|
17
|
+
redis_objects[name.to_sym] = options.merge(:type => :list)
|
18
18
|
klass_name = '::' + self.name
|
19
19
|
if options[:global]
|
20
20
|
instance_eval <<-EndMethods
|
@@ -42,4 +42,4 @@ class Redis
|
|
42
42
|
end
|
43
43
|
end
|
44
44
|
end
|
45
|
-
end
|
45
|
+
end
|
data/lib/redis/objects/locks.rb
CHANGED
@@ -17,7 +17,7 @@ class Redis
|
|
17
17
|
def lock(name, options={})
|
18
18
|
options[:timeout] ||= 5 # seconds
|
19
19
|
lock_name = "#{name}_lock"
|
20
|
-
|
20
|
+
redis_objects[lock_name.to_sym] = options.merge(:type => :lock)
|
21
21
|
klass_name = '::' + self.name
|
22
22
|
if options[:global]
|
23
23
|
instance_eval <<-EndMethods
|
@@ -46,7 +46,7 @@ class Redis
|
|
46
46
|
verify_lock_defined!(name)
|
47
47
|
raise ArgumentError, "Missing block to #{self.name}.obtain_lock" unless block_given?
|
48
48
|
lock_name = "#{name}_lock"
|
49
|
-
Redis::Lock.new(redis_field_key(lock_name, id), redis,
|
49
|
+
Redis::Lock.new(redis_field_key(lock_name, id), redis, redis_objects[lock_name.to_sym]).lock(&block)
|
50
50
|
end
|
51
51
|
|
52
52
|
# Clear the lock. Use with care - usually only in an Admin page to clear
|
@@ -57,9 +57,9 @@ class Redis
|
|
57
57
|
end
|
58
58
|
|
59
59
|
private
|
60
|
-
|
60
|
+
|
61
61
|
def verify_lock_defined!(name)
|
62
|
-
unless
|
62
|
+
unless redis_objects.has_key?("#{name}_lock".to_sym)
|
63
63
|
raise Redis::Objects::UndefinedLock, "Undefined lock :#{name} for class #{self.name}"
|
64
64
|
end
|
65
65
|
end
|
data/lib/redis/objects/sets.rb
CHANGED
@@ -14,7 +14,7 @@ class Redis
|
|
14
14
|
# Define a new list. It will function like a regular instance
|
15
15
|
# method, so it can be used alongside ActiveRecord, DataMapper, etc.
|
16
16
|
def set(name, options={})
|
17
|
-
|
17
|
+
redis_objects[name.to_sym] = options.merge(:type => :set)
|
18
18
|
klass_name = '::' + self.name
|
19
19
|
if options[:global]
|
20
20
|
instance_eval <<-EndMethods
|
@@ -34,7 +34,7 @@ class Redis
|
|
34
34
|
end
|
35
35
|
EndMethods
|
36
36
|
end
|
37
|
-
|
37
|
+
|
38
38
|
end
|
39
39
|
end
|
40
40
|
|
@@ -43,4 +43,4 @@ class Redis
|
|
43
43
|
end
|
44
44
|
end
|
45
45
|
end
|
46
|
-
end
|
46
|
+
end
|
@@ -14,7 +14,7 @@ class Redis
|
|
14
14
|
# Define a new list. It will function like a regular instance
|
15
15
|
# method, so it can be used alongside ActiveRecord, DataMapper, etc.
|
16
16
|
def sorted_set(name, options={})
|
17
|
-
|
17
|
+
redis_objects[name.to_sym] = options.merge(:type => :sorted_set)
|
18
18
|
klass_name = '::' + self.name
|
19
19
|
if options[:global]
|
20
20
|
instance_eval <<-EndMethods
|
@@ -42,4 +42,4 @@ class Redis
|
|
42
42
|
end
|
43
43
|
end
|
44
44
|
end
|
45
|
-
end
|
45
|
+
end
|