cachew 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -14,23 +14,61 @@ ClassLength:
14
14
  CyclomaticComplexity:
15
15
  Max: 6
16
16
 
17
+ EmptyLineBetweenDefs:
18
+ AllowAdjacentOneLineDefs: true
19
+
17
20
  BlockNesting:
18
21
  Max: 3
19
22
 
20
23
  HashSyntax:
21
24
  EnforcedStyle: hash_rockets
22
25
 
23
- Encoding:
24
- Enabled: false
25
-
26
26
  StringLiterals:
27
27
  EnforcedStyle: double_quotes
28
28
 
29
+ AlignParameters:
30
+ EnforcedStyle: with_fixed_indentation
31
+
32
+ IndentHash:
33
+ EnforcedStyle: consistent
34
+
35
+ PercentLiteralDelimiters:
36
+ PreferredDelimiters:
37
+ '%': ()
38
+ '%i': ()
39
+ '%q': ()
40
+ '%Q': ()
41
+ '%r': '{}'
42
+ '%s': ()
43
+ '%w': '[]'
44
+ '%W': '[]'
45
+ '%x': ()
46
+
47
+ Encoding:
48
+ Enabled: false
49
+
29
50
  BracesAroundHashParameters:
30
51
  Enabled: false
31
52
 
32
53
  Documentation:
33
54
  Enabled: false
34
55
 
35
- EmptyLines:
56
+ # Not all trivial readers/writers can be defined with attr_* methods
57
+ TrivialAccessors:
58
+ Enabled: false
59
+
60
+ # New lambda syntax is UGLY, don't enforce it
61
+ Lambda:
36
62
  Enabled: false
63
+
64
+ # Ridiculous (and IMHO useless) restriction, that makes impossible aligning
65
+ # code like this:
66
+ #
67
+ # redis.hset :k1, now
68
+ # redis.hincrby :k2, 123
69
+ SingleSpaceBeforeFirstArg:
70
+ Enabled: false
71
+
72
+ AllCops:
73
+ Include:
74
+ - Gemfile
data/Gemfile CHANGED
@@ -1,17 +1,20 @@
1
- source 'https://rubygems.org'
1
+ source "https://rubygems.org"
2
+
3
+ platforms :ruby_19, :ruby_20, :ruby_21 do
4
+ group :development do
5
+ gem "celluloid-io"
6
+ gem "guard-rspec"
7
+ end
8
+
9
+ gem "rubocop"
10
+ end
2
11
 
3
12
  gem "rake"
4
- gem "rspec"
5
- gem "guard-rspec"
6
- gem "rubocop"
13
+ gem "rspec", "~> 3.0"
14
+ gem "timecop"
15
+
7
16
  gem "coveralls", :require => false
8
17
  gem "simplecov", :require => false
9
18
 
10
- platforms :rbx do
11
- gem "racc"
12
- gem "rubinius-coverage", "~> 2.0"
13
- gem "rubysl", "~> 2.0"
14
- end
15
-
16
19
  # Specify your gem's dependencies in cachew.gemspec
17
20
  gemspec
data/README.md CHANGED
@@ -24,24 +24,57 @@ Or install it yourself as:
24
24
  ## Usage
25
25
 
26
26
  ``` ruby
27
- # With HashAdapter
28
- cache = Cachew.new({})
27
+ # In-memory Hash
28
+ cache = Cachew::Hash.new
29
29
 
30
- cache.has?(:foo) # => false
31
- cache.fetch(:foo) { :bar } # => :bar
30
+ cache.fetch(:foo) { Time.now.to_i } # => 1405724687
31
+ cache.fetch(:foo) { Time.now.to_i } # => 1405724687
32
32
 
33
- cache.has?(:foo) # => true
34
- cache.fetch(:foo) { :moo } # => :bar
33
+ # With ttl
34
+ cache.fetch(:foo, :ttl => 10) { Time.now.to_i } # => 1405724689
35
35
 
36
+ # Before 10 seconds will expire
37
+ cache.fetch(:foo, :ttl => 10) { Time.now.to_i } # => 1405724689
36
38
 
37
- # With (default) NullAdapter
38
- cache = Cachew.new
39
+ # Once 10+ seconds pass
40
+ cache.fetch(:foo, :ttl => 10) { Time.now.to_i } # => 1405724695
39
41
 
40
- cache.has?(:foo) # => false
41
- cache.fetch(:foo) { :bar } # => :bar
42
+ # Withot cache (think of it as NullLogger)
43
+ cache = Cachew::Null.new
42
44
 
43
- cache.has?(:foo) # => false
44
- cache.fetch(:foo) { :moo } # => :moo
45
+ cache.fetch(:foo) { Time.now.to_i } # => 1405724687
46
+ cache.fetch(:foo) { Time.now.to_i } # => 1405724688
47
+ ```
48
+
49
+ You can easily write your own adapters:
50
+
51
+ ```
52
+ class Cachew::Redis < Cache::Adapter
53
+ def initialize(client)
54
+ @client = client
55
+ end
56
+
57
+ private
58
+
59
+ def __set__(key, val, ttl)
60
+ val = Marshal.dump val
61
+
62
+ if 0 == ttl
63
+ @client.set key, val
64
+ else
65
+ @client.setex key, val, ttl
66
+ end
67
+ end
68
+
69
+ def __get__(key)
70
+ val = @client.get(key)
71
+ val ? Marshal.load(val) : UNDEFINED
72
+ end
73
+
74
+ def __key__(*)
75
+ "cachew:#{super}"
76
+ end
77
+ end
45
78
  ```
46
79
 
47
80
  ## Contributing
data/Rakefile CHANGED
@@ -1,9 +1,9 @@
1
1
  require "bundler/gem_tasks"
2
2
 
3
- require 'rspec/core/rake_task'
3
+ require "rspec/core/rake_task"
4
4
  RSpec::Core::RakeTask.new
5
5
 
6
- require 'rubocop/rake_task'
7
- Rubocop::RakeTask.new
6
+ require "rubocop/rake_task"
7
+ RuboCop::RakeTask.new
8
8
 
9
9
  task :default => [:spec, :rubocop]
@@ -18,5 +18,5 @@ Gem::Specification.new do |spec|
18
18
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
19
  spec.require_paths = ["lib"]
20
20
 
21
- spec.add_development_dependency "bundler", "~> 1.5"
21
+ spec.add_development_dependency "bundler", "~> 1.6"
22
22
  end
@@ -1,19 +1,4 @@
1
+ require "cachew/adapter"
2
+ require "cachew/hash"
3
+ require "cachew/null"
1
4
  require "cachew/version"
2
- require "cachew/adapters"
3
-
4
- # Unified cache interface
5
- class Cachew
6
- extend Forwardable
7
-
8
- attr_reader :adapter
9
-
10
- def initialize(store = nil)
11
- @adapter = Adapters.build_adapter_for store
12
- end
13
-
14
- def fetch(key)
15
- has?(key) ? get(key) : set(key, yield)
16
- end
17
-
18
- def_delegators :adapter, :set, :get, :has?
19
- end
@@ -0,0 +1,78 @@
1
+ require "digest/md5"
2
+ require "json"
3
+
4
+ module Cachew
5
+ # Base class for Cachew adapters.
6
+ #
7
+ # @example Building custom adapters
8
+ #
9
+ # class Cachew::Redis < Cache::Adapter
10
+ # def initialize(client)
11
+ # @client = client
12
+ # end
13
+ #
14
+ # private
15
+ #
16
+ # def __set__(key, val, ttl)
17
+ # val = Marshal.dump val
18
+ #
19
+ # if 0 == ttl
20
+ # @client.set key, val
21
+ # else
22
+ # @client.setex key, val, ttl
23
+ # end
24
+ # end
25
+ #
26
+ # def __get__(key)
27
+ # val = @client.get(key)
28
+ # val ? Marshal.load(val) : UNDEFINED
29
+ # end
30
+ #
31
+ # def __key__(*)
32
+ # "cachew:#{super}"
33
+ # end
34
+ # end
35
+ #
36
+ class Adapter
37
+ # Internal constant used by `__get__` to notify that value is not in the
38
+ # cache or became stale
39
+ UNDEFINED = Object.new.freeze
40
+
41
+ # @example Usage
42
+ #
43
+ # cachew.fetch "some_key", :ttl => 60 do
44
+ # HTTP.get("http://example.com").to_s
45
+ # end
46
+ #
47
+ def fetch(key, opts = {})
48
+ key = __key__ key, opts
49
+ val = __get__ key
50
+
51
+ if UNDEFINED.equal? val
52
+ val = yield
53
+ ttl = opts.fetch(:ttl) { 0 }.to_i
54
+
55
+ __set__ key, val, ttl
56
+ end
57
+
58
+ val
59
+ end
60
+
61
+ private
62
+
63
+ # :nodoc:
64
+ def __set__(_key, _val, _ttl)
65
+ fail "Not implemented"
66
+ end
67
+
68
+ # :nodoc:
69
+ def __get__(_key)
70
+ fail "Not implemented"
71
+ end
72
+
73
+ # :nodoc:
74
+ def __key__(*args)
75
+ Digest::MD5.hexdigest JSON.dump args
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,25 @@
1
+ require "cachew/adapter"
2
+
3
+ module Cachew
4
+ # In-memory cache adapter.
5
+ class Hash < Adapter
6
+ # @param [#to_hash] store Underlying Hash used for storage
7
+ def initialize(store = {})
8
+ @store = store.to_hash
9
+ end
10
+
11
+ private
12
+
13
+ # :nodoc:
14
+ def __set__(key, val, ttl)
15
+ ttl = 0 >= ttl ? 0 : Time.now.to_i + ttl
16
+ @store[key] = [val, ttl]
17
+ end
18
+
19
+ # :nodoc:
20
+ def __get__(key)
21
+ val, ttl = @store[key]
22
+ ttl && (0 == ttl || ttl > Time.now.to_i) ? val : UNDEFINED
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,28 @@
1
+ require "cachew/adapter"
2
+
3
+ module Cachew
4
+ # Null version of Cachew. Never caches anything, and returns value of given
5
+ # block directly.
6
+ class Null < Adapter
7
+ class << self
8
+ # Returns singleton instance of Null adapter.
9
+ # @return [Null]
10
+ def new
11
+ @instance ||= super
12
+ end
13
+ end
14
+
15
+ private
16
+
17
+ # :nodoc:
18
+ def __get__(*)
19
+ UNDEFINED
20
+ end
21
+
22
+ # :nodoc:
23
+ def __set__(*) end
24
+
25
+ # :nodoc:
26
+ def __key__(*) end
27
+ end
28
+ end
@@ -1,3 +1,4 @@
1
- class Cachew
2
- VERSION = "0.1.1"
1
+ module Cachew
2
+ # Cachew version
3
+ VERSION = "0.2.0"
3
4
  end
@@ -0,0 +1,75 @@
1
+ require "spec_helper"
2
+
3
+ RSpec.describe Cachew::Hash do
4
+ describe ".new" do
5
+ it "accepts any #to_hash object" do
6
+ storage = double(:to_hash => {})
7
+
8
+ described_class.new(storage).fetch(:foo) { :bar }
9
+
10
+ expect(storage.to_hash).not_to be_empty
11
+ end
12
+
13
+ it "tolerates argument-less call" do
14
+ expect { described_class.new }.not_to raise_error
15
+ end
16
+ end
17
+
18
+ describe "#[]" do
19
+ let(:cachew) { described_class.new }
20
+
21
+ context "when value is not cahed yet" do
22
+ it "evaluates given block" do
23
+ expect { |b| cachew.fetch :foo, &b }.to yield_control
24
+ end
25
+
26
+ it "returns evaluated value" do
27
+ expect(cachew.fetch(:foo) { :bar }).to be :bar
28
+ end
29
+ end
30
+
31
+ context "when value was already cached" do
32
+ before { cachew.fetch(:foo) { :cached } }
33
+
34
+ it "does not evaluates block" do
35
+ expect { |b| cachew.fetch(:foo, &b) }.not_to yield_control
36
+ end
37
+
38
+ it "returns previously cached value" do
39
+ expect(cachew.fetch(:foo) { :bar }).to be :cached
40
+ end
41
+ end
42
+
43
+ context "when value with expiration still fresh" do
44
+ before { cachew.fetch(:foo, :ttl => 84) { :cached } }
45
+
46
+ it "does not evaluates given block" do
47
+ Timecop.travel 42 do
48
+ expect { |b| cachew.fetch :foo, :ttl => 84, &b }.not_to yield_control
49
+ end
50
+ end
51
+
52
+ it "returns previously cached value" do
53
+ Timecop.travel 42 do
54
+ expect(cachew.fetch(:foo, :ttl => 84) { :bar }).to be :cached
55
+ end
56
+ end
57
+ end
58
+
59
+ context "when value with expiration became stale" do
60
+ before { cachew.fetch(:foo, :ttl => 21) { :cached } }
61
+
62
+ it "evaluates given block" do
63
+ Timecop.travel 42 do
64
+ expect { |b| cachew.fetch :foo, :ttl => 21, &b }.to yield_control
65
+ end
66
+ end
67
+
68
+ it "returns evaluated value" do
69
+ Timecop.travel 42 do
70
+ expect(cachew.fetch(:foo, :ttl => 21) { :bar }).to be :bar
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,23 @@
1
+ require "spec_helper"
2
+
3
+ RSpec.describe Cachew::Null do
4
+ let(:cachew) { described_class.new }
5
+
6
+ describe ".new" do
7
+ it "returns Singleton instance" do
8
+ expect(cachew).to be described_class.new
9
+ end
10
+ end
11
+
12
+ describe "#[]" do
13
+ it "always evaluates given block" do
14
+ expect { |b| cachew.fetch(:foo, &b) }.to yield_control
15
+ end
16
+
17
+ it "returns value of given block" do
18
+ [:foo, :bar, :baz].each do |val|
19
+ expect(cachew.fetch(:foo) { val }).to be val
20
+ end
21
+ end
22
+ end
23
+ end
@@ -1,23 +1,20 @@
1
1
  require "simplecov"
2
2
  require "coveralls"
3
-
3
+ require "timecop"
4
4
 
5
5
  SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
6
6
  SimpleCov::Formatter::HTMLFormatter,
7
7
  Coveralls::SimpleCov::Formatter
8
8
  ]
9
9
 
10
-
11
10
  SimpleCov.start do
12
11
  add_filter "/spec/"
13
12
  end
14
13
 
14
+ Timecop.safe_mode = true
15
15
 
16
16
  require "cachew"
17
17
 
18
-
19
18
  RSpec.configure do |config|
20
- config.expect_with :rspec do |c|
21
- c.syntax = :expect
22
- end
19
+ config.disable_monkey_patching!
23
20
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cachew
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2014-02-03 00:00:00.000000000 Z
12
+ date: 2014-07-19 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bundler
@@ -18,7 +18,7 @@ dependencies:
18
18
  requirements:
19
19
  - - ~>
20
20
  - !ruby/object:Gem::Version
21
- version: '1.5'
21
+ version: '1.6'
22
22
  type: :development
23
23
  prerelease: false
24
24
  version_requirements: !ruby/object:Gem::Requirement
@@ -26,7 +26,7 @@ dependencies:
26
26
  requirements:
27
27
  - - ~>
28
28
  - !ruby/object:Gem::Version
29
- version: '1.5'
29
+ version: '1.6'
30
30
  description: Unified cache interface.
31
31
  email:
32
32
  - ixti@member.fsf.org
@@ -44,15 +44,12 @@ files:
44
44
  - Rakefile
45
45
  - cachew.gemspec
46
46
  - lib/cachew.rb
47
- - lib/cachew/adapters.rb
48
- - lib/cachew/adapters/base_adapter.rb
49
- - lib/cachew/adapters/hash_adapter.rb
50
- - lib/cachew/adapters/null_adapter.rb
47
+ - lib/cachew/adapter.rb
48
+ - lib/cachew/hash.rb
49
+ - lib/cachew/null.rb
51
50
  - lib/cachew/version.rb
52
- - spec/lib/cachew/adapters/hash_adapter_spec.rb
53
- - spec/lib/cachew/adapters/null_adapter_spec.rb
54
- - spec/lib/cachew/adapters_spec.rb
55
- - spec/lib/cachew_spec.rb
51
+ - spec/lib/cachew/hash_spec.rb
52
+ - spec/lib/cachew/null_spec.rb
56
53
  - spec/spec_helper.rb
57
54
  homepage: https://github.com/ixti/cachew
58
55
  licenses:
@@ -69,7 +66,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
69
66
  version: '0'
70
67
  segments:
71
68
  - 0
72
- hash: 797334216198397338
69
+ hash: 4155366000220806670
73
70
  required_rubygems_version: !ruby/object:Gem::Requirement
74
71
  none: false
75
72
  requirements:
@@ -78,16 +75,14 @@ required_rubygems_version: !ruby/object:Gem::Requirement
78
75
  version: '0'
79
76
  segments:
80
77
  - 0
81
- hash: 797334216198397338
78
+ hash: 4155366000220806670
82
79
  requirements: []
83
80
  rubyforge_project:
84
- rubygems_version: 1.8.23
81
+ rubygems_version: 1.8.23.2
85
82
  signing_key:
86
83
  specification_version: 3
87
- summary: cachew-0.1.1
84
+ summary: cachew-0.2.0
88
85
  test_files:
89
- - spec/lib/cachew/adapters/hash_adapter_spec.rb
90
- - spec/lib/cachew/adapters/null_adapter_spec.rb
91
- - spec/lib/cachew/adapters_spec.rb
92
- - spec/lib/cachew_spec.rb
86
+ - spec/lib/cachew/hash_spec.rb
87
+ - spec/lib/cachew/null_spec.rb
93
88
  - spec/spec_helper.rb
@@ -1,16 +0,0 @@
1
- require "cachew/adapters/base_adapter"
2
- require "cachew/adapters/hash_adapter"
3
- require "cachew/adapters/null_adapter"
4
-
5
- class Cachew
6
- module Adapters
7
- def self.build_adapter_for(store)
8
- case store
9
- when BaseAdapter then store
10
- when Cachew then store.adapter
11
- when Hash then HashAdapter.new(store)
12
- else NullAdapter.new
13
- end
14
- end
15
- end
16
- end
@@ -1,5 +0,0 @@
1
- class Cachew
2
- module Adapters
3
- BaseAdapter = Struct.new(:store)
4
- end
5
- end
@@ -1,17 +0,0 @@
1
- class Cachew
2
- module Adapters
3
- class HashAdapter < BaseAdapter
4
- def set(key, value)
5
- store[key] = value
6
- end
7
-
8
- def get(key)
9
- store[key]
10
- end
11
-
12
- def has?(key)
13
- store.key?(key)
14
- end
15
- end
16
- end
17
- end
@@ -1,17 +0,0 @@
1
- class Cachew
2
- module Adapters
3
- class NullAdapter < BaseAdapter
4
- def set(_, value)
5
- value
6
- end
7
-
8
- def get(_)
9
- nil
10
- end
11
-
12
- def has?(_)
13
- false
14
- end
15
- end
16
- end
17
- end
@@ -1,34 +0,0 @@
1
- require "spec_helper"
2
-
3
- describe Cachew::Adapters::HashAdapter do
4
- let(:adapter) { described_class.new({}) }
5
-
6
- describe "#set" do
7
- it "returns back given value" do
8
- expect(adapter.set(:foo, :bar)).to be :bar
9
- end
10
-
11
- it "sets value in the underlying store Hash" do
12
- adapter.set(:foo, :bar)
13
- expect(adapter.store).to eq :foo => :bar
14
- end
15
- end
16
-
17
- describe "#get" do
18
- it "returns nil if value does not exists" do
19
- expect(adapter.get(:foo)).to be_nil
20
- end
21
-
22
- it "returns saved value if exists" do
23
- adapter.store[:foo] = :bar
24
- expect(adapter.get(:foo)).to be :bar
25
- end
26
- end
27
-
28
- describe "#has?" do
29
- it "returns true even if stored value is nil" do
30
- adapter.store[:foo] = nil
31
- expect(adapter.has?(:foo)).to be_true
32
- end
33
- end
34
- end
@@ -1,25 +0,0 @@
1
- require "spec_helper"
2
-
3
- describe Cachew::Adapters::NullAdapter do
4
- let(:adapter) { described_class.new }
5
-
6
- describe "#set" do
7
- it "returns back given value" do
8
- expect(adapter.set(:foo, :bar)).to be :bar
9
- end
10
- end
11
-
12
- describe "#get" do
13
- it "always returns nil" do
14
- adapter.set(:foo, :bar)
15
- expect(adapter.get :foo).to be_nil
16
- end
17
- end
18
-
19
- describe "#has?" do
20
- it "always returns false" do
21
- adapter.set(:foo, :bar)
22
- expect(adapter.has? :foo).to be_false
23
- end
24
- end
25
- end
@@ -1,31 +0,0 @@
1
- require "spec_helper"
2
-
3
- describe Cachew::Adapters do
4
- describe ".build_adapter_for" do
5
- context "with Hash instance" do
6
- it "returns HashAdapter" do
7
- adapter = described_class.build_adapter_for :foo => :bar
8
- expect(adapter).to be_a Cachew::Adapters::HashAdapter
9
- end
10
- end
11
-
12
- context "with instance of BaseAdapter" do
13
- it "returns adapter as is" do
14
- adapter = Cachew::Adapters::HashAdapter.new :foo => :bar
15
- expect(described_class.build_adapter_for adapter).to be adapter
16
- end
17
- end
18
-
19
- context "with instance of Cachew" do
20
- it "returns original #adapter" do
21
- original = Cachew.new :foo => :bar
22
- expect(Cachew.new(original).adapter).to be original.adapter
23
- end
24
- end
25
-
26
- it "returns NullAdapter if can't find better candidate" do
27
- expect(described_class.build_adapter_for "test")
28
- .to be_a Cachew::Adapters::NullAdapter
29
- end
30
- end
31
- end
@@ -1,53 +0,0 @@
1
- require "spec_helper"
2
-
3
- describe Cachew do
4
- describe ".new" do
5
- it "initiates with NullAdapter by default" do
6
- expect(described_class.new.adapter).to be_a Cachew::Adapters::BaseAdapter
7
- end
8
-
9
- it "calls Adapters.build_adapter_for with given store" do
10
- store = { :foo => :bar }
11
- expect(Cachew::Adapters).to receive(:build_adapter_for).with store
12
- described_class.new store
13
- end
14
- end
15
-
16
- describe "#set" do
17
- subject(:cachew) { described_class.new.set }
18
- specify { expect { cachew.adapter.to receive :set } }
19
- end
20
-
21
- describe "#get" do
22
- subject(:cachew) { described_class.new.get }
23
- specify { expect { cachew.adapter.to receive :get } }
24
- end
25
-
26
- describe "#has?" do
27
- subject(:cachew) { described_class.new.has? }
28
- specify { expect { cachew.adapter.to receive :has? } }
29
- end
30
-
31
- describe "#fetch" do
32
- let(:cachew) { described_class.new :foo => :bar }
33
-
34
- it "calls has?" do
35
- expect(cachew).to receive(:has?)
36
- cachew.fetch(:foo) { :moo }
37
- end
38
-
39
- context "when adapter has? key" do
40
- it "calls get" do
41
- expect(cachew).to receive(:get)
42
- cachew.fetch(:foo) { :moo }
43
- end
44
- end
45
-
46
- context "when adapter does not has? key" do
47
- it "calls set" do
48
- expect(cachew).to receive(:set)
49
- cachew.fetch(:bar) { :moo }
50
- end
51
- end
52
- end
53
- end