benschwarz-merb-cache 1.0.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/LICENSE +20 -0
- data/README +224 -0
- data/Rakefile +17 -0
- data/lib/merb-cache.rb +15 -0
- data/lib/merb-cache/cache.rb +91 -0
- data/lib/merb-cache/cache_request.rb +48 -0
- data/lib/merb-cache/core_ext/enumerable.rb +9 -0
- data/lib/merb-cache/core_ext/hash.rb +21 -0
- data/lib/merb-cache/merb_ext/controller/class_methods.rb +244 -0
- data/lib/merb-cache/merb_ext/controller/instance_methods.rb +163 -0
- data/lib/merb-cache/stores/fundamental/abstract_store.rb +101 -0
- data/lib/merb-cache/stores/fundamental/file_store.rb +113 -0
- data/lib/merb-cache/stores/fundamental/memcached_store.rb +110 -0
- data/lib/merb-cache/stores/strategy/abstract_strategy_store.rb +119 -0
- data/lib/merb-cache/stores/strategy/action_store.rb +61 -0
- data/lib/merb-cache/stores/strategy/adhoc_store.rb +69 -0
- data/lib/merb-cache/stores/strategy/gzip_store.rb +63 -0
- data/lib/merb-cache/stores/strategy/mintcache_store.rb +75 -0
- data/lib/merb-cache/stores/strategy/page_store.rb +68 -0
- data/lib/merb-cache/stores/strategy/sha1_store.rb +62 -0
- data/spec/merb-cache/cache_request_spec.rb +78 -0
- data/spec/merb-cache/cache_spec.rb +88 -0
- data/spec/merb-cache/core_ext/enumerable_spec.rb +26 -0
- data/spec/merb-cache/core_ext/hash_spec.rb +51 -0
- data/spec/merb-cache/merb_ext/controller_spec.rb +5 -0
- data/spec/merb-cache/stores/fundamental/abstract_store_spec.rb +118 -0
- data/spec/merb-cache/stores/fundamental/file_store_spec.rb +205 -0
- data/spec/merb-cache/stores/fundamental/memcached_store_spec.rb +258 -0
- data/spec/merb-cache/stores/strategy/abstract_strategy_store_spec.rb +78 -0
- data/spec/merb-cache/stores/strategy/action_store_spec.rb +208 -0
- data/spec/merb-cache/stores/strategy/adhoc_store_spec.rb +227 -0
- data/spec/merb-cache/stores/strategy/gzip_store_spec.rb +68 -0
- data/spec/merb-cache/stores/strategy/mintcache_store_spec.rb +59 -0
- data/spec/merb-cache/stores/strategy/page_store_spec.rb +146 -0
- data/spec/merb-cache/stores/strategy/sha1_store_spec.rb +84 -0
- data/spec/spec_helper.rb +95 -0
- metadata +112 -0
@@ -0,0 +1,63 @@
|
|
1
|
+
# Pinched from Tobias Luetke's "cacheable" rails plugin (http://github.com/tobi/cacheable/tree/master)
|
2
|
+
require 'zlib'
|
3
|
+
require 'stringio'
|
4
|
+
|
5
|
+
module Merb::Cache
|
6
|
+
# Store that compresses cached data using GZip.
|
7
|
+
# Usually wraps other stores and good for caching of
|
8
|
+
# large pages.
|
9
|
+
class GzipStore < AbstractStrategyStore
|
10
|
+
def writable?(key, parameters = {}, conditions = {})
|
11
|
+
@stores.any? {|c| c.writable?(key, parameters, conditions)}
|
12
|
+
end
|
13
|
+
|
14
|
+
def read(key, parameters = {})
|
15
|
+
decompress(@stores.capture_first {|c| c.read(key, parameters)})
|
16
|
+
end
|
17
|
+
|
18
|
+
def write(key, data = nil, parameters = {}, conditions = {})
|
19
|
+
if writable?(key, parameters, conditions)
|
20
|
+
@stores.capture_first {|c| c.write(key, compress(data), parameters, conditions)}
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def write_all(key, data = nil, parameters = {}, conditions = {})
|
25
|
+
if writable?(key, parameters, conditions)
|
26
|
+
@stores.map {|c| c.write_all(key, compress(data), parameters, conditions)}.all?
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def fetch(key, parameters = {}, conditions = {}, &blk)
|
31
|
+
wrapper_blk = lambda { compress(blk.call) }
|
32
|
+
decompress(read(key, parameters) || @stores.capture_first {|s| s.fetch(key, parameters, conditions, &wrapper_blk)})
|
33
|
+
end
|
34
|
+
|
35
|
+
def exists?(key, parameters = {})
|
36
|
+
@stores.any? {|c| c.exists?(key, parameters)}
|
37
|
+
end
|
38
|
+
|
39
|
+
def delete(key, parameters = {})
|
40
|
+
@stores.map {|c| c.delete(key, parameters)}.any?
|
41
|
+
end
|
42
|
+
|
43
|
+
def delete_all!
|
44
|
+
@stores.map {|c| c.delete_all! }.all?
|
45
|
+
end
|
46
|
+
|
47
|
+
def compress(data)
|
48
|
+
return if data.nil?
|
49
|
+
|
50
|
+
output = StringIO.new
|
51
|
+
gz = Zlib::GzipWriter.new(output)
|
52
|
+
gz.write(Marshal.dump(data))
|
53
|
+
gz.close
|
54
|
+
output.string
|
55
|
+
end
|
56
|
+
|
57
|
+
def decompress(data)
|
58
|
+
return if data.nil?
|
59
|
+
|
60
|
+
Marshal.load(Zlib::GzipReader.new(StringIO.new(data)).read)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
module Merb::Cache
|
2
|
+
class MintCacheStore < AbstractStrategyStore
|
3
|
+
|
4
|
+
def writable?(key, parameters = {}, conditions = {})
|
5
|
+
@stores.capture_first {|s| s.writable?(key, parameters, conditions)}
|
6
|
+
end
|
7
|
+
|
8
|
+
def read(key, parameters = {})
|
9
|
+
cache_read = @stores.capture_first {|c| c.read(key, parameters)}
|
10
|
+
return cache_read || read_mint_cache(key, parameters)
|
11
|
+
end
|
12
|
+
|
13
|
+
def write(key, data = nil, parameters = {}, conditions = {})
|
14
|
+
if writable?(key, parameters, conditions)
|
15
|
+
write_mint_cache(key, data, parameters, conditions)
|
16
|
+
@stores.capture_first {|c| c.write(key, data, parameters, conditions)}
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# if you're wrapping multiple stores in a strategy store,
|
21
|
+
# it will write to all the wrapped stores, not just the first store that is successful
|
22
|
+
def write_all(key, data = nil, parameters = {}, conditions = {})
|
23
|
+
if writable?(key, parameters, conditions)
|
24
|
+
key_write = @stores.map {|c| c.write_all(key, data, parameters, conditions)}.all?
|
25
|
+
validity_write = @stores.map {|c| c.write_all(validity_key(key), data, parameters, conditions)}.all?
|
26
|
+
data_write = @stores.map {|c| c.write_all(data_key(key), data, parameters, conditions)}.all?
|
27
|
+
|
28
|
+
return (key_write and validity_write and data_write) ? true : false
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def fetch(key, parameters = {}, conditions = {}, &blk)
|
33
|
+
wrapper_blk = lambda { blk.call }
|
34
|
+
cache_read = read(key, parameters) || @stores.capture_first {|s| s.fetch(key, parameters, conditions, &wrapper_blk)}
|
35
|
+
return cache_read || read_mint_cache(key, parameters)
|
36
|
+
end
|
37
|
+
|
38
|
+
def exists?(key, parameters = {})
|
39
|
+
@stores.any?{|store| store.exists?(key, parameters)} || @stores.any? {|store| store.exists?(validity_key(key), parameters)}
|
40
|
+
end
|
41
|
+
|
42
|
+
def delete(key, parameters = {})
|
43
|
+
[key, validity_key(key), data_key(key)].map{|k| @stores.map {|c| c.delete(k, parameters)} }.flatten.any?
|
44
|
+
end
|
45
|
+
|
46
|
+
def delete_all!
|
47
|
+
@stores.map {|c| c.delete_all! }.all?
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
def validity_key(key); "#{key}_validity"; end
|
52
|
+
def data_key(key); "#{key}_data"; end
|
53
|
+
|
54
|
+
def write_mint_cache(key, data = nil, parameters = {}, conditions = {})
|
55
|
+
expiry = (conditions[:expire_in].nil?) ? 3600 : (conditions[:expire_in] * 2)
|
56
|
+
|
57
|
+
@stores.capture_first {|c| c.write(validity_key(key), (Time.now + expiry), parameters, conditions.merge({:expire_in => expiry}))}
|
58
|
+
@stores.capture_first {|c| c.write(data_key(key), data, parameters, conditions.merge({:expire_in => expiry}))}
|
59
|
+
end
|
60
|
+
|
61
|
+
def read_mint_cache(key, parameters = {})
|
62
|
+
validity_time = @stores.capture_first {|c| c.read(validity_key(key), parameters)}
|
63
|
+
data = @stores.capture_first {|c| c.read(data_key(key), parameters)}
|
64
|
+
|
65
|
+
unless validity_time.nil?
|
66
|
+
if Time.now < validity_time
|
67
|
+
write(key, data, parameters)
|
68
|
+
return nil
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
return data
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module Merb::Cache
|
2
|
+
# Store well suited for page caching.
|
3
|
+
class PageStore < AbstractStrategyStore
|
4
|
+
def writable?(dispatch, parameters = {}, conditions = {})
|
5
|
+
if Merb::Controller === dispatch && dispatch.request.method == :get &&
|
6
|
+
!dispatch.request.uri.nil? && !dispatch.request.uri.empty? &&
|
7
|
+
!conditions.has_key?(:if) && !conditions.has_key?(:unless) &&
|
8
|
+
query_string_present?(dispatch)
|
9
|
+
@stores.any? {|s| s.writable?(normalize(dispatch), parameters, conditions)}
|
10
|
+
else
|
11
|
+
false
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def read(dispatch, parameters = {})
|
16
|
+
nil
|
17
|
+
end
|
18
|
+
|
19
|
+
def write(dispatch, data = nil, parameters = {}, conditions = {})
|
20
|
+
if writable?(dispatch, parameters, conditions)
|
21
|
+
return @stores.capture_first {|s| s.write(normalize(dispatch), data || dispatch.body, {}, conditions)}
|
22
|
+
else
|
23
|
+
return false
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def write_all(dispatch, data = nil, parameters = {}, conditions = {})
|
28
|
+
if writable?(dispatch, parameters, conditions)
|
29
|
+
@stores.map {|s| s.write_all(normalize(dispatch), data || dispatch.body, {}, conditions)}.all?
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def fetch(dispatch, parameters = {}, conditions = {}, &blk)
|
34
|
+
if writable?(dispatch, parameters, conditions)
|
35
|
+
read(dispatch, parameters) || @stores.capture_first {|s| s.fetch(normalize(dispatch), data || dispatch.body, {}, conditions, &blk)}
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def exists?(dispatch, parameters = {})
|
40
|
+
if writable?(dispatch, parameters)
|
41
|
+
return @stores.any? {|s| s.exists?(normalize(dispatch), {})}
|
42
|
+
else
|
43
|
+
return false
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def delete(dispatch, parameters = {})
|
48
|
+
if writable?(dispatch, parameters)
|
49
|
+
@stores.map {|s| s.delete(normalize(dispatch), {})}.any?
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def delete_all!
|
54
|
+
@stores.map {|s| s.delete_all!}.all?
|
55
|
+
end
|
56
|
+
|
57
|
+
def normalize(dispatch)
|
58
|
+
key = dispatch.request.uri.split('?').first
|
59
|
+
key << "index" if key =~ /\/$/
|
60
|
+
key << ".#{dispatch.content_type}" unless key =~ /\.\w{2,6}/
|
61
|
+
key
|
62
|
+
end
|
63
|
+
|
64
|
+
def query_string_present?(dispatch)
|
65
|
+
dispatch.request.env["REQUEST_URI"] == dispatch.request.uri
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'digest/sha1'
|
2
|
+
|
3
|
+
module Merb::Cache
|
4
|
+
# Strategy store that uses SHA1 hex of
|
5
|
+
# base cache key and parameters as
|
6
|
+
# cache key.
|
7
|
+
#
|
8
|
+
# It is good for caching of expensive
|
9
|
+
# search queries that use multiple
|
10
|
+
# parameters passed via query string
|
11
|
+
# of request.
|
12
|
+
class SHA1Store < AbstractStrategyStore
|
13
|
+
def initialize(config = {})
|
14
|
+
super(config)
|
15
|
+
@map = {}
|
16
|
+
end
|
17
|
+
|
18
|
+
def writable?(key, parameters = {}, conditions = {})
|
19
|
+
case key
|
20
|
+
when String, Numeric, Symbol
|
21
|
+
@stores.any? {|c| c.writable?(digest(key, parameters), {}, conditions)}
|
22
|
+
else nil
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def read(key, parameters = {})
|
27
|
+
@stores.capture_first {|c| c.read(digest(key, parameters))}
|
28
|
+
end
|
29
|
+
|
30
|
+
def write(key, data = nil, parameters = {}, conditions = {})
|
31
|
+
if writable?(key, parameters, conditions)
|
32
|
+
@stores.capture_first {|c| c.write(digest(key, parameters), data, {}, conditions)}
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def write_all(key, data = nil, parameters = {}, conditions = {})
|
37
|
+
if writable?(key, parameters, conditions)
|
38
|
+
@stores.map {|c| c.write_all(digest(key, parameters), data, {}, conditions)}.all?
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def fetch(key, parameters = {}, conditions = {}, &blk)
|
43
|
+
read(key, parameters) || (writable?(key, parameters, conditions) && @stores.capture_first {|c| c.fetch(digest(key, parameters), {}, conditions, &blk)})
|
44
|
+
end
|
45
|
+
|
46
|
+
def exists?(key, parameters = {})
|
47
|
+
@stores.any? {|c| x = c.exists?(digest(key, parameters))}
|
48
|
+
end
|
49
|
+
|
50
|
+
def delete(key, parameters = {})
|
51
|
+
@stores.map {|c| c.delete(digest(key, parameters))}.any?
|
52
|
+
end
|
53
|
+
|
54
|
+
def delete_all!
|
55
|
+
@stores.map {|c| c.delete_all! }.all?
|
56
|
+
end
|
57
|
+
|
58
|
+
def digest(key, parameters = {})
|
59
|
+
@map[[key, parameters]] ||= Digest::SHA1.hexdigest("#{key}#{parameters.to_sha2}")
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../spec_helper'
|
2
|
+
|
3
|
+
describe Merb::Cache::CacheRequest do
|
4
|
+
it "should subclass Merb::Request" do
|
5
|
+
Merb::Cache::CacheRequest.superclass.should == Merb::Request
|
6
|
+
end
|
7
|
+
|
8
|
+
describe "#env" do
|
9
|
+
it "can be specified in the constructor" do
|
10
|
+
Merb::Cache::CacheRequest.new('', {}, 'foo' => 'bar').env['foo'].should == 'bar'
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
describe "#env[Merb::Const::REQUEST_URI]" do
|
15
|
+
it "should give the uri with the query string" do
|
16
|
+
Merb::Cache::CacheRequest.new('/test?q=1').env[Merb::Const::REQUEST_URI].should == '/test?q=1'
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe "#host" do
|
21
|
+
it "should return the correct host if the uri is absolute" do
|
22
|
+
Merb::Cache::CacheRequest.new('http://example.org:453/').host.should == "example.org:453"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe "#method" do
|
27
|
+
it "should be :get by default" do
|
28
|
+
Merb::Cache::CacheRequest.new('/test?q=1').method.should == :get
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should be set by the :method parameter" do
|
32
|
+
Merb::Cache::CacheRequest.new('/test?q=1', {}, :method => :put).method.should == :put
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
describe "#path" do
|
37
|
+
it "can be specified without manipulating the env" do
|
38
|
+
Merb::Cache::CacheRequest.new('/path/to/foo').path.should == '/path/to/foo'
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should return the path without the query string" do
|
42
|
+
Merb::Cache::CacheRequest.new('/path/to/foo?q=1').path.should == '/path/to/foo'
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
describe "#params" do
|
47
|
+
it "can be specified without manipulating the env" do
|
48
|
+
Merb::Cache::CacheRequest.new('/', 'foo' => 'bar').params.should == {'foo' => 'bar'}
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
describe "#subdomains" do
|
53
|
+
it "should return the correct subdomains if the uri is absolute" do
|
54
|
+
Merb::Cache::CacheRequest.new('http://test.example.org:453/').subdomains.should == ['test']
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
describe "#uri" do
|
59
|
+
it "should give the uri without the query string" do
|
60
|
+
Merb::Cache::CacheRequest.new('/test?q=1').uri.should == '/test'
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
it "should be compatiple with page store's way of detecting the presence of a query string" do
|
65
|
+
request = Merb::Cache::CacheRequest.new("/test?q=1")
|
66
|
+
(request.env[Merb::Const::REQUEST_URI] == request.uri).should be_false
|
67
|
+
request = Merb::Cache::CacheRequest.new("/test")
|
68
|
+
(request.env[Merb::Const::REQUEST_URI] == request.uri).should be_true
|
69
|
+
end
|
70
|
+
|
71
|
+
it "should not setup the uri if no uri is provided and env[Merb::Const::REQUEST_URI] is not nil" do
|
72
|
+
Merb::Cache::CacheRequest.new(nil, {}, {Merb::Const::REQUEST_URI => '/test'}).uri.should == '/test'
|
73
|
+
end
|
74
|
+
|
75
|
+
it "should setup a default env" do
|
76
|
+
Merb::Cache::CacheRequest.new('').env.should_not be_empty
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../spec_helper'
|
2
|
+
|
3
|
+
describe Merb::Cache do
|
4
|
+
before(:each) do
|
5
|
+
Merb::Cache.stores.clear
|
6
|
+
Thread.current[:'merb-cache'] = nil
|
7
|
+
end
|
8
|
+
|
9
|
+
describe ".setup" do
|
10
|
+
it "should have have access to the Merb::Cache.register method from the block" do
|
11
|
+
Merb::Cache.setup do
|
12
|
+
self.respond_to?(:register).should == true
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
describe ".register" do
|
18
|
+
it "should add the store name and instance to the store hash" do
|
19
|
+
Merb::Cache.stores.should_not have_key(:foo)
|
20
|
+
Merb::Cache.register(:foo, DummyStore)
|
21
|
+
Merb::Cache.stores.should have_key(:foo)
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should use :default when no name is supplied" do
|
25
|
+
Merb::Cache.stores.should_not have_key(:default)
|
26
|
+
Merb::Cache.register(DummyStore)
|
27
|
+
Merb::Cache.stores.should have_key(:default)
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should not allow a store to be redefined" do
|
31
|
+
Merb::Cache.register(DummyStore)
|
32
|
+
lambda do
|
33
|
+
Merb::Cache.register(DummyStore)
|
34
|
+
end.should raise_error(Merb::Cache::StoreExists)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
describe ".exists?" do
|
39
|
+
it "should return true if a repository is setup" do
|
40
|
+
Merb::Cache.register(DummyStore)
|
41
|
+
Merb::Cache.register(:store_that_exists, DummyStore)
|
42
|
+
Merb::Cache.exists?(:default).should be_true
|
43
|
+
Merb::Cache.exists?(:store_that_exists).should be_true
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should return false if a repository is not setup" do
|
47
|
+
Merb::Cache.exists?(:not_here).should be_false
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
describe ".[]" do
|
52
|
+
it "should clone the stores so to keep them threadsafe" do
|
53
|
+
Merb::Cache.register(DummyStore)
|
54
|
+
Merb::Cache[:default].should_not be_nil
|
55
|
+
Merb::Cache[:default].should_not == Merb::Cache.stores[:default]
|
56
|
+
end
|
57
|
+
|
58
|
+
it "should cache the thread local stores in Thread.current" do
|
59
|
+
Merb::Cache.register(DummyStore)
|
60
|
+
Thread.current[:'merb-cache'].should be_nil
|
61
|
+
Merb::Cache[:default]
|
62
|
+
Thread.current[:'merb-cache'].should_not be_nil
|
63
|
+
end
|
64
|
+
|
65
|
+
it "should create an adhoc store if multiple store names are supplied" do
|
66
|
+
Merb::Cache.register(DummyStore)
|
67
|
+
Merb::Cache.register(:dummy, DummyStore)
|
68
|
+
Merb::Cache[:default, :dummy].class.should == Merb::Cache::AdhocStore
|
69
|
+
end
|
70
|
+
|
71
|
+
it "should let you create new stores after accessing the old ones" do
|
72
|
+
Merb::Cache.register(DummyStore)
|
73
|
+
Merb::Cache.register(:one, DummyStore)
|
74
|
+
Merb::Cache[:default].should_not be_nil
|
75
|
+
Merb::Cache[:one].should_not be_nil
|
76
|
+
Merb::Cache.register(:two, DummyStore)
|
77
|
+
Merb::Cache[:two].should_not be_nil
|
78
|
+
end
|
79
|
+
|
80
|
+
it "should raise an error if the cache has not been setup" do
|
81
|
+
Merb::Cache.register(DummyStore)
|
82
|
+
Merb::Cache[:default].should_not be_nil
|
83
|
+
lambda do
|
84
|
+
Merb::Cache[:does_not_exist]
|
85
|
+
end.should raise_error(Merb::Cache::StoreNotFound)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../../spec_helper'
|
2
|
+
|
3
|
+
describe Enumerable do
|
4
|
+
describe "#capture_first" do
|
5
|
+
it "should return the result of the first block call that is non-nil, not the item sent to the block" do
|
6
|
+
[1, 2, 3].capture_first {|i| i ** i if i % 2 == 0}.should == 4
|
7
|
+
end
|
8
|
+
|
9
|
+
it "should return nil if all block calls are nil" do
|
10
|
+
[1, 2, 3].capture_first {|i| nil }.should be_nil
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should stop calling the block once a block evaluates to non-nil" do
|
14
|
+
lambda {
|
15
|
+
[1, 2, 3].capture_first do |i|
|
16
|
+
raise "#{i} is divisible by 3!" if i % 3 == 0
|
17
|
+
i ** i if i % 2 == 0
|
18
|
+
end
|
19
|
+
}.should_not raise_error
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should return the first item 'captured'" do
|
23
|
+
[1, 2, 3].capture_first{|i| i }.should == 1
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|