rudis 0.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/LICENSE +21 -0
- data/README.md +52 -0
- data/Rakefile +7 -0
- data/init.rb +1 -0
- data/lib/rudis.rb +19 -0
- data/lib/rudis/base.rb +54 -0
- data/lib/rudis/structure.rb +63 -0
- data/lib/rudis/structures/counter.rb +27 -0
- data/lib/rudis/structures/hash.rb +86 -0
- data/lib/rudis/structures/list.rb +71 -0
- data/lib/rudis/structures/lock.rb +61 -0
- data/lib/rudis/structures/set.rb +52 -0
- data/lib/rudis/structures/zset.rb +103 -0
- data/lib/rudis/type.rb +5 -0
- data/lib/rudis/types/default.rb +12 -0
- data/lib/rudis/types/integer.rb +11 -0
- data/lib/rudis/types/json.rb +17 -0
- data/lib/rudis/types/symbol.rb +11 -0
- data/lib/rudis/types/time.rb +11 -0
- data/spec/base_spec.rb +22 -0
- data/spec/counter_spec.rb +17 -0
- data/spec/hash_spec.rb +32 -0
- data/spec/list_spec.rb +23 -0
- data/spec/set_spec.rb +27 -0
- data/spec/zset_spec.rb +40 -0
- data/vendor/core_ext/init.rb +7 -0
- data/vendor/core_ext/lib/class.rb +9 -0
- data/vendor/core_ext/lib/enumerable.rb +23 -0
- data/vendor/core_ext/lib/file.rb +17 -0
- data/vendor/core_ext/lib/hash.rb +46 -0
- data/vendor/core_ext/lib/infinity.rb +1 -0
- data/vendor/core_ext/lib/main.rb +11 -0
- data/vendor/core_ext/lib/object.rb +11 -0
- data/vendor/core_ext/lib/string.rb +32 -0
- metadata +115 -0
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
#MIT license. See http://www.opensource.org/licenses/mit-license.php
|
2
|
+
|
3
|
+
Copyright (c) 2010 Philotic, Inc.
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
Rudis
|
2
|
+
=====
|
3
|
+
|
4
|
+
Rudis is a simple framework for implementing your favorite Redis recipes in Ruby. There are only two concepts to Rudis:
|
5
|
+
|
6
|
+
Types
|
7
|
+
-----
|
8
|
+
|
9
|
+
A type consists of any object that responds to `put` and `get`.These are used to transparently serialize and unserialize elements of your Redis sets, lists, zsets, and hashes. For example:
|
10
|
+
|
11
|
+
>> s = Rudis::Set.new(:type => Rudis::JSONType)
|
12
|
+
>> s.add [1,2,3,4] # actually adds [1,2,3,4].to_json to the set
|
13
|
+
>> s.add {'foo' => 'bar'} # => '{foo:"bar"}' is added
|
14
|
+
>> s.include? {'foo' => 'bar'}
|
15
|
+
true
|
16
|
+
>> s.to_a
|
17
|
+
[[1,2,3,4], {'foo' => 'bar'}]
|
18
|
+
|
19
|
+
You can write your own types, too!
|
20
|
+
|
21
|
+
class ActiveRecordType
|
22
|
+
def initialize(model)
|
23
|
+
@model = model
|
24
|
+
end
|
25
|
+
def self.put(val)
|
26
|
+
val.id
|
27
|
+
end
|
28
|
+
def self.get(val)
|
29
|
+
@model.find(val.to_i)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
Recipes
|
34
|
+
-------
|
35
|
+
|
36
|
+
A recipe is a subclass of `Rudis::Base`. Rudis provides two handy-dandy methods: `key` and `redis`. `redis` is both an instance method and a class method that gives you an instance of Redis (writable, with sensible defaults). `key` is really handy:
|
37
|
+
|
38
|
+
class Foo < Rudis::Base
|
39
|
+
end
|
40
|
+
|
41
|
+
>> Foo.new('foo').key
|
42
|
+
=> "foo"
|
43
|
+
>> Foo.new('foo').key('bar', 'baz')
|
44
|
+
=> "foo:bar:baz"
|
45
|
+
>> Foo.key_sep = '/'
|
46
|
+
>> Foo.new('foo').key('bar', 'baz')
|
47
|
+
=> "foo/bar/baz"
|
48
|
+
>> Foo.key_base = ['foo', 'bar']
|
49
|
+
>> Foo.new('zot').key('zongo')
|
50
|
+
=> "foo/bar/zot/zongo"
|
51
|
+
|
52
|
+
Enjoy! I have lots of TODOs, including better SORT integration, more builtin types (like Marshall), and always more examples. Checkout `examples/` for a few neat examples.
|
data/Rakefile
ADDED
data/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'lib', 'rudis'))
|
data/lib/rudis.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
#only once
|
2
|
+
require begin
|
3
|
+
File.expand_path(
|
4
|
+
File.join(
|
5
|
+
File.dirname(__FILE__),
|
6
|
+
'..',
|
7
|
+
'vendor',
|
8
|
+
'core_ext',
|
9
|
+
'init'
|
10
|
+
)
|
11
|
+
)
|
12
|
+
end
|
13
|
+
|
14
|
+
class Rudis
|
15
|
+
end
|
16
|
+
|
17
|
+
require_local 'rudis/base'
|
18
|
+
require_local 'rudis/type'
|
19
|
+
require_local 'rudis/structure'
|
data/lib/rudis/base.rb
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
class Rudis
|
2
|
+
class << self
|
3
|
+
attr_writer :redis
|
4
|
+
def redis
|
5
|
+
@redis ||= begin
|
6
|
+
require 'rubygems'
|
7
|
+
require 'redis'
|
8
|
+
Redis.new
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
attr_writer :key_base
|
13
|
+
def key_base
|
14
|
+
@key_base ||= ['rudis']
|
15
|
+
end
|
16
|
+
|
17
|
+
attr_writer :key_sep
|
18
|
+
def key_sep
|
19
|
+
@key_sep ||= ':'
|
20
|
+
end
|
21
|
+
|
22
|
+
def key(*args)
|
23
|
+
([key_base].flatten + args).join(key_sep)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
class Base < Rudis
|
29
|
+
class << self
|
30
|
+
attr_writer :redis
|
31
|
+
def self.redis
|
32
|
+
@redis ||= super
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def redis
|
37
|
+
@redis ||= self.class.redis
|
38
|
+
end
|
39
|
+
|
40
|
+
def initialize(key, options={})
|
41
|
+
@key = key
|
42
|
+
@options = options
|
43
|
+
@options.rmerge!(default_options)
|
44
|
+
end
|
45
|
+
|
46
|
+
def default_options
|
47
|
+
{}
|
48
|
+
end
|
49
|
+
|
50
|
+
def key(*args)
|
51
|
+
self.class.key(@key, *args)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
class Rudis
|
2
|
+
class Structure < Base
|
3
|
+
def type
|
4
|
+
@options[:type]
|
5
|
+
end
|
6
|
+
|
7
|
+
def default_options
|
8
|
+
{:type => DefaultType}
|
9
|
+
end
|
10
|
+
|
11
|
+
def exists?
|
12
|
+
redis.exists(key)
|
13
|
+
end
|
14
|
+
alias exist? exists?
|
15
|
+
|
16
|
+
def del
|
17
|
+
redis.del(key)
|
18
|
+
end
|
19
|
+
alias delete! del
|
20
|
+
|
21
|
+
def rename(new_key)
|
22
|
+
redis.rename(key, self.class.key(new_key))
|
23
|
+
@key = new_key
|
24
|
+
end
|
25
|
+
|
26
|
+
def redis_type
|
27
|
+
redis.type(key)
|
28
|
+
end
|
29
|
+
|
30
|
+
def expire(time)
|
31
|
+
redis.expire(key, time)
|
32
|
+
end
|
33
|
+
|
34
|
+
def expire_at(time)
|
35
|
+
redis.expire_at(key, time)
|
36
|
+
end
|
37
|
+
|
38
|
+
def ttl
|
39
|
+
redis.ttl(key)
|
40
|
+
end
|
41
|
+
|
42
|
+
def watch(tries=0)
|
43
|
+
return redis.watch(key) unless block_given?
|
44
|
+
|
45
|
+
begin
|
46
|
+
redis.watch(key)
|
47
|
+
c = 0
|
48
|
+
while yield.nil? && (tries==0 || (c+=1) <= tries)
|
49
|
+
puts "Optimistic lock failed for #{key}, retrying #{c} time(s)"
|
50
|
+
end
|
51
|
+
ensure
|
52
|
+
redis.unwatch(key)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
require_local 'structures/counter'
|
59
|
+
require_local 'structures/list'
|
60
|
+
require_local 'structures/lock'
|
61
|
+
require_local 'structures/hash'
|
62
|
+
require_local 'structures/set'
|
63
|
+
require_local 'structures/zset'
|
@@ -0,0 +1,27 @@
|
|
1
|
+
class Rudis
|
2
|
+
class Counter < Structure
|
3
|
+
def incr
|
4
|
+
redis.incr(key)
|
5
|
+
end
|
6
|
+
|
7
|
+
def decr
|
8
|
+
redis.decr(key)
|
9
|
+
end
|
10
|
+
|
11
|
+
def incrby(i)
|
12
|
+
redis.incrby(key, i.to_i)
|
13
|
+
end
|
14
|
+
|
15
|
+
def decrby(i)
|
16
|
+
redis.decrby(key, i.to_i)
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_i
|
20
|
+
redis.get(key).to_i
|
21
|
+
end
|
22
|
+
|
23
|
+
def zero?
|
24
|
+
to_i.zero?
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
class Rudis
|
2
|
+
class Hash < Structure
|
3
|
+
def default_options
|
4
|
+
{
|
5
|
+
:type => DefaultType,
|
6
|
+
:key_type => DefaultType
|
7
|
+
}
|
8
|
+
end
|
9
|
+
|
10
|
+
def key_type
|
11
|
+
@options[:key_type]
|
12
|
+
end
|
13
|
+
|
14
|
+
def get(k)
|
15
|
+
e = redis.hget(key, key_type.put(k))
|
16
|
+
e && type.get(e)
|
17
|
+
end
|
18
|
+
alias [] get
|
19
|
+
|
20
|
+
def set(k,v)
|
21
|
+
redis.hset(key, key_type.put(k), type.put(v))
|
22
|
+
end
|
23
|
+
alias []= set
|
24
|
+
|
25
|
+
def mget(*ks)
|
26
|
+
ks.zip(redis.hmget(key, ks.map { |k|
|
27
|
+
key_type.put(k)
|
28
|
+
}).map { |v|
|
29
|
+
type.get(v)
|
30
|
+
}).to_h
|
31
|
+
end
|
32
|
+
alias slice mget
|
33
|
+
|
34
|
+
def mset(hsh)
|
35
|
+
hsh = hsh.dup
|
36
|
+
hsh.map! {|k,v| [key_type.put(k), type.put(v)]}
|
37
|
+
redis.hmset(key, *hsh.to_a.flatten)
|
38
|
+
end
|
39
|
+
alias merge! mset
|
40
|
+
|
41
|
+
def keys
|
42
|
+
redis.hkeys(key).map { |k| key_type.get(k) }
|
43
|
+
end
|
44
|
+
|
45
|
+
def vals
|
46
|
+
redis.hvals(key).map { |v| type.get(v) }
|
47
|
+
end
|
48
|
+
alias values vals
|
49
|
+
|
50
|
+
def all
|
51
|
+
redis.hgetall(key).map! do |k,v|
|
52
|
+
[key_type.get(k), type.get(v)]
|
53
|
+
end
|
54
|
+
end
|
55
|
+
alias to_h all
|
56
|
+
|
57
|
+
def len
|
58
|
+
redis.hlen(key)
|
59
|
+
end
|
60
|
+
alias length len
|
61
|
+
alias count len
|
62
|
+
alias size len
|
63
|
+
|
64
|
+
def empty?
|
65
|
+
len == 0
|
66
|
+
end
|
67
|
+
|
68
|
+
def del(k)
|
69
|
+
redis.hdel(key, key_type.put(k))
|
70
|
+
end
|
71
|
+
|
72
|
+
def has_key?(k)
|
73
|
+
redis.hexists(key, key_type.put(k))
|
74
|
+
end
|
75
|
+
alias include? has_key?
|
76
|
+
|
77
|
+
def incrby(k, i)
|
78
|
+
redis.hincrby(key, key_type.put(k), i.to_i)
|
79
|
+
end
|
80
|
+
|
81
|
+
def incr(k)
|
82
|
+
incrby(k, 1)
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
class Rudis
|
2
|
+
class List < Structure
|
3
|
+
def len
|
4
|
+
redis.llen(key)
|
5
|
+
end
|
6
|
+
alias length len
|
7
|
+
alias size len
|
8
|
+
alias count len
|
9
|
+
|
10
|
+
def empty?
|
11
|
+
len == 0
|
12
|
+
end
|
13
|
+
|
14
|
+
def index(i)
|
15
|
+
type.get(redis.lindex(key, i.to_i))
|
16
|
+
end
|
17
|
+
|
18
|
+
def range(range)
|
19
|
+
redis.lrange(key, range.first.to_i, range.last.to_i).map do |e|
|
20
|
+
type.get(e)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def all
|
25
|
+
range 0..-1
|
26
|
+
end
|
27
|
+
alias to_a all
|
28
|
+
|
29
|
+
def [](thing)
|
30
|
+
if thing.is_a? Fixnum
|
31
|
+
index thing
|
32
|
+
elsif thing.is_a? Range
|
33
|
+
range thing
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def set(i, val)
|
38
|
+
redis.lset(key, i.to_i, type.put(val))
|
39
|
+
end
|
40
|
+
alias []= set
|
41
|
+
|
42
|
+
def rpush(val)
|
43
|
+
redis.rpush(key, type.put(val))
|
44
|
+
end
|
45
|
+
alias push rpush
|
46
|
+
alias << rpush
|
47
|
+
|
48
|
+
def rpop
|
49
|
+
e = redis.rpop(key)
|
50
|
+
e && type.get(e)
|
51
|
+
end
|
52
|
+
alias pop rpop
|
53
|
+
|
54
|
+
def lpush(val)
|
55
|
+
redis.lpush(key, type.put(val))
|
56
|
+
end
|
57
|
+
alias unshift lpush
|
58
|
+
alias >> lpush
|
59
|
+
|
60
|
+
def lpop
|
61
|
+
e = redis.lpop(key)
|
62
|
+
e && type.get(e)
|
63
|
+
end
|
64
|
+
alias shift lpop
|
65
|
+
|
66
|
+
def trim(range)
|
67
|
+
redis.trim(key, range.first.to_i, range.last.to_i)
|
68
|
+
end
|
69
|
+
alias trim! trim
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
class Rudis
|
2
|
+
class Lock < Structure
|
3
|
+
class LockFailed < Exception; end
|
4
|
+
|
5
|
+
def acquire(options={})
|
6
|
+
options.rmerge!(
|
7
|
+
:tries => 1,
|
8
|
+
:sleep => 1
|
9
|
+
)
|
10
|
+
|
11
|
+
return set(options) unless block_given?
|
12
|
+
|
13
|
+
1.upto options[:tries] do
|
14
|
+
if set(options)
|
15
|
+
return begin
|
16
|
+
yield
|
17
|
+
ensure
|
18
|
+
clear
|
19
|
+
end
|
20
|
+
end
|
21
|
+
sleep options[:sleep]
|
22
|
+
end
|
23
|
+
|
24
|
+
# oops, we couldn't get the lock
|
25
|
+
raise LockFailed, <<-msg.squish
|
26
|
+
Unable to acquire lock after #{options[:tries]} time(s)
|
27
|
+
msg
|
28
|
+
return false
|
29
|
+
end
|
30
|
+
|
31
|
+
# implements the SETNX locking algorithm from
|
32
|
+
# http://code.google.com/p/redis/wiki/SetnxCommand
|
33
|
+
def set(options={})
|
34
|
+
options.rmerge!(
|
35
|
+
:timeout => 30
|
36
|
+
)
|
37
|
+
if redis.setnx(key, timestamp(options[:timeout]))
|
38
|
+
return true
|
39
|
+
else
|
40
|
+
# check the timestamp
|
41
|
+
old = redis.getset(key, timestamp(options[:timeout]))
|
42
|
+
if old < timestamp
|
43
|
+
# expired lock, we're good
|
44
|
+
return true
|
45
|
+
else
|
46
|
+
# lock is not expired, put it back
|
47
|
+
redis.set(key, old)
|
48
|
+
return false
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
alias clear del
|
54
|
+
|
55
|
+
private
|
56
|
+
def timestamp(timeout=0)
|
57
|
+
Time.now.to_i + timeout
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
class Rudis
|
2
|
+
class Set < Structure
|
3
|
+
def members
|
4
|
+
mems = redis.smembers(key)
|
5
|
+
mems.map! do |k|
|
6
|
+
type.get(k)
|
7
|
+
end
|
8
|
+
mems
|
9
|
+
end
|
10
|
+
alias all members
|
11
|
+
alias to_a members
|
12
|
+
|
13
|
+
def add(val)
|
14
|
+
redis.sadd(key, type.put(val))
|
15
|
+
end
|
16
|
+
alias << add
|
17
|
+
|
18
|
+
def is_member?(val)
|
19
|
+
redis.sismember(key, type.put(val))
|
20
|
+
end
|
21
|
+
alias member? is_member?
|
22
|
+
alias include? is_member?
|
23
|
+
|
24
|
+
def card
|
25
|
+
redis.scard(key)
|
26
|
+
end
|
27
|
+
alias count card
|
28
|
+
alias size card
|
29
|
+
alias length card
|
30
|
+
|
31
|
+
def rem(val)
|
32
|
+
redis.srem(key, type.put(val))
|
33
|
+
end
|
34
|
+
alias remove rem
|
35
|
+
alias delete rem
|
36
|
+
|
37
|
+
def randmember
|
38
|
+
e = redis.srandmember(key)
|
39
|
+
e && type.get(e)
|
40
|
+
end
|
41
|
+
alias rand randmember
|
42
|
+
|
43
|
+
def pop
|
44
|
+
e = redis.spop(key)
|
45
|
+
e && type.get(e)
|
46
|
+
end
|
47
|
+
|
48
|
+
def sort(*args)
|
49
|
+
#TODO
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
class Rudis
|
2
|
+
class ZSet < Structure
|
3
|
+
def default_options
|
4
|
+
{
|
5
|
+
:type => DefaultType,
|
6
|
+
:score_type => IntegerType
|
7
|
+
}
|
8
|
+
end
|
9
|
+
|
10
|
+
def score_type
|
11
|
+
@options[:score_type]
|
12
|
+
end
|
13
|
+
|
14
|
+
def add(member, score=1)
|
15
|
+
redis.zadd(key, score_type.put(score), type.put(member))
|
16
|
+
end
|
17
|
+
alias << add
|
18
|
+
|
19
|
+
def rem(member)
|
20
|
+
redis.zrem(key, type.put(member))
|
21
|
+
end
|
22
|
+
|
23
|
+
def incrby(member, i)
|
24
|
+
redis.zincrby(key, i.to_i, member)
|
25
|
+
end
|
26
|
+
|
27
|
+
def incr(member)
|
28
|
+
incrby(member, 1)
|
29
|
+
end
|
30
|
+
|
31
|
+
def rank(member)
|
32
|
+
i = redis.zrank(key, member)
|
33
|
+
i && i.to_i
|
34
|
+
end
|
35
|
+
|
36
|
+
def card
|
37
|
+
redis.zcard(key)
|
38
|
+
end
|
39
|
+
alias size card
|
40
|
+
alias length card
|
41
|
+
alias count card
|
42
|
+
|
43
|
+
def empty?
|
44
|
+
card == 0
|
45
|
+
end
|
46
|
+
|
47
|
+
def range(ran)
|
48
|
+
redis.zrange(key, ran.first.to_i, ran.last.to_i).map do |e|
|
49
|
+
type.get(e)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def revrange(ran)
|
54
|
+
redis.zrevrange(key, ran.first.to_i, ran.last.to_i).map do |e|
|
55
|
+
type.get(e)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
alias rev_range revrange
|
59
|
+
|
60
|
+
def rangebyscore(min, max)
|
61
|
+
redis.zrangebyscore(key,
|
62
|
+
score_type.put(min),
|
63
|
+
score_type.put(max)
|
64
|
+
).map do |e|
|
65
|
+
type.get(e)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
alias range_by_score rangebyscore
|
69
|
+
|
70
|
+
def [](val)
|
71
|
+
if val.is_a? Range
|
72
|
+
range(val)
|
73
|
+
else
|
74
|
+
self[val..val]
|
75
|
+
end
|
76
|
+
end
|
77
|
+
alias slice []
|
78
|
+
|
79
|
+
def all
|
80
|
+
range(0..-1)
|
81
|
+
end
|
82
|
+
alias to_a all
|
83
|
+
|
84
|
+
def first
|
85
|
+
self[0..0].first
|
86
|
+
end
|
87
|
+
|
88
|
+
def last
|
89
|
+
self[-1..-1].first
|
90
|
+
end
|
91
|
+
|
92
|
+
def score(member)
|
93
|
+
s = redis.zscore(key, type.put(member))
|
94
|
+
s && score_type.get(s)
|
95
|
+
end
|
96
|
+
|
97
|
+
def member?(val)
|
98
|
+
!score(val).nil?
|
99
|
+
end
|
100
|
+
alias include? member?
|
101
|
+
|
102
|
+
end
|
103
|
+
end
|
data/lib/rudis/type.rb
ADDED
data/spec/base_spec.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
describe Rudis::Base do
|
2
|
+
before :all do
|
3
|
+
class Foo < Rudis::Base
|
4
|
+
end
|
5
|
+
end
|
6
|
+
|
7
|
+
it "has a writable base key" do
|
8
|
+
foo = Foo.new('foo_key')
|
9
|
+
Foo.key.should == 'rudis'
|
10
|
+
foo.key.should == 'rudis:foo_key'
|
11
|
+
Foo.key_base = ['Foo']
|
12
|
+
Foo.key.should == 'Foo'
|
13
|
+
foo.key.should == 'Foo:foo_key'
|
14
|
+
end
|
15
|
+
|
16
|
+
it "has a writable key separator" do
|
17
|
+
Foo.key_sep = '/'
|
18
|
+
Foo.key_base = ['Foo']
|
19
|
+
foo = Foo.new(:a)
|
20
|
+
foo.key(:b, "c:d:e").should == 'Foo/a/b/c:d:e'
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
describe Rudis::Counter do
|
2
|
+
before :each do
|
3
|
+
Rudis::Counter.redis.flushdb
|
4
|
+
@c = Rudis::Counter.new('my_counter')
|
5
|
+
end
|
6
|
+
|
7
|
+
it "counts!" do
|
8
|
+
@c.to_i.should == 0
|
9
|
+
@c.should be_zero
|
10
|
+
@c.incr.should == 1
|
11
|
+
@c.incr.should == 2
|
12
|
+
@c.decr.should == 1
|
13
|
+
@c.incrby(4).should == 5
|
14
|
+
@c.decrby(2).should == 3
|
15
|
+
@c.incrby(-1).should == 2
|
16
|
+
end
|
17
|
+
end
|
data/spec/hash_spec.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
describe Rudis::Hash do
|
2
|
+
before :each do
|
3
|
+
Rudis::Hash.redis.flushdb
|
4
|
+
@hash = Rudis::Hash.new('myhash',
|
5
|
+
:key_type => Rudis::SymbolType,
|
6
|
+
:type => Rudis::IntegerType
|
7
|
+
)
|
8
|
+
end
|
9
|
+
|
10
|
+
it "implements the hash commands" do
|
11
|
+
@hash.should be_empty
|
12
|
+
@hash.length.should == 0
|
13
|
+
@hash[:foo] = 3
|
14
|
+
@hash.should_not be_empty
|
15
|
+
@hash[:foo].should == 3
|
16
|
+
@hash.all.should == {:foo => 3}
|
17
|
+
@hash[:bar] = 4
|
18
|
+
@hash.keys.to_set.should == Set.new([:foo, :bar])
|
19
|
+
@hash.values.sort.should == [3,4]
|
20
|
+
@hash.count.should == 2
|
21
|
+
@hash.to_h.should == {:foo => 3, :bar => 4}
|
22
|
+
@hash[:foo] = 5
|
23
|
+
@hash[:foo].should == 5
|
24
|
+
@hash.size.should == 2
|
25
|
+
@hash.slice(:foo).should == {:foo => 5}
|
26
|
+
@hash.merge!(:bar => 6, :baz => 7)
|
27
|
+
@hash.count.should == 3
|
28
|
+
@hash.to_h.should == {:foo => 5, :bar => 6, :baz => 7}
|
29
|
+
@hash.get(:baz).should == 7
|
30
|
+
@hash.get(:idontexist).should be_nil
|
31
|
+
end
|
32
|
+
end
|
data/spec/list_spec.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
describe Rudis::List do
|
2
|
+
before :each do
|
3
|
+
Rudis::List.redis.flushdb
|
4
|
+
@list = Rudis::List.new('mylist', :type => Rudis::IntegerType)
|
5
|
+
end
|
6
|
+
|
7
|
+
it "implements the list commands" do
|
8
|
+
@list.size.should == 0
|
9
|
+
@list << 1
|
10
|
+
@list.to_a.should == [1]
|
11
|
+
@list.unshift 2
|
12
|
+
@list.count.should == 2
|
13
|
+
@list.all.should == [2,1]
|
14
|
+
@list.lpush 3
|
15
|
+
@list.shift.should == 3
|
16
|
+
@list.pop.should == 1
|
17
|
+
@list.lpop.should == 2
|
18
|
+
@list.length.should == 0
|
19
|
+
@list.rpop.should be_nil
|
20
|
+
@list.should be_empty
|
21
|
+
@list.to_a.should == []
|
22
|
+
end
|
23
|
+
end
|
data/spec/set_spec.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
describe Rudis::Set do
|
2
|
+
before :each do
|
3
|
+
@key = (1..rand(5)).map { Time.now.hash.to_s }
|
4
|
+
@set = Rudis::Set.new(@key)
|
5
|
+
end
|
6
|
+
|
7
|
+
before :each do
|
8
|
+
Rudis::Set.redis.flushdb
|
9
|
+
end
|
10
|
+
|
11
|
+
it "implements the set commands" do
|
12
|
+
@set.key.should == "rudis:#{@key.join(':')}"
|
13
|
+
@set.card.should == 0
|
14
|
+
@set.add "foo"
|
15
|
+
@set.size.should == 1
|
16
|
+
@set << "bar"
|
17
|
+
@set.count.should == 2
|
18
|
+
@set.delete "foo"
|
19
|
+
@set.size.should == 1
|
20
|
+
@set.to_a.should == ["bar"]
|
21
|
+
@set.rand.should == "bar"
|
22
|
+
@set.pop.should == "bar"
|
23
|
+
@set.to_a.should == []
|
24
|
+
@set.pop.should be_nil
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
data/spec/zset_spec.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
describe Rudis::ZSet do
|
2
|
+
before :each do
|
3
|
+
Rudis::ZSet.redis.flushdb
|
4
|
+
@zset = Rudis::ZSet.new('my_zset',
|
5
|
+
:type => Rudis::JSONType,
|
6
|
+
:score_type => Rudis::TimeType
|
7
|
+
)
|
8
|
+
end
|
9
|
+
|
10
|
+
it "implements the zset commands" do
|
11
|
+
now = Time.now
|
12
|
+
yesterday = now - 60*60*24
|
13
|
+
tomorrow = now + 60*60*24
|
14
|
+
@zset.should be_empty
|
15
|
+
@zset.count.should == 0
|
16
|
+
@zset.add([1,2,3], now)
|
17
|
+
@zset.length.should == 1
|
18
|
+
@zset.should_not be_empty
|
19
|
+
@zset.first.should == [1,2,3]
|
20
|
+
@zset.add({'four' => 4}, yesterday)
|
21
|
+
@zset.size.should == 2
|
22
|
+
@zset.add(['five' => 5, 'six' => 6], tomorrow)
|
23
|
+
@zset.all.should == [
|
24
|
+
{'four' => 4},
|
25
|
+
[1,2,3],
|
26
|
+
['five' => 5, 'six' => 6]
|
27
|
+
]
|
28
|
+
@zset.revrange(0..-2).should == [
|
29
|
+
['five' => 5, 'six' => 6],
|
30
|
+
[1,2,3]
|
31
|
+
]
|
32
|
+
@zset.range_by_score(yesterday, now).should == [
|
33
|
+
{'four' => 4},
|
34
|
+
[1,2,3]
|
35
|
+
]
|
36
|
+
@zset.score("idontexist").should be_nil
|
37
|
+
@zset.should_not include("idontexist")
|
38
|
+
@zset.score({'four' => 4}).to_i.should == yesterday.to_i
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Enumerable
|
2
|
+
def to_set
|
3
|
+
require 'set'
|
4
|
+
Set.new(self)
|
5
|
+
end
|
6
|
+
|
7
|
+
# assumes a collection of k-v pairs
|
8
|
+
def to_h
|
9
|
+
Hash[self]
|
10
|
+
end
|
11
|
+
|
12
|
+
def histogram
|
13
|
+
hist = Hash.new(0)
|
14
|
+
self.each do |e|
|
15
|
+
hist[e] += 1
|
16
|
+
end
|
17
|
+
hist
|
18
|
+
end
|
19
|
+
|
20
|
+
def hashmap
|
21
|
+
map { [self, yield(self)] }.to_h
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class File
|
2
|
+
def self.write(filename, content)
|
3
|
+
open(filename, "w") do |f|
|
4
|
+
f.write(content)
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.append(filename, content)
|
9
|
+
open(filename, "a") do |f|
|
10
|
+
f << content
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.add_line(filename, content)
|
15
|
+
self.append(filename, content.chomp + $/)
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
class Hash
|
2
|
+
def rmerge!(hsh)
|
3
|
+
hsh.each do |k,v|
|
4
|
+
self[k] = v unless self.has_key? k
|
5
|
+
end
|
6
|
+
self
|
7
|
+
end
|
8
|
+
|
9
|
+
def rmerge(hsh)
|
10
|
+
self.dup.rmerge!(hsh)
|
11
|
+
end
|
12
|
+
|
13
|
+
def accept_options!(hsh)
|
14
|
+
opts_diff = self.keys - hash.keys
|
15
|
+
raise ArgumentError <<-msg.squish unless opts_diff.empty?
|
16
|
+
Unrecognized options #{opts_diff.inspect}
|
17
|
+
msg
|
18
|
+
options.rmerge!(hsh)
|
19
|
+
end
|
20
|
+
|
21
|
+
def accept_options(hsh)
|
22
|
+
self.dup.accept_options!(hsh)
|
23
|
+
end
|
24
|
+
|
25
|
+
def map_keys!
|
26
|
+
keys.each do |k|
|
27
|
+
self[yield(k)] = self.delete(k)
|
28
|
+
end
|
29
|
+
self
|
30
|
+
end
|
31
|
+
|
32
|
+
def map_values!
|
33
|
+
self.each do |k,v|
|
34
|
+
self[k] = v
|
35
|
+
end
|
36
|
+
self
|
37
|
+
end
|
38
|
+
|
39
|
+
def map!
|
40
|
+
self.keys.each do |k|
|
41
|
+
new_k, new_v = yield(k, self.delete(k))
|
42
|
+
self[new_k] = new_v
|
43
|
+
end
|
44
|
+
self
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
::Infinity = 1.0/0
|
@@ -0,0 +1,32 @@
|
|
1
|
+
class String
|
2
|
+
def match?(regex)
|
3
|
+
self.match(regex) ? true : false
|
4
|
+
end
|
5
|
+
|
6
|
+
def squish!
|
7
|
+
self.strip!.gsub!(/\s+/,' ')
|
8
|
+
self
|
9
|
+
end
|
10
|
+
|
11
|
+
def squish
|
12
|
+
self.dup.squish!
|
13
|
+
end
|
14
|
+
|
15
|
+
def lines
|
16
|
+
self.split($/)
|
17
|
+
end
|
18
|
+
|
19
|
+
def map_parts(delim=$/, &blk)
|
20
|
+
self.split(delim).map(&blk).join(delim)
|
21
|
+
end
|
22
|
+
|
23
|
+
def unchomp!(ch=$/)
|
24
|
+
self.chomp!(ch)
|
25
|
+
self << ch
|
26
|
+
self
|
27
|
+
end
|
28
|
+
|
29
|
+
def unchomp(ch)
|
30
|
+
self.dup.unchomp!(ch)
|
31
|
+
end
|
32
|
+
end
|
metadata
ADDED
@@ -0,0 +1,115 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rudis
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 9
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
version: "0.1"
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Jay Adkisson
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2010-06-20 00:00:00 -07:00
|
18
|
+
default_executable:
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: redis
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
hash: 3
|
29
|
+
segments:
|
30
|
+
- 2
|
31
|
+
- 0
|
32
|
+
version: "2.0"
|
33
|
+
type: :runtime
|
34
|
+
version_requirements: *id001
|
35
|
+
description: " Rudis wraps redis-rb in objects that keep track of their own\n redis instances and keys.\n"
|
36
|
+
email: j4yferd@gmail.com
|
37
|
+
executables: []
|
38
|
+
|
39
|
+
extensions: []
|
40
|
+
|
41
|
+
extra_rdoc_files: []
|
42
|
+
|
43
|
+
files:
|
44
|
+
- lib/rudis/structures/set.rb
|
45
|
+
- lib/rudis/structures/counter.rb
|
46
|
+
- lib/rudis/structures/zset.rb
|
47
|
+
- lib/rudis/structures/list.rb
|
48
|
+
- lib/rudis/structures/hash.rb
|
49
|
+
- lib/rudis/structures/lock.rb
|
50
|
+
- lib/rudis/base.rb
|
51
|
+
- lib/rudis/structure.rb
|
52
|
+
- lib/rudis/type.rb
|
53
|
+
- lib/rudis/types/default.rb
|
54
|
+
- lib/rudis/types/integer.rb
|
55
|
+
- lib/rudis/types/symbol.rb
|
56
|
+
- lib/rudis/types/json.rb
|
57
|
+
- lib/rudis/types/time.rb
|
58
|
+
- lib/rudis.rb
|
59
|
+
- spec/base_spec.rb
|
60
|
+
- spec/counter_spec.rb
|
61
|
+
- spec/set_spec.rb
|
62
|
+
- spec/list_spec.rb
|
63
|
+
- spec/hash_spec.rb
|
64
|
+
- spec/zset_spec.rb
|
65
|
+
- vendor/core_ext/init.rb
|
66
|
+
- vendor/core_ext/lib/string.rb
|
67
|
+
- vendor/core_ext/lib/file.rb
|
68
|
+
- vendor/core_ext/lib/object.rb
|
69
|
+
- vendor/core_ext/lib/enumerable.rb
|
70
|
+
- vendor/core_ext/lib/infinity.rb
|
71
|
+
- vendor/core_ext/lib/main.rb
|
72
|
+
- vendor/core_ext/lib/hash.rb
|
73
|
+
- vendor/core_ext/lib/class.rb
|
74
|
+
- init.rb
|
75
|
+
- Rakefile
|
76
|
+
- README.md
|
77
|
+
- LICENSE
|
78
|
+
has_rdoc: true
|
79
|
+
homepage: http://github.com/jayferd/rudis
|
80
|
+
licenses:
|
81
|
+
- MIT
|
82
|
+
post_install_message:
|
83
|
+
rdoc_options: []
|
84
|
+
|
85
|
+
require_paths:
|
86
|
+
- lib
|
87
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
88
|
+
none: false
|
89
|
+
requirements:
|
90
|
+
- - ">="
|
91
|
+
- !ruby/object:Gem::Version
|
92
|
+
hash: 57
|
93
|
+
segments:
|
94
|
+
- 1
|
95
|
+
- 8
|
96
|
+
- 7
|
97
|
+
version: 1.8.7
|
98
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
99
|
+
none: false
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
hash: 3
|
104
|
+
segments:
|
105
|
+
- 0
|
106
|
+
version: "0"
|
107
|
+
requirements: []
|
108
|
+
|
109
|
+
rubyforge_project:
|
110
|
+
rubygems_version: 1.3.7
|
111
|
+
signing_key:
|
112
|
+
specification_version: 3
|
113
|
+
summary: An extensible OO redis client for ruby
|
114
|
+
test_files: []
|
115
|
+
|