juno 0.2.8 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/.travis.yml CHANGED
@@ -9,7 +9,7 @@ services:
9
9
  - riak
10
10
  - couchdb
11
11
  - redis-server
12
- # - cassandra
12
+ - cassandra
13
13
  - memcached
14
14
  - mongodb
15
15
  before_install:
data/Gemfile CHANGED
@@ -42,8 +42,9 @@ gem 'sequel'
42
42
  gem 'dalli'
43
43
  gem 'riak-client'
44
44
  gem 'hashery'
45
- #gem 'cassandra'
45
+ gem 'cassandra'
46
46
  #gem 'localmemcache'
47
+ alternatives :mri => 'leveldb-ruby'
47
48
  alternatives :mri => 'tokyocabinet'
48
49
  alternatives :mri => 'memcached', :jruby => 'jruby-memcached'
49
50
  alternatives :mri => 'sqlite3', :jruby => %w(jdbc-sqlite3 activerecord-jdbc-adapter activerecord-jdbcsqlite3-adapter)
data/README.md CHANGED
@@ -1,5 +1,5 @@
1
1
  Juno: A unified interface for key/value stores
2
- ================================================
2
+ ==============================================
3
3
 
4
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
5
 
@@ -14,8 +14,10 @@ Juno is very feature rich:
14
14
  * Custom serialization via `Juno::Transformer` proxy (Marshal/JSON/YAML and many more)
15
15
  * Custom key transformation via `Juno::Transformer` proxy
16
16
  * Value compression via `Juno::Transformer` proxy (Zlib, Snappy, QuickLZ, LZO)
17
- * Expiration for all stores (Added via proxy if not supported natively)
18
- * Integration with Rack as session store and Rack-Cache
17
+ * Expiration for all stores (Added via proxy `Juno::Expires` if not supported natively)
18
+ * Integration with [Rack](http://rack.github.com/) as cookie and session store and [Rack-Cache](https://github.com/rtomayko/rack-cache)
19
+
20
+ Juno is tested thoroughly using [Travis-CI](http://travis-ci.org/minad/juno).
19
21
 
20
22
  Supported backends
21
23
  ------------------
@@ -23,44 +25,40 @@ Supported backends
23
25
  Out of the box, it supports the following backends:
24
26
 
25
27
  * Memory:
26
- * In-memory store (:Memory)
27
- * LRU hash (:LRUHash)
28
- * LocalMemCache (:LocalMemCache)
29
- * Memcached store (:Memcached, :MemcachedNative and :MemcachedDalli)
28
+ * In-memory store (`:Memory`)
29
+ * LRU hash (`:LRUHash`)
30
+ * LocalMemCache (`:LocalMemCache`)
31
+ * Memcached store (`:Memcached`, `:MemcachedNative` and `:MemcachedDalli`)
30
32
  * Relational Databases:
31
- * DataMapper (:DataMapper)
32
- * ActiveRecord (:ActiveRecord)
33
- * Sequel (:Sequel)
34
- * Sqlite3 (:Sqlite)
33
+ * DataMapper (`:DataMapper`)
34
+ * ActiveRecord (`:ActiveRecord`)
35
+ * Sequel (`:Sequel`)
36
+ * Sqlite3 (`:Sqlite`)
35
37
  * Filesystem:
36
- * PStore (:PStore)
37
- * YAML store (:YAML)
38
- * Filesystem directory store (:File)
39
- * Filesystem directory store which spreads files in subdirectories using md5 hash (:HashFile)
38
+ * PStore (`:PStore`)
39
+ * YAML store (`:YAML`)
40
+ * Filesystem directory store (`:File`)
41
+ * Filesystem directory store which spreads files in subdirectories using md5 hash (`:HashFile`)
40
42
  * Key/value databases:
41
- * Berkeley DB (:DBM)
42
- * GDBM (:GDBM)
43
- * SDBM (:SDBM)
44
- * Redis (:Redis)
45
- * Riak (:Riak)
46
- * TokyoCabinet (:TokyoCabinet)
47
- * Cassandra (:Cassandra)
43
+ * Berkeley DB (`:DBM`)
44
+ * GDBM (`:GDBM`)
45
+ * SDBM (`:SDBM`)
46
+ * Redis (`:Redis`)
47
+ * Riak (`:Riak`)
48
+ * TokyoCabinet (`:TokyoCabinet`)
49
+ * Cassandra (`:Cassandra`)
50
+ * LevelDB (`:LevelDB`)
48
51
  * Document databases:
49
- * CouchDB (:Couch)
50
- * MongoDB (:Mongo)
52
+ * CouchDB (`:Couch`)
53
+ * MongoDB (`:Mongo`)
51
54
  * Other
52
- * Fog cloud storage which supports Amazon S3, Rackspace, etc. (:Fog)
53
- * Storage which doesn't store anything (:Null)
54
-
55
- Supported serializers:
55
+ * Fog cloud storage which supports Amazon S3, Rackspace, etc. (`:Fog`)
56
+ * Storage which doesn't store anything (`:Null`)
56
57
 
57
- * Marshal
58
- * YAML
59
- * JSON (via multi_json)
60
- * MessagePack
61
- * BSON
62
- * Ox
63
- * BERT
58
+ Some of the backends are not exactly based on key/value stores, e.g. the relational ones. These
59
+ are useful if you already use the corresponding backend in your application. You get a key/value
60
+ store for free then without installing any additional services and you still have the possibility
61
+ to upgrade to a real key/value store.
64
62
 
65
63
  Proxies
66
64
  -------
@@ -69,16 +67,45 @@ In addition it supports proxies (Similar to [Rack middlewares](http://rack.githu
69
67
  add additional features to storage backends:
70
68
 
71
69
  * `Juno::Proxy` proxy base class
72
- * `Juno::Expires` to add expiration support to stores which don't support it natively
73
- * `Juno::Stack` to stack multiple stores (Read returns result from first where the key is found, writes go to all stores)
74
- * `Juno::Transformer` transforms keys and values (Marshal, YAML, JSON, Base64, MD5, ...)
75
- * `Juno::Cache` combine two stores, one as backend and one as cache (e.g. Juno::Adapters::File + Juno::Adapters::Memory)
76
- * `Juno::Lock` to make store thread safe
77
- * `Juno::Logger` to log database accesses
70
+ * `Juno::Expires` to add expiration support to stores which don't support it natively. Add it in the builder using `use :Expires`.
71
+ * `Juno::Stack` to stack multiple stores (Read returns result from first where the key is found, writes go to all stores). Add it in the builder using `use :Stack`.
72
+ * `Juno::Transformer` transforms keys and values (Marshal, YAML, JSON, Base64, MD5, ...). Add it in the builder using `use :Transformer`.
73
+ * `Juno::Cache` combine two stores, one as backend and one as cache (e.g. `Juno::Adapters::File` + `Juno::Adapters::Memory`). Add it in the builder using `use :Cache`.
74
+ * `Juno::Lock` to make store thread safe. Add it in the builder using `use :Lock`.
75
+ * `Juno::Logger` to log database accesses. Add it in the builder using `use :Logger`.
78
76
 
79
77
  The Juno API is purposely extremely similar to the Hash API. In order so support an
80
78
  identical API across stores, it does not support iteration or partial matches.
81
79
 
80
+ Supported serializers and compressors (`Juno::Transformer`)
81
+ -----------------------------------------------------------
82
+
83
+ Supported serializers:
84
+
85
+ * BEncode (`:bencode`)
86
+ * BERT (`:bert`)
87
+ * BSON (`:bson`)
88
+ * JSON (`:json`)
89
+ * Marshal (`:marshal`)
90
+ * MessagePack (`:msgpack`)
91
+ * Ox (`:ox`)
92
+ * TNetStrings (`:tnet`)
93
+ * YAML (`:yaml`)
94
+
95
+ Supported value compressors:
96
+
97
+ * LZMA (`:lzma`)
98
+ * LZO (`:lzo`)
99
+ * Snappy (`:snappy`)
100
+ * QuickLZ (`:quicklz`)
101
+ * Zlib (`:zlib`)
102
+
103
+ Special transformers:
104
+
105
+ * Digests (MD5, Shas, ...)
106
+ * Add prefix to keys (`:prefix`)
107
+ * HMAC to verify values (`:hmac`, useful for `Rack::JunoCookies`)
108
+
82
109
  Links
83
110
  -----
84
111
 
@@ -175,9 +202,9 @@ end
175
202
  Framework Integration
176
203
  ---------------------
177
204
 
178
- Inspired by [redis-store](https://github.com/jodosha/redis-store) there exist integration classes for Rack and Rack-Cache.
205
+ Inspired by [redis-store](https://github.com/jodosha/redis-store) there exist integration classes for [Rack](http://rack.github.com/) and [Rack-Cache](https://github.com/rtomayko/rack-cache).
179
206
 
180
- Use Juno as a Rack session store:
207
+ Use Juno as a [Rack](http://rack.github.com/) session store:
181
208
 
182
209
  ~~~ ruby
183
210
  require 'rack/session/juno'
@@ -191,7 +218,7 @@ use Rack::Session::Juno do
191
218
  end
192
219
  ~~~
193
220
 
194
- Use Juno as a Rack-Cache store:
221
+ Use Juno as a [Rack-Cache](https://github.com/rtomayko/rack-cache) store:
195
222
 
196
223
  ~~~ ruby
197
224
  require 'rack/cache/juno'
@@ -210,20 +237,23 @@ use Rack::Cache,
210
237
  :entity_store => 'juno://named_entitystore'
211
238
  ~~~
212
239
 
213
- Use Juno to store cookies in Rack:
240
+ Use Juno to store cookies in [Rack](http://rack.github.com/). It uses the `Juno::Adapters::Cookie`. You might
241
+ wonder what the purpose of this store or Rack middleware is: It makes it possible
242
+ to use all the transformers on the cookies (e.g. `:prefix`, `:marshal` and `:hmac` for value verification).
214
243
 
215
244
  ~~~ ruby
216
245
  require 'rack/juno_cookies'
217
246
 
218
247
  use Rack::JunoCookies, :domain => 'example.com', :path => '/path'
219
- run lambda { |env|
248
+ run lambda do |env|
220
249
  req = Rack::Request.new(env)
221
- req.cookies #=> is now a Juno store!!
250
+ req.cookies #=> is now a Juno store!
251
+ env['rack.request.cookie_hash'] #=> is now a Juno store!
222
252
  req.cookies['key'] #=> retrieves 'key'
223
253
  req.cookies['key'] = 'value' #=> sets 'key'
224
254
  req.cookies.delete('key') #=> removes 'key'
225
- [200,{},[]]
226
- }
255
+ [200, {}, []]
256
+ end
227
257
  ~~~
228
258
 
229
259
  Alternatives
data/SPEC.md CHANGED
@@ -65,20 +65,7 @@ The following methods may all take an additional Hash as a final argument. This
65
65
 
66
66
  In the case of methods with optional arguments, the Hash MUST be provided as the final argument. Keys in this Hash MUST be Symbols.
67
67
 
68
- # Key Equality
69
-
70
- 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.
71
-
72
- # Storage and Serialization
73
-
74
- In a Juno-compliant adapter, any Ruby object that can be serialized using Ruby's marshalling system may be used for keys or values.
75
-
76
- 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.
77
-
78
68
  # Atomicity
79
69
 
80
70
  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.
81
71
 
82
- # Expiry
83
-
84
- 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.
data/lib/juno.rb CHANGED
@@ -19,6 +19,7 @@ module Juno
19
19
  autoload :File, 'juno/adapters/file'
20
20
  autoload :Fog, 'juno/adapters/fog'
21
21
  autoload :GDBM, 'juno/adapters/gdbm'
22
+ autoload :LevelDB, 'juno/adapters/leveldb'
22
23
  autoload :LocalMemCache, 'juno/adapters/localmemcache'
23
24
  autoload :LRUHash, 'juno/adapters/lruhash'
24
25
  autoload :Memcached, 'juno/adapters/memcached'
@@ -73,7 +74,7 @@ module Juno
73
74
  transformer = { :key => [key_serializer], :value => [value_serializer], :prefix => options.delete(:prefix) }
74
75
  transformer[:key] << :prefix if transformer[:prefix]
75
76
  transformer[:value] << (Symbol === compress ? compress : :zlib) if compress
76
- raise 'Name must be Symbol' unless Symbol === name
77
+ raise ArgumentError, 'Name must be Symbol' unless Symbol === name
77
78
  case name
78
79
  when :Sequel, :ActiveRecord, :Couch
79
80
  # Sequel accept only base64 keys and values
@@ -13,42 +13,66 @@ module Juno
13
13
  # @param [Hash] options
14
14
  #
15
15
  # Options:
16
- # * :keyspace - Cassandra keyspace (default Juno)
17
- # * :column_family - Cassandra column family (default :Juno)
16
+ # * :keyspace - Cassandra keyspace (default 'juno')
17
+ # * :column_family - Cassandra column family (default 'juno')
18
18
  # * :host - Server host name (default 127.0.0.1)
19
19
  # * :port - Server port (default 9160)
20
20
  def initialize(options = {})
21
- options[:keyspace] ||= 'Juno'
22
- options[:host] ||= '127.0.0.1'
23
- options[:port] ||= 9160
24
- @column_family = options[:column_family] || :Juno
25
- @client = ::Cassandra.new(options[:keyspace], "#{options[:host]}:#{options[:port]}")
21
+ options[:host] ||= '127.0.0.1'
22
+ options[:port] ||= 9160
23
+ keyspace = (options[:keyspace] ||= 'juno')
24
+ @cf = (options[:column_family] || 'juno').to_sym
25
+ @client = ::Cassandra.new('system', "#{options[:host]}:#{options[:port]}")
26
+ unless @client.keyspaces.include?(keyspace)
27
+ cf_def = ::Cassandra::ColumnFamily.new(:keyspace => keyspace, :name => @cf.to_s)
28
+ ks_def = ::Cassandra::Keyspace.new(:name => keyspace,
29
+ :strategy_class => 'SimpleStrategy',
30
+ :strategy_options => { 'replication_factor' => '1' },
31
+ :replication_factor => 1,
32
+ :cf_defs => [cf_def])
33
+ # Wait for keyspace to be created (issue #24)
34
+ 10.times do
35
+ begin
36
+ @client.add_keyspace(ks_def)
37
+ rescue Exception => ex
38
+ puts "Cassandra: #{ex.message}"
39
+ end
40
+ break if @client.keyspaces.include?(keyspace)
41
+ sleep 0.1
42
+ end
43
+ end
44
+ @client.keyspace = keyspace
26
45
  end
27
46
 
28
47
  def key?(key, options = {})
29
- @client.exists?(@column_family, key)
48
+ @client.exists?(@cf, key)
30
49
  end
31
50
 
32
51
  def load(key, options = {})
33
- value = @client.get(@column_family, key)
34
- value ? value['value'] : nil
52
+ value = @client.get(@cf, key)
53
+ if value
54
+ if options.include?(:expires)
55
+ store(key, value['value'], options)
56
+ else
57
+ value['value']
58
+ end
59
+ end
35
60
  end
36
61
 
37
62
  def delete(key, options = {})
38
63
  if value = load(key, options)
39
- @client.remove(@column_family, key)
64
+ @client.remove(@cf, key)
40
65
  value
41
66
  end
42
67
  end
43
68
 
44
69
  def store(key, value, options = {})
45
- @client.insert(@column_family, key,
46
- {'value' => value}, :ttl => options[:expires])
70
+ @client.insert(@cf, key, {'value' => value}, :ttl => options[:expires])
47
71
  value
48
72
  end
49
73
 
50
74
  def clear(options = {})
51
- @client.each_key(@column_family) do |key|
75
+ @client.each_key(@cf) do |key|
52
76
  delete(key)
53
77
  end
54
78
  self
@@ -12,7 +12,7 @@ module Juno
12
12
  # Options:
13
13
  # * :db - Couch database
14
14
  def initialize(options = {})
15
- raise 'Option :db is required' unless options[:db]
15
+ raise ArgumentError, 'Option :db is required' unless options[:db]
16
16
  @db = ::CouchRest.database!(options[:db])
17
17
  end
18
18
 
@@ -23,13 +23,13 @@ module Juno
23
23
  end
24
24
 
25
25
  def load(key, options = {})
26
- @db.get(key)['data']
26
+ @db.get(key)['value']
27
27
  rescue RestClient::ResourceNotFound
28
28
  nil
29
29
  end
30
30
 
31
31
  def store(key, value, options = {})
32
- doc = {'_id' => key, 'data' => value}
32
+ doc = {'_id' => key, 'value' => value}
33
33
  begin
34
34
  doc['_rev'] = @db.get(key)['_rev']
35
35
  rescue RestClient::ResourceNotFound
@@ -43,7 +43,7 @@ module Juno
43
43
  def delete(key, options = {})
44
44
  value = @db.get(key)
45
45
  @db.delete_doc('_id' => value['_id'], '_rev' => value['_rev'])
46
- value['data']
46
+ value['value']
47
47
  rescue RestClient::ResourceNotFound
48
48
  nil
49
49
  end
@@ -21,7 +21,7 @@ module Juno
21
21
  # * :repository - Repository name (default :juno)
22
22
  # * :table - Table name (default :juno)
23
23
  def initialize(options = {})
24
- raise 'Option :setup is required' unless options[:setup]
24
+ raise ArgumentError, 'Option :setup is required' unless options[:setup]
25
25
  @repository = options.delete(:repository) || :juno
26
26
  Store.storage_names[@repository] = (options.delete(:table) || :juno).to_s
27
27
  ::DataMapper.setup(@repository, options[:setup])
@@ -12,7 +12,7 @@ module Juno
12
12
  # Options:
13
13
  # * :file - Database file
14
14
  def initialize(options = {})
15
- raise 'Option :file is required' unless options[:file]
15
+ raise ArgumentError, 'Option :file is required' unless options[:file]
16
16
  @memory = ::DBM.new(options[:file])
17
17
  end
18
18
 
@@ -6,9 +6,9 @@ module Juno
6
6
  # @api public
7
7
  class File < Base
8
8
  def initialize(options = {})
9
- raise 'Option :dir is required' unless @dir = options[:dir]
9
+ raise ArgumentError, 'Option :dir is required' unless @dir = options[:dir]
10
10
  FileUtils.mkpath(@dir)
11
- raise "#{@dir} is not a dir" unless ::File.directory?(@dir)
11
+ raise "#{@dir} is not a directory" unless ::File.directory?(@dir)
12
12
  end
13
13
 
14
14
  def key?(key, options = {})
@@ -13,7 +13,7 @@ module Juno
13
13
  # * :dir - Fog directory
14
14
  # * Other options passed to Fog::Storage#new
15
15
  def initialize(options = {})
16
- raise 'Option :dir is required' unless dir = options.delete(:dir)
16
+ raise ArgumentError, 'Option :dir is required' unless dir = options.delete(:dir)
17
17
  storage = ::Fog::Storage.new(options)
18
18
  @directory = storage.directories.create(:key => dir)
19
19
  end
@@ -12,7 +12,7 @@ module Juno
12
12
  # Options:
13
13
  # * :file - Database file
14
14
  def initialize(options = {})
15
- raise 'Option :file is required' unless options[:file]
15
+ raise ArgumentError, 'Option :file is required' unless options[:file]
16
16
  @memory = ::GDBM.new(options[:file])
17
17
  end
18
18
 
@@ -0,0 +1,35 @@
1
+ require 'leveldb'
2
+
3
+ module Juno
4
+ module Adapters
5
+ # LevelDB backend
6
+ # @api public
7
+ class LevelDB < Memory
8
+ # Constructor
9
+ #
10
+ # @param [Hash] options
11
+ #
12
+ # Options:
13
+ # * :dir - Database path
14
+ # * All other options passed to LevelDB::DB#new
15
+ def initialize(options = {})
16
+ raise ArgumentError, 'Option :dir is required' unless options[:dir]
17
+ @memory = ::LevelDB::DB.new(options[:dir])
18
+ end
19
+
20
+ def key?(key, options = {})
21
+ @memory.includes?(key)
22
+ end
23
+
24
+ def clear(options = {})
25
+ @memory.each {|k,v| delete(k, options) }
26
+ self
27
+ end
28
+
29
+ def close
30
+ @memory.close
31
+ nil
32
+ end
33
+ end
34
+ end
35
+ end