caches 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- !binary "U0hBMQ==":
3
- metadata.gz: 7a1b49f6dedacf8fe673dd362a21bea4b7c58e70
4
- data.tar.gz: 3d4610b424dba43177a5578335b10d6c8aa05d3a
5
- !binary "U0hBNTEy":
6
- metadata.gz: b379b47737f67aa92c477341b9408ef39e5eb59fed51af6393c11a6e1f66d8901c898fab63101ee01b0055155946e9504a360c3af3d0dc71b7d8f26360d144b0
7
- data.tar.gz: 1efbb38364ee01c8fef3b5380c3ae34b1afad063575f673efab18bea28e1ec1f08a226b0fd14089efed1b75e656d2628d60cdffdf5d561f724ce319062d25451
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: 5)
16
+ h = Caches::TTL.new(ttl: 0.01)
17
17
  h[:a] = 'aardvark'
18
18
  h[:a] #=> 'aardvark'
19
- sleep(6)
19
+ sleep(0.02)
20
20
  h[:a] #=> nil
21
21
  ```
22
22
 
23
- If you pass `refresh: true`, reading a value will reset its timer; otherwise, only writing will.
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
@@ -83,6 +83,12 @@ module Caches
83
83
  self if present?
84
84
  end
85
85
 
86
+ def delete(node)
87
+ excise(node).tap {
88
+ self.length = length - 1
89
+ }
90
+ end
91
+
86
92
  private
87
93
  attr_accessor :head, :tail
88
94
  attr_writer :length
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 = options.fetch(:ttl) { 3600 }
10
- self.refresh = !!(options.fetch(:refresh, false))
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 (current_time - data[key][:time]) < ttl
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[key] = {time: current_time, value: val}
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
- data.keys.length
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
- attr_accessor :data
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
@@ -1,3 +1,3 @@
1
1
  module Caches
2
- VERSION = "0.0.2"
2
+ VERSION = "0.0.3"
3
3
  end
@@ -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
- 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)
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(:options) { {} }
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(1)
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.stub(:current_time).and_return(before_ttl)
26
- expect(cache[:c]).to eq('Caspian')
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
- cache.stub(:current_time).and_return(after_ttl)
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
- cache.stub(:current_time).and_return(after_ttl)
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.stub(:current_time).and_return(before_ttl)
65
+ expect(cache[:c]).to eq('Caspian')
66
+ sleep(most_of_ttl)
44
67
  cache[:c] = 'Cornelius'
45
- cache.stub(:current_time).and_return(after_ttl)
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
- cache.stub(:current_time).and_return(after_ttl)
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
- cache.stub(:current_time).and_return(before_ttl)
85
+ sleep(most_of_ttl)
62
86
  expect(cache[:c]).to eq('Caspian')
63
- cache.stub(:current_time).and_return(after_ttl)
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.stub(:current_time).and_return(after_ttl)
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.stub(:current_time).and_return(after_ttl)
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
- cache.stub(:current_time).and_return(before_ttl)
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
- cache.stub(:current_time).and_return(after_ttl)
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.2
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-07-28 00:00:00.000000000 Z
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