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