hash_cache 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +2 -69
- data/Rakefile +8 -0
- data/TODO.md +29 -0
- data/hash_cache.gemspec +2 -2
- data/lib/hash_cache/accessible.rb +19 -0
- data/lib/hash_cache/all.rb +2 -0
- data/lib/hash_cache/linked_list.rb +115 -0
- data/lib/hash_cache/lru.rb +47 -0
- data/lib/hash_cache/ttl.rb +5 -12
- data/lib/hash_cache/version.rb +1 -1
- data/spec/fetch_examples.rb +24 -0
- data/spec/hash_cache/linked_list_spec.rb +120 -0
- data/spec/hash_cache/lru_spec.rb +108 -0
- data/spec/hash_cache/ttl_spec.rb +6 -23
- metadata +15 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: dbcce2aa290c97f9ea5e548fec1cd869cf2b427a
|
4
|
+
data.tar.gz: dbca8cf97e3aefbda13e2d33576d193df35179b1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b29355549ca2d293c4adb71f5281708af0ceb144f7d81538627d590bdc5d502d5e0610541fbc78855d7816b145ef8e11a11df7cc697c007cfee105e9d0986c10
|
7
|
+
data.tar.gz: be73b34d5de59cb592b0fff58b726520960c1bb80fce502b34e92df5d0bbd45bc3fde16020c07b979a77354c65fdbd9ab031fee104e67d794ddd06f20fd715e9
|
data/README.md
CHANGED
@@ -1,70 +1,3 @@
|
|
1
|
-
#
|
1
|
+
# Renamed
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
## Usage
|
6
|
-
|
7
|
-
HashCache provides the following classes.
|
8
|
-
|
9
|
-
### HashCache::TTL
|
10
|
-
|
11
|
-
TTL (Time To Live) remembers values for as many seconds as you tell it. The default is 3600 seconds (1 hour).
|
12
|
-
|
13
|
-
```ruby
|
14
|
-
require 'hash_cache/ttl'
|
15
|
-
|
16
|
-
h = HashCache::TTL.new(ttl: 5)
|
17
|
-
h[:a] = 'aardvark'
|
18
|
-
h[:a] #=> 'aardvark'
|
19
|
-
sleep(6)
|
20
|
-
h[:a] #=> nil
|
21
|
-
```
|
22
|
-
|
23
|
-
If you pass `refresh: true`, reading a value will reset its timer; otherwise, only writing will.
|
24
|
-
|
25
|
-
The `memoize` method fetches a key if it exists and isn't expired; otherwise, it calculates the value using the block and saves it.
|
26
|
-
|
27
|
-
```ruby
|
28
|
-
h = HashCache::TTL.new(ttl: 5)
|
29
|
-
|
30
|
-
# Runs the block
|
31
|
-
h.memoize(:a) { |k| calculation_for(k) }
|
32
|
-
|
33
|
-
# Returns the previously-calculated value
|
34
|
-
h.memoize(:a) { |k| calculation_for(k) }
|
35
|
-
|
36
|
-
sleep(6)
|
37
|
-
|
38
|
-
# Runs the block
|
39
|
-
h.memoize(:a) { |k| calculation_for(k) }
|
40
|
-
```
|
41
|
-
|
42
|
-
## Installation
|
43
|
-
|
44
|
-
Add this line to your application's Gemfile:
|
45
|
-
|
46
|
-
gem 'hash_cache'
|
47
|
-
|
48
|
-
And then execute:
|
49
|
-
|
50
|
-
$ bundle
|
51
|
-
|
52
|
-
Or install it yourself as:
|
53
|
-
|
54
|
-
$ gem install hash_cache
|
55
|
-
|
56
|
-
## Coming Soon
|
57
|
-
|
58
|
-
Other classes with different cache expiration strategies.
|
59
|
-
|
60
|
-
## Contributing
|
61
|
-
|
62
|
-
1. Fork it
|
63
|
-
2. Create your feature branch (`git checkout -b my-new-feature`)
|
64
|
-
3. Commit your changes (`git commit -am 'Add some feature'`)
|
65
|
-
4. Push to the branch (`git push origin my-new-feature`)
|
66
|
-
5. Create new Pull Request
|
67
|
-
|
68
|
-
# Thanks
|
69
|
-
|
70
|
-
Original tests were adapted from [volatile_hash](https://github.com/satyap/volatile_hash).
|
3
|
+
See [https://github.com/nathanl/caches](https://github.com/nathanl/caches)
|
data/Rakefile
CHANGED
data/TODO.md
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# TODO
|
2
|
+
|
3
|
+
## General
|
4
|
+
|
5
|
+
- Refactor LinkedList
|
6
|
+
- Add benchmarks: speed (and maybe memory usage), runnable with rake task for all cache classes. Be able to compare over the course of development (eg, version 0.0.1 vs 0.0.3). Maybe output ASCII graphs for fun.
|
7
|
+
- Add Travis CI testing
|
8
|
+
- Give all caches a way to manually invalidate a given key. Return nil if it wasn't here and the value if it was.
|
9
|
+
- Add "least used" cache class
|
10
|
+
- Define `blank?`, etc on list and caches
|
11
|
+
- Should TTL keys be renewed by updates? LRU ones aren't. If the cache exists to be read, probably not.
|
12
|
+
- Ride a skateboard and, simultaneously, shoot a potato gun
|
13
|
+
|
14
|
+
## New types
|
15
|
+
|
16
|
+
Build LU - Least Used. Expiration criteria is essentially "uses per second" - reads divided by time since write.
|
17
|
+
|
18
|
+
Implementation ideas:
|
19
|
+
- Passage of time doesn't change ranking of keys; only writing does.
|
20
|
+
- Keep a doubly-linked list of keys ordered by usefulness and a reference to the tail for easy dropping
|
21
|
+
- When updating a key, be able to quickly compare it with the one ahead of it and swap if it overtook it in rank
|
22
|
+
- Normal hash data structure has pointers into linked list segments
|
23
|
+
|
24
|
+
|
25
|
+
## Benchmarks
|
26
|
+
|
27
|
+
TTL: Prepopulate instances differing in size by orders of magnitude. Measure N operations of various types. These vary on three axes: read/write, expired/fresh, and update/insert for writes. Output results as graph. Visually confirm that it appears O(1) and is reasonably fast (?) in absolute terms.
|
28
|
+
|
29
|
+
LRU: ?
|
data/hash_cache.gemspec
CHANGED
@@ -8,8 +8,8 @@ Gem::Specification.new do |spec|
|
|
8
8
|
spec.version = HashCache::VERSION
|
9
9
|
spec.authors = ["Nathan Long"]
|
10
10
|
spec.email = ["nathanmlong@gmail.com"]
|
11
|
-
spec.description = %q{
|
12
|
-
spec.summary = %q{
|
11
|
+
spec.description = %q{Renamed - see 'caches' gem}
|
12
|
+
spec.summary = %q{Renamed - see 'caches' gem}
|
13
13
|
spec.homepage = "https://github.com/nathanl/hash_cache"
|
14
14
|
spec.license = "MIT"
|
15
15
|
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module HashCache
|
2
|
+
module Accessible
|
3
|
+
|
4
|
+
def fetch(key, default = (default_omitted = true; nil))
|
5
|
+
return self[key] if data.has_key?(key)
|
6
|
+
return yield(key) if block_given?
|
7
|
+
return default unless default_omitted
|
8
|
+
raise KeyError
|
9
|
+
end
|
10
|
+
|
11
|
+
def memoize(key)
|
12
|
+
raise ArgumentError, "Block is required" unless block_given?
|
13
|
+
self[key] # triggers flush or refresh if expired
|
14
|
+
return self[key] if data.has_key?(key)
|
15
|
+
self[key] = yield(key) if block_given?
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
module HashCache
|
2
|
+
# This LinkedList class is unusual in that it gives direct access to its nodes.
|
3
|
+
# It trusts the user not to break it! The advantage is that outsiders with
|
4
|
+
# a node reference can reorder the list (eg, using #move_to_head) in O(1) time.
|
5
|
+
class LinkedList
|
6
|
+
include Enumerable
|
7
|
+
|
8
|
+
attr_reader :length
|
9
|
+
|
10
|
+
def initialize(item = nil)
|
11
|
+
if item
|
12
|
+
Node.new(item).tap { |node|
|
13
|
+
self.head = self.tail = node
|
14
|
+
self.length = 1
|
15
|
+
}
|
16
|
+
else
|
17
|
+
self.length = 0
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def each
|
22
|
+
current = head
|
23
|
+
while current do
|
24
|
+
yield(current.value)
|
25
|
+
current = current.right
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def append(item)
|
30
|
+
return initialize(item) if empty?
|
31
|
+
Node.new(item, left: tail).tap { |new_tail|
|
32
|
+
tail.right = new_tail
|
33
|
+
self.tail = new_tail
|
34
|
+
self.length = length + 1
|
35
|
+
}
|
36
|
+
end
|
37
|
+
|
38
|
+
def prepend(item)
|
39
|
+
return initialize(item) if empty?
|
40
|
+
Node.new(item, right: head).tap { |new_head|
|
41
|
+
head.left = new_head
|
42
|
+
self.head = new_head
|
43
|
+
self.length = length + 1
|
44
|
+
}
|
45
|
+
end
|
46
|
+
alias :unshift :prepend
|
47
|
+
|
48
|
+
def pop
|
49
|
+
tail.tap {
|
50
|
+
new_tail = tail.left
|
51
|
+
new_tail.right = nil
|
52
|
+
self.tail = new_tail
|
53
|
+
self.length = length - 1
|
54
|
+
}
|
55
|
+
end
|
56
|
+
|
57
|
+
def lpop
|
58
|
+
head.tap {
|
59
|
+
new_head = head.right
|
60
|
+
new_head.left = nil
|
61
|
+
self.head = new_head
|
62
|
+
self.length = length - 1
|
63
|
+
}
|
64
|
+
end
|
65
|
+
alias :shift :lpop
|
66
|
+
|
67
|
+
def move_to_head(node)
|
68
|
+
excise(node)
|
69
|
+
node.right = head
|
70
|
+
self.head = node
|
71
|
+
end
|
72
|
+
|
73
|
+
def empty?
|
74
|
+
length == 0
|
75
|
+
end
|
76
|
+
|
77
|
+
# TODO - move these to a module and use in caches, too
|
78
|
+
alias :blank? :empty?
|
79
|
+
def present?
|
80
|
+
!empty?
|
81
|
+
end
|
82
|
+
def presence
|
83
|
+
self if present?
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
attr_accessor :head, :tail
|
88
|
+
attr_writer :length
|
89
|
+
|
90
|
+
def excise(node)
|
91
|
+
raise InvalidNode unless node.is_a?(Node)
|
92
|
+
left = node.left
|
93
|
+
right = node.right
|
94
|
+
left.right = right unless left.nil?
|
95
|
+
right.left = left unless right.nil?
|
96
|
+
self.head = right if head == node
|
97
|
+
self.tail = left if tail == node
|
98
|
+
node
|
99
|
+
end
|
100
|
+
|
101
|
+
class Node
|
102
|
+
attr_accessor :value, :right, :left
|
103
|
+
def initialize(value, pointers = {})
|
104
|
+
self.value = value
|
105
|
+
self.right = pointers[:right]
|
106
|
+
self.left = pointers[:left]
|
107
|
+
end
|
108
|
+
|
109
|
+
def right?; !right.nil?; end
|
110
|
+
def left?; !left.nil? end
|
111
|
+
end
|
112
|
+
|
113
|
+
InvalidNode = Class.new(StandardError)
|
114
|
+
end
|
115
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require_relative 'accessible'
|
2
|
+
require_relative 'linked_list'
|
3
|
+
|
4
|
+
module HashCache
|
5
|
+
class LRU
|
6
|
+
include Accessible
|
7
|
+
attr_accessor :max_keys
|
8
|
+
|
9
|
+
def initialize(options = {})
|
10
|
+
self.max_keys = options.fetch(:max_keys, 20)
|
11
|
+
self.data = {}
|
12
|
+
self.keys = LinkedList.new
|
13
|
+
end
|
14
|
+
|
15
|
+
def [](key)
|
16
|
+
return nil unless data.has_key?(key)
|
17
|
+
value, node = data[key]
|
18
|
+
keys.move_to_head(node)
|
19
|
+
value
|
20
|
+
end
|
21
|
+
|
22
|
+
def []=(key, val)
|
23
|
+
if data.has_key?(key)
|
24
|
+
data[key][0] = val
|
25
|
+
else
|
26
|
+
node = keys.prepend(key)
|
27
|
+
data[key] = [val, node]
|
28
|
+
end
|
29
|
+
prune
|
30
|
+
val
|
31
|
+
end
|
32
|
+
|
33
|
+
def size
|
34
|
+
keys.length
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
attr_accessor :keys, :data
|
39
|
+
|
40
|
+
def prune
|
41
|
+
return unless keys.length > max_keys
|
42
|
+
expiring_node = keys.pop
|
43
|
+
data.delete(expiring_node.value)
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
data/lib/hash_cache/ttl.rb
CHANGED
@@ -1,5 +1,8 @@
|
|
1
|
+
require_relative 'accessible'
|
2
|
+
|
1
3
|
module HashCache
|
2
4
|
class TTL
|
5
|
+
include Accessible
|
3
6
|
attr_accessor :ttl, :refresh
|
4
7
|
|
5
8
|
def initialize(options = {})
|
@@ -23,18 +26,8 @@ module HashCache
|
|
23
26
|
data[key] = {time: current_time, value: val}
|
24
27
|
end
|
25
28
|
|
26
|
-
def
|
27
|
-
|
28
|
-
return default unless default_omitted
|
29
|
-
return yield(key) if block_given?
|
30
|
-
raise KeyError
|
31
|
-
end
|
32
|
-
|
33
|
-
def memoize(key)
|
34
|
-
raise ArgumentError, "Block is required" unless block_given?
|
35
|
-
self[key] # triggers flush or refresh if expired
|
36
|
-
return self[key] if data.has_key?(key)
|
37
|
-
self[key] = yield(key) if block_given?
|
29
|
+
def size
|
30
|
+
data.keys.length
|
38
31
|
end
|
39
32
|
|
40
33
|
private
|
data/lib/hash_cache/version.rb
CHANGED
@@ -0,0 +1,24 @@
|
|
1
|
+
shared_examples "fetch" do
|
2
|
+
|
3
|
+
it "returns the value if an existing key is accessed" do
|
4
|
+
expect(cache.fetch(:c)).to eq('Caspian')
|
5
|
+
end
|
6
|
+
|
7
|
+
it "raises an error if a missing key is accessed" do
|
8
|
+
expect{cache.fetch(:nonexistent)}.to raise_error(KeyError)
|
9
|
+
end
|
10
|
+
|
11
|
+
it "uses a default value if one is supplied" do
|
12
|
+
expect(cache.fetch(:nonexistent, 'hi')).to eq('hi')
|
13
|
+
end
|
14
|
+
|
15
|
+
it "gets its default from a block if one is supplied" do
|
16
|
+
expect(cache.fetch(:nonexistent) { |key| key.to_s.upcase }).to eq('NONEXISTENT')
|
17
|
+
end
|
18
|
+
|
19
|
+
it "prefers a block value to an immediate one (like native hashes do)" do
|
20
|
+
expect(cache.fetch(:nonexistent, 'hi') { |key| key.to_s.upcase }).to eq('NONEXISTENT')
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
@@ -0,0 +1,120 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require_relative '../../lib/hash_cache/linked_list'
|
3
|
+
|
4
|
+
describe HashCache::LinkedList do
|
5
|
+
|
6
|
+
let(:empty_list) { described_class.new }
|
7
|
+
let(:list) { described_class.new('lemur') }
|
8
|
+
let(:node_class) { HashCache::LinkedList::Node }
|
9
|
+
|
10
|
+
it "can convert itself to an array of values" do
|
11
|
+
expect(list.to_a).to eq(['lemur'])
|
12
|
+
end
|
13
|
+
|
14
|
+
describe '#append' do
|
15
|
+
|
16
|
+
it "adds an item to the end" do
|
17
|
+
list.append('wombat')
|
18
|
+
expect(list.to_a).to eq(['lemur', 'wombat'])
|
19
|
+
end
|
20
|
+
|
21
|
+
it "returns a Node" do
|
22
|
+
node = list.append('wombat')
|
23
|
+
expect(node).to be_a(node_class)
|
24
|
+
end
|
25
|
+
|
26
|
+
it "sets up the list correctly if it was empty to start with" do
|
27
|
+
empty_list.append('jimmy')
|
28
|
+
expect{empty_list.prepend('jammy')}.not_to raise_error
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
describe "#prepend" do
|
34
|
+
|
35
|
+
it "adds an item to the beginning" do
|
36
|
+
list.prepend('vole')
|
37
|
+
expect(list.to_a).to eq(['vole', 'lemur'])
|
38
|
+
end
|
39
|
+
|
40
|
+
it "returns a Node" do
|
41
|
+
node = list.prepend('vole')
|
42
|
+
expect(node).to be_a(node_class)
|
43
|
+
end
|
44
|
+
|
45
|
+
it "sets up the list correctly if it was empty to start with" do
|
46
|
+
empty_list.prepend('jimmy')
|
47
|
+
expect{empty_list.append('jammy')}.not_to raise_error
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
it "knows its length" do
|
53
|
+
expect(list.length).to eq(1)
|
54
|
+
list.append('wombat')
|
55
|
+
list.prepend('vole')
|
56
|
+
expect(list.length).to eq(3)
|
57
|
+
end
|
58
|
+
|
59
|
+
describe "#move_to_head" do
|
60
|
+
|
61
|
+
context "when the argument is a Node" do
|
62
|
+
|
63
|
+
it "makes it the new head" do
|
64
|
+
list.append('mittens')
|
65
|
+
node = list.append('cozy')
|
66
|
+
list.move_to_head(node)
|
67
|
+
expect(list.to_a).to eq(%w[cozy lemur mittens])
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
|
72
|
+
context "when the argument is not a Node" do
|
73
|
+
|
74
|
+
it "raises an error" do
|
75
|
+
list.append('cozy')
|
76
|
+
expect{list.move_to_head('super duper')}.to raise_error(HashCache::LinkedList::InvalidNode)
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
|
83
|
+
context "when there's more than one item" do
|
84
|
+
|
85
|
+
let(:list) {
|
86
|
+
list = described_class.new('lemur')
|
87
|
+
list.append('muskrat')
|
88
|
+
list
|
89
|
+
}
|
90
|
+
|
91
|
+
describe "#pop" do
|
92
|
+
|
93
|
+
it "removes the last item" do
|
94
|
+
list.pop
|
95
|
+
expect(list.to_a).to eq(['lemur'])
|
96
|
+
end
|
97
|
+
|
98
|
+
it "returns a node" do
|
99
|
+
expect(list.pop).to be_a(node_class)
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
103
|
+
|
104
|
+
|
105
|
+
describe "#lpop" do
|
106
|
+
|
107
|
+
it "removes the first item" do
|
108
|
+
list.lpop
|
109
|
+
expect(list.to_a).to eq(['muskrat'])
|
110
|
+
end
|
111
|
+
|
112
|
+
it "returns a node" do
|
113
|
+
expect(list.lpop).to be_a(node_class)
|
114
|
+
end
|
115
|
+
|
116
|
+
end
|
117
|
+
|
118
|
+
end
|
119
|
+
|
120
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'fetch_examples'
|
3
|
+
require_relative '../../lib/hash_cache/lru'
|
4
|
+
|
5
|
+
describe HashCache::LRU do
|
6
|
+
|
7
|
+
let(:options) { {max_keys: 4} }
|
8
|
+
let(:cache) {
|
9
|
+
described_class.new(options).tap {|c|
|
10
|
+
c[:a] = 'Alambil'
|
11
|
+
c[:b] = 'Belisar'
|
12
|
+
c[:c] = 'Caspian'
|
13
|
+
}
|
14
|
+
}
|
15
|
+
|
16
|
+
it "remembers its values" do
|
17
|
+
expect(cache[:a]).to eq('Alambil')
|
18
|
+
expect(cache[:b]).to eq('Belisar')
|
19
|
+
expect(cache[:c]).to eq('Caspian')
|
20
|
+
end
|
21
|
+
|
22
|
+
it "can report its size" do
|
23
|
+
expect(cache.size).to eq(3)
|
24
|
+
end
|
25
|
+
|
26
|
+
it "can grow up to the max size given" do
|
27
|
+
cache[:d] = 'Destrier'
|
28
|
+
expect(cache.size).to eq(4)
|
29
|
+
end
|
30
|
+
|
31
|
+
it "does not grow beyond the max size given" do
|
32
|
+
cache[:d] = 'Destrier'
|
33
|
+
cache[:e] = 'Erimon'
|
34
|
+
expect(cache.size).to eq(4)
|
35
|
+
end
|
36
|
+
|
37
|
+
it "drops the least-recently accessed key" do
|
38
|
+
cache[:d] = 'Destrier'
|
39
|
+
cache[:e] = 'Erimon'
|
40
|
+
expect(cache[:a]).to be_nil
|
41
|
+
end
|
42
|
+
|
43
|
+
it "counts a read as access" do
|
44
|
+
cache[:d] = 'Destrier'
|
45
|
+
cache[:a]
|
46
|
+
cache[:e] = 'Erimon'
|
47
|
+
expect(cache[:a]).to eq('Alambil')
|
48
|
+
expect(cache[:b]).to be_nil
|
49
|
+
end
|
50
|
+
|
51
|
+
include_examples "fetch"
|
52
|
+
|
53
|
+
describe "memoize" do
|
54
|
+
|
55
|
+
it "requires a block" do
|
56
|
+
expect{cache_memoize(:c)}.to raise_error
|
57
|
+
end
|
58
|
+
|
59
|
+
let(:greeting) { 'hi' }
|
60
|
+
|
61
|
+
context "when the key already exists" do
|
62
|
+
|
63
|
+
it "does not calculate the value" do
|
64
|
+
expect(greeting).not_to receive(:upcase)
|
65
|
+
cache.memoize(:c) { greeting.upcase }
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
context "when the key has not been set" do
|
71
|
+
|
72
|
+
let(:key) { :nonexistent }
|
73
|
+
|
74
|
+
it "calculates the value" do
|
75
|
+
expect(greeting).to receive(:upcase)
|
76
|
+
cache.memoize(:nonexistent) { greeting.upcase }
|
77
|
+
end
|
78
|
+
|
79
|
+
it "sets the value" do
|
80
|
+
cache.memoize(:nonexistent) { greeting.upcase }
|
81
|
+
expect(cache[:nonexistent]).to eq(greeting.upcase)
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
|
86
|
+
context "when the key has been dropped" do
|
87
|
+
|
88
|
+
before :each do
|
89
|
+
cache[:d] = 'Destrier'
|
90
|
+
cache[:e] = 'Erimon'
|
91
|
+
end
|
92
|
+
|
93
|
+
it "calculates the value" do
|
94
|
+
expect(greeting).to receive(:upcase)
|
95
|
+
cache.memoize(:a) { greeting.upcase }
|
96
|
+
end
|
97
|
+
|
98
|
+
it "sets the value" do
|
99
|
+
cache.memoize(:a) { greeting.upcase }
|
100
|
+
expect(cache[:a]).to eq(greeting.upcase)
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|
106
|
+
|
107
|
+
end
|
108
|
+
|
data/spec/hash_cache/ttl_spec.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'spec_helper'
|
2
|
+
require 'fetch_examples'
|
2
3
|
require_relative '../../lib/hash_cache/ttl'
|
3
4
|
|
4
5
|
describe HashCache::TTL do
|
@@ -15,6 +16,10 @@ describe HashCache::TTL do
|
|
15
16
|
let(:before_ttl) { start_time + 1800 }
|
16
17
|
let(:after_ttl) { start_time + 3601 }
|
17
18
|
|
19
|
+
it "can report its size" do
|
20
|
+
expect(cache.size).to eq(1)
|
21
|
+
end
|
22
|
+
|
18
23
|
it "remembers cached values before the TTL expires" do
|
19
24
|
expect(cache[:c]).to eq('Caspian')
|
20
25
|
cache.stub(:current_time).and_return(before_ttl)
|
@@ -61,29 +66,7 @@ describe HashCache::TTL do
|
|
61
66
|
|
62
67
|
end
|
63
68
|
|
64
|
-
|
65
|
-
|
66
|
-
it "returns the value if an existing key is accessed" do
|
67
|
-
expect(cache.fetch(:c)).to eq('Caspian')
|
68
|
-
end
|
69
|
-
|
70
|
-
it "raises an error if a missing key is accessed" do
|
71
|
-
expect{cache.fetch(:nonexistent)}.to raise_error(KeyError)
|
72
|
-
end
|
73
|
-
|
74
|
-
it "uses a default value if one is supplied" do
|
75
|
-
expect(cache.fetch(:nonexistent, 'hi')).to eq('hi')
|
76
|
-
end
|
77
|
-
|
78
|
-
it "gets its default from a block if one is supplied" do
|
79
|
-
expect(cache.fetch(:nonexistent) { |key| key.to_s.upcase }).to eq('NONEXISTENT')
|
80
|
-
end
|
81
|
-
|
82
|
-
it "uses the immediate value if both it and the block are supplied" do
|
83
|
-
expect(cache.fetch(:nonexistent, 'hi') { |key| key.to_s.upcase }).to eq('hi')
|
84
|
-
end
|
85
|
-
|
86
|
-
end
|
69
|
+
include_examples "fetch"
|
87
70
|
|
88
71
|
describe "memoize" do
|
89
72
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hash_cache
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nathan Long
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-09-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -52,7 +52,7 @@ dependencies:
|
|
52
52
|
- - ~>
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '2.14'
|
55
|
-
description:
|
55
|
+
description: Renamed - see 'caches' gem
|
56
56
|
email:
|
57
57
|
- nathanmlong@gmail.com
|
58
58
|
executables: []
|
@@ -64,10 +64,18 @@ files:
|
|
64
64
|
- LICENSE.txt
|
65
65
|
- README.md
|
66
66
|
- Rakefile
|
67
|
+
- TODO.md
|
67
68
|
- hash_cache.gemspec
|
68
69
|
- lib/hash_cache.rb
|
70
|
+
- lib/hash_cache/accessible.rb
|
71
|
+
- lib/hash_cache/all.rb
|
72
|
+
- lib/hash_cache/linked_list.rb
|
73
|
+
- lib/hash_cache/lru.rb
|
69
74
|
- lib/hash_cache/ttl.rb
|
70
75
|
- lib/hash_cache/version.rb
|
76
|
+
- spec/fetch_examples.rb
|
77
|
+
- spec/hash_cache/linked_list_spec.rb
|
78
|
+
- spec/hash_cache/lru_spec.rb
|
71
79
|
- spec/hash_cache/ttl_spec.rb
|
72
80
|
- spec/spec_helper.rb
|
73
81
|
homepage: https://github.com/nathanl/hash_cache
|
@@ -93,7 +101,10 @@ rubyforge_project:
|
|
93
101
|
rubygems_version: 2.0.14
|
94
102
|
signing_key:
|
95
103
|
specification_version: 4
|
96
|
-
summary:
|
104
|
+
summary: Renamed - see 'caches' gem
|
97
105
|
test_files:
|
106
|
+
- spec/fetch_examples.rb
|
107
|
+
- spec/hash_cache/linked_list_spec.rb
|
108
|
+
- spec/hash_cache/lru_spec.rb
|
98
109
|
- spec/hash_cache/ttl_spec.rb
|
99
110
|
- spec/spec_helper.rb
|