hash_cache 0.0.1
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 +7 -0
- data/.gitignore +19 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +70 -0
- data/Rakefile +9 -0
- data/hash_cache.gemspec +25 -0
- data/lib/hash_cache/ttl.rb +49 -0
- data/lib/hash_cache/version.rb +3 -0
- data/lib/hash_cache.rb +5 -0
- data/spec/hash_cache/ttl_spec.rb +158 -0
- data/spec/spec_helper.rb +6 -0
- metadata +99 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 61ac041d1e693549eb5e98520b4e974b0f8197f7
|
4
|
+
data.tar.gz: 42c22e0f28c1df9c7d91073e29455cab89c4075a
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 6ee6af4165117ee25f8f8c707aeba7c8a923ef9af0bea40af20ffe2c72f443391edbb81959157f97c97819468e001ec7d6d9ddf7c6a39409cbc5c35f482bf0bc
|
7
|
+
data.tar.gz: f9be59948e4c22d7801f5e3055ba1ccdbb2c7a2d9b1a0bf12b4e8a933215db8abf5399b2575218bd9cd7fc6a10dd458e736d65a42928395587fd9ad03e3db96d
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Nathan Long
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
# HashCache
|
2
|
+
|
3
|
+
HashCache is a small collection of hashes that cache data.
|
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).
|
data/Rakefile
ADDED
data/hash_cache.gemspec
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'hash_cache/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "hash_cache"
|
8
|
+
spec.version = HashCache::VERSION
|
9
|
+
spec.authors = ["Nathan Long"]
|
10
|
+
spec.email = ["nathanmlong@gmail.com"]
|
11
|
+
spec.description = %q{A small collection of hashes that cache}
|
12
|
+
spec.summary = %q{A small collection of hashes that cache}
|
13
|
+
spec.homepage = "https://github.com/nathanl/hash_cache"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
22
|
+
spec.add_development_dependency "rake"
|
23
|
+
spec.add_development_dependency "rspec", "~> 2.14"
|
24
|
+
|
25
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module HashCache
|
2
|
+
class TTL
|
3
|
+
attr_accessor :ttl, :refresh
|
4
|
+
|
5
|
+
def initialize(options = {})
|
6
|
+
self.ttl = options.fetch(:ttl) { 3600 }
|
7
|
+
self.refresh = !!(options.fetch(:refresh, false))
|
8
|
+
self.data = {}
|
9
|
+
end
|
10
|
+
|
11
|
+
def [](key)
|
12
|
+
return nil unless data.has_key?(key)
|
13
|
+
if (current_time - data[key][:time]) < ttl
|
14
|
+
data[key][:time] = current_time if refresh
|
15
|
+
data[key][:value]
|
16
|
+
else
|
17
|
+
data.delete(key)
|
18
|
+
nil
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def []=(key, val)
|
23
|
+
data[key] = {time: current_time, value: val}
|
24
|
+
end
|
25
|
+
|
26
|
+
def fetch(key, default = (default_omitted = true; nil))
|
27
|
+
return self[key] if data.has_key?(key)
|
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?
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
attr_accessor :data
|
43
|
+
|
44
|
+
def current_time
|
45
|
+
Time.now
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
end
|
data/lib/hash_cache.rb
ADDED
@@ -0,0 +1,158 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require_relative '../../lib/hash_cache/ttl'
|
3
|
+
|
4
|
+
describe HashCache::TTL do
|
5
|
+
|
6
|
+
let(:options) { {} }
|
7
|
+
let(:cache) {
|
8
|
+
described_class.new(options).tap {|c|
|
9
|
+
c[:c] = 'Caspian'
|
10
|
+
c.stub(:current_time).and_return(start_time)
|
11
|
+
}
|
12
|
+
}
|
13
|
+
|
14
|
+
let(:start_time) { Time.now }
|
15
|
+
let(:before_ttl) { start_time + 1800 }
|
16
|
+
let(:after_ttl) { start_time + 3601 }
|
17
|
+
|
18
|
+
it "remembers cached values before the TTL expires" do
|
19
|
+
expect(cache[:c]).to eq('Caspian')
|
20
|
+
cache.stub(:current_time).and_return(before_ttl)
|
21
|
+
expect(cache[:c]).to eq('Caspian')
|
22
|
+
end
|
23
|
+
|
24
|
+
it "forgets cached values after the TTL expires" do
|
25
|
+
expect(cache[:c]).to eq('Caspian')
|
26
|
+
cache.stub(:current_time).and_return(after_ttl)
|
27
|
+
expect(cache[:c]).to be_nil
|
28
|
+
end
|
29
|
+
|
30
|
+
it "continues returning nil for cached values after the TTL expires" do
|
31
|
+
expect(cache[:c]).to eq('Caspian')
|
32
|
+
cache.stub(:current_time).and_return(after_ttl)
|
33
|
+
expect(cache[:c]).to be_nil
|
34
|
+
expect(cache[:c]).to be_nil
|
35
|
+
end
|
36
|
+
|
37
|
+
it "resets TTL when an item is updated" do
|
38
|
+
cache.stub(:current_time).and_return(before_ttl)
|
39
|
+
cache[:c] = 'Cornelius'
|
40
|
+
cache.stub(:current_time).and_return(after_ttl)
|
41
|
+
expect(cache[:c]).to eq('Cornelius')
|
42
|
+
end
|
43
|
+
|
44
|
+
it "doesn't reset TTL when an item is accessed" do
|
45
|
+
cache.stub(:current_time).and_return(before_ttl)
|
46
|
+
expect(cache[:c]).to eq('Caspian')
|
47
|
+
cache.stub(:current_time).and_return(after_ttl)
|
48
|
+
expect(cache[:c]).to be_nil
|
49
|
+
end
|
50
|
+
|
51
|
+
context "when asked to refresh TTL on access" do
|
52
|
+
|
53
|
+
let(:options) { {refresh: true} }
|
54
|
+
|
55
|
+
it "keeps values that were accessed before the TTL expired" do
|
56
|
+
cache.stub(:current_time).and_return(before_ttl)
|
57
|
+
expect(cache[:c]).to eq('Caspian')
|
58
|
+
cache.stub(:current_time).and_return(after_ttl)
|
59
|
+
expect(cache[:c]).to eq('Caspian')
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
describe "fetch" do
|
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
|
87
|
+
|
88
|
+
describe "memoize" do
|
89
|
+
|
90
|
+
it "requires a block" do
|
91
|
+
expect{cache_memoize(:c)}.to raise_error
|
92
|
+
end
|
93
|
+
|
94
|
+
let(:greeting) { 'hi' }
|
95
|
+
|
96
|
+
context "when the key already exists" do
|
97
|
+
|
98
|
+
context "before the TTL is up" do
|
99
|
+
|
100
|
+
it "returns the existing value" do
|
101
|
+
expect(cache.memoize(:c) { 'Eustace' } ).to eq('Caspian')
|
102
|
+
end
|
103
|
+
|
104
|
+
it "does not calculate the value" do
|
105
|
+
cache.stub(:current_time).and_return(before_ttl)
|
106
|
+
expect(greeting).not_to receive(:upcase)
|
107
|
+
cache.memoize(:c) { greeting.upcase }
|
108
|
+
end
|
109
|
+
|
110
|
+
end
|
111
|
+
|
112
|
+
context "after the TTL is up" do
|
113
|
+
|
114
|
+
it "recalculates the value" do
|
115
|
+
cache.stub(:current_time).and_return(after_ttl)
|
116
|
+
expect(greeting).to receive(:upcase)
|
117
|
+
cache.memoize(:c) { greeting.upcase }
|
118
|
+
end
|
119
|
+
|
120
|
+
it "returns the calculated value" do
|
121
|
+
cache.stub(:current_time).and_return(after_ttl)
|
122
|
+
expect(cache.memoize(:c) { |key| key.to_s.upcase }).to eq('C')
|
123
|
+
end
|
124
|
+
|
125
|
+
end
|
126
|
+
|
127
|
+
end
|
128
|
+
|
129
|
+
context "when the key does not exist" do
|
130
|
+
|
131
|
+
it "calculates the value" do
|
132
|
+
expect(greeting).to receive(:upcase)
|
133
|
+
cache.memoize(:nonexistent) { greeting.upcase }
|
134
|
+
end
|
135
|
+
|
136
|
+
it "returns the value" do
|
137
|
+
expect(cache.memoize(:nonexistent) { |key| key.to_s.upcase }).to eq('NONEXISTENT')
|
138
|
+
end
|
139
|
+
|
140
|
+
it "does not calculate the value again within the TTL" do
|
141
|
+
cache.memoize(:nonexistent) { greeting.upcase }
|
142
|
+
cache.stub(:current_time).and_return(before_ttl)
|
143
|
+
expect(greeting).not_to receive(:upcase)
|
144
|
+
cache.memoize(:nonexistent) { greeting.upcase }
|
145
|
+
end
|
146
|
+
|
147
|
+
it "does calculate the value again after the TTL is up" do
|
148
|
+
cache.memoize(:nonexistent) { greeting.upcase }
|
149
|
+
cache.stub(:current_time).and_return(after_ttl)
|
150
|
+
expect(greeting).to receive(:upcase)
|
151
|
+
cache.memoize(:nonexistent) { greeting.upcase }
|
152
|
+
end
|
153
|
+
|
154
|
+
end
|
155
|
+
|
156
|
+
end
|
157
|
+
|
158
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: hash_cache
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Nathan Long
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-01-17 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.3'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.3'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ~>
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '2.14'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '2.14'
|
55
|
+
description: A small collection of hashes that cache
|
56
|
+
email:
|
57
|
+
- nathanmlong@gmail.com
|
58
|
+
executables: []
|
59
|
+
extensions: []
|
60
|
+
extra_rdoc_files: []
|
61
|
+
files:
|
62
|
+
- .gitignore
|
63
|
+
- Gemfile
|
64
|
+
- LICENSE.txt
|
65
|
+
- README.md
|
66
|
+
- Rakefile
|
67
|
+
- hash_cache.gemspec
|
68
|
+
- lib/hash_cache.rb
|
69
|
+
- lib/hash_cache/ttl.rb
|
70
|
+
- lib/hash_cache/version.rb
|
71
|
+
- spec/hash_cache/ttl_spec.rb
|
72
|
+
- spec/spec_helper.rb
|
73
|
+
homepage: https://github.com/nathanl/hash_cache
|
74
|
+
licenses:
|
75
|
+
- MIT
|
76
|
+
metadata: {}
|
77
|
+
post_install_message:
|
78
|
+
rdoc_options: []
|
79
|
+
require_paths:
|
80
|
+
- lib
|
81
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
82
|
+
requirements:
|
83
|
+
- - '>='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
87
|
+
requirements:
|
88
|
+
- - '>='
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: '0'
|
91
|
+
requirements: []
|
92
|
+
rubyforge_project:
|
93
|
+
rubygems_version: 2.0.14
|
94
|
+
signing_key:
|
95
|
+
specification_version: 4
|
96
|
+
summary: A small collection of hashes that cache
|
97
|
+
test_files:
|
98
|
+
- spec/hash_cache/ttl_spec.rb
|
99
|
+
- spec/spec_helper.rb
|