Class: TimeoutCache

Inherits:
Object
  • Object
show all
Defined in:
lib/timeout_cache.rb

Overview

TimeoutCache is a simple key-value store where entries expire after a certain time. It implements parts of the Hash interface.

cache = TimeoutCache.new
cache[:foo] = :bar
cache[:foo] #=> bar

# wait some time (by default, 60 seconds)

cache[:foo] #=> nil

Defined Under Namespace

Classes: TimedObject

Constant Summary collapse

VERSION =
"0.0.2"
DEFAULT_TIMEOUT =

The default number of seconds an object stays alive.

60

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(timeout = DEFAULT_TIMEOUT) ⇒ TimeoutCache

Creates a new TimeoutCache.

If timeout is specified, #to_int is called on the argument, and the return value is used as the default survival time for a cache entry in seconds.

If no default value is used, then DEFAULT_TIMEOUT is the default time-to-expire in seconds.

Raises:

  • (ArgumentError)


47
48
49
50
51
52
53
54
55
56
# File 'lib/timeout_cache.rb', line 47

def initialize(timeout = DEFAULT_TIMEOUT)
  timeout = timeout.to_int if timeout.respond_to?(:to_int)
  
  raise ArgumentError.new("Timeout must be > 0") unless timeout > 0
  
  @timeout = timeout
  
  # we can use this for look-ups in O(1), instead of only find-min in O(1)
  @store = {}
end

Instance Attribute Details

#timeoutObject (readonly)

Returns the value of attribute timeout.



37
38
39
# File 'lib/timeout_cache.rb', line 37

def timeout
  @timeout
end

Instance Method Details

#[](key) ⇒ Object Also known as: get

Returns the value for the given key. If there is no such key in the cache, then this returns nil. If the value has an expire time earlier than or equal to the current time, this returns nil.

This method calls #prune whenever it finds an element that has expired.



70
71
72
73
74
75
76
77
78
79
80
81
82
83
# File 'lib/timeout_cache.rb', line 70

def [](key)
  val = @store[key]
  
  if val
    if val.expired?
      prune
      nil
    else
      val.value
    end
  else
    nil
  end
end

#[]=(key, value) ⇒ Object

Stores an object in the cache with an expire time DEFAULT_TIMEOUT seconds from now.



93
94
95
96
# File 'lib/timeout_cache.rb', line 93

def []=(key, value)
  v = TimedObject.new(value, Time.now + timeout)
  @store[key] = v
end

#delete(key) ⇒ Object

Deletes the key-value pair for the given key.

Returns nil if there is no such key, otherwise returns the deleted value.



147
148
149
150
# File 'lib/timeout_cache.rb', line 147

def delete(key)
  v = @store.delete(key)
  v ? v.value : v
end

#empty?Boolean

Calls #prune and then returns true if the cache is empty, otherwise false.

Returns:

  • (Boolean)


139
140
141
142
# File 'lib/timeout_cache.rb', line 139

def empty?
  prune
  @store.empty?
end

#expire_time(key) ⇒ Object

Returns the expire time of the value for the given key.



87
88
89
# File 'lib/timeout_cache.rb', line 87

def expire_time(key)
  @store[key].expires_at
end

#inspectObject



166
167
168
# File 'lib/timeout_cache.rb', line 166

def inspect
  %Q{#<#{self.class} #{@store.inspect}>}
end

#pruneObject

Removes any expired entries from the cache.

If nothing was removed, returns nil, otherwise returns the number of elements removed.



156
157
158
159
160
161
162
163
164
# File 'lib/timeout_cache.rb', line 156

def prune
  return nil if @store.empty?
  
  count = 0
  
  @store.delete_if { |k, v| v.expired? && count += 1 }
  
  count == 0 ? nil : count
end

#set(key, value, options = {}) ⇒ Object

Stores an object in the cache. options is a hash with symbol keys:

:time

can be either an Integer representing the number of seconds the object should be alive or a Time object representing a fixed time.

If time is not specified in the hash, the default timeout length is used.

If the object would expire immediately, returns nil, otherwise returns the object stored.

Raises:

  • (ArgumentError)


106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
# File 'lib/timeout_cache.rb', line 106

def set(key, value, options = {})
  time = options[:time] || timeout
  
  # we can technically do away with checking against Integer explicitly since
  # the to_int call will take care of it for us through Integer#to_int returning
  # self, but it's here for the sake of clarity, mostly.
  #
  # on the other hand, the #to_time call is necessary, since, for some reason,
  # Time#to_time only exists if you require "time".
  time = case time
         when Integer
           Time.now + time
         when Time
           time
         else
           if time.respond_to?(:to_int)
             Time.now + time.to_int
           elsif time.respond_to?(:to_time)
             time.to_time
           end
         end
  
  raise(ArgumentError, "time argument #{time.inspect} could not be converted to a time") if time.nil?
  
  return nil if time <= Time.now
  
  v = TimedObject.new(value, time)
  @store[key] = v
  
  value
end

#sizeObject Also known as: length

Calls #prune and then returns the number of items in the cache.



59
60
61
62
# File 'lib/timeout_cache.rb', line 59

def size
  prune
  @store.size
end