juno 0.2.0 → 0.2.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.
Files changed (41) hide show
  1. data/Gemfile +3 -2
  2. data/README.md +6 -1
  3. data/lib/juno.rb +20 -28
  4. data/lib/juno/adapters/activerecord.rb +24 -17
  5. data/lib/juno/adapters/couch.rb +6 -1
  6. data/lib/juno/adapters/datamapper.rb +7 -5
  7. data/lib/juno/adapters/sequel.rb +14 -7
  8. data/lib/juno/adapters/sqlite.rb +4 -4
  9. data/lib/juno/transformer.rb +14 -19
  10. data/lib/juno/version.rb +1 -1
  11. data/spec/adapter_activerecord_spec.rb +4 -4
  12. data/spec/adapter_datamapper_spec.rb +6 -6
  13. data/spec/adapter_sequel_spec.rb +2 -2
  14. data/spec/adapter_sqlite_spec.rb +2 -2
  15. data/spec/generate.rb +123 -18
  16. data/spec/junospecs.rb +63 -0
  17. data/spec/simpl_memory_with_expires_spec.rb +53 -0
  18. data/spec/simple_activerecord_spec.rb +2 -2
  19. data/spec/simple_activerecord_with_expires_spec.rb +53 -0
  20. data/spec/simple_couch_with_expires_spec.rb +53 -0
  21. data/spec/simple_datamapper_spec.rb +2 -2
  22. data/spec/simple_datamapper_with_expires_spec.rb +55 -0
  23. data/spec/simple_datamapper_with_repository_spec.rb +2 -2
  24. data/spec/simple_dbm_with_expires_spec.rb +53 -0
  25. data/spec/simple_file_with_expires_spec.rb +53 -0
  26. data/spec/simple_fog_with_expires_spec.rb +63 -0
  27. data/spec/simple_gdbm_with_expires_spec.rb +53 -0
  28. data/spec/simple_hashfile_with_expires_spec.rb +53 -0
  29. data/spec/simple_localmemcache_with_expires_spec.rb +53 -0
  30. data/spec/simple_memory_with_expires_spec.rb +53 -0
  31. data/spec/simple_mongo_with_expires_spec.rb +53 -0
  32. data/spec/simple_pstore_with_expires_spec.rb +53 -0
  33. data/spec/simple_riak_with_expires_spec.rb +57 -0
  34. data/spec/simple_sdbm_with_expires_spec.rb +53 -0
  35. data/spec/simple_sequel_spec.rb +2 -2
  36. data/spec/simple_sequel_with_expires_spec.rb +53 -0
  37. data/spec/simple_sqlite_spec.rb +2 -2
  38. data/spec/simple_sqlite_with_expires_spec.rb +53 -0
  39. data/spec/simple_tokyocabinet_with_expires_spec.rb +53 -0
  40. data/spec/simple_yaml_with_expires_spec.rb +53 -0
  41. metadata +40 -2
data/Gemfile CHANGED
@@ -8,13 +8,13 @@ gem 'parallel_tests'
8
8
 
9
9
  # Serializer
10
10
  #gem 'tnetstring'
11
- gem 'msgpack'
12
11
  gem 'bson'
13
12
  gem 'multi_json'
14
13
  gem 'json' # Ripple/Riak needs json
15
14
 
16
15
  # Backends
17
- gem 'datamapper'
16
+ gem 'dm-core'
17
+ gem 'dm-migrations'
18
18
  gem 'dm-sqlite-adapter'
19
19
  gem 'fog'
20
20
  gem 'activerecord'
@@ -30,6 +30,7 @@ if defined?(JRUBY_VERSION)
30
30
  gem 'activerecord-jdbc-adapter'
31
31
  gem 'activerecord-jdbcsqlite3-adapter'
32
32
  else
33
+ gem 'msgpack'
33
34
  gem 'tokyocabinet'
34
35
  gem 'memcached'
35
36
  gem 'sqlite3'
data/README.md CHANGED
@@ -106,6 +106,7 @@ store = Juno.build do
106
106
  # Memory backend
107
107
  adapter :Memory
108
108
  end
109
+ ~~~
109
110
 
110
111
  Expiration
111
112
  ----------
@@ -126,10 +127,14 @@ end
126
127
  You can add the expires feature to other backends using the Expires proxy:
127
128
 
128
129
  ~~~ ruby
130
+ # Using the :expires option
131
+ cache = Juno.new(:File, :dir => '...', :expires => true)
132
+
133
+ # or using the proxy...
129
134
  cache = Juno::Expires.new(Juno::Adapters::File.new(:dir => '...'))
130
135
  cache.store(key, value, :expires => 10)
131
136
 
132
- # Or using the builder...
137
+ # or using the builder...
133
138
  cache = Juno.build do
134
139
  use :Expires
135
140
  adapter :File, :dir => '...'
data/lib/juno.rb CHANGED
@@ -34,46 +34,38 @@ module Juno
34
34
  end
35
35
 
36
36
  def self.new(name, options = {})
37
+ expires = options.delete(:expires)
38
+ transformer = {:key => :marshal, :value => :marshal}
37
39
  raise 'Name must be Symbol' unless Symbol === name
38
40
  case name
39
- when :Sequel, :ActiveRecord, :Couch
41
+ when :Sequel, :ActiveRecord, :Couch, :Mongo
40
42
  # Sequel accept only base64 keys and values
41
43
  # FIXME: ActiveRecord and Couch should work only with :marshal but this
42
44
  # raises an error on 1.9
43
- build(options) do
44
- use :Transformer, :key => [:marshal, :base64], :value => [:marshal, :base64]
45
- adapter name
46
- end
45
+ # Mongo accepts only valid UTF-8 strings
46
+ transformer = {:key => [:marshal, :base64], :value => [:marshal, :base64]}
47
47
  when :Memcached, :MemcachedDalli, :MemcachedNative
48
- # Memcached accept only base64 keys
49
- build(options) do
50
- use :Transformer, :key => [:marshal, :base64], :value => :marshal
51
- adapter name
52
- end
48
+ # Memcached accept only base64 keys, expires already supported
49
+ expires = false
50
+ transformer = {:key => [:marshal, :base64], :value => :marshal}
53
51
  when :PStore, :YAML, :DataMapper, :Null
54
52
  # For PStore, YAML and DataMapper only the key has to be a string
55
- build(options) do
56
- use :Transformer, :key => :marshal
57
- adapter name
58
- end
53
+ transformer = {:key => :marshal}
59
54
  when :HashFile
60
55
  # Use spreading hashes
61
- build(options) do
62
- use :Transformer, :key => [:marshal, :md5, :spread], :value => :marshal
63
- adapter :File
64
- end
56
+ transformer = {:key => [:marshal, :md5, :spread], :value => :marshal}
57
+ name = :File
65
58
  when :File
66
59
  # Use escaping
67
- build(options) do
68
- use :Transformer, :key => [:marshal, :escape], :value => :marshal
69
- adapter :File
70
- end
71
- else
72
- # For all other stores marshal key and value
73
- build(options) do
74
- use :Transformer, :key => :marshal, :value => :marshal
75
- adapter name
76
- end
60
+ transformer = {:key => [:marshal, :escape], :value => :marshal}
61
+ when :Cassandra, :Redis
62
+ # Expires already supported
63
+ expires = false
64
+ end
65
+ build(options) do
66
+ use :Expires if expires
67
+ use :Transformer, transformer
68
+ adapter name
77
69
  end
78
70
  end
79
71
 
@@ -17,36 +17,43 @@ module Juno
17
17
  c
18
18
  end
19
19
  @table.establish_connection(options[:connection]) if options[:connection]
20
- @table.connection.create_table @table.table_name do |t|
21
- t.binary 'key', :primary => true
22
- t.binary 'value'
23
- end unless @table.table_exists?
20
+ unless @table.table_exists?
21
+ @table.connection.create_table(@table.table_name) do |t|
22
+ t.binary :k, :null => false
23
+ t.binary :v
24
+ end
25
+ @table.connection.add_index(@table.table_name, :k, :unique => true)
26
+ end
24
27
  end
25
28
 
26
29
  def key?(key, options = {})
27
- !!@table.find_by_key(key)
30
+ !!@table.find_by_k(key)
28
31
  end
29
32
 
30
33
  def load(key, options = {})
31
- record = @table.find_by_key(key)
32
- record ? record.value : nil
34
+ record = @table.find_by_k(key)
35
+ record ? record.v : nil
33
36
  end
34
37
 
35
38
  def delete(key, options = {})
36
- record = @table.find_by_key(key)
37
- if record
38
- value = record.value
39
- record.destroy
40
- value
39
+ @table.transaction do
40
+ record = @table.find_by_k(key)
41
+ if record
42
+ value = record.v
43
+ record.destroy
44
+ value
45
+ end
41
46
  end
42
47
  end
43
48
 
44
49
  def store(key, value, options = {})
45
- record = @table.find_by_key(key)
46
- record ||= @table.new(:key => key)
47
- record.value = value
48
- record.save!
49
- value
50
+ @table.transaction do
51
+ record = @table.find_by_k(key)
52
+ record ||= @table.new(:k => key)
53
+ record.v = value
54
+ record.save!
55
+ value
56
+ end
50
57
  end
51
58
 
52
59
  def clear(options = {})
@@ -20,7 +20,12 @@ module Juno
20
20
  end
21
21
 
22
22
  def store(key, value, options = {})
23
- @db.save_doc('_id' => key, 'data' => value)
23
+ doc = {'_id' => key, 'data' => value}
24
+ begin
25
+ doc['_rev'] = @db.get(key)['_rev']
26
+ rescue RestClient::ResourceNotFound
27
+ end
28
+ @db.save_doc(doc)
24
29
  value
25
30
  rescue RestClient::RequestFailed
26
31
  value
@@ -33,19 +33,21 @@ module Juno
33
33
  context do
34
34
  record = Store.get(key)
35
35
  if record
36
- record.update(key, value)
36
+ record.update(:k => key, :v => value)
37
37
  else
38
38
  Store.create(:k => key, :v => value)
39
39
  end
40
+ value
40
41
  end
41
- value
42
42
  end
43
43
 
44
44
  def delete(key, options = {})
45
45
  context do
46
- value = load(key, options)
47
- Store.all(:k => key).destroy!
48
- value
46
+ if record = Store.get(key)
47
+ value = record.v
48
+ record.destroy!
49
+ value
50
+ end
49
51
  end
50
52
  end
51
53
 
@@ -8,8 +8,7 @@ module Juno
8
8
  @table = options.delete(:table) || :juno
9
9
  @db = ::Sequel.connect(db, options)
10
10
  @db.create_table?(@table) do
11
- primary_key :k
12
- String :k
11
+ String :k, :null => false, :primary_key => true
13
12
  String :v
14
13
  end
15
14
  end
@@ -24,14 +23,22 @@ module Juno
24
23
  end
25
24
 
26
25
  def store(key, value, options = {})
27
- sequel_table.insert(:k => key, :v => value)
28
- value
26
+ @db.transaction do
27
+ if key?(key, options)
28
+ sequel_table.update(:k => key, :v => value)
29
+ else
30
+ sequel_table.insert(:k => key, :v => value)
31
+ end
32
+ value
33
+ end
29
34
  end
30
35
 
31
36
  def delete(key, options = {})
32
- if value = load(key, options)
33
- sequel_table.filter(:k => key).delete
34
- value
37
+ @db.transaction do
38
+ if value = load(key, options)
39
+ sequel_table.filter(:k => key).delete
40
+ value
41
+ end
35
42
  end
36
43
  end
37
44
 
@@ -7,10 +7,10 @@ module Juno
7
7
  raise 'No option :file specified' unless options[:file]
8
8
  table = options[:table] || 'juno'
9
9
  @db = ::SQLite3::Database.new(options[:file])
10
- @db.execute("create table if not exists #{table} (key string primary key, value string)")
11
- @select = @db.prepare("select value from #{table} where key = ?")
12
- @insert = @db.prepare("insert into #{table} values (?, ?)")
13
- @delete = @db.prepare("delete from #{table} where key = ?")
10
+ @db.execute("create table if not exists #{table} (k blob not null primary key, v blob)")
11
+ @select = @db.prepare("select v from #{table} where k = ?")
12
+ @insert = @db.prepare("insert or replace into #{table} values (?, ?)")
13
+ @delete = @db.prepare("delete from #{table} where k = ?")
14
14
  @clear = @db.prepare("delete from #{table}")
15
15
  end
16
16
 
@@ -1,11 +1,5 @@
1
1
  module Juno
2
2
  class Transformer < Proxy
3
- @classes = {}
4
-
5
- class << self
6
- alias_method :original_new, :new
7
- end
8
-
9
3
  VALUE_TRANSFORMER = {
10
4
  :marshal => { :load => '::Marshal.load(VALUE)', :dump => '::Marshal.dump(VALUE)' },
11
5
  :base64 => { :load => "VALUE.unpack('m').first", :dump => "[VALUE].pack('m').strip" },
@@ -29,7 +23,11 @@ module Juno
29
23
  :msgpack => { :transform => '(TMP = KEY; String === TMP ? TMP : ::MessagePack.pack(TMP))', :require => 'msgpack' },
30
24
  }
31
25
 
26
+ @classes = {}
27
+
32
28
  class << self
29
+ alias_method :original_new, :new
30
+
33
31
  def compile(keys, values)
34
32
  tmp, key = 0, 'key'
35
33
  keys.each do |tn|
@@ -39,20 +37,8 @@ module Juno
39
37
  tmp += 1
40
38
  end
41
39
 
42
- dumper = 'value'
43
- values.each do |tn|
44
- raise "Unknown value transformer #{tn}" unless t = VALUE_TRANSFORMER[tn]
45
- require t[:require] if t[:require]
46
- dumper = t[:dump].gsub('VALUE', dumper)
47
- end
48
-
49
- loader = 'value'
50
- values.reverse.each do |t|
51
- loader = VALUE_TRANSFORMER[t][:load].gsub('VALUE', loader)
52
- end
53
-
54
40
  klass = Class.new(Transformer)
55
- if loader == 'value'
41
+ if values.empty?
56
42
  klass.class_eval <<-end_eval, __FILE__, __LINE__
57
43
  def key?(key, options = {})
58
44
  @adapter.key?(#{key}, options)
@@ -71,6 +57,14 @@ module Juno
71
57
  end
72
58
  end_eval
73
59
  else
60
+ dumper, loader = 'value', 'value'
61
+ values.each_index do |i|
62
+ raise "Unknown value transformer #{values[i]}" unless t = VALUE_TRANSFORMER[values[i]]
63
+ require t[:require] if t[:require]
64
+ dumper = t[:dump].gsub('VALUE', dumper)
65
+ loader = VALUE_TRANSFORMER[values[-i-1]][:load].gsub('VALUE', loader)
66
+ end
67
+
74
68
  klass.class_eval <<-end_eval, __FILE__, __LINE__
75
69
  def key?(key, options = {})
76
70
  @adapter.key?(#{key}, options)
@@ -98,6 +92,7 @@ module Juno
98
92
  def new(store, options = {})
99
93
  keys = [options[:key]].flatten.compact
100
94
  values = [options[:value]].flatten.compact
95
+ raise 'No option :key or :value specified' if keys.empty? && values.empty?
101
96
  klass = @classes["#{keys.join('-')}+#{values.join('-')}"] ||= compile(keys, values)
102
97
  klass.original_new(store, options)
103
98
  end
data/lib/juno/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Juno
2
- VERSION = '0.2.0'
2
+ VERSION = '0.2.1'
3
3
  end
@@ -2,11 +2,11 @@
2
2
  require 'helper'
3
3
 
4
4
  begin
5
- Juno::Adapters::ActiveRecord.new(:connection => { :adapter => (defined?(JRUBY_VERSION) ? 'jdbcsqlite3' : 'sqlite3'), :database => File.join(make_tempdir, 'adapter_activerecord.sqlite3') }).close
5
+ Juno::Adapters::ActiveRecord.new(:connection => { :adapter => (defined?(JRUBY_VERSION) ? 'jdbcsqlite3' : 'sqlite3'), :database => File.join(make_tempdir, 'adapter_activerecord') }).close
6
6
 
7
7
  describe "adapter_activerecord" do
8
8
  before do
9
- @store = Juno::Adapters::ActiveRecord.new(:connection => { :adapter => (defined?(JRUBY_VERSION) ? 'jdbcsqlite3' : 'sqlite3'), :database => File.join(make_tempdir, 'adapter_activerecord.sqlite3') })
9
+ @store = Juno::Adapters::ActiveRecord.new(:connection => { :adapter => (defined?(JRUBY_VERSION) ? 'jdbcsqlite3' : 'sqlite3'), :database => File.join(make_tempdir, 'adapter_activerecord') })
10
10
  @store.clear
11
11
  end
12
12
 
@@ -21,12 +21,12 @@ begin
21
21
  it 'updates an existing key/value' do
22
22
  @store['foo/bar'] = '1'
23
23
  @store['foo/bar'] = '2'
24
- records = @store.table.find :all, :conditions => { :key => 'foo/bar' }
24
+ records = @store.table.find :all, :conditions => { :k => 'foo/bar' }
25
25
  records.count.should == 1
26
26
  end
27
27
 
28
28
  it 'uses an existing connection' do
29
- ActiveRecord::Base.establish_connection :adapter => (defined?(JRUBY_VERSION) ? 'jdbcsqlite3' : 'sqlite3'), :database => File.join(make_tempdir, 'activerecord-existing.sqlite3')
29
+ ActiveRecord::Base.establish_connection :adapter => (defined?(JRUBY_VERSION) ? 'jdbcsqlite3' : 'sqlite3'), :database => File.join(make_tempdir, 'activerecord-existing')
30
30
 
31
31
  store = Juno::Adapters::ActiveRecord.new
32
32
  store.table.table_exists?.should == true
@@ -4,11 +4,11 @@ require 'helper'
4
4
  begin
5
5
  require 'dm-core'
6
6
  DataMapper.setup(:default, :adapter => :in_memory)
7
- Juno::Adapters::DataMapper.new(:setup => "sqlite3://#{make_tempdir}/adapter_datamapper.sqlite3").close
7
+ Juno::Adapters::DataMapper.new(:setup => "sqlite3://#{make_tempdir}/adapter_datamapper").close
8
8
 
9
9
  describe "adapter_datamapper" do
10
10
  before do
11
- @store = Juno::Adapters::DataMapper.new(:setup => "sqlite3://#{make_tempdir}/adapter_datamapper.sqlite3")
11
+ @store = Juno::Adapters::DataMapper.new(:setup => "sqlite3://#{make_tempdir}/adapter_datamapper")
12
12
  @store.clear
13
13
  end
14
14
 
@@ -24,10 +24,10 @@ begin
24
24
  it_should_behave_like 'store_stringkey_objectvalue'
25
25
 
26
26
  it 'does not cross contaminate when storing' do
27
- first = Juno::Adapters::DataMapper.new(:setup => "sqlite3://#{make_tempdir}/datamapper-first.sqlite3")
27
+ first = Juno::Adapters::DataMapper.new(:setup => "sqlite3://#{make_tempdir}/datamapper-first")
28
28
  first.clear
29
29
 
30
- second = Juno::Adapters::DataMapper.new(:repository => :sample, :setup => "sqlite3://#{make_tempdir}/datamapper-second.sqlite3")
30
+ second = Juno::Adapters::DataMapper.new(:repository => :sample, :setup => "sqlite3://#{make_tempdir}/datamapper-second")
31
31
  second.clear
32
32
 
33
33
  first['key'] = 'value'
@@ -38,10 +38,10 @@ begin
38
38
  end
39
39
 
40
40
  it 'does not cross contaminate when deleting' do
41
- first = Juno::Adapters::DataMapper.new(:setup => "sqlite3://#{make_tempdir}/datamapper-first.sqlite3")
41
+ first = Juno::Adapters::DataMapper.new(:setup => "sqlite3://#{make_tempdir}/datamapper-first")
42
42
  first.clear
43
43
 
44
- second = Juno::Adapters::DataMapper.new(:repository => :sample, :setup => "sqlite3://#{make_tempdir}/datamapper-second.sqlite3")
44
+ second = Juno::Adapters::DataMapper.new(:repository => :sample, :setup => "sqlite3://#{make_tempdir}/datamapper-second")
45
45
  second.clear
46
46
 
47
47
  first['key'] = 'value'
@@ -2,11 +2,11 @@
2
2
  require 'helper'
3
3
 
4
4
  begin
5
- Juno::Adapters::Sequel.new(:db => (defined?(JRUBY_VERSION) ? 'jdbc:sqlite:/' : 'sqlite:/')).close
5
+ Juno::Adapters::Sequel.new(:db => (defined?(JRUBY_VERSION) ? "jdbc:sqlite:" : "sqlite:") + File.join(make_tempdir, "adapter_sequel")).close
6
6
 
7
7
  describe "adapter_sequel" do
8
8
  before do
9
- @store = Juno::Adapters::Sequel.new(:db => (defined?(JRUBY_VERSION) ? 'jdbc:sqlite:/' : 'sqlite:/'))
9
+ @store = Juno::Adapters::Sequel.new(:db => (defined?(JRUBY_VERSION) ? "jdbc:sqlite:" : "sqlite:") + File.join(make_tempdir, "adapter_sequel"))
10
10
  @store.clear
11
11
  end
12
12
 
@@ -2,11 +2,11 @@
2
2
  require 'helper'
3
3
 
4
4
  begin
5
- Juno::Adapters::Sqlite.new(:file => ":memory:").close
5
+ Juno::Adapters::Sqlite.new(:file => File.join(make_tempdir, "adapter_sqlite")).close
6
6
 
7
7
  describe "adapter_sqlite" do
8
8
  before do
9
- @store = Juno::Adapters::Sqlite.new(:file => ":memory:")
9
+ @store = Juno::Adapters::Sqlite.new(:file => File.join(make_tempdir, "adapter_sqlite"))
10
10
  @store.clear
11
11
  end
12
12