juno 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. data/.gitignore +4 -0
  2. data/.travis.yml +25 -0
  3. data/Gemfile +19 -0
  4. data/LICENSE +20 -0
  5. data/README.md +83 -0
  6. data/Rakefile +15 -0
  7. data/SPEC.md +95 -0
  8. data/juno.gemspec +22 -0
  9. data/lib/juno.rb +31 -0
  10. data/lib/juno/activerecord.rb +58 -0
  11. data/lib/juno/base.rb +113 -0
  12. data/lib/juno/couch.rb +43 -0
  13. data/lib/juno/datamapper.rb +63 -0
  14. data/lib/juno/dbm.rb +15 -0
  15. data/lib/juno/expires.rb +45 -0
  16. data/lib/juno/file.rb +62 -0
  17. data/lib/juno/gdbm.rb +15 -0
  18. data/lib/juno/hashfile.rb +12 -0
  19. data/lib/juno/localmemcache.rb +16 -0
  20. data/lib/juno/memcached.rb +7 -0
  21. data/lib/juno/memcached_dalli.rb +55 -0
  22. data/lib/juno/memcached_native.rb +56 -0
  23. data/lib/juno/memory.rb +7 -0
  24. data/lib/juno/mongodb.rb +43 -0
  25. data/lib/juno/proxy.rb +5 -0
  26. data/lib/juno/pstore.rb +49 -0
  27. data/lib/juno/redis.rb +34 -0
  28. data/lib/juno/riak.rb +45 -0
  29. data/lib/juno/sdbm.rb +15 -0
  30. data/lib/juno/sequel.rb +51 -0
  31. data/lib/juno/sqlite.rb +50 -0
  32. data/lib/juno/tokyocabinet.rb +36 -0
  33. data/lib/juno/version.rb +3 -0
  34. data/lib/juno/yaml.rb +9 -0
  35. data/test/helper.rb +182 -0
  36. data/test/test_active_record.rb +36 -0
  37. data/test/test_couch.rb +13 -0
  38. data/test/test_datamapper.rb +64 -0
  39. data/test/test_dbm.rb +13 -0
  40. data/test/test_expires.rb +10 -0
  41. data/test/test_file.rb +9 -0
  42. data/test/test_gdbm.rb +13 -0
  43. data/test/test_hashfile.rb +9 -0
  44. data/test/test_localmemcache.rb +13 -0
  45. data/test/test_memcached.rb +15 -0
  46. data/test/test_memcached_dalli.rb +15 -0
  47. data/test/test_memcached_native.rb +15 -0
  48. data/test/test_memory.rb +9 -0
  49. data/test/test_mongodb.rb +13 -0
  50. data/test/test_proxy.rb +9 -0
  51. data/test/test_pstore.rb +9 -0
  52. data/test/test_redis.rb +13 -0
  53. data/test/test_riak.rb +13 -0
  54. data/test/test_sdbm.rb +13 -0
  55. data/test/test_sequel.rb +13 -0
  56. data/test/test_sqlite.rb +13 -0
  57. data/test/test_tokyocabinet.rb +13 -0
  58. data/test/test_yaml.rb +9 -0
  59. data/unsupported/benchmarks.rb +234 -0
  60. data/unsupported/cassandra.rb +45 -0
  61. data/unsupported/fog.rb +60 -0
  62. data/unsupported/test_cassandra.rb +13 -0
  63. data/unsupported/test_rackspace.rb +15 -0
  64. data/unsupported/test_s3.rb +15 -0
  65. data/unsupported/test_tokyotyrant.rb +13 -0
  66. data/unsupported/tokyotyrant.rb +29 -0
  67. metadata +165 -0
@@ -0,0 +1,4 @@
1
+ test/tmp
2
+ *~
3
+ *.swp
4
+ *.rdb
@@ -0,0 +1,25 @@
1
+ rvm:
2
+ - 1.8.7
3
+ - 1.9.3
4
+ # - ruby-head
5
+ # - jruby
6
+ - rbx-18mode
7
+ - rbx-19mode
8
+ services:
9
+ - riak
10
+ - couchdb
11
+ - redis-server
12
+ # - cassandra
13
+ # - memcached
14
+ # - mongodb
15
+ before_install:
16
+ - sudo apt-get install -qq libtokyocabinet8 libtokyocabinet-dev # tokyotyrant
17
+ - sudo /bin/bash /etc/init.d/mongodb start
18
+ - memcached -d -p 22122
19
+ matrix:
20
+ allow_failures:
21
+ - rvm: 1.8.7
22
+ # - rvm: ruby-head
23
+ - rvm: rbx-18mode
24
+ - rvm: rbx-19mode
25
+
data/Gemfile ADDED
@@ -0,0 +1,19 @@
1
+ source :rubygems
2
+ gemspec
3
+
4
+ gem 'activerecord'
5
+ gem 'sqlite3'
6
+ gem 'redis'
7
+ gem 'datamapper'
8
+ gem 'dm-sqlite-adapter'
9
+ gem 'ripple'
10
+ gem 'tokyocabinet'
11
+ gem 'mongo'
12
+ gem 'couchrest'
13
+ gem 'sequel'
14
+ gem 'dalli'
15
+ gem 'memcached'
16
+ #gem 'cassandra'
17
+ #gem 'localmemcache'
18
+ #gem 'fog'
19
+ #gem 'tokyotyrant'
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Yehuda Katz, 2012 Daniel Mendler
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,83 @@
1
+ Juno: A unified interface for key/value stores
2
+ ================================================
3
+
4
+ [![Build Status](https://secure.travis-ci.org/minad/juno.png?branch=master)](http://travis-ci.org/minad/juno) [![Dependency Status](https://gemnasium.com/minad/juno.png?travis)](https://gemnasium.com/minad/juno) [![Code Climate](https://codeclimate.com/badge.png)](https://codeclimate.com/github/minad/juno)
5
+
6
+ Juno provides a standard interface for interacting with various kinds of key/value stores. Juno
7
+ is based on Moneta and replaces it with a mostly compatible interface. The reason for the
8
+ fork was that Moneta was unmaintained for a long time.
9
+
10
+ Out of the box, it supports:
11
+
12
+ * File Store
13
+ * Memcached store (memcached and dalli)
14
+ * In-memory store
15
+ * DataMapper
16
+ * BerkeleyDB (dbm)
17
+ * GDBM
18
+ * SDBM
19
+ * Redis
20
+ * Riak
21
+ * TokyoCabinet
22
+ * CouchDB
23
+ * MongoDB
24
+ * ActiveRecord
25
+ * YAML store
26
+ * PStore
27
+ * LocalMemCache
28
+ * Sequel
29
+ * Sqlite3
30
+
31
+ The Juno API is purposely extremely similar to the Hash API. In order so support an
32
+ identical API across stores, it does not support iteration or partial matches.
33
+
34
+ The API
35
+ =======
36
+
37
+ ```
38
+ #initialize(options) options differs per-store, and is used to set up the store
39
+
40
+ #[](key) retrieve a key. if the key is not available, return nil
41
+
42
+ #fetch(key, options = {}, &block) retrieve a key. if the key is not available, execute the
43
+ block and return its return value.
44
+
45
+ #fetch(key, value, options = {}) retrieve a key. if the key is not available, return the value
46
+
47
+ #[]=(key, value) set a value for a key. if the key is already used, clobber it.
48
+ keys set using []= will never expire
49
+
50
+ #delete(key, options = {}) delete the key from the store and return the current value
51
+
52
+ #key?(key, options = {}) true if the key exists, false if it does not
53
+
54
+ #store(key, value, options = {}) same as []=, but you can supply options
55
+
56
+ #clear(options = {}) clear all keys in this store
57
+
58
+ #close close database connection
59
+ ```
60
+
61
+ Proxy store & Expiry
62
+ ====================
63
+
64
+ The memcached backend supports expires values directly:
65
+
66
+ ```ruby
67
+ cache = Juno::Memcached.new
68
+ # Expires in 10 seconds
69
+ cache.store(key, value, :expires => 10)
70
+ ```
71
+
72
+ You can add the expires feature to other backends using the Expires proxy:
73
+
74
+ ```ruby
75
+ cache = Juno::Expires.new(Juno::File.new(...))
76
+ cache.store(key, value, :expires => 10)
77
+ ```
78
+
79
+ Authors
80
+ =======
81
+
82
+ * Moneta originally by wycats
83
+ * Juno by Daniel Mendler
@@ -0,0 +1,15 @@
1
+ begin
2
+ require 'bundler'
3
+ Bundler::GemHelper.install_tasks
4
+ rescue Exception
5
+ end
6
+
7
+ require 'rake/testtask'
8
+
9
+ Rake::TestTask.new(:test) do |t|
10
+ t.libs << 'lib' << 'test'
11
+ t.test_files = FileList['test/test_*.rb']
12
+ t.verbose = true
13
+ end
14
+
15
+ task :default => :test
data/SPEC.md ADDED
@@ -0,0 +1,95 @@
1
+ # Juno Specification
2
+
3
+ The purpose of the juno specification is to create a general-purpose API for interacting with key-value stores. In general, libraries that need to interact with key-value stores should be able to specify that they can use any "juno-compliant store".
4
+
5
+ # Juno Executable Specs
6
+
7
+ Juno ships with a set of executable specs which you can use to verify spec-compliance with your juno adapter.
8
+
9
+ # Juno Library
10
+
11
+ Juno ships with proof-of-concept adapters for over a dozen key-value stores, including in-memory, memcache, database-backed and S3. These adapters are meant as proofs-of-concept, and while the juno project intends to maintain them (and will accept patches to improve them), you should not consider them the core of the project.
12
+
13
+ # Requirements for a Juno Adapter
14
+
15
+ (See RFC 2119 for use of MUST, SHOULD, MAY, MUST NOT, and SHOULD NOT)
16
+
17
+ A Juno adapter must expose a class with the following characteristics:
18
+
19
+ ## Class Methods
20
+
21
+ ### <code>new(options[Hash]) => Object</code>
22
+
23
+ Return an instance of the juno adapter, with the instance methods listed below. The <code>options</code> hash is a required parameter, and the adapter may specify whatever additional requirements it needs to properly instantiate it.
24
+
25
+ ## Instance Methods
26
+
27
+ ### <code>\[\](key[Object]) => Object</code>
28
+
29
+ Return the value stored in the key-value-store under the provided key. Adapters MUST return a duplicate of the original value, and consumers should expect that adapters might serialize and deserialize the key and value. As a result, both the key and value MUST be objects that can be serialized using Ruby's Marshal system.
30
+
31
+ ### <code>\[\]=(key[Object], value[Object]) => Object(value)</code>
32
+
33
+ Store the value in the key-value-store under the provided key. Adapters MAY serialize the value using Ruby's Marshal system, and MUST NOT store a reference to the original value in the store, unless Ruby disallows duplication of the original value. Adapters SHOULD NOT simply call <code>dup</code> on the value, unless the value stores no references to other Object. For example, an adapter MAY store a <code>dup</code> of a String, but SHOULD NOT store a <code>dup</code> of <code>["hello", "world"]</code>.
34
+
35
+ ### <code>fetch(key[Object]) => Object</code>
36
+
37
+ Return the value stored in the key-value-store under the provided key. If no value is stored under the provided key, the adapter MUST raise an IndexError.
38
+
39
+ ### <code>fetch(key[Object], &block) => Object</code>
40
+
41
+ Return the value stored in the key-value-store under the provided key. If no value is stored under the provided key, the adapter MUST yield to the block, and return the value. The adapter MUST NOT store the value returned from the block in the key-value-store.
42
+
43
+ ### <code>fetch(key[Object], value[Object]) => Object</code>
44
+
45
+ Return the value stored in the key-value-store under the provided key. If no value is stored under the provided key, the adapter MUST return the default value provided. The adapter MUST NOT store the default value in the key-value-store.
46
+
47
+ ### <code>delete(key[Object]) => Object</code>
48
+
49
+ Delete the value stored in the key-value-store for the key provided, and return the value previously stored there. After this operation, the key-value-store MUST behave as though no value was stored for the provided key.
50
+
51
+ ### <code>key?(key[Object]) => [TrueClass, FalseClass]</code>
52
+
53
+ Determine whether a value exists in the key-value-store for the key provided. If a value exists, the adapter MUST return <code>true</code>. Otherwise, the adapter MUST return <code>false</code>.
54
+
55
+ ### <code>store(key[Object], value[Object]) => Object(value)</code>
56
+
57
+ Behaves the same as <code>[]=</code>, but allows the client to send additional options which can be specified by the adapter (and which may be specified by extensions to this specification).
58
+
59
+ ### <code>clear</code>
60
+
61
+ Completely empty all keys and values from the key-value-store. Adapters MAY allow a namespace during initialization, which can scope this operation to a particular subset of keys. After calling <code>clear</code>, a <code>[]</code> operation MUST return nil for every possible key, and a <code>key?</code> query MUST return false for every possible key.
62
+
63
+ # Additional Options Hashes
64
+
65
+ The following methods may all take an additional Hash as a final argument. This allows the client to send additional options which can be specified by the adapter (and which may be specified by extensions to this specification).
66
+
67
+ * fetch
68
+ * store
69
+ * delete
70
+ * key?
71
+ * clear
72
+
73
+ In the case of methods with optional arguments, the Hash MUST be provided as the final argument, and all optional arguments MUST be specified.
74
+
75
+ Keys in this Hash MUST be Strings or Symbols. If they are Strings, they MUST be prefixed with a unique namespace. Namespaces MUST be separated from the name of the key with a single ".". The namespace SHOULD be the name of the gem that exposes the key.
76
+
77
+ Keys in this Hash MUST NOT be Symbols unless this specification or an official extension to this specification defines a Symbol key.
78
+
79
+ # Key Equality
80
+
81
+ Adapters MUST consider keys as equal to one another if and only if the value of <code>Marshal.dump(keya)</code> is the same (byte-for-byte) as <code>Marshal.dump(keyb)</code>. This does not mean that adapters are required to use <code>Marshal.dump</code> to calculate the key to use for a given key specified by the consumer of the adapter. However, if an adapter does not, it MUST guarantee that the value returned for every key is identical to the value that would be returned if it did a byte-for-byte comparison of the result of <code>Marshal.dump</code> for every operation involving a key.
82
+
83
+ # Storage and Serialization
84
+
85
+ In a Juno-compliant adapter, any Ruby object that can be serialized using Ruby's marshalling system may be used for keys or values.
86
+
87
+ Adapters MAY use the marshalling system to serialize Ruby objects. Adapters MUST NOT return an Object from a fetch operation that existed on the heap prior to the fetch operation. The intention of this requirement is to prevent adapters that use the heap for persistence to store direct references to Objects passed into the <code>store</code> or <code>[]=</code> methods.
88
+
89
+ # Atomicity
90
+
91
+ The base Juno specification does not specify any atomicity guarantees. However, extensions to this spec may specify extensions that define additional guarantees for any of the defined operations.
92
+
93
+ # Expiry
94
+
95
+ The base Juno specification does not specify any mechanism for time-based expiry. However, extensions to this spec may specify mechanisms (using <code>store</code> to provide expiration semantics.
@@ -0,0 +1,22 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.dirname(__FILE__) + '/lib/juno/version'
3
+ require 'date'
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = 'juno'
7
+ s.version = Juno::VERSION
8
+ s.date = Date.today.to_s
9
+ s.authors = ['Yehuda Katz', 'Derek Kastner', 'Daniel Mendler']
10
+ s.email = %w{wycats@gmail.com dkastner@gmail.com mail@daniel-mendler.de}
11
+ s.description = 'A unified interface to key/value stores (moneta replacement)'
12
+ s.extra_rdoc_files = %w{README.md SPEC.md LICENSE}
13
+ s.files = `git ls-files`.split("\n")
14
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
15
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
16
+ s.homepage = 'http://github.com/minad/juno'
17
+ s.require_paths = ['lib']
18
+ s.summary = %{A unified interface to key/value stores, including MongoDB, Redis, Tokyo, and ActiveRecord}
19
+
20
+ s.add_development_dependency 'rake'
21
+ s.add_development_dependency 'minitest'
22
+ end
@@ -0,0 +1,31 @@
1
+ module Juno
2
+ autoload :ActiveRecord, 'juno/activerecord'
3
+ autoload :Base, 'juno/base'
4
+ #autoload :Cassandra, 'juno/cassandra'
5
+ autoload :Couch, 'juno/couch'
6
+ autoload :DataMapper, 'juno/datamapper'
7
+ autoload :DBM, 'juno/dbm'
8
+ autoload :Expires, 'juno/expires'
9
+ autoload :File, 'juno/file'
10
+ #autoload :Fog, 'juno/fog'
11
+ #autoload :S3, 'juno/fog'
12
+ #autoload :Rackspace, 'juno/fog'
13
+ autoload :GDBM, 'juno/gdbm'
14
+ autoload :HashFile, 'juno/hashfile'
15
+ autoload :LocalMemCache, 'juno/localmemcache'
16
+ autoload :Memcached, 'juno/memcached'
17
+ autoload :MemcachedDalli, 'juno/memcached_dalli'
18
+ autoload :MemcachedNative, 'juno/memcached_native'
19
+ autoload :Memory, 'juno/memory'
20
+ autoload :MongoDB, 'juno/mongodb'
21
+ autoload :Proxy, 'juno/proxy'
22
+ autoload :PStore, 'juno/pstore'
23
+ autoload :Redis, 'juno/redis'
24
+ autoload :Riak, 'juno/riak'
25
+ autoload :SDBM, 'juno/sdbm'
26
+ autoload :Sequel, 'juno/sequel'
27
+ autoload :Sqlite, 'juno/sqlite'
28
+ autoload :TokyoCabinet, 'juno/tokyocabinet'
29
+ #autoload :TokyoTyrant, 'juno/tokyotyrant'
30
+ autoload :YAML, 'juno/yaml'
31
+ end
@@ -0,0 +1,58 @@
1
+ require 'active_record'
2
+
3
+ module Juno
4
+ class ActiveRecord < Base
5
+ def self.tables
6
+ @tables ||= {}
7
+ end
8
+
9
+ attr_reader :table
10
+
11
+ def initialize(options = {})
12
+ table = options[:table] || 'juno'
13
+ @table = self.class.tables[table] ||= begin
14
+ c = Class.new(::ActiveRecord::Base)
15
+ c.table_name = table
16
+ c
17
+ end
18
+ @table.establish_connection(options[:connection]) if options[:connection]
19
+ end
20
+
21
+ def migrate
22
+ @table.connection.create_table @table.table_name do |t|
23
+ t.string 'key', :primary => :true
24
+ t.string 'value'
25
+ end unless @table.table_exists?
26
+ end
27
+
28
+ def key?(key, options = {})
29
+ !!@table.find_by_key(key_for(key))
30
+ end
31
+
32
+ def [](key)
33
+ record = @table.find_by_key(key_for(key))
34
+ record ? deserialize(record.value) : nil
35
+ end
36
+
37
+ def delete(key, options = {})
38
+ record = @table.find_by_key(key_for(key))
39
+ if record
40
+ @table.where(:key => key_for(record.key)).delete_all
41
+ deserialize(record.value)
42
+ end
43
+ end
44
+
45
+ def store(key, value, options = {})
46
+ record = @table.find_by_key(key_for(key))
47
+ record ||= @table.new(:key => key_for(key))
48
+ record.value = serialize(value)
49
+ record.save!
50
+ value
51
+ end
52
+
53
+ def clear(options = {})
54
+ @table.delete_all
55
+ nil
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,113 @@
1
+ module Juno
2
+ # Simple interface to key/value stores with Hash-like interface.
3
+ #
4
+ # @abstract
5
+ class Base
6
+ # Exists the value with key
7
+ #
8
+ # @param [Object] key
9
+ # @return [Boolean]
10
+ # @param [Hash] options
11
+ # @api public
12
+ def key?(key, options = {})
13
+ @store.has_key?(key_for(key))
14
+ end
15
+
16
+ # Fetch value with key. Return nil if the key doesn't exist
17
+ #
18
+ # @param [Object] key
19
+ # @return [Object] value
20
+ # @api public
21
+ def [](key)
22
+ deserialize(@store[key_for(key)])
23
+ end
24
+
25
+ # Store value with key
26
+ #
27
+ # @param [Object] key
28
+ # @param [Object] value
29
+ # @param [Hash] options
30
+ # @return value
31
+ # @api public
32
+ def store(key, value, options = {})
33
+ @store[key_for(key)] = serialize(value)
34
+ value
35
+ end
36
+
37
+ # Delete the key from the store and return the current value
38
+ #
39
+ # @param [Object] key
40
+ # @return [Object] current value
41
+ # @param [Hash] options
42
+ # @api public
43
+ def delete(key, options = {})
44
+ deserialize(@store.delete(key_for(key)))
45
+ end
46
+
47
+ # Clear all keys in this store
48
+ #
49
+ # @param [Hash] options
50
+ # @return [void]
51
+ # @api public
52
+ def clear(options = {})
53
+ @store.clear
54
+ nil
55
+ end
56
+
57
+ # Fetch value with key. Return default if value is nil.
58
+ #
59
+ # @param [Object] key
60
+ # @param [Object] value Default value
61
+ # @param [Hash] options
62
+ # @return [Object] value from store
63
+ # @api public
64
+ def fetch(key, value = nil, options = {})
65
+ self[key] || (block_given? && yield(key)) || value
66
+ end
67
+
68
+ # Store value with key
69
+ #
70
+ # @param [Object] key
71
+ # @param [Object] value
72
+ # @return value
73
+ # @api public
74
+ def []=(key, value)
75
+ store(key, value)
76
+ end
77
+
78
+ # Explicitly close the store
79
+ # @api public
80
+ def close
81
+ nil
82
+ end
83
+
84
+ protected
85
+
86
+ # Serialize value
87
+ #
88
+ # @param [Object] value Serializable object
89
+ # @return [String] serialized object
90
+ # @api private
91
+ def serialize(value)
92
+ Marshal.dump(value)
93
+ end
94
+
95
+ # Deserialize value
96
+ #
97
+ # @param [String] value Serialized object
98
+ # @return [Object] Deserialized object
99
+ # @api private
100
+ def deserialize(value)
101
+ value && Marshal.load(value)
102
+ end
103
+
104
+ # Convert key to string
105
+ #
106
+ # @param [Object] key Key
107
+ # @return [String] Marshalled key
108
+ # @api private
109
+ def key_for(key)
110
+ String === key ? key : Marshal.dump(key)
111
+ end
112
+ end
113
+ end