redis-objects 0.3.2 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- 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:
|