timeout_cache 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gemtest +0 -0
- data/Gemfile +3 -0
- data/LICENSE +21 -0
- data/README.md +71 -0
- data/lib/timeout_cache.rb +151 -0
- data/lib/timeout_cache.rb~ +151 -0
- data/rakefile +26 -0
- data/test/timeout_cache_spec.rb +167 -0
- data/test/timeout_cache_spec.rb~ +163 -0
- data/timeout_cache.gemspec +19 -0
- metadata +89 -0
data/.gemtest
ADDED
File without changes
|
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
(MIT License)
|
2
|
+
|
3
|
+
Copyright (c) 2012 Adam Prescott
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
# TimeoutCache
|
2
|
+
|
3
|
+
A `TimeoutCache` is a simple cache where objects are stored with a fixed expiration time. It is useful when you want to keep an object around for a sufficiently long period of time, but want them to disappear after, say, 5 seconds, or on the first day of next month.
|
4
|
+
|
5
|
+
# How do I use this thing?
|
6
|
+
|
7
|
+
A `TimeoutCache` is easy to use.
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
cache = TimeoutCache.new
|
11
|
+
cache[:foo] = :bar
|
12
|
+
cache[:foo] #=> bar
|
13
|
+
|
14
|
+
# wait some time (by default, 60 seconds)
|
15
|
+
|
16
|
+
cache[:foo] #=> nil
|
17
|
+
```
|
18
|
+
|
19
|
+
By default, retrievals return `nil` 60 seconds after they're put into the cache. You can control the length of time by using `#set` with the `:time` option:
|
20
|
+
|
21
|
+
```ruby
|
22
|
+
cache.set(:a, :b, :time => 5)
|
23
|
+
cache[:a] #=> :b
|
24
|
+
sleep 5
|
25
|
+
cache[:a] #=> nil
|
26
|
+
```
|
27
|
+
|
28
|
+
You can also use an instance of `Time`:
|
29
|
+
|
30
|
+
```ruby
|
31
|
+
cache.set(:x, :y, :time => Time.now + 50)
|
32
|
+
```
|
33
|
+
|
34
|
+
If you want to change the default expiration time, do so when you make a new instance of `TimeoutCache` by passing the length of time in seconds.
|
35
|
+
|
36
|
+
```ruby
|
37
|
+
cache = TimeoutCache.new(10 * 60 * 60) # 10 hours
|
38
|
+
```
|
39
|
+
|
40
|
+
Unless an object is added to the cache using the `:time` option, the default is used. This default length of time is 60 seconds.
|
41
|
+
|
42
|
+
# Entry deletion and pruning
|
43
|
+
|
44
|
+
Expired entries are deleted lazily. If an entry is added with an expire time of 15 seconds and nothing touches the cache, the entry will still be in the cache after 20 seconds. Expired entries are deleted only when a call to `#[]` (or, equivalently, `#get`) finds an expired entry. At that point, `#prune` is called.
|
45
|
+
|
46
|
+
If you want to manually prune the cache, you may do so by calling `#prune`.
|
47
|
+
|
48
|
+
# Documentation
|
49
|
+
|
50
|
+
There's not much to the code, but it's all commented and you can generate documentation with `rake docs`.
|
51
|
+
|
52
|
+
# Tests
|
53
|
+
|
54
|
+
You can run the tests with `rake test`.
|
55
|
+
|
56
|
+
# License / Copyright
|
57
|
+
|
58
|
+
Copyright (c) 2012 Adam Prescott, released under the MIT license. See the LICENSE file for details.
|
59
|
+
|
60
|
+
# Contributing
|
61
|
+
|
62
|
+
The official repository is on GitHub at [aprescott/timeout_cache](https://github.com/aprescott/timeout_cache). Issues should be opened there, as well as pull requests. Submissions should be on a separate feature branch:
|
63
|
+
|
64
|
+
* Fork and clone
|
65
|
+
* Create a feature branch
|
66
|
+
* Add code and tests
|
67
|
+
* Run all the tests
|
68
|
+
* Push you branch to your GitHub repository
|
69
|
+
* Send a pull request
|
70
|
+
|
71
|
+
Your contribution will be assumed to also be licensed under the MIT license.
|
@@ -0,0 +1,151 @@
|
|
1
|
+
# Copyright (c) 2012 Adam Prescott, licensed under the MIT license. See LICENSE.
|
2
|
+
|
3
|
+
#
|
4
|
+
# TimeoutCache is a simple key-value store where entries expire after a certain
|
5
|
+
# time. It implements parts of the Hash interface.
|
6
|
+
#
|
7
|
+
# cache = TimeoutCache.new
|
8
|
+
# cache[:foo] = :bar
|
9
|
+
# cache[:foo] #=> bar
|
10
|
+
#
|
11
|
+
# # wait some time (by default, 60 seconds)
|
12
|
+
#
|
13
|
+
# cache[:foo] #=> nil
|
14
|
+
#
|
15
|
+
class TimeoutCache
|
16
|
+
VERSION = "0.0.1"
|
17
|
+
|
18
|
+
# Wraps an object by attaching a time to it.
|
19
|
+
class TimedObject # :nodoc:
|
20
|
+
attr_reader :value, :expires_at
|
21
|
+
|
22
|
+
def initialize(value, time)
|
23
|
+
@value = value
|
24
|
+
@expires_at = time
|
25
|
+
end
|
26
|
+
|
27
|
+
# Returns true if the object has expired.
|
28
|
+
# Returns false if the object has not yet expired.
|
29
|
+
def expired?
|
30
|
+
@expires_at <= Time.now
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# The default number of seconds an object stays alive.
|
35
|
+
DEFAULT_TIMEOUT = 60
|
36
|
+
|
37
|
+
attr_reader :timeout
|
38
|
+
|
39
|
+
# Creates a new TimeoutCache.
|
40
|
+
#
|
41
|
+
# If <tt>timeout</tt> is specified, the default survival time for
|
42
|
+
# a cache entry is <tt>timeout</tt> in seconds.
|
43
|
+
#
|
44
|
+
# If no default value is used, then DEFAULT_TIMEOUT is the default
|
45
|
+
# time-to-expire in seconds.
|
46
|
+
def initialize(timeout = DEFAULT_TIMEOUT)
|
47
|
+
raise ArgumentError.new("Timeout must be > 0") unless timeout > 0
|
48
|
+
|
49
|
+
@timeout = timeout
|
50
|
+
|
51
|
+
# we can use this for look-ups in O(1), instead of only find-min in O(1)
|
52
|
+
@store = {}
|
53
|
+
end
|
54
|
+
|
55
|
+
# Returns the number of items in the cache.
|
56
|
+
def size
|
57
|
+
@store.size
|
58
|
+
end
|
59
|
+
alias_method :length, :size
|
60
|
+
|
61
|
+
# Returns the value for the given key. If there is no such key in the cache,
|
62
|
+
# then this returns nil. If the value has an expire time earlier than or equal
|
63
|
+
# to the current time, this returns nil.
|
64
|
+
#
|
65
|
+
# As an implementation detail, this method calls #prune whenever it finds
|
66
|
+
# an element that has expired.
|
67
|
+
def [](key)
|
68
|
+
val = @store[key]
|
69
|
+
|
70
|
+
if val
|
71
|
+
if val.expired?
|
72
|
+
prune
|
73
|
+
nil
|
74
|
+
else
|
75
|
+
val.value
|
76
|
+
end
|
77
|
+
else
|
78
|
+
nil
|
79
|
+
end
|
80
|
+
end
|
81
|
+
alias_method :get, :[]
|
82
|
+
|
83
|
+
# Returns the expire time of the value for the given key.
|
84
|
+
def expire_time(key)
|
85
|
+
@store[key].expires_at
|
86
|
+
end
|
87
|
+
|
88
|
+
# Stores an object in the cache with an expire time DEFAULT_TIMEOUT
|
89
|
+
# seconds from now.
|
90
|
+
def []=(key, value)
|
91
|
+
v = TimedObject.new(value, Time.now + timeout)
|
92
|
+
@store[key] = v
|
93
|
+
end
|
94
|
+
|
95
|
+
# Stores an object in the cache. options is a hash with symbol keys:
|
96
|
+
#
|
97
|
+
# :time:: can be either an Integer representing the number of seconds the object should be alive
|
98
|
+
# or a Time object representing a fixed time.
|
99
|
+
#
|
100
|
+
# If time is not specified in the hash, the default timeout length is used.
|
101
|
+
#
|
102
|
+
# If the object would expire immediately, returns nil, otherwise returns the object stored.
|
103
|
+
def set(key, value, options = {})
|
104
|
+
time = options[:time] || timeout
|
105
|
+
|
106
|
+
time = case time
|
107
|
+
when Integer
|
108
|
+
Time.now + time
|
109
|
+
when Time
|
110
|
+
time
|
111
|
+
end
|
112
|
+
|
113
|
+
return nil if time <= Time.now
|
114
|
+
|
115
|
+
v = TimedObject.new(value, time)
|
116
|
+
@store[key] = v
|
117
|
+
|
118
|
+
value
|
119
|
+
end
|
120
|
+
|
121
|
+
# Returns true if the cache is empty, otherwise false.
|
122
|
+
def empty?
|
123
|
+
@store.empty?
|
124
|
+
end
|
125
|
+
|
126
|
+
# Deletes the key-value pair for the given key.
|
127
|
+
#
|
128
|
+
# Returns nil if there is no such key, otherwise returns the deleted value.
|
129
|
+
def delete(key)
|
130
|
+
v = @store.delete(key)
|
131
|
+
v ? v.value : v
|
132
|
+
end
|
133
|
+
|
134
|
+
# Removes any expired entries from the cache.
|
135
|
+
#
|
136
|
+
# If nothing was removed, returns nil, otherwise returns
|
137
|
+
# the number of elements removed.
|
138
|
+
def prune
|
139
|
+
return nil if empty?
|
140
|
+
|
141
|
+
count = 0
|
142
|
+
|
143
|
+
@store.delete_if { |k, v| v.expired? && count += 1 }
|
144
|
+
|
145
|
+
count == 0 ? nil : count
|
146
|
+
end
|
147
|
+
|
148
|
+
def inspect
|
149
|
+
%Q{#<#{self.class} #{@store.inspect}>}
|
150
|
+
end
|
151
|
+
end
|
@@ -0,0 +1,151 @@
|
|
1
|
+
# Copyright (c) 2012 Adam Prescott, licensed under the MIT license. See LICENSE.
|
2
|
+
|
3
|
+
#
|
4
|
+
# TimeoutCache is a simple key-value store where entries expire after a certain
|
5
|
+
# time. It implements parts of the Hash interface.
|
6
|
+
#
|
7
|
+
# cache = TimeoutCache.new
|
8
|
+
# cache[:foo] = :bar
|
9
|
+
# cache[:foo] #=> bar
|
10
|
+
#
|
11
|
+
# # wait some time (by default, 60 seconds)
|
12
|
+
#
|
13
|
+
# cache[:foo] #=> nil
|
14
|
+
#
|
15
|
+
class TimeoutCache
|
16
|
+
VERSION = "0.0.1"
|
17
|
+
|
18
|
+
# Wraps an object by attaching a time to it.
|
19
|
+
class TimedObject #:nodoc:
|
20
|
+
attr_reader :value, :expires_at
|
21
|
+
|
22
|
+
def initialize(value, time)
|
23
|
+
@value = value
|
24
|
+
@expires_at = time
|
25
|
+
end
|
26
|
+
|
27
|
+
# Returns true if the object has expired.
|
28
|
+
# Returns false if the object has not yet expired.
|
29
|
+
def expired?
|
30
|
+
@expires_at <= Time.now
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# The default number of seconds an object stays alive.
|
35
|
+
DEFAULT_TIMEOUT = 60
|
36
|
+
|
37
|
+
attr_reader :timeout
|
38
|
+
|
39
|
+
# Creates a new TimeoutCache.
|
40
|
+
#
|
41
|
+
# If <tt>timeout</tt> is specified, the default survival time for
|
42
|
+
# a cache entry is <tt>timeout</tt> in seconds.
|
43
|
+
#
|
44
|
+
# If no default value is used, then DEFAULT_TIMEOUT is the default
|
45
|
+
# time-to-expire in seconds.
|
46
|
+
def initialize(timeout = DEFAULT_TIMEOUT)
|
47
|
+
raise ArgumentError.new("Timeout must be > 0") unless timeout > 0
|
48
|
+
|
49
|
+
@timeout = timeout
|
50
|
+
|
51
|
+
# we can use this for look-ups in O(1), instead of only find-min in O(1)
|
52
|
+
@store = {}
|
53
|
+
end
|
54
|
+
|
55
|
+
# Returns the number of items in the cache.
|
56
|
+
def size
|
57
|
+
@store.size
|
58
|
+
end
|
59
|
+
alias_method :length, :size
|
60
|
+
|
61
|
+
# Returns the value for the given key. If there is no such key in the cache,
|
62
|
+
# then this returns nil. If the value has an expire time earlier than or equal
|
63
|
+
# to the current time, this returns nil.
|
64
|
+
#
|
65
|
+
# As an implementation detail, this method calls #prune whenever it finds
|
66
|
+
# an element that has expired.
|
67
|
+
def [](key)
|
68
|
+
val = @store[key]
|
69
|
+
|
70
|
+
if val
|
71
|
+
if val.expired?
|
72
|
+
prune
|
73
|
+
nil
|
74
|
+
else
|
75
|
+
val.value
|
76
|
+
end
|
77
|
+
else
|
78
|
+
nil
|
79
|
+
end
|
80
|
+
end
|
81
|
+
alias_method :get, :[]
|
82
|
+
|
83
|
+
# Returns the expire time of the value for the given key.
|
84
|
+
def expire_time(key)
|
85
|
+
@store[key].expires_at
|
86
|
+
end
|
87
|
+
|
88
|
+
# Stores an object in the cache with an expire time DEFAULT_TIMEOUT
|
89
|
+
# seconds from now.
|
90
|
+
def []=(key, value)
|
91
|
+
v = TimedObject.new(value, Time.now + timeout)
|
92
|
+
@store[key] = v
|
93
|
+
end
|
94
|
+
|
95
|
+
# Stores an object in the cache. options is a hash with symbol keys:
|
96
|
+
#
|
97
|
+
# :time:: can be either an Integer representing the number of seconds the object should be alive
|
98
|
+
# or a Time object representing a fixed time.
|
99
|
+
#
|
100
|
+
# If time is not specified in the hash, the default timeout length is used.
|
101
|
+
#
|
102
|
+
# If the object would expire immediately, returns nil, otherwise returns the object stored.
|
103
|
+
def set(key, value, options = {})
|
104
|
+
time = options[:time] || timeout
|
105
|
+
|
106
|
+
time = case time
|
107
|
+
when Integer
|
108
|
+
Time.now + time
|
109
|
+
when Time
|
110
|
+
time
|
111
|
+
end
|
112
|
+
|
113
|
+
return nil if time <= Time.now
|
114
|
+
|
115
|
+
v = TimedObject.new(value, time)
|
116
|
+
@store[key] = v
|
117
|
+
|
118
|
+
value
|
119
|
+
end
|
120
|
+
|
121
|
+
# Returns true if the cache is empty, otherwise false.
|
122
|
+
def empty?
|
123
|
+
@store.empty?
|
124
|
+
end
|
125
|
+
|
126
|
+
# Deletes the key-value pair for the given key.
|
127
|
+
#
|
128
|
+
# Returns nil if there is no such key, otherwise returns the deleted value.
|
129
|
+
def delete(key)
|
130
|
+
v = @store.delete(key)
|
131
|
+
v ? v.value : v
|
132
|
+
end
|
133
|
+
|
134
|
+
# Removes any expired entries from the cache.
|
135
|
+
#
|
136
|
+
# If nothing was removed, returns nil, otherwise returns
|
137
|
+
# the number of elements removed.
|
138
|
+
def prune
|
139
|
+
return nil if empty?
|
140
|
+
|
141
|
+
count = 0
|
142
|
+
|
143
|
+
@store.delete_if { |k, v| v.expired? && count += 1 }
|
144
|
+
|
145
|
+
count == 0 ? nil : count
|
146
|
+
end
|
147
|
+
|
148
|
+
def inspect
|
149
|
+
%Q{#<#{self.class} #{@store.inspect}>}
|
150
|
+
end
|
151
|
+
end
|
data/rakefile
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
require "rake"
|
2
|
+
require "rdoc/task"
|
3
|
+
require "rspec/core/rake_task"
|
4
|
+
|
5
|
+
$:.unshift(File.expand_path("lib", __FILE__))
|
6
|
+
require "timeout_cache"
|
7
|
+
|
8
|
+
RSpec::Core::RakeTask.new(:test) do |t|
|
9
|
+
t.rspec_opts = "-I test --color --format nested"
|
10
|
+
t.pattern = "test/**/*_spec.rb"
|
11
|
+
t.verbose = false
|
12
|
+
t.fail_on_error = true
|
13
|
+
end
|
14
|
+
|
15
|
+
task :doc => [:docs]
|
16
|
+
|
17
|
+
Rake::RDocTask.new(:docs) do |rd|
|
18
|
+
#rd.main = "docs/README.md"
|
19
|
+
rd.main = "" # I'm broken, fix me
|
20
|
+
rd.rdoc_dir = "docs"
|
21
|
+
rd.title = "TimeoutCache #{TimeoutCache::VERSION}"
|
22
|
+
rd.options << '--line-numbers' << '--inline-source'
|
23
|
+
rd.rdoc_files.include("README.md", "lib")
|
24
|
+
end
|
25
|
+
|
26
|
+
task :default => :test
|
@@ -0,0 +1,167 @@
|
|
1
|
+
require "timeout_cache"
|
2
|
+
require "rspec"
|
3
|
+
|
4
|
+
describe TimeoutCache do
|
5
|
+
it "uses the global default timeout with no time specified" do
|
6
|
+
subject.timeout.should == TimeoutCache::DEFAULT_TIMEOUT
|
7
|
+
end
|
8
|
+
|
9
|
+
it "uses a specified timeout if one is given" do
|
10
|
+
TimeoutCache.new(50).timeout.should == 50
|
11
|
+
end
|
12
|
+
|
13
|
+
it "cannot be instantiated with a global timeout <= 0" do
|
14
|
+
[0, -1, -5].each do |i|
|
15
|
+
expect { TimeoutCache.new(i) }.to raise_error(ArgumentError)
|
16
|
+
end
|
17
|
+
|
18
|
+
expect { TimeoutCache.new(1) }.to_not raise_error
|
19
|
+
end
|
20
|
+
|
21
|
+
describe "#set" do
|
22
|
+
it "sets the given key-value pair" do
|
23
|
+
subject.set(:a, 1)
|
24
|
+
subject.get(:a).should == 1
|
25
|
+
end
|
26
|
+
|
27
|
+
it "sets the given key-value pair with a timeout" do
|
28
|
+
t = Time.now + 10
|
29
|
+
subject.set(:a, 1, :time => t)
|
30
|
+
subject.expire_time(:a).should == t
|
31
|
+
end
|
32
|
+
|
33
|
+
# here be dragons. if it takes too long to execute these
|
34
|
+
# commands, the times won't match, so this test isn't deterministic
|
35
|
+
it "sets a default timeout time with no time value specified" do
|
36
|
+
t = Time.now + subject.timeout
|
37
|
+
subject.set(:a, 1)
|
38
|
+
subject.expire_time(:a).should_not be_nil
|
39
|
+
subject.expire_time(:a).to_i.should == t.to_i
|
40
|
+
end
|
41
|
+
|
42
|
+
it "doesn't set a value if the timeout time is <= now" do
|
43
|
+
subject.set(:a, 1, :time => Time.now)
|
44
|
+
subject.get(:a).should be_nil
|
45
|
+
end
|
46
|
+
|
47
|
+
it "can have a negative time value" do
|
48
|
+
expect { subject.set(:a, 1, :time => -1) }.to_not raise_error
|
49
|
+
subject.get(:a).should be_nil
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
describe "#get" do
|
54
|
+
it "returns nil if there is no matching key" do
|
55
|
+
subject.get(:no_such_key).should be_nil
|
56
|
+
end
|
57
|
+
|
58
|
+
it "returns nil if the object has expired" do
|
59
|
+
subject.set(:a, 1, :time => 1)
|
60
|
+
subject.get(:a).should == 1
|
61
|
+
sleep 2
|
62
|
+
subject.get(:a).should be_nil
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
describe "#prune" do
|
67
|
+
it "erases expired entries" do
|
68
|
+
subject.set(:a, 1, :time => 1)
|
69
|
+
subject.get(:a).should == 1
|
70
|
+
sleep 2
|
71
|
+
subject.prune
|
72
|
+
subject.get(:a).should be_nil
|
73
|
+
end
|
74
|
+
|
75
|
+
it "returns nil if nothing was pruned" do
|
76
|
+
subject.prune.should be_nil
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
describe "#delete" do
|
81
|
+
it "deletes entries from the cache" do
|
82
|
+
subject[0] = 1
|
83
|
+
subject.delete(0)
|
84
|
+
subject[0].should be_nil
|
85
|
+
end
|
86
|
+
|
87
|
+
it "returns nil if nothing was deleted" do
|
88
|
+
subject[:no_such_key].should be_nil
|
89
|
+
|
90
|
+
subject[0] = 1
|
91
|
+
subject.delete(0)
|
92
|
+
subject.delete(0).should be_nil
|
93
|
+
end
|
94
|
+
|
95
|
+
it "returns the value deleted if the key is deleted" do
|
96
|
+
subject[0] = 1
|
97
|
+
subject.delete(0).should == 1
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
describe "#size" do
|
102
|
+
it "returns the number of entries in the cache" do
|
103
|
+
subject.size.should == 0
|
104
|
+
subject[:a] = :b
|
105
|
+
subject.size.should == 1
|
106
|
+
(1..10).each { |n| subject[n] = n }
|
107
|
+
subject.size.should == 11
|
108
|
+
end
|
109
|
+
|
110
|
+
it "is 0 if the cache is empty" do
|
111
|
+
subject.empty?.should be_true
|
112
|
+
subject.size.should == 0
|
113
|
+
end
|
114
|
+
|
115
|
+
it "is non-zero if the cache is empty" do
|
116
|
+
subject[:a] = 1
|
117
|
+
subject.size.should_not == 0
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
describe "pruning" do
|
122
|
+
it "happens when calling #get(key) when get(key) is expired" do
|
123
|
+
subject.set(:a, 1, :time => 2)
|
124
|
+
subject.set(:b, 1, :time => 2)
|
125
|
+
sleep 3
|
126
|
+
subject.size.should == 2
|
127
|
+
subject.get(:a)
|
128
|
+
subject.size.should == 0
|
129
|
+
end
|
130
|
+
|
131
|
+
it "does not happen when calling #get(key) when get(key) is not expired" do
|
132
|
+
subject.set(:a, 1, :time => 200)
|
133
|
+
subject.set(:b, 1, :time => 2)
|
134
|
+
sleep 3
|
135
|
+
subject.size.should == 2
|
136
|
+
subject.get(:a)
|
137
|
+
subject.size.should == 2
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
describe "#empty?" do
|
142
|
+
it "is true if the cache is empty" do
|
143
|
+
subject.empty?.should be_true
|
144
|
+
end
|
145
|
+
|
146
|
+
it "is false if the cache is not empty" do
|
147
|
+
subject.set(:a, 1)
|
148
|
+
subject.empty?.should be_false
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
describe TimeoutCache::TimedObject do
|
154
|
+
describe "#expired?" do
|
155
|
+
it "returns true for expire time > now" do
|
156
|
+
TimeoutCache::TimedObject.new(:value, Time.now + 50)
|
157
|
+
end
|
158
|
+
|
159
|
+
it "returns false for expire time < now" do
|
160
|
+
TimeoutCache::TimedObject.new(:value, Time.now - 50)
|
161
|
+
end
|
162
|
+
|
163
|
+
it "returns false for expire time = now" do
|
164
|
+
TimeoutCache::TimedObject.new(:value, Time.now)
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
@@ -0,0 +1,163 @@
|
|
1
|
+
require "timeout_cache"
|
2
|
+
require "rspec"
|
3
|
+
|
4
|
+
describe TimeoutCache do
|
5
|
+
it "uses the global default timeout with no time specified" do
|
6
|
+
subject.timeout.should == TimeoutCache::DEFAULT_TIMEOUT
|
7
|
+
end
|
8
|
+
|
9
|
+
it "uses a specified timeout if one is given" do
|
10
|
+
TimeoutCache.new(50).timeout.should == 50
|
11
|
+
end
|
12
|
+
|
13
|
+
it "cannot be instantiated with a global timeout <= 0" do
|
14
|
+
[0, -1, -5].each do |i|
|
15
|
+
expect { TimeoutCache.new(i) }.to raise_error(ArgumentError)
|
16
|
+
end
|
17
|
+
|
18
|
+
expect { TimeoutCache.new(1) }.to_not raise_error
|
19
|
+
end
|
20
|
+
|
21
|
+
describe "#set" do
|
22
|
+
it "sets the given key-value pair" do
|
23
|
+
subject.set(:a, 1)
|
24
|
+
subject.get(:a).should == 1
|
25
|
+
end
|
26
|
+
|
27
|
+
it "sets the given key-value pair with a timeout" do
|
28
|
+
t = Time.now + 10
|
29
|
+
subject.set(:a, 1, :time => t)
|
30
|
+
subject.expire_time(:a).should == t
|
31
|
+
end
|
32
|
+
|
33
|
+
# here be dragons. if it takes too long to execute these
|
34
|
+
# commands, the times won't match, so this test isn't deterministic
|
35
|
+
it "sets a default timeout time with no time value specified" do
|
36
|
+
t = Time.now + subject.timeout
|
37
|
+
subject.set(:a, 1)
|
38
|
+
subject.expire_time(:a).should_not be_nil
|
39
|
+
subject.expire_time(:a).to_i.should == t.to_i
|
40
|
+
end
|
41
|
+
|
42
|
+
it "doesn't set a value if the timeout time is <= now" do
|
43
|
+
subject.set(:a, 1, :time => Time.now)
|
44
|
+
subject.get(:a).should be_nil
|
45
|
+
end
|
46
|
+
|
47
|
+
it "can have a negative time value" do
|
48
|
+
expect { subject.set(:a, 1, :time => -1) }.to_not raise_error
|
49
|
+
subject.get(:a).should be_nil
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
describe "#get" do
|
54
|
+
it "returns nil if there is no matching key" do
|
55
|
+
subject.get(:no_such_key).should be_nil
|
56
|
+
end
|
57
|
+
|
58
|
+
it "returns nil if the object has expired" do
|
59
|
+
subject.set(:a, 1, :time => 1)
|
60
|
+
subject.get(:a).should == 1
|
61
|
+
sleep 2
|
62
|
+
subject.get(:a).should be_nil
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
describe "#prune" do
|
67
|
+
it "erases expired entries" do
|
68
|
+
subject.set(:a, 1, :time => 1)
|
69
|
+
subject.get(:a).should == 1
|
70
|
+
sleep 2
|
71
|
+
subject.prune
|
72
|
+
subject.get(:a).should be_nil
|
73
|
+
end
|
74
|
+
|
75
|
+
it "returns nil if nothing was pruned" do
|
76
|
+
subject.prune.should be_nil
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
describe "#delete" do
|
81
|
+
it "deletes entries from the cache" do
|
82
|
+
subject[0] = 1
|
83
|
+
subject.delete(0)
|
84
|
+
subject[0].should be_nil
|
85
|
+
end
|
86
|
+
|
87
|
+
it "returns nil if nothing was deleted" do
|
88
|
+
subject[:no_such_key].should be_nil
|
89
|
+
end
|
90
|
+
|
91
|
+
it "returns the value deleted if the key is deleted" do
|
92
|
+
subject[0] = 1
|
93
|
+
subject.delete(0).should == 1
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
describe "#size" do
|
98
|
+
it "returns the number of entries in the cache" do
|
99
|
+
subject.size.should == 0
|
100
|
+
subject[:a] = :b
|
101
|
+
subject.size.should == 1
|
102
|
+
(1..10).each { |n| subject[n] = n }
|
103
|
+
subject.size.should == 11
|
104
|
+
end
|
105
|
+
|
106
|
+
it "is 0 if the cache is empty" do
|
107
|
+
subject.empty?.should be_true
|
108
|
+
subject.size.should == 0
|
109
|
+
end
|
110
|
+
|
111
|
+
it "is non-zero if the cache is empty" do
|
112
|
+
subject[:a] = 1
|
113
|
+
subject.size.should_not == 0
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
describe "pruning" do
|
118
|
+
it "happens when calling #get(key) when get(key) is expired" do
|
119
|
+
subject.set(:a, 1, :time => 2)
|
120
|
+
subject.set(:b, 1, :time => 2)
|
121
|
+
sleep 3
|
122
|
+
subject.size.should == 2
|
123
|
+
subject.get(:a)
|
124
|
+
subject.size.should == 0
|
125
|
+
end
|
126
|
+
|
127
|
+
it "does not happen when calling #get(key) when get(key) is not expired" do
|
128
|
+
subject.set(:a, 1, :time => 200)
|
129
|
+
subject.set(:b, 1, :time => 2)
|
130
|
+
sleep 3
|
131
|
+
subject.size.should == 2
|
132
|
+
subject.get(:a)
|
133
|
+
subject.size.should == 2
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
describe "#empty?" do
|
138
|
+
it "is true if the cache is empty" do
|
139
|
+
subject.empty?.should be_true
|
140
|
+
end
|
141
|
+
|
142
|
+
it "is false if the cache is not empty" do
|
143
|
+
subject.set(:a, 1)
|
144
|
+
subject.empty?.should be_false
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
describe TimeoutCache::TimedObject do
|
150
|
+
describe "#expired?" do
|
151
|
+
it "returns true for expire time > now" do
|
152
|
+
TimeoutCache::TimedObject.new(:value, Time.now + 50)
|
153
|
+
end
|
154
|
+
|
155
|
+
it "returns false for expire time < now" do
|
156
|
+
TimeoutCache::TimedObject.new(:value, Time.now - 50)
|
157
|
+
end
|
158
|
+
|
159
|
+
it "returns false for expire time = now" do
|
160
|
+
TimeoutCache::TimedObject.new(:value, Time.now)
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
lib = File.expand_path('../lib/', __FILE__)
|
2
|
+
$:.unshift lib unless $:.include?(lib)
|
3
|
+
|
4
|
+
require "timeout_cache"
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = "timeout_cache"
|
8
|
+
s.version = TimeoutCache::VERSION
|
9
|
+
s.authors = ["Adam Prescott"]
|
10
|
+
s.email = ["adam@aprescott.com"]
|
11
|
+
s.homepage = "https://github.com/aprescott/timeout_cache"
|
12
|
+
s.summary = "Simple time-based cache."
|
13
|
+
s.description = "Simple time-based cache."
|
14
|
+
s.files = Dir["{lib/**/*,test/**/*}"] + %w[timeout_cache.gemspec .gemtest LICENSE Gemfile rakefile README.md]
|
15
|
+
s.require_path = "lib"
|
16
|
+
s.test_files = Dir["test/*"]
|
17
|
+
s.add_development_dependency "rake"
|
18
|
+
s.add_development_dependency "rspec"
|
19
|
+
end
|
metadata
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: timeout_cache
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Adam Prescott
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-08-10 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rake
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rspec
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
description: Simple time-based cache.
|
47
|
+
email:
|
48
|
+
- adam@aprescott.com
|
49
|
+
executables: []
|
50
|
+
extensions: []
|
51
|
+
extra_rdoc_files: []
|
52
|
+
files:
|
53
|
+
- lib/timeout_cache.rb~
|
54
|
+
- lib/timeout_cache.rb
|
55
|
+
- test/timeout_cache_spec.rb
|
56
|
+
- test/timeout_cache_spec.rb~
|
57
|
+
- timeout_cache.gemspec
|
58
|
+
- .gemtest
|
59
|
+
- LICENSE
|
60
|
+
- Gemfile
|
61
|
+
- rakefile
|
62
|
+
- README.md
|
63
|
+
homepage: https://github.com/aprescott/timeout_cache
|
64
|
+
licenses: []
|
65
|
+
post_install_message:
|
66
|
+
rdoc_options: []
|
67
|
+
require_paths:
|
68
|
+
- lib
|
69
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
70
|
+
none: false
|
71
|
+
requirements:
|
72
|
+
- - ! '>='
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '0'
|
75
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
76
|
+
none: false
|
77
|
+
requirements:
|
78
|
+
- - ! '>='
|
79
|
+
- !ruby/object:Gem::Version
|
80
|
+
version: '0'
|
81
|
+
requirements: []
|
82
|
+
rubyforge_project:
|
83
|
+
rubygems_version: 1.8.24
|
84
|
+
signing_key:
|
85
|
+
specification_version: 3
|
86
|
+
summary: Simple time-based cache.
|
87
|
+
test_files:
|
88
|
+
- test/timeout_cache_spec.rb
|
89
|
+
- test/timeout_cache_spec.rb~
|