redis-objects 0.3.2 → 0.4.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 +23 -1
- data/README.rdoc +32 -4
- data/lib/redis/base_object.rb +10 -0
- data/lib/redis/counter.rb +4 -4
- data/lib/redis/hash.rb +126 -0
- data/lib/redis/list.rb +12 -9
- data/lib/redis/lock.rb +5 -5
- data/lib/redis/objects/hashes.rb +46 -0
- data/lib/redis/objects.rb +2 -0
- data/lib/redis/set.rb +7 -11
- data/lib/redis/sorted_set.rb +37 -24
- data/lib/redis/value.rb +5 -5
- data/spec/redis_objects_instance_spec.rb +126 -11
- data/spec/redis_objects_model_spec.rb +17 -2
- metadata +11 -8
data/CHANGELOG.rdoc
CHANGED
@@ -1,8 +1,30 @@
|
|
1
1
|
= Changelog for Redis::Objects
|
2
2
|
|
3
|
+
== 0.4.0 [Final] (11 August 2010)
|
4
|
+
|
5
|
+
* Full support for redis hashes via new Redis::Hash class [Julio Capote, Nate Wiger]
|
6
|
+
|
7
|
+
* Now dependent on redis-rb client 2.0.4 or later. Should still be backwards compatible with redis-server 1.x
|
8
|
+
|
9
|
+
* Fixes to sets and sorted sets to bring them up to speed with redis-rb 2.0 from tomstuart [Tom Stuart]
|
10
|
+
|
11
|
+
* Incompatible change: Update list[x,y] and sorted_set[x,y] to work consistently with Ruby in all cases [Tom Stuart]
|
12
|
+
|
13
|
+
* Refactoring to make constructors common across all object types from dbalatero [David Balatero]
|
14
|
+
|
15
|
+
* Renamed :withscores option to :with_scores for consistency with redis-rb 2.0, but kept backwards compat [Tom Stuart, Nate Wiger]
|
16
|
+
|
17
|
+
== 0.3.2 [Final] (21 July 2010)
|
18
|
+
|
19
|
+
* New "maxlength" option to Redis::List can create length-limited lists (eg, like a ring buffer) from dbalatero [David Balatero]
|
20
|
+
|
21
|
+
* Fix score conversions in Redis::SortedSet (scores are floats, not ints) from tomstuart [Tom Stuart]
|
22
|
+
|
23
|
+
* Switched from rspec to bacon for tests
|
24
|
+
|
3
25
|
== 0.3.1 [Final] (1 June 2010)
|
4
26
|
|
5
|
-
* Integrated fixes for sorted_set deletions from capotej
|
27
|
+
* Integrated fixes for sorted_set deletions from capotej [Julio Capote]
|
6
28
|
|
7
29
|
== 0.3.0 [Final] (14 April 2010)
|
8
30
|
|
data/README.rdoc
CHANGED
@@ -65,6 +65,8 @@ You can include Redis::Objects in any type of class:
|
|
65
65
|
list :on_base
|
66
66
|
set :outfielders
|
67
67
|
value :at_bat
|
68
|
+
sorted_set :rank, :global => true
|
69
|
+
hash_key :pitchers_faced # "hash" is taken by Ruby
|
68
70
|
end
|
69
71
|
|
70
72
|
Familiar Ruby array operations Just Work (TM):
|
@@ -211,7 +213,31 @@ Complex data types are no problem with :marshal => true:
|
|
211
213
|
@list.each do |el|
|
212
214
|
puts "#{el[:name]} lives in #{el[:city]}"
|
213
215
|
end
|
214
|
-
|
216
|
+
|
217
|
+
=== Hashes
|
218
|
+
|
219
|
+
Hashes work like a Ruby {Hash}[http://ruby-doc.org/core/classes/Hash.html], with
|
220
|
+
a few Redis-specific additions.
|
221
|
+
|
222
|
+
require 'redis/hash'
|
223
|
+
@hash = Redis::Hash.new('hash_name')
|
224
|
+
@hash['a'] = 1
|
225
|
+
@hash['b'] = 2
|
226
|
+
@hash.each do |k,v|
|
227
|
+
puts "#{k} = #{v}"
|
228
|
+
end
|
229
|
+
@hash['c'] = 3
|
230
|
+
puts @hash.all # {"a"=>"1","b"=>"2","c"=>"3"}
|
231
|
+
@hash.clear
|
232
|
+
|
233
|
+
Redis also adds incrementing and bulk operations:
|
234
|
+
|
235
|
+
@hash.incr('c', 6) # 9
|
236
|
+
@hash.bulk_set('d' => 5, 'e' => 6)
|
237
|
+
@hash.bulk_get('d','e') # "5", "6"
|
238
|
+
|
239
|
+
Remember that numbers become strings in Redis. Unlike with other Redis data types, redis-objects can't guess at your data type in this situation.
|
240
|
+
|
215
241
|
=== Sets
|
216
242
|
|
217
243
|
Sets work like the Ruby {Set}[http://ruby-doc.org/core/classes/Set.html] class:
|
@@ -221,8 +247,9 @@ Sets work like the Ruby {Set}[http://ruby-doc.org/core/classes/Set.html] class:
|
|
221
247
|
@set << 'a'
|
222
248
|
@set << 'b'
|
223
249
|
@set << 'a' # dup ignored
|
224
|
-
@set.member? 'c'
|
225
|
-
@set.members
|
250
|
+
@set.member? 'c' # false
|
251
|
+
@set.members # ['a','b']
|
252
|
+
@set.members.reverse # ['b','a']
|
226
253
|
@set.each do |member|
|
227
254
|
puts member
|
228
255
|
end
|
@@ -293,9 +320,10 @@ a Hash and an Array. You assign like a Hash, but retrieve like an Array:
|
|
293
320
|
|
294
321
|
@sorted_set['Newbie'] = 1
|
295
322
|
@sorted_set.members # => ["Newbie", "Nate", "Jeff", "Peter"]
|
323
|
+
@sorted_set.members.reverse # => ["Peter", "Jeff", "Nate", "Newbie"]
|
296
324
|
|
297
325
|
@sorted_set.rangebyscore(10, 100, :limit => 2) # => ["Nate", "Jeff"]
|
298
|
-
@sorted_set.members(:
|
326
|
+
@sorted_set.members(:with_scores => true) # => [["Newbie", 1], ["Nate", 16], ["Jeff", 28], ["Peter", 76]]
|
299
327
|
|
300
328
|
# atomic increment
|
301
329
|
@sorted_set.increment('Nate')
|
@@ -0,0 +1,10 @@
|
|
1
|
+
class Redis
|
2
|
+
# Defines base functionality for all redis-objects.
|
3
|
+
class BaseObject
|
4
|
+
def initialize(key, *args)
|
5
|
+
@key = key.is_a?(Array) ? key.flatten.join(':') : key
|
6
|
+
@options = args.last.is_a?(::Hash) ? args.pop : {} # ::Hash because of Redis::Hash
|
7
|
+
@redis = args.first || $redis
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
data/lib/redis/counter.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/base_object'
|
2
|
+
|
1
3
|
class Redis
|
2
4
|
#
|
3
5
|
# Class representing a Redis counter. This functions like a proxy class, in
|
@@ -6,15 +8,13 @@ class Redis
|
|
6
8
|
# directly, or you can use the counter :foo class method in your
|
7
9
|
# class to define a counter.
|
8
10
|
#
|
9
|
-
class Counter
|
11
|
+
class Counter < BaseObject
|
10
12
|
require 'redis/helpers/core_commands'
|
11
13
|
include Redis::Helpers::CoreCommands
|
12
14
|
|
13
15
|
attr_reader :key, :options, :redis
|
14
16
|
def initialize(key, *args)
|
15
|
-
|
16
|
-
@options = args.last.is_a?(Hash) ? args.pop : {}
|
17
|
-
@redis = args.first || $redis
|
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
|
data/lib/redis/hash.rb
ADDED
@@ -0,0 +1,126 @@
|
|
1
|
+
class Redis
|
2
|
+
#
|
3
|
+
# Class representing a Redis hash.
|
4
|
+
#
|
5
|
+
class Hash < BaseObject
|
6
|
+
require 'enumerator'
|
7
|
+
include Enumerable
|
8
|
+
require 'redis/helpers/core_commands'
|
9
|
+
include Redis::Helpers::CoreCommands
|
10
|
+
require 'redis/helpers/serialize'
|
11
|
+
include Redis::Helpers::Serialize
|
12
|
+
|
13
|
+
attr_reader :key, :redis
|
14
|
+
|
15
|
+
# Needed since Redis::Hash masks bare Hash in redis.rb
|
16
|
+
def self.[](*args)
|
17
|
+
::Hash[*args]
|
18
|
+
end
|
19
|
+
|
20
|
+
# Sets a field to value
|
21
|
+
def []=(field, value)
|
22
|
+
store(field, value)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Gets the value of a field
|
26
|
+
def [](field)
|
27
|
+
fetch(field)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Redis: HSET
|
31
|
+
def store(field, value)
|
32
|
+
redis.hset(key, field, value)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Redis: HGET
|
36
|
+
def fetch(field)
|
37
|
+
redis.hget(key, field)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Verify that a field exists. Redis: HEXISTS
|
41
|
+
def has_key?(field)
|
42
|
+
redis.hexists(key, field)
|
43
|
+
end
|
44
|
+
alias_method :include?, :has_key?
|
45
|
+
alias_method :key?, :has_key?
|
46
|
+
alias_method :member?, :has_key?
|
47
|
+
|
48
|
+
# Delete field. Redis: HDEL
|
49
|
+
def delete(field)
|
50
|
+
redis.hdel(key, field)
|
51
|
+
end
|
52
|
+
|
53
|
+
# Return all the keys of the hash. Redis: HKEYS
|
54
|
+
def keys
|
55
|
+
redis.hkeys(key)
|
56
|
+
end
|
57
|
+
|
58
|
+
# Return all the values of the hash. Redis: HVALS
|
59
|
+
def values
|
60
|
+
redis.hvals(key)
|
61
|
+
end
|
62
|
+
alias_method :vals, :values
|
63
|
+
|
64
|
+
# Retrieve the entire hash. Redis: HGETALL
|
65
|
+
def all
|
66
|
+
redis.hgetall(key)
|
67
|
+
end
|
68
|
+
alias_method :clone, :all
|
69
|
+
|
70
|
+
# Enumerate through all fields. Redis: HGETALL
|
71
|
+
def each(&block)
|
72
|
+
all.each(&block)
|
73
|
+
end
|
74
|
+
|
75
|
+
# Enumerate through each keys. Redis: HKEYS
|
76
|
+
def each_key(&block)
|
77
|
+
keys.each(&block)
|
78
|
+
end
|
79
|
+
|
80
|
+
# Enumerate through all values. Redis: HVALS
|
81
|
+
def each_value(&block)
|
82
|
+
values.each(&block)
|
83
|
+
end
|
84
|
+
|
85
|
+
# Return the size of the dict. Redis: HLEN
|
86
|
+
def size
|
87
|
+
redis.hlen(key)
|
88
|
+
end
|
89
|
+
alias_method :length, :size
|
90
|
+
alias_method :count, :size
|
91
|
+
|
92
|
+
# Returns true if dict is empty
|
93
|
+
def empty?
|
94
|
+
true if size == 0
|
95
|
+
end
|
96
|
+
|
97
|
+
# Clears the dict of all keys/values. Redis: DEL
|
98
|
+
def clear
|
99
|
+
redis.del(key)
|
100
|
+
end
|
101
|
+
|
102
|
+
# Set keys in bulk, takes a hash of field/values {'field1' => 'val1'}. Redis: HMSET
|
103
|
+
def bulk_set(*args)
|
104
|
+
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.flatten)
|
106
|
+
end
|
107
|
+
|
108
|
+
# Get keys in bulk, takes an array of fields as arguments. Redis: HMGET
|
109
|
+
def bulk_get(*fields)
|
110
|
+
hsh = {}
|
111
|
+
res = redis.hmget(key, *fields.flatten)
|
112
|
+
fields.each do |k|
|
113
|
+
hsh[k] = res.shift
|
114
|
+
end
|
115
|
+
hsh
|
116
|
+
end
|
117
|
+
|
118
|
+
# Increment value by integer at field. Redis: HINCRBY
|
119
|
+
def incrby(field, val = 1)
|
120
|
+
redis.hincrby(key, field, val).to_i
|
121
|
+
end
|
122
|
+
alias_method :incr, :incrby
|
123
|
+
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
data/lib/redis/list.rb
CHANGED
@@ -1,9 +1,11 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/base_object'
|
2
|
+
|
1
3
|
class Redis
|
2
4
|
#
|
3
|
-
# Class representing a Redis list. Instances of Redis::List are designed to
|
5
|
+
# Class representing a Redis list. Instances of Redis::List are designed to
|
4
6
|
# behave as much like Ruby arrays as possible.
|
5
7
|
#
|
6
|
-
class List
|
8
|
+
class List < BaseObject
|
7
9
|
require 'enumerator'
|
8
10
|
include Enumerable
|
9
11
|
require 'redis/helpers/core_commands'
|
@@ -12,11 +14,6 @@ class Redis
|
|
12
14
|
include Redis::Helpers::Serialize
|
13
15
|
|
14
16
|
attr_reader :key, :options, :redis
|
15
|
-
def initialize(key, *args)
|
16
|
-
@key = key
|
17
|
-
@options = args.last.is_a?(Hash) ? args.pop : {}
|
18
|
-
@redis = args.first || $redis
|
19
|
-
end
|
20
17
|
|
21
18
|
# Works like push. Can chain together: list << 'a' << 'b'
|
22
19
|
def <<(value)
|
@@ -48,7 +45,8 @@ class Redis
|
|
48
45
|
|
49
46
|
# Return all values in the list. Redis: LRANGE(0,-1)
|
50
47
|
def values
|
51
|
-
from_redis range(0, -1)
|
48
|
+
v = from_redis range(0, -1)
|
49
|
+
v.nil? ? [] : v
|
52
50
|
end
|
53
51
|
alias_method :get, :values
|
54
52
|
|
@@ -59,11 +57,16 @@ class Redis
|
|
59
57
|
if index.is_a? Range
|
60
58
|
range(index.first, index.last)
|
61
59
|
elsif length
|
62
|
-
|
60
|
+
case length <=> 0
|
61
|
+
when 1 then range(index, index + length - 1)
|
62
|
+
when 0 then []
|
63
|
+
when -1 then nil # Ruby does this (a bit weird)
|
64
|
+
end
|
63
65
|
else
|
64
66
|
at(index)
|
65
67
|
end
|
66
68
|
end
|
69
|
+
alias_method :slice, :[]
|
67
70
|
|
68
71
|
# Delete the element(s) from the list that match name. If count is specified,
|
69
72
|
# only the first-N (if positive) or last-N (if negative) will be removed.
|
data/lib/redis/lock.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/base_object'
|
2
|
+
|
1
3
|
class Redis
|
2
4
|
#
|
3
5
|
# Class representing a lock. This functions like a proxy class, in
|
@@ -6,14 +8,12 @@ class Redis
|
|
6
8
|
# directly, but it is better to use the lock :foo class method in your
|
7
9
|
# class to define a lock.
|
8
10
|
#
|
9
|
-
class Lock
|
11
|
+
class Lock < BaseObject
|
10
12
|
class LockTimeout < StandardError; end #:nodoc:
|
11
13
|
|
12
14
|
attr_reader :key, :options, :redis
|
13
15
|
def initialize(key, *args)
|
14
|
-
|
15
|
-
@options = args.last.is_a?(Hash) ? args.pop : {}
|
16
|
-
@redis = args.first || $redis
|
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
|
@@ -81,4 +81,4 @@ class Redis
|
|
81
81
|
@options[:expiration].nil? ? 1 : (Time.now + @options[:expiration].to_f + 1).to_f
|
82
82
|
end
|
83
83
|
end
|
84
|
-
end
|
84
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# This is the class loader, for use as "include Redis::Objects::Hashes"
|
2
|
+
# For the object itself, see "Redis::Hash"
|
3
|
+
require 'redis/hash'
|
4
|
+
class Redis
|
5
|
+
module Objects
|
6
|
+
module Hashes
|
7
|
+
def self.included(klass)
|
8
|
+
klass.send :include, InstanceMethods
|
9
|
+
klass.extend ClassMethods
|
10
|
+
end
|
11
|
+
|
12
|
+
# Class methods that appear in your class when you include Redis::Objects.
|
13
|
+
module ClassMethods
|
14
|
+
# Define a new hash key. It will function like a regular instance
|
15
|
+
# method, so it can be used alongside ActiveRecord, DataMapper, etc.
|
16
|
+
def hash_key(name, options={})
|
17
|
+
@redis_objects[name.to_sym] = options.merge(:type => :dict)
|
18
|
+
if options[:global]
|
19
|
+
instance_eval <<-EndMethods
|
20
|
+
def #{name}
|
21
|
+
@#{name} ||= Redis::Hash.new(field_key(:#{name}), redis, @redis_objects[:#{name}])
|
22
|
+
end
|
23
|
+
EndMethods
|
24
|
+
class_eval <<-EndMethods
|
25
|
+
def #{name}
|
26
|
+
self.class.#{name}
|
27
|
+
end
|
28
|
+
EndMethods
|
29
|
+
else
|
30
|
+
class_eval <<-EndMethods
|
31
|
+
def #{name}
|
32
|
+
@#{name} ||= Redis::Hash.new(field_key(:#{name}), redis, self.class.redis_objects[:#{name}])
|
33
|
+
end
|
34
|
+
EndMethods
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# Instance methods that appear in your class when you include Redis::Objects.
|
40
|
+
module InstanceMethods
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
|
data/lib/redis/objects.rb
CHANGED
@@ -44,6 +44,7 @@ class Redis
|
|
44
44
|
autoload :Sets, File.join(dir, 'sets')
|
45
45
|
autoload :SortedSets, File.join(dir, 'sorted_sets')
|
46
46
|
autoload :Values, File.join(dir, 'values')
|
47
|
+
autoload :Hashes, File.join(dir, 'hashes')
|
47
48
|
|
48
49
|
class NotConnected < StandardError; end
|
49
50
|
|
@@ -67,6 +68,7 @@ class Redis
|
|
67
68
|
klass.send :include, Redis::Objects::Sets
|
68
69
|
klass.send :include, Redis::Objects::SortedSets
|
69
70
|
klass.send :include, Redis::Objects::Values
|
71
|
+
klass.send :include, Redis::Objects::Hashes
|
70
72
|
end
|
71
73
|
end
|
72
74
|
|
data/lib/redis/set.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 set.
|
4
6
|
#
|
5
|
-
class Set
|
7
|
+
class Set < BaseObject
|
6
8
|
require 'enumerator'
|
7
9
|
include Enumerable
|
8
10
|
require 'redis/helpers/core_commands'
|
@@ -11,20 +13,13 @@ class Redis
|
|
11
13
|
include Redis::Helpers::Serialize
|
12
14
|
|
13
15
|
attr_reader :key, :options, :redis
|
14
|
-
|
15
|
-
# Create a new Set.
|
16
|
-
def initialize(key, *args)
|
17
|
-
@key = key
|
18
|
-
@options = args.last.is_a?(Hash) ? args.pop : {}
|
19
|
-
@redis = args.first || $redis
|
20
|
-
end
|
21
16
|
|
22
17
|
# Works like add. Can chain together: list << 'a' << 'b'
|
23
18
|
def <<(value)
|
24
19
|
add(value)
|
25
20
|
self # for << 'a' << 'b'
|
26
21
|
end
|
27
|
-
|
22
|
+
|
28
23
|
# Add the specified value to the set only if it does not exist already.
|
29
24
|
# Redis: SADD
|
30
25
|
def add(value)
|
@@ -33,7 +28,8 @@ class Redis
|
|
33
28
|
|
34
29
|
# Return all members in the set. Redis: SMEMBERS
|
35
30
|
def members
|
36
|
-
from_redis redis.smembers(key)
|
31
|
+
v = from_redis redis.smembers(key)
|
32
|
+
v.nil? ? [] : v
|
37
33
|
end
|
38
34
|
alias_method :get, :members
|
39
35
|
|
@@ -156,7 +152,7 @@ class Redis
|
|
156
152
|
def to_s
|
157
153
|
members.join(', ')
|
158
154
|
end
|
159
|
-
|
155
|
+
|
160
156
|
private
|
161
157
|
|
162
158
|
def keys_from_objects(sets)
|
data/lib/redis/sorted_set.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 sorted set.
|
4
6
|
#
|
5
|
-
class SortedSet
|
7
|
+
class SortedSet < BaseObject
|
6
8
|
# require 'enumerator'
|
7
9
|
# include Enumerable
|
8
10
|
require 'redis/helpers/core_commands'
|
@@ -11,13 +13,6 @@ class Redis
|
|
11
13
|
include Redis::Helpers::Serialize
|
12
14
|
|
13
15
|
attr_reader :key, :options, :redis
|
14
|
-
|
15
|
-
# Create a new SortedSet.
|
16
|
-
def initialize(key, *args)
|
17
|
-
@key = key.is_a?(Array) ? key.flatten.join(':') : key
|
18
|
-
@options = args.last.is_a?(Hash) ? args.pop : {}
|
19
|
-
@redis = args.first || $redis
|
20
|
-
end
|
21
16
|
|
22
17
|
# How to add values using a sorted set. The key is the member, eg,
|
23
18
|
# "Peter", and the value is the score, eg, 163. So:
|
@@ -40,11 +35,16 @@ class Redis
|
|
40
35
|
if index.is_a? Range
|
41
36
|
range(index.first, index.last)
|
42
37
|
elsif length
|
43
|
-
|
38
|
+
case length <=> 0
|
39
|
+
when 1 then range(index, index + length - 1)
|
40
|
+
when 0 then []
|
41
|
+
when -1 then nil # Ruby does this (a bit weird)
|
42
|
+
end
|
44
43
|
else
|
45
44
|
score(index)
|
46
45
|
end
|
47
46
|
end
|
47
|
+
alias_method :slice, :[]
|
48
48
|
|
49
49
|
# Return the score of the specified element of the sorted set at key. If the
|
50
50
|
# specified element does not exist in the sorted set, or the key does not exist
|
@@ -68,14 +68,15 @@ class Redis
|
|
68
68
|
# Return all members of the sorted set with their scores. Extremely CPU-intensive.
|
69
69
|
# Better to use a range instead.
|
70
70
|
def members(options={})
|
71
|
-
range(0, -1, options)
|
71
|
+
v = from_redis range(0, -1, options)
|
72
|
+
v.nil? ? [] : v
|
72
73
|
end
|
73
74
|
|
74
75
|
# Return a range of values from +start_index+ to +end_index+. Can also use
|
75
76
|
# the familiar list[start,end] Ruby syntax. Redis: ZRANGE
|
76
77
|
def range(start_index, end_index, options={})
|
77
|
-
if options[:withscores]
|
78
|
-
val = from_redis redis.zrange(key, start_index, end_index,
|
78
|
+
if options[:withscores] || options[:with_scores]
|
79
|
+
val = from_redis redis.zrange(key, start_index, end_index, :with_scores => true)
|
79
80
|
ret = []
|
80
81
|
while k = val.shift and v = val.shift
|
81
82
|
ret << [k, v.to_f]
|
@@ -88,8 +89,8 @@ class Redis
|
|
88
89
|
|
89
90
|
# Return a range of values from +start_index+ to +end_index+ in reverse order. Redis: ZREVRANGE
|
90
91
|
def revrange(start_index, end_index, options={})
|
91
|
-
if options[:withscores]
|
92
|
-
val = from_redis redis.zrevrange(key, start_index, end_index,
|
92
|
+
if options[:withscores] || options[:with_scores]
|
93
|
+
val = from_redis redis.zrevrange(key, start_index, end_index, :with_scores => true)
|
93
94
|
ret = []
|
94
95
|
while k = val.shift and v = val.shift
|
95
96
|
ret << [k, v.to_f]
|
@@ -106,20 +107,20 @@ class Redis
|
|
106
107
|
# :withscores - if true, scores are returned as well
|
107
108
|
# Redis: ZRANGEBYSCORE
|
108
109
|
def rangebyscore(min, max, options={})
|
109
|
-
args =
|
110
|
-
args
|
110
|
+
args = {}
|
111
|
+
args[:limit] = [options[:offset] || 0, options[:limit] || options[:count]] if
|
111
112
|
options[:offset] || options[:limit] || options[:count]
|
112
|
-
args
|
113
|
-
from_redis redis.zrangebyscore(key, min, max,
|
113
|
+
args[:with_scores] = true if options[:withscores] || options[:with_scores]
|
114
|
+
from_redis redis.zrangebyscore(key, min, max, args)
|
114
115
|
end
|
115
116
|
|
116
117
|
# Forwards compat (not yet implemented in Redis)
|
117
118
|
def revrangebyscore(min, max, options={})
|
118
|
-
args =
|
119
|
-
args
|
119
|
+
args = {}
|
120
|
+
args[:limit] = [options[:offset] || 0, options[:limit] || options[:count]] if
|
120
121
|
options[:offset] || options[:limit] || options[:count]
|
121
|
-
args
|
122
|
-
from_redis redis.zrevrangebyscore(key, min, max,
|
122
|
+
args[:with_scores] = true if options[:withscores] || options[:with_scores]
|
123
|
+
from_redis redis.zrevrangebyscore(key, min, max, args)
|
123
124
|
end
|
124
125
|
|
125
126
|
# Remove all elements in the sorted set at key with rank between start and end. Start and end are
|
@@ -139,7 +140,19 @@ class Redis
|
|
139
140
|
|
140
141
|
# Delete the value from the set. Redis: ZREM
|
141
142
|
def delete(value)
|
142
|
-
redis.zrem(key, value)
|
143
|
+
redis.zrem(key, to_redis(value))
|
144
|
+
end
|
145
|
+
|
146
|
+
# Delete element if it matches block
|
147
|
+
def delete_if(&blocK)
|
148
|
+
raise ArgumentError, "Missing block to SortedSet#delete_if" unless block_given?
|
149
|
+
res = false
|
150
|
+
redis.zrevrange(key, 0, -1).each do |m|
|
151
|
+
if block.call(from_redis(m))
|
152
|
+
res = redis.zrem(key, m)
|
153
|
+
end
|
154
|
+
end
|
155
|
+
res
|
143
156
|
end
|
144
157
|
|
145
158
|
# Increment the rank of that member atomically and return the new value. This
|
@@ -272,4 +285,4 @@ class Redis
|
|
272
285
|
end
|
273
286
|
|
274
287
|
end
|
275
|
-
end
|
288
|
+
end
|
data/lib/redis/value.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 simple value. You can use standard Ruby operations on it.
|
4
6
|
#
|
5
|
-
class Value
|
7
|
+
class Value < BaseObject
|
6
8
|
require 'redis/helpers/core_commands'
|
7
9
|
include Redis::Helpers::CoreCommands
|
8
10
|
require 'redis/helpers/serialize'
|
@@ -10,9 +12,7 @@ class Redis
|
|
10
12
|
|
11
13
|
attr_reader :key, :options, :redis
|
12
14
|
def initialize(key, *args)
|
13
|
-
|
14
|
-
@options = args.last.is_a?(Hash) ? args.pop : {}
|
15
|
-
@redis = args.first || $redis
|
15
|
+
super(key, *args)
|
16
16
|
@redis.setnx(key, @options[:default]) if @options[:default]
|
17
17
|
end
|
18
18
|
|
@@ -32,4 +32,4 @@ class Redis
|
|
32
32
|
def ==(x); value == x; end
|
33
33
|
def nil?; value.nil?; end
|
34
34
|
end
|
35
|
-
end
|
35
|
+
end
|
@@ -7,6 +7,7 @@ require 'redis/value'
|
|
7
7
|
require 'redis/lock'
|
8
8
|
require 'redis/set'
|
9
9
|
require 'redis/sorted_set'
|
10
|
+
require 'redis/hash'
|
10
11
|
|
11
12
|
describe Redis::Value do
|
12
13
|
before do
|
@@ -143,13 +144,22 @@ describe Redis::List do
|
|
143
144
|
@list.get.should == ['a','c','f']
|
144
145
|
@list << 'j'
|
145
146
|
@list.should == ['a','c','f','j']
|
146
|
-
|
147
|
-
@list
|
148
|
-
@list[
|
147
|
+
# Test against similar Ruby functionality
|
148
|
+
a = @list.values
|
149
|
+
@list[0..2].should == a[0..2]
|
150
|
+
@list.slice(0..2).should == a.slice(0..2)
|
151
|
+
@list[0, 2].should == a[0, 2]
|
152
|
+
@list.range(0, 2).should == a[0..2] # range for Redis works like .. in Ruby
|
153
|
+
@list[0, 1].should == a[0, 1]
|
154
|
+
@list.range(0, 1).should == a[0..1] # range for Redis works like .. in Ruby
|
155
|
+
@list[1, 3].should == a[1, 3]
|
156
|
+
@list.slice(1, 3).should == a.slice(1, 3)
|
157
|
+
@list[0, 0].should == []
|
158
|
+
@list[0, -1].should == a[0, -1]
|
149
159
|
@list.length.should == 4
|
150
160
|
@list.size.should == 4
|
151
|
-
@list.should ==
|
152
|
-
@list.get.should ==
|
161
|
+
@list.should == a
|
162
|
+
@list.get.should == a
|
153
163
|
|
154
164
|
i = -1
|
155
165
|
@list.each do |st|
|
@@ -345,6 +355,102 @@ describe Redis::Lock do
|
|
345
355
|
end
|
346
356
|
end
|
347
357
|
|
358
|
+
|
359
|
+
describe Redis::Hash do
|
360
|
+
before do
|
361
|
+
@hash = Redis::Hash.new('test_hash')
|
362
|
+
@hash.clear
|
363
|
+
end
|
364
|
+
|
365
|
+
it "should get and set values" do
|
366
|
+
@hash['foo'] = 'bar'
|
367
|
+
@hash['foo'].should == 'bar'
|
368
|
+
end
|
369
|
+
|
370
|
+
it "should know what exists" do
|
371
|
+
@hash['foo'] = 'bar'
|
372
|
+
@hash.include?('foo').should == true
|
373
|
+
end
|
374
|
+
|
375
|
+
it "should delete values" do
|
376
|
+
@hash['abc'] = 'xyz'
|
377
|
+
@hash.delete('abc')
|
378
|
+
@hash['abc'].should == nil
|
379
|
+
end
|
380
|
+
|
381
|
+
it "should respond to each" do
|
382
|
+
@hash['foo'] = 'bar'
|
383
|
+
@hash.each do |key, val|
|
384
|
+
key.should == 'foo'
|
385
|
+
val.should == 'bar'
|
386
|
+
end
|
387
|
+
end
|
388
|
+
|
389
|
+
it "should have 1 item" do
|
390
|
+
@hash['foo'] = 'bar'
|
391
|
+
@hash.size.should == 1
|
392
|
+
end
|
393
|
+
|
394
|
+
it "should respond to each_key" do
|
395
|
+
@hash['foo'] = 'bar'
|
396
|
+
@hash.each_key do |key|
|
397
|
+
key.should == 'foo'
|
398
|
+
end
|
399
|
+
end
|
400
|
+
|
401
|
+
it "should respond to each_value" do
|
402
|
+
@hash['foo'] = 'bar'
|
403
|
+
@hash.each_value do |val|
|
404
|
+
val.should == 'bar'
|
405
|
+
end
|
406
|
+
end
|
407
|
+
|
408
|
+
it "should respond to empty?" do
|
409
|
+
@empty = Redis::Hash.new('test_empty_hash')
|
410
|
+
@empty.respond_to?(:empty?).should == true
|
411
|
+
end
|
412
|
+
|
413
|
+
it "should be empty after a clear" do
|
414
|
+
@hash['foo'] = 'bar'
|
415
|
+
@hash.all.should == {'foo' => 'bar'}
|
416
|
+
@hash.clear
|
417
|
+
@hash.should.be.empty
|
418
|
+
end
|
419
|
+
|
420
|
+
it "should respond to bulk_set" do
|
421
|
+
@hash.bulk_set({'abc' => 'xyz', 'bizz' => 'bazz'})
|
422
|
+
@hash['abc'].should == 'xyz'
|
423
|
+
@hash['bizz'].should == 'bazz'
|
424
|
+
|
425
|
+
@hash.bulk_set('abc' => '123', 'bang' => 'michael')
|
426
|
+
@hash['abc'].should == '123'
|
427
|
+
@hash['bang'].should == 'michael'
|
428
|
+
|
429
|
+
@hash.bulk_set(:sym1 => 'val1', :sym2 => 'val2')
|
430
|
+
@hash['sym1'].should == 'val1'
|
431
|
+
@hash['sym2'].should == 'val2'
|
432
|
+
end
|
433
|
+
|
434
|
+
it "should respond to bulk_get" do
|
435
|
+
@hash['foo'] = 'bar'
|
436
|
+
hsh = @hash.bulk_get('abc','foo')
|
437
|
+
hsh['abc'].should == nil
|
438
|
+
hsh['foo'].should == 'bar'
|
439
|
+
end
|
440
|
+
|
441
|
+
it "should increment field" do
|
442
|
+
@hash.incr('counter')
|
443
|
+
@hash.incr('counter')
|
444
|
+
@hash['counter'].to_i.should == 2
|
445
|
+
end
|
446
|
+
|
447
|
+
|
448
|
+
after do
|
449
|
+
@hash.clear
|
450
|
+
end
|
451
|
+
|
452
|
+
end
|
453
|
+
|
348
454
|
describe Redis::Set do
|
349
455
|
before do
|
350
456
|
@set = Redis::Set.new('spec/set')
|
@@ -366,6 +472,7 @@ describe Redis::Set do
|
|
366
472
|
@set.to_s.should == 'a, b'
|
367
473
|
@set.should == ['a','b']
|
368
474
|
@set.members.should == ['a','b']
|
475
|
+
@set.members.reverse.should == ['b','a'] # common question
|
369
476
|
@set.get.should == ['a','b']
|
370
477
|
@set << 'c'
|
371
478
|
@set.sort.should == ['a','b','c']
|
@@ -483,20 +590,28 @@ describe Redis::SortedSet do
|
|
483
590
|
@set['b'].should == 5.6
|
484
591
|
@set['c'] = 4
|
485
592
|
|
486
|
-
@set
|
487
|
-
@set[0
|
488
|
-
@set[0
|
593
|
+
a = @set.members
|
594
|
+
@set[0,-1].should == a[0,-1]
|
595
|
+
@set[0..2].should == a[0..2]
|
596
|
+
@set.slice(0..2).should == a.slice(0..2)
|
597
|
+
@set[0, 2].should == a[0,2]
|
598
|
+
@set.slice(0, 2).should == a.slice(0, 2)
|
599
|
+
@set.range(0, 2).should == a[0..2]
|
600
|
+
@set[0, 0].should == []
|
489
601
|
@set.range(0,1,:withscores => true).should == [['a',3],['c',4]]
|
490
|
-
@set.range(0,-1).should == [
|
491
|
-
@set.revrange(0,-1).should == [
|
492
|
-
@set[0..1].should == [
|
602
|
+
@set.range(0,-1).should == a[0..-1]
|
603
|
+
@set.revrange(0,-1).should == a[0..-1].reverse
|
604
|
+
@set[0..1].should == a[0..1]
|
493
605
|
@set[1].should == 0 # missing
|
494
606
|
@set.at(1).should == 'c'
|
495
607
|
@set.first.should == 'a'
|
496
608
|
@set.last.should == 'b'
|
497
609
|
|
498
610
|
@set.members.should == ['a','c','b']
|
611
|
+
@set.members.reverse.should == ['b','c','a']
|
499
612
|
@set.members(:withscores => true).should == [['a',3],['c',4],['b',5.6]]
|
613
|
+
@set.members(:with_scores => true).should == [['a',3],['c',4],['b',5.6]]
|
614
|
+
@set.members(:withscores => true).reverse.should == [['b',5.6],['c',4],['a',3]]
|
500
615
|
|
501
616
|
@set['b'] = 5
|
502
617
|
@set['b'] = 6
|
@@ -9,10 +9,12 @@ class Roster
|
|
9
9
|
counter :available_slots, :start => 10
|
10
10
|
counter :pitchers, :limit => :max_pitchers
|
11
11
|
counter :basic
|
12
|
+
hash_key :contact_information
|
12
13
|
lock :resort, :timeout => 2
|
13
14
|
value :starting_pitcher, :marshal => true
|
14
15
|
list :player_stats, :marshal => true
|
15
16
|
set :outfielders, :marshal => true
|
17
|
+
sorted_set :rank
|
16
18
|
|
17
19
|
# global class counters
|
18
20
|
counter :total_players_online, :global => true
|
@@ -49,6 +51,7 @@ describe Redis::Objects do
|
|
49
51
|
@roster.starting_pitcher.delete
|
50
52
|
@roster.player_stats.clear
|
51
53
|
@roster.outfielders.clear
|
54
|
+
@roster.contact_information.clear
|
52
55
|
@roster_1.outfielders.clear
|
53
56
|
@roster_2.outfielders.clear
|
54
57
|
@roster_3.outfielders.clear
|
@@ -73,21 +76,33 @@ describe Redis::Objects do
|
|
73
76
|
# Roster.redis.should.be.kind_of(Redis)
|
74
77
|
end
|
75
78
|
|
76
|
-
it "should support
|
79
|
+
it "should support interpolation of key names" do
|
77
80
|
@roster.player_totals.incr
|
78
81
|
@roster.redis.get('players/user1/total').should == '1'
|
79
82
|
@roster.redis.get('players/#{username}/total').should.be.nil
|
80
83
|
@roster.all_player_stats << 'a'
|
81
84
|
@roster.redis.lindex('players:all_stats', 0).should == 'a'
|
82
85
|
@roster.total_wins << 'a'
|
83
|
-
|
86
|
+
# test for interpolation of key names
|
84
87
|
@roster.redis.smembers('players:#{id}:all_stats').should == []
|
88
|
+
@roster.redis.smembers('players:1:all_stats').should == ['a']
|
85
89
|
@roster.my_rank = 'a'
|
86
90
|
@roster.redis.get('players:my_rank:user1').should == 'a'
|
87
91
|
Roster.weird_key = 'tuka'
|
88
92
|
Roster.redis.get('players:weird_key:#{raise}').should == 'tuka'
|
89
93
|
end
|
90
94
|
|
95
|
+
it "should be able to get/set contact info" do
|
96
|
+
@roster.contact_information['John_Phone'] = '123415352'
|
97
|
+
@roster.contact_information['John_Address'] = '123 LANE'
|
98
|
+
@roster.contact_information['John_Phone'].should == '123415352'
|
99
|
+
@roster.contact_information['John_Address'].should == '123 LANE'
|
100
|
+
@roster.contact_information['asdasd'].should.be.nil
|
101
|
+
@roster.contact_information.size.should == 2
|
102
|
+
end
|
103
|
+
|
104
|
+
|
105
|
+
|
91
106
|
it "should create counter accessors" do
|
92
107
|
[:available_slots, :pitchers, :basic].each do |m|
|
93
108
|
@roster.respond_to?(m).should == true
|
metadata
CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
|
|
4
4
|
prerelease: false
|
5
5
|
segments:
|
6
6
|
- 0
|
7
|
-
-
|
8
|
-
-
|
9
|
-
version: 0.
|
7
|
+
- 4
|
8
|
+
- 0
|
9
|
+
version: 0.4.0
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Nate Wiger
|
@@ -14,7 +14,7 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2010-
|
17
|
+
date: 2010-08-11 00:00:00 -07:00
|
18
18
|
default_executable:
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
@@ -25,10 +25,10 @@ dependencies:
|
|
25
25
|
- - ">="
|
26
26
|
- !ruby/object:Gem::Version
|
27
27
|
segments:
|
28
|
-
-
|
28
|
+
- 2
|
29
29
|
- 0
|
30
|
-
-
|
31
|
-
version:
|
30
|
+
- 4
|
31
|
+
version: 2.0.4
|
32
32
|
type: :runtime
|
33
33
|
version_requirements: *id001
|
34
34
|
description: Map Redis types directly to Ruby objects. Works with any class or ORM.
|
@@ -43,12 +43,15 @@ extra_rdoc_files:
|
|
43
43
|
- Rakefile
|
44
44
|
- README.rdoc
|
45
45
|
files:
|
46
|
+
- lib/redis/base_object.rb
|
46
47
|
- lib/redis/counter.rb
|
48
|
+
- lib/redis/hash.rb
|
47
49
|
- lib/redis/helpers/core_commands.rb
|
48
50
|
- lib/redis/helpers/serialize.rb
|
49
51
|
- lib/redis/list.rb
|
50
52
|
- lib/redis/lock.rb
|
51
53
|
- lib/redis/objects/counters.rb
|
54
|
+
- lib/redis/objects/hashes.rb
|
52
55
|
- lib/redis/objects/lists.rb
|
53
56
|
- lib/redis/objects/locks.rb
|
54
57
|
- lib/redis/objects/sets.rb
|
@@ -90,7 +93,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
90
93
|
- 0
|
91
94
|
version: "0"
|
92
95
|
requirements:
|
93
|
-
- redis,
|
96
|
+
- redis, v2.0.4 or greater
|
94
97
|
rubyforge_project: redis-objects
|
95
98
|
rubygems_version: 1.3.6
|
96
99
|
signing_key:
|