caches 0.0.2 → 0.0.3
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.
- checksums.yaml +6 -6
- data/CHANGELOG.md +18 -0
- data/README.md +13 -9
- data/lib/caches/linked_list.rb +6 -0
- data/lib/caches/ttl.rb +42 -7
- data/lib/caches/version.rb +1 -1
- data/spec/{hash_cache → caches}/linked_list_spec.rb +29 -5
- data/spec/{hash_cache → caches}/ttl_spec.rb +96 -24
- metadata +13 -12
- /data/spec/{hash_cache → caches}/lru_spec.rb +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
5
|
-
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 5b755f8f05416c7bd3e24c8ba950f77ac2daea8c
|
4
|
+
data.tar.gz: a698c0d6cfeaf897adef53e6f13b028e2531ce1d
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 639943fc289cfe0bb9e78c52671ca55f6908b2ec282da95222b60303a15138f3274b18ce8627de05a22f0d233d31430dc5196df3d8b475917dfe8b23a7b56cc4
|
7
|
+
data.tar.gz: 890232456a3682cb1883332c317cc0cfb68868801ef468f17faa7f0e4d130dec53590e61820e42176506210fa4e6e5397c38349c47834d35c662efdd396d5d76
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
# Change Log
|
2
|
+
All notable changes to this project will be documented in this file.
|
3
|
+
|
4
|
+
## Unreleased
|
5
|
+
|
6
|
+
### Added
|
7
|
+
|
8
|
+
Nothing
|
9
|
+
|
10
|
+
### Changed
|
11
|
+
Nothing
|
12
|
+
|
13
|
+
### Removed
|
14
|
+
Nothing
|
15
|
+
|
16
|
+
## 0.0.3 - 2014-08-22
|
17
|
+
|
18
|
+
Currently have Caches::TTL and Caches::LRU
|
data/README.md
CHANGED
@@ -8,21 +8,29 @@
|
|
8
8
|
|
9
9
|
### Caches::TTL
|
10
10
|
|
11
|
-
TTL (Time To Live) remembers values for as many seconds as you tell it. The default is 3600 seconds (1 hour).
|
11
|
+
TTL (Time To Live) remembers values for as many seconds as you tell it. The default is 3600 seconds (1 hour). Sub-seconds are supported; the tests will fail if the value is too low for your machine.
|
12
12
|
|
13
13
|
```ruby
|
14
14
|
require 'caches/ttl'
|
15
15
|
|
16
|
-
h = Caches::TTL.new(ttl:
|
16
|
+
h = Caches::TTL.new(ttl: 0.01)
|
17
17
|
h[:a] = 'aardvark'
|
18
18
|
h[:a] #=> 'aardvark'
|
19
|
-
sleep(
|
19
|
+
sleep(0.02)
|
20
20
|
h[:a] #=> nil
|
21
21
|
```
|
22
22
|
|
23
|
-
|
23
|
+
#### Initialization Options
|
24
|
+
|
25
|
+
- With `refresh: true`, reading a value will reset its timer; otherwise, only writing will.
|
26
|
+
- With `max_keys: 5`, on insertion, it will evict the oldest item if necessary to keep from exceeding 5 keys.
|
27
|
+
|
28
|
+
#### Methods
|
29
|
+
|
30
|
+
`keys` and `values` work the same as for a hash. They **do not** check whether each key is current.
|
31
|
+
|
32
|
+
`memoize` method fetches a key if it exists and isn't expired; otherwise, it calculates the value using the block and saves it.
|
24
33
|
|
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
34
|
|
27
35
|
```ruby
|
28
36
|
h = Caches::TTL.new(ttl: 5)
|
@@ -67,10 +75,6 @@ Or install it yourself as:
|
|
67
75
|
|
68
76
|
$ gem install caches
|
69
77
|
|
70
|
-
## Coming Soon
|
71
|
-
|
72
|
-
Other classes with different cache expiration strategies.
|
73
|
-
|
74
78
|
## Contributing
|
75
79
|
|
76
80
|
1. Fork it
|
data/lib/caches/linked_list.rb
CHANGED
data/lib/caches/ttl.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
require 'time'
|
1
2
|
require_relative 'accessible'
|
2
3
|
|
3
4
|
module Caches
|
@@ -6,33 +7,67 @@ module Caches
|
|
6
7
|
attr_accessor :ttl, :refresh
|
7
8
|
|
8
9
|
def initialize(options = {})
|
9
|
-
self.ttl
|
10
|
-
self.refresh
|
11
|
-
self.data
|
10
|
+
self.ttl = options.fetch(:ttl) { 3600 }
|
11
|
+
self.refresh = !!(options.fetch(:refresh, false))
|
12
|
+
self.data = {}
|
13
|
+
self.nodes = LinkedList.new
|
14
|
+
self.max_keys = options[:max_keys]
|
12
15
|
end
|
13
16
|
|
14
17
|
def [](key)
|
15
18
|
return nil unless data.has_key?(key)
|
16
|
-
if (
|
19
|
+
if current?(key)
|
17
20
|
data[key][:time] = current_time if refresh
|
18
21
|
data[key][:value]
|
19
22
|
else
|
23
|
+
node = data[key][:node]
|
24
|
+
nodes.delete(node)
|
20
25
|
data.delete(key)
|
21
26
|
nil
|
22
27
|
end
|
23
28
|
end
|
24
29
|
|
25
30
|
def []=(key, val)
|
26
|
-
data
|
31
|
+
if data.has_key?(key)
|
32
|
+
node = data[key][:node]
|
33
|
+
nodes.move_to_head(node)
|
34
|
+
else
|
35
|
+
if full?
|
36
|
+
evicted_node = nodes.pop
|
37
|
+
data.delete(evicted_node.value)
|
38
|
+
end
|
39
|
+
node = nodes.prepend(key)
|
40
|
+
end
|
41
|
+
data[key] = {time: current_time, value: val, node: node}
|
27
42
|
end
|
28
43
|
|
29
44
|
def size
|
30
|
-
|
45
|
+
keys.length
|
46
|
+
end
|
47
|
+
|
48
|
+
def keys
|
49
|
+
data.keys
|
50
|
+
end
|
51
|
+
|
52
|
+
def values
|
53
|
+
data.values.map { |h| h.fetch(:value) }
|
31
54
|
end
|
32
55
|
|
33
56
|
private
|
34
57
|
|
35
|
-
|
58
|
+
def current?(key)
|
59
|
+
(current_time - data[key][:time]) < ttl
|
60
|
+
end
|
61
|
+
|
62
|
+
def expired?(key)
|
63
|
+
!current?(key)
|
64
|
+
end
|
65
|
+
|
66
|
+
def full?
|
67
|
+
max_keys && size >= max_keys
|
68
|
+
end
|
69
|
+
|
70
|
+
attr_accessor :data, :nodes, :max_keys
|
36
71
|
|
37
72
|
def current_time
|
38
73
|
Time.now
|
data/lib/caches/version.rb
CHANGED
@@ -11,6 +11,13 @@ describe Caches::LinkedList do
|
|
11
11
|
expect(list.to_a).to eq(['lemur'])
|
12
12
|
end
|
13
13
|
|
14
|
+
it "knows its length" do
|
15
|
+
expect(list.length).to eq(1)
|
16
|
+
list.append('wombat')
|
17
|
+
list.prepend('vole')
|
18
|
+
expect(list.length).to eq(3)
|
19
|
+
end
|
20
|
+
|
14
21
|
describe '#append' do
|
15
22
|
|
16
23
|
it "adds an item to the end" do
|
@@ -49,11 +56,28 @@ describe Caches::LinkedList do
|
|
49
56
|
|
50
57
|
end
|
51
58
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
59
|
+
describe "#delete" do
|
60
|
+
|
61
|
+
before :each do
|
62
|
+
@wombat_node = list.append('wombat')
|
63
|
+
list.append('vole')
|
64
|
+
expect(list.to_a).to eq(%w[lemur wombat vole])
|
65
|
+
expect(list.length).to eq(3)
|
66
|
+
@deleted_node = list.delete(@wombat_node)
|
67
|
+
end
|
68
|
+
|
69
|
+
it "removes an item" do
|
70
|
+
expect(list.to_a).to eq(%w[lemur vole])
|
71
|
+
end
|
72
|
+
|
73
|
+
it "returns the removed item" do
|
74
|
+
expect(@deleted_node).to eq(@wombat_node)
|
75
|
+
end
|
76
|
+
|
77
|
+
it "decrements the length" do
|
78
|
+
expect(list.length).to eq(2)
|
79
|
+
end
|
80
|
+
|
57
81
|
end
|
58
82
|
|
59
83
|
describe "#move_to_head" do
|
@@ -4,63 +4,87 @@ require_relative '../../lib/caches/ttl'
|
|
4
4
|
|
5
5
|
describe Caches::TTL do
|
6
6
|
|
7
|
-
let(:
|
7
|
+
let(:ttl) { 0.01 }
|
8
|
+
let(:most_of_ttl) { ttl * 0.8 }
|
9
|
+
let(:options) { { ttl: ttl} }
|
8
10
|
let(:cache) {
|
9
11
|
described_class.new(options).tap {|c|
|
12
|
+
c[:a] = 'Aravis'
|
13
|
+
c[:b] = 'Bern'
|
10
14
|
c[:c] = 'Caspian'
|
11
|
-
c.stub(:current_time).and_return(start_time)
|
12
15
|
}
|
13
16
|
}
|
14
17
|
|
15
|
-
let(:start_time) { Time.now }
|
16
|
-
let(:before_ttl) { start_time + 1800 }
|
17
|
-
let(:after_ttl) { start_time + 3601 }
|
18
|
-
|
19
18
|
it "can report its size" do
|
20
|
-
expect(cache.size).to eq(
|
19
|
+
expect(cache.size).to eq(3)
|
21
20
|
end
|
22
21
|
|
23
22
|
it "remembers cached values before the TTL expires" do
|
24
23
|
expect(cache[:c]).to eq('Caspian')
|
25
|
-
cache.
|
26
|
-
|
24
|
+
expect(cache.size).to eq(3)
|
25
|
+
# testing private method
|
26
|
+
expect(cache.send(:nodes).length).to eq(3)
|
27
27
|
end
|
28
28
|
|
29
|
-
it "forgets cached values after the TTL expires" do
|
29
|
+
it "forgets cached values when a read is attempted after the TTL expires" do
|
30
30
|
expect(cache[:c]).to eq('Caspian')
|
31
|
-
|
31
|
+
sleep(ttl)
|
32
32
|
expect(cache[:c]).to be_nil
|
33
|
+
expect(cache.size).to eq(2)
|
34
|
+
# testing private method
|
35
|
+
expect(cache.send(:nodes).length).to eq(2)
|
36
|
+
expect(cache[:a]).to be_nil
|
37
|
+
expect(cache.size).to eq(1)
|
38
|
+
# testing private method
|
39
|
+
expect(cache.send(:nodes).length).to eq(1)
|
40
|
+
end
|
41
|
+
|
42
|
+
describe "listing keys and values" do
|
43
|
+
|
44
|
+
it "can list them" do
|
45
|
+
expect(cache.keys).to eq(%i[a b c])
|
46
|
+
expect(cache.values).to eq(%w[Aravis Bern Caspian])
|
47
|
+
end
|
48
|
+
|
49
|
+
it "does not check whether they are current" do
|
50
|
+
sleep(ttl)
|
51
|
+
expect(cache.keys).to eq(%i[a b c])
|
52
|
+
expect(cache.values).to eq(%w[Aravis Bern Caspian])
|
53
|
+
end
|
54
|
+
|
33
55
|
end
|
34
56
|
|
35
57
|
it "continues returning nil for cached values after the TTL expires" do
|
36
58
|
expect(cache[:c]).to eq('Caspian')
|
37
|
-
|
59
|
+
sleep(ttl)
|
38
60
|
expect(cache[:c]).to be_nil
|
39
61
|
expect(cache[:c]).to be_nil
|
40
62
|
end
|
41
63
|
|
42
64
|
it "resets TTL when an item is updated" do
|
43
|
-
cache
|
65
|
+
expect(cache[:c]).to eq('Caspian')
|
66
|
+
sleep(most_of_ttl)
|
44
67
|
cache[:c] = 'Cornelius'
|
45
|
-
|
68
|
+
sleep(most_of_ttl)
|
46
69
|
expect(cache[:c]).to eq('Cornelius')
|
47
70
|
end
|
48
71
|
|
49
72
|
it "doesn't reset TTL when an item is accessed" do
|
50
|
-
cache.stub(:current_time).and_return(before_ttl)
|
51
73
|
expect(cache[:c]).to eq('Caspian')
|
52
|
-
|
74
|
+
sleep(most_of_ttl)
|
75
|
+
expect(cache[:c]).to eq('Caspian')
|
76
|
+
sleep(most_of_ttl)
|
53
77
|
expect(cache[:c]).to be_nil
|
54
78
|
end
|
55
79
|
|
56
80
|
context "when asked to refresh TTL on access" do
|
57
81
|
|
58
|
-
let(:options) { {refresh: true} }
|
82
|
+
let(:options) { {ttl: ttl, refresh: true} }
|
59
83
|
|
60
84
|
it "keeps values that were accessed before the TTL expired" do
|
61
|
-
|
85
|
+
sleep(most_of_ttl)
|
62
86
|
expect(cache[:c]).to eq('Caspian')
|
63
|
-
|
87
|
+
sleep(most_of_ttl)
|
64
88
|
expect(cache[:c]).to eq('Caspian')
|
65
89
|
end
|
66
90
|
|
@@ -85,7 +109,6 @@ describe Caches::TTL do
|
|
85
109
|
end
|
86
110
|
|
87
111
|
it "does not calculate the value" do
|
88
|
-
cache.stub(:current_time).and_return(before_ttl)
|
89
112
|
expect(greeting).not_to receive(:upcase)
|
90
113
|
cache.memoize(:c) { greeting.upcase }
|
91
114
|
end
|
@@ -95,13 +118,15 @@ describe Caches::TTL do
|
|
95
118
|
context "after the TTL is up" do
|
96
119
|
|
97
120
|
it "recalculates the value" do
|
98
|
-
cache
|
121
|
+
expect(cache[:c]).to eq('Caspian')
|
122
|
+
sleep(ttl)
|
99
123
|
expect(greeting).to receive(:upcase)
|
100
124
|
cache.memoize(:c) { greeting.upcase }
|
101
125
|
end
|
102
126
|
|
103
127
|
it "returns the calculated value" do
|
104
|
-
cache
|
128
|
+
expect(cache[:c]).to eq('Caspian')
|
129
|
+
sleep(ttl)
|
105
130
|
expect(cache.memoize(:c) { |key| key.to_s.upcase }).to eq('C')
|
106
131
|
end
|
107
132
|
|
@@ -122,14 +147,14 @@ describe Caches::TTL do
|
|
122
147
|
|
123
148
|
it "does not calculate the value again within the TTL" do
|
124
149
|
cache.memoize(:nonexistent) { greeting.upcase }
|
125
|
-
|
150
|
+
sleep(most_of_ttl)
|
126
151
|
expect(greeting).not_to receive(:upcase)
|
127
152
|
cache.memoize(:nonexistent) { greeting.upcase }
|
128
153
|
end
|
129
154
|
|
130
155
|
it "does calculate the value again after the TTL is up" do
|
131
156
|
cache.memoize(:nonexistent) { greeting.upcase }
|
132
|
-
|
157
|
+
sleep(ttl)
|
133
158
|
expect(greeting).to receive(:upcase)
|
134
159
|
cache.memoize(:nonexistent) { greeting.upcase }
|
135
160
|
end
|
@@ -138,4 +163,51 @@ describe Caches::TTL do
|
|
138
163
|
|
139
164
|
end
|
140
165
|
|
166
|
+
describe "supporting a maximum size" do
|
167
|
+
|
168
|
+
let(:options) { {ttl: ttl, max_keys: 3} }
|
169
|
+
|
170
|
+
let!(:cache) {
|
171
|
+
described_class.new(options).tap {|c|
|
172
|
+
c[:a] = 'Aravis'
|
173
|
+
c[:b] = 'Bern'
|
174
|
+
}
|
175
|
+
}
|
176
|
+
|
177
|
+
context "when it's not full" do
|
178
|
+
|
179
|
+
it "doesn't evict on insertion" do
|
180
|
+
expect(cache.size).to eq(2)
|
181
|
+
cache[:c] = 'Caspian'
|
182
|
+
expect(cache.size).to eq(3)
|
183
|
+
end
|
184
|
+
|
185
|
+
end
|
186
|
+
|
187
|
+
context "when it's full" do
|
188
|
+
|
189
|
+
before :each do
|
190
|
+
cache[:c] = 'Caspian'
|
191
|
+
expect(cache.size).to eq(3)
|
192
|
+
end
|
193
|
+
|
194
|
+
it "evicts the oldest key on insertion" do
|
195
|
+
cache[:d] = 'Drinian'
|
196
|
+
expect(cache.size).to eq(3)
|
197
|
+
end
|
198
|
+
|
199
|
+
it "doesn't evict on update" do
|
200
|
+
cache[:c] = 'Corin'
|
201
|
+
expect(cache.size).to eq(3)
|
202
|
+
end
|
203
|
+
|
204
|
+
it "doesn't evict on read" do
|
205
|
+
cache[:c]
|
206
|
+
expect(cache.size).to eq(3)
|
207
|
+
end
|
208
|
+
|
209
|
+
end
|
210
|
+
|
211
|
+
end
|
212
|
+
|
141
213
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: caches
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.3
|
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
|
@@ -28,14 +28,14 @@ dependencies:
|
|
28
28
|
name: rake
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- -
|
31
|
+
- - '>='
|
32
32
|
- !ruby/object:Gem::Version
|
33
33
|
version: '0'
|
34
34
|
type: :development
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
|
-
- -
|
38
|
+
- - '>='
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '0'
|
41
41
|
- !ruby/object:Gem::Dependency
|
@@ -61,6 +61,7 @@ extensions: []
|
|
61
61
|
extra_rdoc_files: []
|
62
62
|
files:
|
63
63
|
- .gitignore
|
64
|
+
- CHANGELOG.md
|
64
65
|
- Gemfile
|
65
66
|
- LICENSE.txt
|
66
67
|
- README.md
|
@@ -74,10 +75,10 @@ files:
|
|
74
75
|
- lib/caches/lru.rb
|
75
76
|
- lib/caches/ttl.rb
|
76
77
|
- lib/caches/version.rb
|
78
|
+
- spec/caches/linked_list_spec.rb
|
79
|
+
- spec/caches/lru_spec.rb
|
80
|
+
- spec/caches/ttl_spec.rb
|
77
81
|
- spec/fetch_examples.rb
|
78
|
-
- spec/hash_cache/linked_list_spec.rb
|
79
|
-
- spec/hash_cache/lru_spec.rb
|
80
|
-
- spec/hash_cache/ttl_spec.rb
|
81
82
|
- spec/spec_helper.rb
|
82
83
|
homepage: https://github.com/nathanl/caches
|
83
84
|
licenses:
|
@@ -89,12 +90,12 @@ require_paths:
|
|
89
90
|
- lib
|
90
91
|
required_ruby_version: !ruby/object:Gem::Requirement
|
91
92
|
requirements:
|
92
|
-
- -
|
93
|
+
- - '>='
|
93
94
|
- !ruby/object:Gem::Version
|
94
95
|
version: '0'
|
95
96
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
96
97
|
requirements:
|
97
|
-
- -
|
98
|
+
- - '>='
|
98
99
|
- !ruby/object:Gem::Version
|
99
100
|
version: '0'
|
100
101
|
requirements: []
|
@@ -104,8 +105,8 @@ signing_key:
|
|
104
105
|
specification_version: 4
|
105
106
|
summary: Caches with hash-like access
|
106
107
|
test_files:
|
108
|
+
- spec/caches/linked_list_spec.rb
|
109
|
+
- spec/caches/lru_spec.rb
|
110
|
+
- spec/caches/ttl_spec.rb
|
107
111
|
- spec/fetch_examples.rb
|
108
|
-
- spec/hash_cache/linked_list_spec.rb
|
109
|
-
- spec/hash_cache/lru_spec.rb
|
110
|
-
- spec/hash_cache/ttl_spec.rb
|
111
112
|
- spec/spec_helper.rb
|
File without changes
|