juno 0.2.8 → 0.3.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.
@@ -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 = ::LocalMemCache.new(:filename => options[:file])
17
17
  end
18
18
 
@@ -29,7 +29,7 @@ module Juno
29
29
 
30
30
  def load(key, options = {})
31
31
  value = @collection.find_one('_id' => key)
32
- value ? value['data'].to_s : nil
32
+ value ? value['value'].to_s : nil
33
33
  end
34
34
 
35
35
  def delete(key, options = {})
@@ -40,7 +40,7 @@ module Juno
40
40
 
41
41
  def store(key, value, options = {})
42
42
  @collection.update({ '_id' => key },
43
- { '_id' => key, 'data' => ::BSON::Binary.new(value) },
43
+ { '_id' => key, 'value' => ::BSON::Binary.new(value) },
44
44
  { :upsert => true })
45
45
  value
46
46
  end
@@ -12,7 +12,7 @@ module Juno
12
12
  # Options:
13
13
  # * :file - PStore 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
  FileUtils.mkpath(::File.dirname(options[:file]))
17
17
  @pstore = new_store(options)
18
18
  end
@@ -14,9 +14,11 @@ module Juno
14
14
  #
15
15
  # Options:
16
16
  # * :bucket - Bucket name (default juno)
17
+ # * :content_type - Default content type (default application/octet-stream)
17
18
  # * All other options passed to Riak::Client#new
18
19
  def initialize(options = {})
19
20
  bucket = options.delete(:bucket) || 'juno'
21
+ @content_type = options.delete(:content_type) || 'application/octet-stream'
20
22
  @bucket = ::Riak::Client.new(options).bucket(bucket)
21
23
  end
22
24
 
@@ -38,7 +40,7 @@ module Juno
38
40
 
39
41
  def store(key, value, options = {})
40
42
  obj = ::Riak::RObject.new(@bucket, key)
41
- obj.content_type = 'application/octet-stream'
43
+ obj.content_type = options[:content_type] || @content_type
42
44
  obj.raw_data = value
43
45
  obj.store(options)
44
46
  value
@@ -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 = ::SDBM.new(options[:file])
17
17
  end
18
18
 
@@ -14,7 +14,7 @@ module Juno
14
14
  # * :table - Table name (default :juno)
15
15
  # * All other options passed to Sequel#connect
16
16
  def initialize(options = {})
17
- raise 'Option :db is required' unless db = options.delete(:db)
17
+ raise ArgumentError, 'Option :db is required' unless db = options.delete(:db)
18
18
  @table = options.delete(:table) || :juno
19
19
  @db = ::Sequel.connect(db, options)
20
20
  @db.create_table?(@table) do
@@ -13,7 +13,7 @@ module Juno
13
13
  # * :file - Database file
14
14
  # * :table - Table name (default juno)
15
15
  def initialize(options = {})
16
- raise 'Option :file is required' unless options[:file]
16
+ raise ArgumentError, 'Option :file is required' unless options[:file]
17
17
  table = options[:table] || 'juno'
18
18
  @db = ::SQLite3::Database.new(options[:file])
19
19
  @db.execute("create table if not exists #{table} (k blob not null primary key, v blob)")
@@ -14,7 +14,7 @@ module Juno
14
14
  # * :type - Database type (default :hdb, :bdb and :hdb possible)
15
15
  def initialize(options = {})
16
16
  file = options[:file]
17
- raise 'Option :file is required' unless options[:file]
17
+ raise ArgumentError, 'Option :file is required' unless options[:file]
18
18
  if options[:type] == :bdb
19
19
  @memory = ::TokyoCabinet::BDB.new
20
20
  @memory.open(file, ::TokyoCabinet::BDB::OWRITER | ::TokyoCabinet::BDB::OCREAT)
data/lib/juno/base.rb CHANGED
@@ -7,15 +7,27 @@ module Juno
7
7
  def close
8
8
  end
9
9
 
10
- # Fetch value with key. Return default if value is nil.
10
+ # Fetch value with key
11
+ #
12
+ # This is a overloaded method:
13
+ #
14
+ # * fetch(key, options = {}, &block) retrieve a key. if the key is not available, execute the
15
+ # block and return its return value.
16
+ #
17
+ # * fetch(key, value, options = {}) retrieve a key. if the key is not available, return the value.
11
18
  #
12
19
  # @param [Object] key
13
- # @param [Object] value Default value
20
+ # @param [Object] default Default value
14
21
  # @param [Hash] options
15
22
  # @return [Object] value from store
16
23
  # @api public
17
- def fetch(key, value = nil, options = {})
18
- load(key, options) || (block_given? && yield(key)) || value
24
+ def fetch(key, default = nil, options = nil)
25
+ if block_given?
26
+ raise ArgumentError, 'Only one argument accepted if block is given' if options
27
+ load(key, default || {}) || yield(key)
28
+ else
29
+ load(key, options || {}) || default
30
+ end
19
31
  end
20
32
 
21
33
  # Fetch value with key. Return nil if the key doesn't exist
data/lib/juno/builder.rb CHANGED
@@ -12,7 +12,7 @@ module Juno
12
12
  end
13
13
 
14
14
  def initialize(&block)
15
- raise 'No block given' unless block_given?
15
+ raise ArgumentError, 'No block given' unless block_given?
16
16
  @proxies = []
17
17
  instance_eval(&block)
18
18
  end
@@ -23,7 +23,7 @@ module Juno
23
23
  # @param [Hash] options Options hash
24
24
  def use(proxy, options = {}, &block)
25
25
  proxy = Juno.const_get(proxy) if Symbol === proxy
26
- raise 'You must give a Class or a Symbol' unless Class === proxy
26
+ raise ArgumentError, 'You must give a Class or a Symbol' unless Class === proxy
27
27
  @proxies.unshift [proxy, options, block]
28
28
  nil
29
29
  end
data/lib/juno/cache.rb CHANGED
@@ -20,13 +20,13 @@ module Juno
20
20
 
21
21
  def backend(store = nil, &block)
22
22
  raise 'Backend already set' if @backend
23
- raise 'Only argument or block allowed' if store && block
23
+ raise ArgumentError, 'Only argument or block allowed' if store && block
24
24
  @backend = store || Juno.build(&block)
25
25
  end
26
26
 
27
27
  def cache(store = nil, &block)
28
28
  raise 'Cache already set' if @cache
29
- raise 'Only argument or block allowed' if store && block
29
+ raise ArgumentError, 'Only argument or block allowed' if store && block
30
30
  @cache = store || Juno.build(&block)
31
31
  end
32
32
 
data/lib/juno/stack.rb CHANGED
@@ -23,7 +23,7 @@ module Juno
23
23
  end
24
24
 
25
25
  def add(store = nil, &block)
26
- raise 'Only argument or block allowed' if store && block
26
+ raise ArgumentError, 'Only argument or block allowed' if store && block
27
27
  @stack << (store || Juno.build(&block))
28
28
  nil
29
29
  end
@@ -11,7 +11,7 @@ module Juno
11
11
  class Transformer < Proxy
12
12
  def initialize(adapter, options = {})
13
13
  super
14
- @prefix = options[:prefix]
14
+ @prefix, @secret = options[:prefix], options[:secret]
15
15
  end
16
16
 
17
17
  class << self
@@ -26,14 +26,14 @@ module Juno
26
26
  # * :key - List of key transformers in the order in which they should be applied
27
27
  # * :value - List of value transformers in the order in which they should be applied
28
28
  # * :prefix - Prefix string for key namespacing (Used by the :prefix key transformer)
29
+ # * :secret - HMAC secret to verify values (Used by the :hmac value transformer)
29
30
  def new(adapter, options = {})
30
31
  keys = [options[:key]].flatten.compact
31
32
  values = [options[:value]].flatten.compact
32
- raise 'Option :key or :value is required' if keys.empty? && values.empty?
33
- raise 'Option :prefix is required' if keys.include?(:prefix) && !options[:prefix]
34
- name = ''
35
- name << keys.map(&:to_s).map(&:capitalize).join << 'Key' unless keys.empty?
36
- name << values.map(&:to_s).map(&:capitalize).join << 'Value' unless values.empty?
33
+ raise ArgumentError, 'Option :key or :value is required' if keys.empty? && values.empty?
34
+ raise ArgumentError, 'Option :prefix is required for :prefix key transformer' if keys.include?(:prefix) && !options[:prefix]
35
+ raise ArgumentError, 'Option :secret is required for :hmac value transformer' if values.include?(:hmac) && !options[:secret]
36
+ name = class_name(keys, values)
37
37
  const_set(name, compile(keys, values)) unless const_defined?(name)
38
38
  const_get(name).original_new(adapter, options)
39
39
  end
@@ -41,12 +41,12 @@ module Juno
41
41
  private
42
42
 
43
43
  def compile(keys, values)
44
- raise 'Invalid key transformer chain' if KEY_TRANSFORMER !~ keys.map(&:inspect).join
45
- raise 'Invalid value transformer chain' if VALUE_TRANSFORMER !~ values.map(&:inspect).join
44
+ raise ArgumentError, 'Invalid key transformer chain' if KEY_TRANSFORMER !~ keys.map(&:inspect).join
45
+ raise ArgumentError, 'Invalid value transformer chain' if VALUE_TRANSFORMER !~ values.map(&:inspect).join
46
46
 
47
47
  key = compile_transformer(keys, 'key')
48
48
 
49
- klass = Class.new(Transformer)
49
+ klass = Class.new(self)
50
50
  if values.empty?
51
51
  klass.class_eval <<-end_eval, __FILE__, __LINE__
52
52
  def key?(key, options = {})
@@ -97,7 +97,7 @@ module Juno
97
97
  # Returned compiled transformer code string
98
98
  def compile_transformer(transformer, var, i = 2)
99
99
  transformer.inject(var) do |value, name|
100
- raise "Unknown transformer #{name}" unless t = TRANSFORMER[name]
100
+ raise ArgumentError, "Unknown transformer #{name}" unless t = TRANSFORMER[name]
101
101
  require t[3] if t[3]
102
102
  code = t[i]
103
103
  if t[0] == :serialize && var == 'key'
@@ -107,6 +107,11 @@ module Juno
107
107
  end
108
108
  end
109
109
  end
110
+
111
+ def class_name(keys, values)
112
+ (keys.empty? ? '' : keys.map(&:to_s).map(&:capitalize).join << 'Key') <<
113
+ (values.empty? ? '' : values.map(&:to_s).map(&:capitalize).join << 'Value')
114
+ end
110
115
  end
111
116
 
112
117
  # Available key/value transformers
@@ -128,8 +133,8 @@ module Juno
128
133
  :zlib => [ :compress, '::Zlib::Inflate.inflate(value)', '::Zlib::Deflate.deflate(value)', 'zlib' ],
129
134
  :base64 => [ :encode, "value.unpack('m').first", "[value].pack('m').strip" ],
130
135
  :uuencode => [ :encode, "value.unpack('u').first", "[value].pack('u').strip" ],
131
- :escape => [ :encode, "value.gsub(/((?:%[0-9a-fA-F]{2})+)/){ [$1.delete('%')].pack('H*') }",
132
- "value.gsub(/[^a-zA-Z0-9_-]+/){ '%' + $&.unpack('H2' * $&.bytesize).join('%').upcase }" ],
136
+ :escape => [ :encode, 'Escape.unescape(value)', 'Escape.escape(value)' ],
137
+ :hmac => [ :hmac, 'HMAC.verify(value, @secret)', 'HMAC.sign(value, @secret)' , 'openssl' ],
133
138
  :md5 => [ :digest, nil, '::Digest::MD5.hexdigest(value)', 'digest/md5' ],
134
139
  :rmd160 => [ :digest, nil, '::Digest::RMD160.hexdigest(value)', 'digest/rmd160' ],
135
140
  :sha1 => [ :digest, nil, '::Digest::SHA1.hexdigest(value)', 'digest/sha1' ],
@@ -141,9 +146,30 @@ module Juno
141
146
  }
142
147
 
143
148
  # Allowed value transformers (Read it like a regular expression!)
144
- VALUE_TRANSFORMER = compile_validator('serialize? compress? encode?')
149
+ VALUE_TRANSFORMER = compile_validator('serialize? compress? hmac? encode?')
145
150
 
146
151
  # Allowed key transformers (Read it like a regular expression!)
147
152
  KEY_TRANSFORMER = compile_validator('serialize? prefix? (encode | (digest spread?))?')
153
+
154
+ module Escape
155
+ def self.escape(value)
156
+ value.gsub(/[^a-zA-Z0-9_-]+/){ '%' + $&.unpack('H2' * $&.bytesize).join('%').upcase }
157
+ end
158
+
159
+ def self.unescape(value)
160
+ value.gsub(/((?:%[0-9a-fA-F]{2})+)/){ [$1.delete('%')].pack('H*') }
161
+ end
162
+ end
163
+
164
+ module HMAC
165
+ def self.verify(value, secret)
166
+ hash, value = value[0..31], value[32..-1]
167
+ value if hash == OpenSSL::HMAC.digest(OpenSSL::Digest::Digest.new('sha256'), secret, value)
168
+ end
169
+
170
+ def self.sign(value, secret)
171
+ OpenSSL::HMAC.digest(OpenSSL::Digest::Digest.new('sha256'), secret, value) << value
172
+ end
173
+ end
148
174
  end
149
175
  end
data/lib/juno/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  module Juno
2
2
  # Juno version number
3
3
  # @api public
4
- VERSION = '0.2.8'
4
+ VERSION = '0.3.0'
5
5
  end
@@ -9,7 +9,7 @@ module Rack
9
9
  # # Add Rack::JunoCookies somewhere in your rack stack
10
10
  # use Rack::JunoCookies
11
11
  #
12
- # run lambda{|env| [200,{},[]] }
12
+ # run lambda { |env| [200, {}, []] }
13
13
  # # But this doesn't do much
14
14
  #
15
15
  # @example config.ru
@@ -23,20 +23,21 @@ module Rack
23
23
  # adapter :Cookie
24
24
  # end
25
25
  #
26
- # run lambda { |env|
26
+ # run lambda do |env|
27
27
  # req = Rack::Request.new(env)
28
- # req.cookies #=> is now a Juno store!!
28
+ # req.cookies #=> is now a Juno store!
29
+ # env['rack.request.cookie_hash'] #=> is now a Juno store!
29
30
  # req.cookies['key'] #=> retrieves 'juno.key'
30
31
  # req.cookies['key'] = 'value' #=> sets 'juno.key'
31
32
  # req.cookies.delete('key') #=> removes 'juno.key'
32
- # [200,{},[]]
33
- # }
33
+ # [200, {}, []]
34
+ # end
34
35
  #
35
36
  class JunoCookies
36
37
  def initialize(app, options = {}, &block)
37
- @app = app
38
+ @app, @pool = app, []
38
39
  if block
39
- raise 'Use either block or options' unless options.empty?
40
+ raise ArgumentError, 'Use either block or options' unless options.empty?
40
41
  @builder = Juno::Builder.new(&block)
41
42
  else
42
43
  @builder = Juno::Builder.new { adapter :Cookie, options }
@@ -44,7 +45,7 @@ module Rack
44
45
  end
45
46
 
46
47
  def call(env)
47
- stores = @builder.build
48
+ stores = @pool.pop || @builder.build
48
49
  env['rack.request.cookie_hash'] = stores.last
49
50
  env['rack.request.cookie_string'] = env['HTTP_COOKIE']
50
51
  stores.first.reset(Rack::Utils.parse_query(env['HTTP_COOKIE']))
@@ -56,6 +57,7 @@ module Rack
56
57
  Rack::Utils.set_cookie_header!(headers, key, cookie)
57
58
  end
58
59
  end
60
+ @pool << stores
59
61
  [status, headers, body]
60
62
  end
61
63
  end
@@ -10,10 +10,10 @@ module Rack
10
10
  def initialize(app, options = {}, &block)
11
11
  super
12
12
  if block
13
- raise 'Use either block or option :store' if options[:store]
13
+ raise ArgumentError, 'Use either block or option :store' if options[:store]
14
14
  @pool = ::Juno.build(&block)
15
15
  else
16
- raise 'Option :store is required' unless @pool = options[:store]
16
+ raise ArgumentError, 'Option :store is required' unless @pool = options[:store]
17
17
  @pool = ::Juno.new(@pool, :expires => true) if Symbol === @pool
18
18
  end
19
19
  @mutex = Mutex.new
data/spec/generate.rb CHANGED
@@ -76,7 +76,8 @@ TESTS = {
76
76
  },
77
77
  'simple_cassandra' => {
78
78
  :store => :Cassandra,
79
- :specs => EXPIRES_SPECS
79
+ :options => ':keyspace => "simple_cassandra"',
80
+ :specs => EXPIRES_SPECS,
80
81
  },
81
82
  'simple_dbm' => {
82
83
  :store => :DBM,
@@ -105,6 +106,15 @@ TESTS = {
105
106
  :options => ':file => File.join(make_tempdir, "simple_sdbm_with_expires"), :expires => true',
106
107
  :specs => EXPIRES_SPECS
107
108
  },
109
+ 'simple_leveldb' => {
110
+ :store => :LevelDB,
111
+ :options => ':dir => File.join(make_tempdir, "simple_leveldb")'
112
+ },
113
+ 'simple_leveldb_with_expires' => {
114
+ :store => :LevelDB,
115
+ :options => ':dir => File.join(make_tempdir, "simple_leveldb_with_expires"), :expires => true',
116
+ :specs => EXPIRES_SPECS
117
+ },
108
118
  'simple_pstore' => {
109
119
  :store => :PStore,
110
120
  :options => ':file => File.join(make_tempdir, "simple_pstore")'
@@ -476,82 +486,89 @@ end},
476
486
  end},
477
487
  :specs => [:null, :store, :returndifferent]
478
488
  },
489
+ 'transformer_marshal_hmac' => {
490
+ :build => %{Juno.build do
491
+ use :Transformer, :key => :marshal, :value => [:marshal, :hmac], :secret => 'secret'
492
+ adapter :Memory
493
+ end},
494
+ :specs => SIMPLE_SPECS
495
+ },
479
496
  'transformer_marshal_base64' => {
480
497
  :build => %{Juno.build do
481
498
  use :Transformer, :key => [:marshal, :base64], :value => [:marshal, :base64]
482
499
  adapter :Memory
483
500
  end},
484
- :specs => [:null, :store, :returndifferent, :marshallable_key]
501
+ :specs => SIMPLE_SPECS
485
502
  },
486
503
  'transformer_marshal_prefix' => {
487
504
  :build => %{Juno.build do
488
505
  use :Transformer, :key => [:marshal, :prefix], :value => :marshal, :prefix => 'juno'
489
506
  adapter :Memory
490
507
  end},
491
- :specs => [:null, :store, :returndifferent, :marshallable_key]
508
+ :specs => SIMPLE_SPECS
492
509
  },
493
510
  'transformer_marshal_uuencode' => {
494
511
  :build => %{Juno.build do
495
512
  use :Transformer, :key => [:marshal, :uuencode], :value => [:marshal, :uuencode]
496
513
  adapter :Memory
497
514
  end},
498
- :specs => [:null, :store, :returndifferent, :marshallable_key]
515
+ :specs => SIMPLE_SPECS
499
516
  },
500
517
  'transformer_marshal_escape' => {
501
518
  :build => %{Juno.build do
502
519
  use :Transformer, :key => [:marshal, :escape], :value => :marshal
503
520
  adapter :Memory
504
521
  end},
505
- :specs => [:null, :store, :returndifferent, :marshallable_key]
522
+ :specs => SIMPLE_SPECS
506
523
  },
507
524
  'transformer_marshal_md5' => {
508
525
  :build => %{Juno.build do
509
526
  use :Transformer, :key => [:marshal, :md5], :value => :marshal
510
527
  adapter :Memory
511
528
  end},
512
- :specs => [:null, :store, :returndifferent, :marshallable_key]
529
+ :specs => SIMPLE_SPECS
513
530
  },
514
531
  'transformer_marshal_sha1' => {
515
532
  :build => %{Juno.build do
516
533
  use :Transformer, :key => [:marshal, :sha1], :value => :marshal
517
534
  adapter :Memory
518
535
  end},
519
- :specs => [:null, :store, :returndifferent, :marshallable_key]
536
+ :specs => SIMPLE_SPECS
520
537
  },
521
538
  'transformer_marshal_sha256' => {
522
539
  :build => %{Juno.build do
523
540
  use :Transformer, :key => [:marshal, :sha256], :value => :marshal
524
541
  adapter :Memory
525
542
  end},
526
- :specs => [:null, :store, :returndifferent, :marshallable_key]
543
+ :specs => SIMPLE_SPECS
527
544
  },
528
545
  'transformer_marshal_sha384' => {
529
546
  :build => %{Juno.build do
530
547
  use :Transformer, :key => [:marshal, :sha384], :value => :marshal
531
548
  adapter :Memory
532
549
  end},
533
- :specs => [:null, :store, :returndifferent, :marshallable_key]
550
+ :specs => SIMPLE_SPECS
534
551
  },
535
552
  'transformer_marshal_sha512' => {
536
553
  :build => %{Juno.build do
537
554
  use :Transformer, :key => [:marshal, :sha512], :value => :marshal
538
555
  adapter :Memory
539
556
  end},
540
- :specs => [:null, :store, :returndifferent, :marshallable_key]
557
+ :specs => SIMPLE_SPECS
541
558
  },
542
559
  'transformer_marshal_rmd160' => {
543
560
  :build => %{Juno.build do
544
561
  use :Transformer, :key => [:marshal, :rmd160], :value => :marshal
545
562
  adapter :Memory
546
563
  end},
547
- :specs => [:null, :store, :returndifferent, :marshallable_key]
564
+ :specs => SIMPLE_SPECS
548
565
  },
549
566
  'transformer_marshal_md5_spread' => {
550
567
  :build => %{Juno.build do
551
568
  use :Transformer, :key => [:marshal, :md5, :spread], :value => :marshal
552
569
  adapter :Memory
553
570
  end},
554
- :specs => [:null, :store, :returndifferent, :marshallable_key]
571
+ :specs => SIMPLE_SPECS
555
572
  },
556
573
  'adapter_activerecord' => {
557
574
  :build => "Juno::Adapters::ActiveRecord.new(:connection => { :adapter => (defined?(JRUBY_VERSION) ? 'jdbcsqlite3' : 'sqlite3'), :database => File.join(make_tempdir, 'adapter_activerecord') })",
@@ -573,7 +590,7 @@ end
573
590
  }
574
591
  },
575
592
  'adapter_cassandra' => {
576
- :build => "Juno::Adapters::Cassandra.new",
593
+ :build => "Juno::Adapters::Cassandra.new(:keyspace => 'adapter_cassandra')",
577
594
  :specs => ADAPTER_SPECS
578
595
  },
579
596
  'adapter_cookie' => {
@@ -692,6 +709,10 @@ end
692
709
  :build => 'Juno::Adapters::SDBM.new(:file => File.join(make_tempdir, "adapter_sdbm"))',
693
710
  :specs => ADAPTER_SPECS
694
711
  },
712
+ 'adapter_leveldb' => {
713
+ :build => 'Juno::Adapters::LevelDB.new(:dir => File.join(make_tempdir, "adapter_leveldb"))',
714
+ :specs => ADAPTER_SPECS
715
+ },
695
716
  'adapter_sequel' => {
696
717
  :build => 'Juno::Adapters::Sequel.new(:db => (defined?(JRUBY_VERSION) ? "jdbc:sqlite:" : "sqlite:") + File.join(make_tempdir, "adapter_sequel"))',
697
718
  :specs => ADAPTER_SPECS
@@ -775,7 +796,8 @@ end
775
796
  it 'should accept options' do
776
797
  store.key?(#{key1}, :option1 => 1).should == false
777
798
  store.load(#{key1}, :option2 => 2).should == nil
778
- store.fetch(#{key1}, nil, :option3 => 3).should == nil
799
+ store.fetch(#{key1}, 42, :option3 => 3).should == 42
800
+ store.fetch(#{key1}, :option3 => 3) { 42 }.should == 42
779
801
  store.delete(#{key1}, :option4 => 4).should == nil
780
802
  store.clear(:option5 => 5).should equal(store)
781
803
  store.store(#{key1}, #{val1}, :option6 => 6).should == #{val1}