redis-objects 0.4.1 → 0.5.1
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 +24 -0
- data/README.rdoc +10 -9
- data/Rakefile +3 -2
- data/VERSION +1 -1
- data/lib/redis/base_object.rb +4 -2
- data/lib/redis/counter.rb +0 -1
- data/lib/redis/{hash.rb → hash_key.rb} +26 -10
- data/lib/redis/helpers/core_commands.rb +6 -8
- data/lib/redis/helpers/serialize.rb +7 -5
- data/lib/redis/list.rb +1 -1
- data/lib/redis/lock.rb +1 -1
- data/lib/redis/objects/counters.rb +18 -4
- data/lib/redis/objects/hashes.rb +3 -3
- data/lib/redis/objects.rb +27 -7
- data/lib/redis/sorted_set.rb +36 -20
- data/lib/redis/value.rb +0 -2
- data/redis-objects.gemspec +37 -43
- data/spec/redis_namespace_compat_spec.rb +24 -0
- data/spec/redis_objects_active_record_spec.rb +105 -0
- data/spec/redis_objects_instance_spec.rb +113 -6
- data/spec/redis_objects_model_spec.rb +52 -9
- data/spec/spec_helper.rb +11 -2
- metadata +13 -35
- data/.gitignore +0 -2
data/CHANGELOG.rdoc
CHANGED
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
= Changelog for Redis::Objects
|
|
2
2
|
|
|
3
|
+
== 0.5.1 [Final] (23 May 2011)
|
|
4
|
+
|
|
5
|
+
* Fixed super class delegation conflicts with Redis Counters vs ActiveRecord [Tim Aßmann]
|
|
6
|
+
|
|
7
|
+
* Added zcount method to SortedSet [dunedain289]
|
|
8
|
+
|
|
9
|
+
* Updated redis-objects to look for Redis.current and prefer it over global $redis variable [Jean Boussier]
|
|
10
|
+
|
|
11
|
+
* Updated URLs to reflect new redis.io website [Jérémy Lecour]
|
|
12
|
+
|
|
13
|
+
== 0.5.0 [Final] (8 November 2010)
|
|
14
|
+
|
|
15
|
+
* Incompatible change: Had to rename Redis::Hash to Redis::HashKey due to internal conflicts with Redis lib and Ruby [Nate Wiger]
|
|
16
|
+
|
|
17
|
+
* Fixed AR counter override so that Redis::Objects doesn't hide AR counters [Mattias Pfeiffer]
|
|
18
|
+
|
|
19
|
+
* Fixed delete problem with Redis::List and complex values [Esdras Mayrink]
|
|
20
|
+
|
|
21
|
+
* Fix Redis::HashKey to support complex (marshaled) types [Mattias Pfeiffer]
|
|
22
|
+
|
|
23
|
+
* Group results of SortedSet#rangebyscore and #revrangebyscore if :withscores option is passed [Szymon Nowak]
|
|
24
|
+
|
|
25
|
+
* Updated Redis DEL semantics per API change [Gabe da Silveira]
|
|
26
|
+
|
|
3
27
|
== 0.4.1 [Final] (23 August 2010)
|
|
4
28
|
|
|
5
29
|
* Fixes for Ruby 1.8 failures due to missing flatten() [Gabe da Silveira]
|
data/README.rdoc
CHANGED
|
@@ -7,7 +7,7 @@ on _individual_ data structures, like counters, lists, and sets. The *atomic* p
|
|
|
7
7
|
Using an ORM wrapper that retrieves a "record", updates values, then sends those values back,
|
|
8
8
|
_removes_ the atomicity, cutting the nuts off the major advantage of Redis. Just use MySQL, k?
|
|
9
9
|
|
|
10
|
-
This gem provides a Rubyish interface to Redis, by mapping {Redis types}[http://
|
|
10
|
+
This gem provides a Rubyish interface to Redis, by mapping {Redis types}[http://redis.io/commands]
|
|
11
11
|
to Ruby objects, via a thin layer over Ezra's +redis+ gem. It offers several advantages
|
|
12
12
|
over the lower-level redis-rb API:
|
|
13
13
|
|
|
@@ -45,7 +45,7 @@ config/initializers/redis.rb is a good place for this.)
|
|
|
45
45
|
|
|
46
46
|
require 'redis'
|
|
47
47
|
require 'redis/objects'
|
|
48
|
-
Redis::Objects.redis = Redis.new(:host => 127.0.0.1, :port => 6379)
|
|
48
|
+
Redis::Objects.redis = Redis.new(:host => '127.0.0.1', :port => 6379)
|
|
49
49
|
|
|
50
50
|
Remember you can use Redis::Objects in any Ruby code. There are *no* dependencies
|
|
51
51
|
on Rails. Standalone, Sinatra, Resque - no problem.
|
|
@@ -111,12 +111,12 @@ Finally, for free, you get a +redis+ method that points directly to a Redis conn
|
|
|
111
111
|
@team.redis.get('somekey')
|
|
112
112
|
@team.redis.smembers('someset')
|
|
113
113
|
|
|
114
|
-
You can use the +redis+ handle to directly call any {Redis API command}[http://
|
|
114
|
+
You can use the +redis+ handle to directly call any {Redis API command}[http://redis.io/commands].
|
|
115
115
|
|
|
116
116
|
== Example 2: Standalone Usage
|
|
117
117
|
|
|
118
118
|
There is a Ruby class that maps to each Redis type, with methods for each
|
|
119
|
-
{Redis API command}[http://
|
|
119
|
+
{Redis API command}[http://redis.io/commands].
|
|
120
120
|
Note that calling +new+ does not imply it's actually a "new" value - it just
|
|
121
121
|
creates a mapping between that object and the corresponding Redis data structure,
|
|
122
122
|
which may already exist on the redis-server.
|
|
@@ -154,7 +154,7 @@ See the section on "Atomicity" for cool uses of atomic counter blocks.
|
|
|
154
154
|
|
|
155
155
|
=== Locks
|
|
156
156
|
|
|
157
|
-
A convenience class that wraps the pattern of {using +setnx+ to perform locking}[http://
|
|
157
|
+
A convenience class that wraps the pattern of {using +setnx+ to perform locking}[http://redis.io/commands/setnx].
|
|
158
158
|
|
|
159
159
|
require 'redis/lock'
|
|
160
160
|
@lock = Redis::Lock.new('image_resizing', :expiration => 15, :timeout => 0.1)
|
|
@@ -217,10 +217,11 @@ Complex data types are no problem with :marshal => true:
|
|
|
217
217
|
=== Hashes
|
|
218
218
|
|
|
219
219
|
Hashes work like a Ruby {Hash}[http://ruby-doc.org/core/classes/Hash.html], with
|
|
220
|
-
a few Redis-specific additions.
|
|
220
|
+
a few Redis-specific additions. (The class name is "HashKey" not just "Hash", due to
|
|
221
|
+
conflicts with the Ruby core Hash class in other gems.)
|
|
221
222
|
|
|
222
|
-
require 'redis/
|
|
223
|
-
@hash = Redis::
|
|
223
|
+
require 'redis/hash_key'
|
|
224
|
+
@hash = Redis::HashKey.new('hash_name')
|
|
224
225
|
@hash['a'] = 1
|
|
225
226
|
@hash['b'] = 2
|
|
226
227
|
@hash.each do |k,v|
|
|
@@ -330,7 +331,7 @@ a Hash and an Array. You assign like a Hash, but retrieve like an Array:
|
|
|
330
331
|
@sorted_set.incr('Peter') # shorthand
|
|
331
332
|
@sorted_set.incr('Jeff', 4)
|
|
332
333
|
|
|
333
|
-
The other Redis Sorted Set commands are supported as well; see {Sorted Sets API}[http://
|
|
334
|
+
The other Redis Sorted Set commands are supported as well; see {Sorted Sets API}[http://redis.io/commands#sorted_set].
|
|
334
335
|
|
|
335
336
|
== Atomic Counters and Locks
|
|
336
337
|
|
data/Rakefile
CHANGED
|
@@ -11,8 +11,8 @@ begin
|
|
|
11
11
|
gem.homepage = "http://github.com/nateware/redis-objects"
|
|
12
12
|
gem.authors = ["Nate Wiger"]
|
|
13
13
|
gem.add_development_dependency "bacon", ">= 0"
|
|
14
|
-
gem.requirements << 'redis, v2.
|
|
15
|
-
gem.add_dependency('redis', '>= 2.
|
|
14
|
+
gem.requirements << 'redis, v2.1.1 or greater'
|
|
15
|
+
gem.add_dependency('redis', '>= 2.1.1') # ALSO: update spec/spec_helper.rb
|
|
16
16
|
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
|
17
17
|
end
|
|
18
18
|
Jeweler::GemcutterTasks.new
|
|
@@ -27,6 +27,7 @@ end
|
|
|
27
27
|
# spec.verbose = true
|
|
28
28
|
# end
|
|
29
29
|
|
|
30
|
+
desc "run all the specs"
|
|
30
31
|
task :test do
|
|
31
32
|
sh "bacon spec/*_spec.rb"
|
|
32
33
|
end
|
data/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.5.1
|
data/lib/redis/base_object.rb
CHANGED
|
@@ -3,8 +3,10 @@ class Redis
|
|
|
3
3
|
class BaseObject
|
|
4
4
|
def initialize(key, *args)
|
|
5
5
|
@key = key.is_a?(Array) ? key.flatten.join(':') : key
|
|
6
|
-
@options = args.last.is_a?(
|
|
7
|
-
@redis = args.first || $redis
|
|
6
|
+
@options = args.last.is_a?(Hash) ? args.pop : {}
|
|
7
|
+
@redis = args.first || $redis || Redis.current
|
|
8
8
|
end
|
|
9
|
+
|
|
10
|
+
alias :inspect :to_s # Ruby 1.9.2
|
|
9
11
|
end
|
|
10
12
|
end
|
data/lib/redis/counter.rb
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
require File.dirname(__FILE__) + '/base_object'
|
|
2
|
+
|
|
1
3
|
class Redis
|
|
2
4
|
#
|
|
3
5
|
# Class representing a Redis hash.
|
|
4
6
|
#
|
|
5
|
-
class
|
|
7
|
+
class HashKey < BaseObject
|
|
6
8
|
require 'enumerator'
|
|
7
9
|
include Enumerable
|
|
8
10
|
require 'redis/helpers/core_commands'
|
|
@@ -10,7 +12,12 @@ class Redis
|
|
|
10
12
|
require 'redis/helpers/serialize'
|
|
11
13
|
include Redis::Helpers::Serialize
|
|
12
14
|
|
|
13
|
-
attr_reader :key, :redis
|
|
15
|
+
attr_reader :key, :options, :redis
|
|
16
|
+
|
|
17
|
+
def initialize(key, *args)
|
|
18
|
+
super
|
|
19
|
+
@options[:marshal_keys] ||= {}
|
|
20
|
+
end
|
|
14
21
|
|
|
15
22
|
# Needed since Redis::Hash masks bare Hash in redis.rb
|
|
16
23
|
def self.[](*args)
|
|
@@ -19,7 +26,7 @@ class Redis
|
|
|
19
26
|
|
|
20
27
|
# Sets a field to value
|
|
21
28
|
def []=(field, value)
|
|
22
|
-
store(field, value)
|
|
29
|
+
store(field, to_redis(value))
|
|
23
30
|
end
|
|
24
31
|
|
|
25
32
|
# Gets the value of a field
|
|
@@ -29,12 +36,12 @@ class Redis
|
|
|
29
36
|
|
|
30
37
|
# Redis: HSET
|
|
31
38
|
def store(field, value)
|
|
32
|
-
redis.hset(key, field, value)
|
|
39
|
+
redis.hset(key, field, to_redis(value, options[:marshal_keys][field]))
|
|
33
40
|
end
|
|
34
41
|
|
|
35
42
|
# Redis: HGET
|
|
36
43
|
def fetch(field)
|
|
37
|
-
redis.hget(key, field)
|
|
44
|
+
from_redis redis.hget(key, field), options[:marshal_keys][field]
|
|
38
45
|
end
|
|
39
46
|
|
|
40
47
|
# Verify that a field exists. Redis: HEXISTS
|
|
@@ -57,13 +64,15 @@ class Redis
|
|
|
57
64
|
|
|
58
65
|
# Return all the values of the hash. Redis: HVALS
|
|
59
66
|
def values
|
|
60
|
-
redis.hvals(key)
|
|
67
|
+
from_redis redis.hvals(key)
|
|
61
68
|
end
|
|
62
69
|
alias_method :vals, :values
|
|
63
70
|
|
|
64
71
|
# Retrieve the entire hash. Redis: HGETALL
|
|
65
72
|
def all
|
|
66
|
-
redis.hgetall(key)
|
|
73
|
+
h = redis.hgetall(key)
|
|
74
|
+
h.each { |k,v| h[k] = from_redis(v, options[:marshal_keys][k]) }
|
|
75
|
+
h
|
|
67
76
|
end
|
|
68
77
|
alias_method :clone, :all
|
|
69
78
|
|
|
@@ -102,7 +111,9 @@ class Redis
|
|
|
102
111
|
# Set keys in bulk, takes a hash of field/values {'field1' => 'val1'}. Redis: HMSET
|
|
103
112
|
def bulk_set(*args)
|
|
104
113
|
raise ArgumentError, "Argument to bulk_set must be hash of key/value pairs" unless args.last.is_a?(::Hash)
|
|
105
|
-
redis.hmset(key, *args.last.inject([]){ |arr,kv|
|
|
114
|
+
redis.hmset(key, *args.last.inject([]){ |arr,kv|
|
|
115
|
+
arr + [kv[0], to_redis(kv[1], options[:marshal_keys][kv[0]])]
|
|
116
|
+
})
|
|
106
117
|
end
|
|
107
118
|
|
|
108
119
|
# Get keys in bulk, takes an array of fields as arguments. Redis: HMGET
|
|
@@ -110,14 +121,19 @@ class Redis
|
|
|
110
121
|
hsh = {}
|
|
111
122
|
res = redis.hmget(key, *fields.flatten)
|
|
112
123
|
fields.each do |k|
|
|
113
|
-
hsh[k] = res.shift
|
|
124
|
+
hsh[k] = from_redis(res.shift, options[:marshal_keys][k])
|
|
114
125
|
end
|
|
115
126
|
hsh
|
|
116
127
|
end
|
|
117
128
|
|
|
118
129
|
# Increment value by integer at field. Redis: HINCRBY
|
|
119
130
|
def incrby(field, val = 1)
|
|
120
|
-
redis.hincrby(key, field, val)
|
|
131
|
+
ret = redis.hincrby(key, field, val)
|
|
132
|
+
unless ret.is_a? Array
|
|
133
|
+
ret.to_i
|
|
134
|
+
else
|
|
135
|
+
nil
|
|
136
|
+
end
|
|
121
137
|
end
|
|
122
138
|
alias_method :incr, :incrby
|
|
123
139
|
|
|
@@ -41,14 +41,12 @@ class Redis
|
|
|
41
41
|
def move(dbindex)
|
|
42
42
|
redis.move key, dbindex
|
|
43
43
|
end
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
# args += ['sort']
|
|
50
|
-
# from_redis redis.sort key
|
|
51
|
-
# end
|
|
44
|
+
|
|
45
|
+
def sort(options={})
|
|
46
|
+
options[:order] ||= "asc alpha"
|
|
47
|
+
redis.sort(key, options)
|
|
48
|
+
end
|
|
52
49
|
end
|
|
50
|
+
|
|
53
51
|
end
|
|
54
52
|
end
|
|
@@ -3,8 +3,8 @@ class Redis
|
|
|
3
3
|
module Serialize
|
|
4
4
|
include Marshal
|
|
5
5
|
|
|
6
|
-
def to_redis(value)
|
|
7
|
-
return value unless options[:marshal]
|
|
6
|
+
def to_redis(value, marshal=false)
|
|
7
|
+
return value unless options[:marshal] || marshal
|
|
8
8
|
case value
|
|
9
9
|
when String, Fixnum, Bignum, Float
|
|
10
10
|
value
|
|
@@ -13,15 +13,17 @@ class Redis
|
|
|
13
13
|
end
|
|
14
14
|
end
|
|
15
15
|
|
|
16
|
-
def from_redis(value)
|
|
17
|
-
return value unless options[:marshal]
|
|
16
|
+
def from_redis(value, marshal=false)
|
|
17
|
+
return value unless options[:marshal] || marshal
|
|
18
18
|
case value
|
|
19
19
|
when Array
|
|
20
20
|
value.collect{|v| from_redis(v)}
|
|
21
|
+
when Hash
|
|
22
|
+
value.inject({}) { |h, (k, v)| h[k] = from_redis(v); h }
|
|
21
23
|
else
|
|
22
24
|
restore(value) rescue value
|
|
23
25
|
end
|
|
24
26
|
end
|
|
25
27
|
end
|
|
26
28
|
end
|
|
27
|
-
end
|
|
29
|
+
end
|
data/lib/redis/list.rb
CHANGED
|
@@ -73,7 +73,7 @@ class Redis
|
|
|
73
73
|
# Use .del to completely delete the entire key.
|
|
74
74
|
# Redis: LREM
|
|
75
75
|
def delete(name, count=0)
|
|
76
|
-
redis.lrem(key, count, name) # weird api
|
|
76
|
+
redis.lrem(key, count, to_redis(name)) # weird api
|
|
77
77
|
end
|
|
78
78
|
|
|
79
79
|
# Iterate through each member of the set. Redis::Objects mixes in Enumerable,
|
data/lib/redis/lock.rb
CHANGED
|
@@ -41,7 +41,7 @@ class Redis
|
|
|
41
41
|
|
|
42
42
|
# Lock is being held. Now check to see if it's expired (if we're using
|
|
43
43
|
# lock expiration).
|
|
44
|
-
# See "Handling Deadlocks" section on http://
|
|
44
|
+
# See "Handling Deadlocks" section on http://redis.io/commands/setnx
|
|
45
45
|
if !@options[:expiration].nil?
|
|
46
46
|
old_expiration = redis.get(key).to_f
|
|
47
47
|
|
|
@@ -55,6 +55,7 @@ class Redis
|
|
|
55
55
|
# Increment a counter with the specified name and id. Accepts a block
|
|
56
56
|
# like the instance method. See Redis::Objects::Counter for details.
|
|
57
57
|
def increment_counter(name, id=nil, by=1, &block)
|
|
58
|
+
return super(name, id) unless counter_defined?(name)
|
|
58
59
|
verify_counter_defined!(name, id)
|
|
59
60
|
initialize_counter!(name, id)
|
|
60
61
|
value = redis.incrby(redis_field_key(name, id), by).to_i
|
|
@@ -64,6 +65,7 @@ class Redis
|
|
|
64
65
|
# Decrement a counter with the specified name and id. Accepts a block
|
|
65
66
|
# like the instance method. See Redis::Objects::Counter for details.
|
|
66
67
|
def decrement_counter(name, id=nil, by=1, &block)
|
|
68
|
+
return super(name, id) unless counter_defined?(name)
|
|
67
69
|
verify_counter_defined!(name, id)
|
|
68
70
|
initialize_counter!(name, id)
|
|
69
71
|
value = redis.decrby(redis_field_key(name, id), by).to_i
|
|
@@ -81,12 +83,16 @@ class Redis
|
|
|
81
83
|
private
|
|
82
84
|
|
|
83
85
|
def verify_counter_defined!(name, id) #:nodoc:
|
|
84
|
-
raise
|
|
86
|
+
raise NoMethodError, "Undefined counter :#{name} for class #{self.name}" unless counter_defined?(name)
|
|
85
87
|
if id.nil? and !@redis_objects[name][:global]
|
|
86
88
|
raise Redis::Objects::MissingID, "Missing ID for non-global counter #{self.name}##{name}"
|
|
87
89
|
end
|
|
88
90
|
end
|
|
89
91
|
|
|
92
|
+
def counter_defined?(name) #:nodoc:
|
|
93
|
+
@redis_objects && @redis_objects.has_key?(name)
|
|
94
|
+
end
|
|
95
|
+
|
|
90
96
|
def initialize_counter!(name, id) #:nodoc:
|
|
91
97
|
key = redis_field_key(name, id)
|
|
92
98
|
unless @initialized_counters[key]
|
|
@@ -117,16 +123,24 @@ class Redis
|
|
|
117
123
|
# It is more efficient to use increment_[counter_name] directly.
|
|
118
124
|
# This is mainly just for completeness to override ActiveRecord.
|
|
119
125
|
def increment(name, by=1)
|
|
120
|
-
send(name)
|
|
126
|
+
if self.class.send("counter_defined?", name)
|
|
127
|
+
send(name).increment(by)
|
|
128
|
+
else
|
|
129
|
+
super
|
|
130
|
+
end
|
|
121
131
|
end
|
|
122
132
|
|
|
123
133
|
# Decrement a counter.
|
|
124
134
|
# It is more efficient to use increment_[counter_name] directly.
|
|
125
135
|
# This is mainly just for completeness to override ActiveRecord.
|
|
126
136
|
def decrement(name, by=1)
|
|
127
|
-
send(name)
|
|
137
|
+
if self.class.send("counter_defined?", name)
|
|
138
|
+
send(name).decrement(by)
|
|
139
|
+
else
|
|
140
|
+
super
|
|
141
|
+
end
|
|
128
142
|
end
|
|
129
143
|
end
|
|
130
144
|
end
|
|
131
145
|
end
|
|
132
|
-
end
|
|
146
|
+
end
|
data/lib/redis/objects/hashes.rb
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# This is the class loader, for use as "include Redis::Objects::Hashes"
|
|
2
2
|
# For the object itself, see "Redis::Hash"
|
|
3
|
-
require 'redis/
|
|
3
|
+
require 'redis/hash_key'
|
|
4
4
|
class Redis
|
|
5
5
|
module Objects
|
|
6
6
|
module Hashes
|
|
@@ -19,7 +19,7 @@ class Redis
|
|
|
19
19
|
if options[:global]
|
|
20
20
|
instance_eval <<-EndMethods
|
|
21
21
|
def #{name}
|
|
22
|
-
@#{name} ||= Redis::
|
|
22
|
+
@#{name} ||= Redis::HashKey.new(redis_field_key(:#{name}), #{klass_name}.redis, #{klass_name}.redis_objects[:#{name}])
|
|
23
23
|
end
|
|
24
24
|
EndMethods
|
|
25
25
|
class_eval <<-EndMethods
|
|
@@ -30,7 +30,7 @@ class Redis
|
|
|
30
30
|
else
|
|
31
31
|
class_eval <<-EndMethods
|
|
32
32
|
def #{name}
|
|
33
|
-
@#{name} ||= Redis::
|
|
33
|
+
@#{name} ||= Redis::HashKey.new(redis_field_key(:#{name}), #{klass_name}.redis, #{klass_name}.redis_objects[:#{name}])
|
|
34
34
|
end
|
|
35
35
|
EndMethods
|
|
36
36
|
end
|
data/lib/redis/objects.rb
CHANGED
|
@@ -46,12 +46,13 @@ class Redis
|
|
|
46
46
|
autoload :Values, File.join(dir, 'values')
|
|
47
47
|
autoload :Hashes, File.join(dir, 'hashes')
|
|
48
48
|
|
|
49
|
-
class NotConnected
|
|
49
|
+
class NotConnected < StandardError; end
|
|
50
|
+
class NilObjectId < StandardError; end
|
|
50
51
|
|
|
51
52
|
class << self
|
|
52
53
|
def redis=(conn) @redis = conn end
|
|
53
54
|
def redis
|
|
54
|
-
@redis ||= $redis || raise(NotConnected, "Redis::Objects.redis not set to a Redis.new connection")
|
|
55
|
+
@redis ||= $redis || Redis.current || raise(NotConnected, "Redis::Objects.redis not set to a Redis.new connection")
|
|
55
56
|
end
|
|
56
57
|
|
|
57
58
|
def included(klass)
|
|
@@ -74,7 +75,9 @@ class Redis
|
|
|
74
75
|
|
|
75
76
|
# Class methods that appear in your class when you include Redis::Objects.
|
|
76
77
|
module ClassMethods
|
|
77
|
-
|
|
78
|
+
attr_writer :redis
|
|
79
|
+
attr_accessor :redis_objects
|
|
80
|
+
def redis() @redis ||= Objects.redis end
|
|
78
81
|
|
|
79
82
|
# Set the Redis redis_prefix to use. Defaults to model_name
|
|
80
83
|
def redis_prefix=(redis_prefix) @redis_prefix = redis_prefix end
|
|
@@ -86,10 +89,19 @@ class Redis
|
|
|
86
89
|
downcase
|
|
87
90
|
end
|
|
88
91
|
|
|
89
|
-
def redis_field_key(name, id=
|
|
92
|
+
def redis_field_key(name, id=nil) #:nodoc:
|
|
90
93
|
klass = first_ancestor_with(name)
|
|
91
|
-
# This can never ever ever ever change or upgrades will corrupt all data
|
|
92
|
-
|
|
94
|
+
# READ THIS: This can never ever ever ever change or upgrades will corrupt all data
|
|
95
|
+
# I don't think people were using Proc as keys before (that would create a weird key). Should be ok
|
|
96
|
+
key = klass.redis_objects[name.to_sym][:key]
|
|
97
|
+
if key && key.respond_to?(:call)
|
|
98
|
+
key = key.call self
|
|
99
|
+
end
|
|
100
|
+
if id.nil? and !klass.redis_objects[name.to_sym][:global]
|
|
101
|
+
raise NilObjectId,
|
|
102
|
+
"[#{klass.redis_objects[name.to_sym]}] Attempt to address redis-object :#{name} on class #{klass.name} with nil id (unsaved record?) [object_id=#{object_id}]"
|
|
103
|
+
end
|
|
104
|
+
key || "#{redis_prefix(klass)}:#{id}:#{name}"
|
|
93
105
|
end
|
|
94
106
|
|
|
95
107
|
def first_ancestor_with(name)
|
|
@@ -107,8 +119,16 @@ class Redis
|
|
|
107
119
|
def redis_field_key(name) #:nodoc:
|
|
108
120
|
klass = self.class.first_ancestor_with(name)
|
|
109
121
|
if key = klass.redis_objects[name.to_sym][:key]
|
|
110
|
-
|
|
122
|
+
if key.respond_to?(:call)
|
|
123
|
+
key.call self
|
|
124
|
+
else
|
|
125
|
+
eval "%(#{key})"
|
|
126
|
+
end
|
|
111
127
|
else
|
|
128
|
+
if id.nil? and !klass.redis_objects[name.to_sym][:global]
|
|
129
|
+
raise NilObjectId,
|
|
130
|
+
"Attempt to address redis-object :#{name} on class #{klass.name} with nil id (unsaved record?) [object_id=#{object_id}]"
|
|
131
|
+
end
|
|
112
132
|
# don't try to refactor into class redis_field_key because fucks up eval context
|
|
113
133
|
"#{klass.redis_prefix}:#{id}:#{name}"
|
|
114
134
|
end
|
data/lib/redis/sorted_set.rb
CHANGED
|
@@ -16,7 +16,7 @@ class Redis
|
|
|
16
16
|
|
|
17
17
|
# How to add values using a sorted set. The key is the member, eg,
|
|
18
18
|
# "Peter", and the value is the score, eg, 163. So:
|
|
19
|
-
# num_posts['Peter'] = 163
|
|
19
|
+
# num_posts['Peter'] = 163
|
|
20
20
|
def []=(member, score)
|
|
21
21
|
add(member, score)
|
|
22
22
|
end
|
|
@@ -77,11 +77,7 @@ class Redis
|
|
|
77
77
|
def range(start_index, end_index, options={})
|
|
78
78
|
if options[:withscores] || options[:with_scores]
|
|
79
79
|
val = from_redis redis.zrange(key, start_index, end_index, :with_scores => true)
|
|
80
|
-
|
|
81
|
-
while k = val.shift and v = val.shift
|
|
82
|
-
ret << [k, v.to_f]
|
|
83
|
-
end
|
|
84
|
-
ret
|
|
80
|
+
group_set_with_scores(val)
|
|
85
81
|
else
|
|
86
82
|
from_redis redis.zrange(key, start_index, end_index)
|
|
87
83
|
end
|
|
@@ -91,11 +87,7 @@ class Redis
|
|
|
91
87
|
def revrange(start_index, end_index, options={})
|
|
92
88
|
if options[:withscores] || options[:with_scores]
|
|
93
89
|
val = from_redis redis.zrevrange(key, start_index, end_index, :with_scores => true)
|
|
94
|
-
|
|
95
|
-
while k = val.shift and v = val.shift
|
|
96
|
-
ret << [k, v.to_f]
|
|
97
|
-
end
|
|
98
|
-
ret
|
|
90
|
+
group_set_with_scores(val)
|
|
99
91
|
else
|
|
100
92
|
from_redis redis.zrevrange(key, start_index, end_index)
|
|
101
93
|
end
|
|
@@ -111,7 +103,13 @@ class Redis
|
|
|
111
103
|
args[:limit] = [options[:offset] || 0, options[:limit] || options[:count]] if
|
|
112
104
|
options[:offset] || options[:limit] || options[:count]
|
|
113
105
|
args[:with_scores] = true if options[:withscores] || options[:with_scores]
|
|
114
|
-
|
|
106
|
+
|
|
107
|
+
if args[:with_scores]
|
|
108
|
+
val = from_redis redis.zrangebyscore(key, min, max, args)
|
|
109
|
+
group_set_with_scores(val)
|
|
110
|
+
else
|
|
111
|
+
from_redis redis.zrangebyscore(key, min, max, args)
|
|
112
|
+
end
|
|
115
113
|
end
|
|
116
114
|
|
|
117
115
|
# Forwards compat (not yet implemented in Redis)
|
|
@@ -120,13 +118,19 @@ class Redis
|
|
|
120
118
|
args[:limit] = [options[:offset] || 0, options[:limit] || options[:count]] if
|
|
121
119
|
options[:offset] || options[:limit] || options[:count]
|
|
122
120
|
args[:with_scores] = true if options[:withscores] || options[:with_scores]
|
|
123
|
-
|
|
121
|
+
|
|
122
|
+
if args[:with_scores]
|
|
123
|
+
val = from_redis redis.zrevrangebyscore(key, min, max, args)
|
|
124
|
+
group_set_with_scores(val)
|
|
125
|
+
else
|
|
126
|
+
from_redis redis.zrevrangebyscore(key, min, max, args)
|
|
127
|
+
end
|
|
124
128
|
end
|
|
125
129
|
|
|
126
130
|
# Remove all elements in the sorted set at key with rank between start and end. Start and end are
|
|
127
131
|
# 0-based with rank 0 being the element with the lowest score. Both start and end can be negative
|
|
128
132
|
# numbers, where they indicate offsets starting at the element with the highest rank. For example:
|
|
129
|
-
# -1 is the element with the highest score, -2 the element with the second highest score and so forth.
|
|
133
|
+
# -1 is the element with the highest score, -2 the element with the second highest score and so forth.
|
|
130
134
|
# Redis: ZREMRANGEBYRANK
|
|
131
135
|
def remrangebyrank(min, max)
|
|
132
136
|
redis.zremrangebyrank(key, min, max)
|
|
@@ -163,7 +167,7 @@ class Redis
|
|
|
163
167
|
alias_method :incr, :increment
|
|
164
168
|
alias_method :incrby, :increment
|
|
165
169
|
|
|
166
|
-
# Convenience to calling increment() with a negative number.
|
|
170
|
+
# Convenience to calling increment() with a negative number.
|
|
167
171
|
def decrement(by=1)
|
|
168
172
|
redis.zincrby(key, -by).to_i
|
|
169
173
|
end
|
|
@@ -187,7 +191,7 @@ class Redis
|
|
|
187
191
|
alias_method :intersect, :intersection
|
|
188
192
|
alias_method :inter, :intersection
|
|
189
193
|
alias_method :&, :intersection
|
|
190
|
-
|
|
194
|
+
|
|
191
195
|
# Calculate the intersection and store it in Redis as +name+. Returns the number
|
|
192
196
|
# of elements in the stored intersection. Redis: SUNIONSTORE
|
|
193
197
|
def interstore(name, *sets)
|
|
@@ -250,7 +254,7 @@ class Redis
|
|
|
250
254
|
def ==(x)
|
|
251
255
|
members == x
|
|
252
256
|
end
|
|
253
|
-
|
|
257
|
+
|
|
254
258
|
def to_s
|
|
255
259
|
members.join(', ')
|
|
256
260
|
end
|
|
@@ -270,19 +274,31 @@ class Redis
|
|
|
270
274
|
def last
|
|
271
275
|
at(-1)
|
|
272
276
|
end
|
|
273
|
-
|
|
277
|
+
|
|
274
278
|
# The number of members in the set. Aliased as size. Redis: ZCARD
|
|
275
279
|
def length
|
|
276
280
|
redis.zcard(key)
|
|
277
281
|
end
|
|
278
282
|
alias_method :size, :length
|
|
283
|
+
|
|
284
|
+
# The number of members within a range of scores. Redis: ZCOUNT
|
|
285
|
+
def range_size(min, max)
|
|
286
|
+
redis.zcount(key, min, max)
|
|
287
|
+
end
|
|
279
288
|
|
|
280
289
|
private
|
|
281
|
-
|
|
290
|
+
|
|
282
291
|
def keys_from_objects(sets)
|
|
283
292
|
raise ArgumentError, "Must pass in one or more set names" if sets.empty?
|
|
284
293
|
sets.collect{|set| set.is_a?(Redis::SortedSet) ? set.key : set}
|
|
285
294
|
end
|
|
286
|
-
|
|
295
|
+
|
|
296
|
+
def group_set_with_scores(set_with_scores)
|
|
297
|
+
ret = []
|
|
298
|
+
while k = set_with_scores.shift and v = set_with_scores.shift
|
|
299
|
+
ret << [k, v.to_f]
|
|
300
|
+
end
|
|
301
|
+
ret
|
|
302
|
+
end
|
|
287
303
|
end
|
|
288
304
|
end
|