Module: MoreCoreExtensions::CacheWithTimeout

Defined in:
lib/more_core_extensions/core_ext/module/cache_with_timeout.rb

Defined Under Namespace

Modules: ClassMethods

Instance Method Summary collapse

Instance Method Details

#cache_with_timeout(method, timeout = nil, &block) ⇒ Object

cache_with_timeout creates singleton methods that cache the

results of the given block, but only for a short amount of time.
If the method is called again after time has passed, then the
cache is cleared and the block is reevaluated.  Note that the
cache is only cleared on the next invocation after the time has
passed, so if the block retains references to large objects,
they will never be garbage collected if the method is never
called again.

The methods created are
- `method`
- `#{method}_clear_cache` - Will clear the cached value on demand
- `#{method}_cached?` - Says whether or not there is a value cached

Example:

class Foo
  cache_with_timeout(:expensive_operation) do
    sleep 100
    rand(100)
  end
end

Foo.expensive_operation # => 42
Foo.expensive_operation # => 42
# ... wait 5+ minutes ...
Foo.expensive_operation # => 18

Parameters:

  • method (String|Symbol)

    The name of the method to create.

  • timeout (Integer) (defaults to: nil)

    The number of seconds after which the cache is cleared (defaults to: 300 (5 minutes))

Raises:

  • (ArgumentError)


39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
# File 'lib/more_core_extensions/core_ext/module/cache_with_timeout.rb', line 39

def cache_with_timeout(method, timeout = nil, &block)
  raise "no block given" if block.nil?
  raise ArgumentError, "method must be a Symbol" unless method.respond_to?(:to_sym)

  key = "#{name}.#{method}".to_sym

  $cache_with_timeout_lock.synchronize(:EX) do
    $cache_with_timeout[key] = {}
  end

  define_singleton_method(method) do |*args|
    force_reload = args.first
    return $cache_with_timeout_lock.synchronize(:EX) do
      cache = $cache_with_timeout[key]

      old_timeout = cache[:timeout]
      cache.clear if force_reload || (old_timeout && Time.now.utc > old_timeout)
      break cache[:value] if cache.key?(:value)

      new_timeout = timeout || 300 # 5 minutes
      new_timeout = new_timeout.call if new_timeout.kind_of?(Proc)
      new_timeout = Time.now.utc + new_timeout
      new_value   = block.call

      cache[:timeout] = new_timeout
      cache[:value]   = new_value
    end
  end

  define_singleton_method("#{method}_clear_cache") do |*_args|
    $cache_with_timeout_lock.synchronize(:EX) do
      $cache_with_timeout[key].clear
    end
  end

  define_singleton_method("#{method}_cached?") do |*_args|
    $cache_with_timeout_lock.synchronize(:EX) do
      !$cache_with_timeout[key].empty?
    end
  end

  method.to_sym
end