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 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