juno 0.1.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/.gitignore +4 -0
- data/.travis.yml +25 -0
- data/Gemfile +19 -0
- data/LICENSE +20 -0
- data/README.md +83 -0
- data/Rakefile +15 -0
- data/SPEC.md +95 -0
- data/juno.gemspec +22 -0
- data/lib/juno.rb +31 -0
- data/lib/juno/activerecord.rb +58 -0
- data/lib/juno/base.rb +113 -0
- data/lib/juno/couch.rb +43 -0
- data/lib/juno/datamapper.rb +63 -0
- data/lib/juno/dbm.rb +15 -0
- data/lib/juno/expires.rb +45 -0
- data/lib/juno/file.rb +62 -0
- data/lib/juno/gdbm.rb +15 -0
- data/lib/juno/hashfile.rb +12 -0
- data/lib/juno/localmemcache.rb +16 -0
- data/lib/juno/memcached.rb +7 -0
- data/lib/juno/memcached_dalli.rb +55 -0
- data/lib/juno/memcached_native.rb +56 -0
- data/lib/juno/memory.rb +7 -0
- data/lib/juno/mongodb.rb +43 -0
- data/lib/juno/proxy.rb +5 -0
- data/lib/juno/pstore.rb +49 -0
- data/lib/juno/redis.rb +34 -0
- data/lib/juno/riak.rb +45 -0
- data/lib/juno/sdbm.rb +15 -0
- data/lib/juno/sequel.rb +51 -0
- data/lib/juno/sqlite.rb +50 -0
- data/lib/juno/tokyocabinet.rb +36 -0
- data/lib/juno/version.rb +3 -0
- data/lib/juno/yaml.rb +9 -0
- data/test/helper.rb +182 -0
- data/test/test_active_record.rb +36 -0
- data/test/test_couch.rb +13 -0
- data/test/test_datamapper.rb +64 -0
- data/test/test_dbm.rb +13 -0
- data/test/test_expires.rb +10 -0
- data/test/test_file.rb +9 -0
- data/test/test_gdbm.rb +13 -0
- data/test/test_hashfile.rb +9 -0
- data/test/test_localmemcache.rb +13 -0
- data/test/test_memcached.rb +15 -0
- data/test/test_memcached_dalli.rb +15 -0
- data/test/test_memcached_native.rb +15 -0
- data/test/test_memory.rb +9 -0
- data/test/test_mongodb.rb +13 -0
- data/test/test_proxy.rb +9 -0
- data/test/test_pstore.rb +9 -0
- data/test/test_redis.rb +13 -0
- data/test/test_riak.rb +13 -0
- data/test/test_sdbm.rb +13 -0
- data/test/test_sequel.rb +13 -0
- data/test/test_sqlite.rb +13 -0
- data/test/test_tokyocabinet.rb +13 -0
- data/test/test_yaml.rb +9 -0
- data/unsupported/benchmarks.rb +234 -0
- data/unsupported/cassandra.rb +45 -0
- data/unsupported/fog.rb +60 -0
- data/unsupported/test_cassandra.rb +13 -0
- data/unsupported/test_rackspace.rb +15 -0
- data/unsupported/test_s3.rb +15 -0
- data/unsupported/test_tokyotyrant.rb +13 -0
- data/unsupported/tokyotyrant.rb +29 -0
- metadata +165 -0
data/lib/juno/proxy.rb
ADDED
data/lib/juno/pstore.rb
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'pstore'
|
2
|
+
|
3
|
+
module Juno
|
4
|
+
class PStore < Base
|
5
|
+
def initialize(options = {})
|
6
|
+
raise 'No option :file specified' unless options[:file]
|
7
|
+
FileUtils.mkpath(::File.dirname(options[:file]))
|
8
|
+
@store = new_store(options)
|
9
|
+
end
|
10
|
+
|
11
|
+
def key?(key, options = {})
|
12
|
+
@store.transaction(true) { @store.root?(key_for(key)) }
|
13
|
+
end
|
14
|
+
|
15
|
+
def [](key)
|
16
|
+
@store.transaction(true) { @store[key_for(key)] }
|
17
|
+
end
|
18
|
+
|
19
|
+
def delete(key, options = {})
|
20
|
+
@store.transaction { @store.delete(key_for(key)) }
|
21
|
+
end
|
22
|
+
|
23
|
+
def store(key, value, options = {})
|
24
|
+
@store.transaction { @store[key_for(key)] = value }
|
25
|
+
end
|
26
|
+
|
27
|
+
def clear(options = {})
|
28
|
+
@store.transaction do
|
29
|
+
@store.roots.each do |key|
|
30
|
+
@store.delete(key)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
nil
|
34
|
+
end
|
35
|
+
|
36
|
+
protected
|
37
|
+
|
38
|
+
if RUBY_VERSION > '1.9'
|
39
|
+
def new_store(options)
|
40
|
+
# Create a thread-safe pstore by default
|
41
|
+
::PStore.new(options[:file], options.include?(:thread_safe) ? options[:thread_safe] : true)
|
42
|
+
end
|
43
|
+
else
|
44
|
+
def new_store(options)
|
45
|
+
::PStore.new(options[:file])
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
data/lib/juno/redis.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'redis'
|
2
|
+
|
3
|
+
module Juno
|
4
|
+
class Redis < Base
|
5
|
+
def initialize(options = {})
|
6
|
+
@store = ::Redis.new(options)
|
7
|
+
end
|
8
|
+
|
9
|
+
def key?(key, options = {})
|
10
|
+
@store.exists(key_for(key))
|
11
|
+
end
|
12
|
+
|
13
|
+
def [](key)
|
14
|
+
deserialize(@store.get(key_for(key)))
|
15
|
+
end
|
16
|
+
|
17
|
+
def delete(key, options = {})
|
18
|
+
string_key = key_for(key)
|
19
|
+
value = self[key]
|
20
|
+
@store.del(string_key) if value
|
21
|
+
value
|
22
|
+
end
|
23
|
+
|
24
|
+
def store(key, value, options = {})
|
25
|
+
@store.set(key_for(key), serialize(value))
|
26
|
+
value
|
27
|
+
end
|
28
|
+
|
29
|
+
def clear(options = {})
|
30
|
+
@store.flushdb
|
31
|
+
nil
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/lib/juno/riak.rb
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
# Copyright: 2011 TMX Credit
|
2
|
+
# Author: Potapov Sergey (aka Blake)
|
3
|
+
|
4
|
+
require 'ripple'
|
5
|
+
|
6
|
+
module Juno
|
7
|
+
class Riak < Base
|
8
|
+
def initialize(options = {})
|
9
|
+
bucket_name = options.delete(:bucket) || 'juno'
|
10
|
+
client = ::Riak::Client.new(options)
|
11
|
+
@bucket = client.bucket(bucket_name)
|
12
|
+
end
|
13
|
+
|
14
|
+
def key?(key, options = {})
|
15
|
+
!!self[key]
|
16
|
+
end
|
17
|
+
|
18
|
+
def [](key)
|
19
|
+
deserialize(@bucket[key_for(key)].data)
|
20
|
+
rescue ::Riak::HTTPFailedRequest => err
|
21
|
+
nil
|
22
|
+
end
|
23
|
+
|
24
|
+
def delete(key, options = {})
|
25
|
+
value = self[key]
|
26
|
+
@bucket.delete(key_for(key))
|
27
|
+
value
|
28
|
+
end
|
29
|
+
|
30
|
+
def store(key, value, options = {})
|
31
|
+
obj = ::Riak::RObject.new(@bucket, key_for(key))
|
32
|
+
obj.content_type = 'text/plain'
|
33
|
+
obj.data = serialize(value)
|
34
|
+
obj.store
|
35
|
+
value
|
36
|
+
end
|
37
|
+
|
38
|
+
def clear(options = {})
|
39
|
+
@bucket.keys(:reload => true) do |keys|
|
40
|
+
keys.each{|key| @bucket.delete(key) }
|
41
|
+
end
|
42
|
+
nil
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
data/lib/juno/sdbm.rb
ADDED
data/lib/juno/sequel.rb
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'sequel'
|
2
|
+
|
3
|
+
module Juno
|
4
|
+
class Sequel < Base
|
5
|
+
def initialize(options = {})
|
6
|
+
raise 'No option :db specified' unless db = options.delete(:db)
|
7
|
+
@table = options.delete(:table) || :juno
|
8
|
+
@db = ::Sequel.connect(db, options)
|
9
|
+
end
|
10
|
+
|
11
|
+
def migrate
|
12
|
+
@db.create_table @table do
|
13
|
+
primary_key :k
|
14
|
+
String :k
|
15
|
+
String :v
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def key?(key, options = {})
|
20
|
+
!!sequel_table[:k => key_for(key)]
|
21
|
+
end
|
22
|
+
|
23
|
+
def [](key)
|
24
|
+
result = sequel_table[:k => key_for(key)]
|
25
|
+
result ? deserialize(result[:v]) : nil
|
26
|
+
end
|
27
|
+
|
28
|
+
def store(key, value, options = {})
|
29
|
+
sequel_table.insert(:k => key_for(key), :v => serialize(value))
|
30
|
+
value
|
31
|
+
end
|
32
|
+
|
33
|
+
def delete(key, options = {})
|
34
|
+
if value = self[key]
|
35
|
+
sequel_table.filter(:k => key_for(key)).delete
|
36
|
+
value
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def clear(options = {})
|
41
|
+
sequel_table.delete
|
42
|
+
nil
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def sequel_table
|
48
|
+
@sequel_table ||= @db[@table]
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
data/lib/juno/sqlite.rb
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'sqlite3'
|
2
|
+
|
3
|
+
module Juno
|
4
|
+
class Sqlite < Base
|
5
|
+
def initialize(options = {})
|
6
|
+
raise 'No option :file specified' unless options[:file]
|
7
|
+
table = options[:table] || 'juno'
|
8
|
+
@db = ::SQLite3::Database.new(options[:file])
|
9
|
+
@db.execute("create table if not exists #{table} (key string primary key, value string)")
|
10
|
+
@select = @db.prepare("select value from #{table} where key = ?")
|
11
|
+
@insert = @db.prepare("insert into #{table} values (?, ?)")
|
12
|
+
@delete = @db.prepare("delete from #{table} where key = ?")
|
13
|
+
@clear = @db.prepare("delete from #{table}")
|
14
|
+
end
|
15
|
+
|
16
|
+
def key?(key, options = {})
|
17
|
+
!@select.execute!(key_for(key)).empty?
|
18
|
+
end
|
19
|
+
|
20
|
+
def [](key)
|
21
|
+
rows = @select.execute!(key_for(key))
|
22
|
+
rows.empty? ? nil : deserialize(rows.first.first)
|
23
|
+
end
|
24
|
+
|
25
|
+
def store(key, value, options = {})
|
26
|
+
@insert.execute!(key_for(key), serialize(value))
|
27
|
+
value
|
28
|
+
end
|
29
|
+
|
30
|
+
def delete(key, options = {})
|
31
|
+
value = self[key]
|
32
|
+
@delete.execute!(key_for(key))
|
33
|
+
value
|
34
|
+
end
|
35
|
+
|
36
|
+
def clear(options = {})
|
37
|
+
@clear.execute!
|
38
|
+
nil
|
39
|
+
end
|
40
|
+
|
41
|
+
def close
|
42
|
+
@select.close
|
43
|
+
@insert.close
|
44
|
+
@delete.close
|
45
|
+
@clear.close
|
46
|
+
@db.close
|
47
|
+
nil
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'tokyocabinet'
|
2
|
+
|
3
|
+
module Juno
|
4
|
+
class TokyoCabinet < Base
|
5
|
+
def initialize(options = {})
|
6
|
+
file = options[:file]
|
7
|
+
raise 'No option :file specified' unless options[:file]
|
8
|
+
@store = ::TokyoCabinet::HDB.new
|
9
|
+
unless @store.open(file, ::TokyoCabinet::HDB::OWRITER | ::TokyoCabinet::HDB::OCREAT)
|
10
|
+
raise @store.errmsg(@store.ecode)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def key?(key, options = {})
|
15
|
+
!!self[key]
|
16
|
+
end
|
17
|
+
|
18
|
+
def delete(key, options = {})
|
19
|
+
value = self[key]
|
20
|
+
if value
|
21
|
+
@store.delete(key_for(key))
|
22
|
+
value
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def clear(options = {})
|
27
|
+
@store.clear
|
28
|
+
nil
|
29
|
+
end
|
30
|
+
|
31
|
+
def close
|
32
|
+
@store.close
|
33
|
+
nil
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
data/lib/juno/version.rb
ADDED
data/lib/juno/yaml.rb
ADDED
data/test/helper.rb
ADDED
@@ -0,0 +1,182 @@
|
|
1
|
+
require 'minitest/autorun'
|
2
|
+
require 'juno'
|
3
|
+
require 'fileutils'
|
4
|
+
|
5
|
+
module Helper
|
6
|
+
def make_tempdir
|
7
|
+
tmp = File.join(File.dirname(__FILE__), 'tmp')
|
8
|
+
FileUtils.mkpath(tmp)
|
9
|
+
tmp
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
JunoSpecification = proc do
|
14
|
+
include Helper
|
15
|
+
|
16
|
+
before do
|
17
|
+
@store = new_store
|
18
|
+
@store.clear
|
19
|
+
end
|
20
|
+
|
21
|
+
after do
|
22
|
+
@store.close.must_equal nil if @store
|
23
|
+
end
|
24
|
+
|
25
|
+
types = {
|
26
|
+
'String' => ['key', 'key2'],
|
27
|
+
'Object' => [{:foo => :bar}, {:bar => :baz}]
|
28
|
+
}
|
29
|
+
|
30
|
+
types.each do |type, (key, key2)|
|
31
|
+
it "reads from keys that are #{type}s like a Hash" do
|
32
|
+
@store[key].must_equal nil
|
33
|
+
end
|
34
|
+
|
35
|
+
it "writes String values to keys that are #{type}s like a Hash" do
|
36
|
+
@store[key] = 'value'
|
37
|
+
@store[key].must_equal 'value'
|
38
|
+
end
|
39
|
+
|
40
|
+
it "guarantees that a different String value is retrieved from the #{type} key" do
|
41
|
+
value = 'value'
|
42
|
+
(@store[key] = value).must_be_same_as value
|
43
|
+
@store[key].wont_be_same_as(value)
|
44
|
+
end
|
45
|
+
|
46
|
+
it "writes Object values to keys that are #{type}s like a Hash" do
|
47
|
+
value = {:foo => :bar}
|
48
|
+
(@store[key] = value).must_be_same_as value
|
49
|
+
@store[key].must_equal(:foo => :bar)
|
50
|
+
end
|
51
|
+
|
52
|
+
it "guarantees that a different Object value is retrieved from the #{type} key" do
|
53
|
+
value = {:foo => :bar}
|
54
|
+
@store[key] = value
|
55
|
+
@store[key].wont_be_same_as(:foo => :bar)
|
56
|
+
end
|
57
|
+
|
58
|
+
it "returns false from key? if a #{type} key is not available" do
|
59
|
+
@store.key?(key).must_equal false
|
60
|
+
end
|
61
|
+
|
62
|
+
it "returns true from key? if a #{type} key is available" do
|
63
|
+
@store[key] = 'value'
|
64
|
+
@store.key?(key).must_equal true
|
65
|
+
end
|
66
|
+
|
67
|
+
it "removes and returns an element with a #{type} key from the backing store via delete if it exists" do
|
68
|
+
@store[key] = 'value'
|
69
|
+
@store.delete(key).must_equal 'value'
|
70
|
+
@store.key?(key).must_equal false
|
71
|
+
end
|
72
|
+
|
73
|
+
it "returns nil from delete if an element for a #{type} key does not exist" do
|
74
|
+
@store.delete(key).must_equal nil
|
75
|
+
end
|
76
|
+
|
77
|
+
it "removes all #{type} keys from the store with clear" do
|
78
|
+
@store[key] = 'value'
|
79
|
+
@store[key2] = 'value2'
|
80
|
+
@store.clear.must_equal nil
|
81
|
+
@store.key?(key).wont_equal true
|
82
|
+
@store.key?(key2).wont_equal true
|
83
|
+
end
|
84
|
+
|
85
|
+
it "fetches a #{type} key with a default value with fetch, if the key is not available" do
|
86
|
+
@store.fetch(key, 'value').must_equal 'value'
|
87
|
+
end
|
88
|
+
|
89
|
+
it "fetches a #{type} key with a block with fetch, if the key is not available" do
|
90
|
+
@store.fetch(key) { |k| 'value' }.must_equal 'value'
|
91
|
+
end
|
92
|
+
|
93
|
+
it "does not run the block if the #{type} key is available" do
|
94
|
+
@store[key] = 'value'
|
95
|
+
unaltered = "unaltered"
|
96
|
+
@store.fetch(key) { unaltered = "altered" }
|
97
|
+
unaltered.must_equal "unaltered"
|
98
|
+
end
|
99
|
+
|
100
|
+
it "fetches a #{type} key with a default value with fetch, if the key is available" do
|
101
|
+
@store[key] = 'value2'
|
102
|
+
@store.fetch(key, 'value').must_equal 'value2'
|
103
|
+
end
|
104
|
+
|
105
|
+
it "stores #{key} values with #store" do
|
106
|
+
@store.store(key, 'value').must_equal 'value'
|
107
|
+
@store[key].must_equal 'value'
|
108
|
+
end
|
109
|
+
|
110
|
+
it 'must accept options' do
|
111
|
+
@store.store(key, 'value', :foo => 42).must_equal 'value'
|
112
|
+
@store.key?(key, :foo => 42).must_equal true
|
113
|
+
@store.fetch(key, nil, :foo => 42).must_equal 'value'
|
114
|
+
@store.delete(key, :foo => 42).must_equal 'value'
|
115
|
+
@store.key?(key, :foo => 42).must_equal false
|
116
|
+
@store.clear(:foo => 42).must_equal nil
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
it "refuses to #[] from keys that cannot be marshalled" do
|
121
|
+
lambda do
|
122
|
+
@store[Struct.new(:foo).new(:bar)]
|
123
|
+
end.must_raise(TypeError)
|
124
|
+
end
|
125
|
+
|
126
|
+
it "refuses to fetch from keys that cannot be marshalled" do
|
127
|
+
lambda do
|
128
|
+
@store.fetch(Struct.new(:foo).new(:bar), true)
|
129
|
+
end.must_raise(TypeError)
|
130
|
+
end
|
131
|
+
|
132
|
+
it "refuses to #[]= to keys that cannot be marshalled" do
|
133
|
+
lambda do
|
134
|
+
@store[Struct.new(:foo).new(:bar)] = 'value'
|
135
|
+
end.must_raise(TypeError)
|
136
|
+
end
|
137
|
+
|
138
|
+
it "refuses to store to keys that cannot be marshalled" do
|
139
|
+
lambda do
|
140
|
+
@store.store Struct.new(:foo).new(:bar), 'value'
|
141
|
+
end.must_raise(TypeError)
|
142
|
+
end
|
143
|
+
|
144
|
+
it "refuses to check for key? if the key cannot be marshalled" do
|
145
|
+
lambda do
|
146
|
+
@store.key? Struct.new(:foo).new(:bar)
|
147
|
+
end.must_raise(TypeError)
|
148
|
+
end
|
149
|
+
|
150
|
+
it "refuses to delete a key if the key cannot be marshalled" do
|
151
|
+
lambda do
|
152
|
+
@store.delete Struct.new(:foo).new(:bar)
|
153
|
+
end.must_raise(TypeError)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
JunoExpiresSpecification = proc do
|
158
|
+
it 'should support expires on store' do
|
159
|
+
@store.store('key', 'value', :expires => 2)
|
160
|
+
@store['key'].must_equal 'value'
|
161
|
+
sleep 3
|
162
|
+
@store['key'].must_equal nil
|
163
|
+
end
|
164
|
+
|
165
|
+
it 'should support updating the expiration time in fetch' do
|
166
|
+
@store.store('key2', 'value2', :expires => 2)
|
167
|
+
@store['key2'].must_equal 'value2'
|
168
|
+
sleep 1
|
169
|
+
@store.fetch('key2', nil, :expires => 3).must_equal 'value2'
|
170
|
+
sleep 1
|
171
|
+
@store['key2'].must_equal 'value2'
|
172
|
+
sleep 3
|
173
|
+
@store['key2'].must_equal nil
|
174
|
+
end
|
175
|
+
|
176
|
+
it 'should respect expires in delete' do
|
177
|
+
@store.store('key', 'value', :expires => 2)
|
178
|
+
@store['key'].must_equal 'value'
|
179
|
+
sleep 3
|
180
|
+
@store.delete('key').must_equal nil
|
181
|
+
end
|
182
|
+
end
|