Class: Elephas::Cache

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

Overview

This is the main class of the framework. Use only this class to access the cache.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(backend = nil) ⇒ Cache

Initialize the cache.

Parameters:

  • backend (Backends::Base) (defaults to: nil)

    The backend to use. By default uses an Hash backend.



21
22
23
24
# File 'lib/elephas/cache.rb', line 21

def initialize(backend = nil)
  @backend = backend || Elephas::Backends::Hash.new
  @prefix = "elephas-#{::Elephas::Version::STRING}-cache"
end

Instance Attribute Details

#backendBackend

Returns The backend used for the caching.

Returns:

  • (Backend)

    The backend used for the caching.



14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
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
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
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
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
# File 'lib/elephas/cache.rb', line 14

class Cache
  attr_accessor :backend
  attr_accessor :prefix

  # Initialize the cache.
  #
  # @param backend [Backends::Base] The backend to use. By default uses an Hash backend.
  def initialize(backend = nil)
    @backend = backend || Elephas::Backends::Hash.new
    @prefix = "elephas-#{::Elephas::Version::STRING}-cache"
  end

  # This is the main method of the framework.
  #
  # It tries reading a key from the cache.
  #
  # If it doesn't find it, it uses the provided block (which receives options as argument) to compute its value and then store it into the cache for later usages.
  #
  # ```ruby
  # cache = Elephas::Cache.new(Elephas::Backends::Hash.new)
  #
  # value = cache.use("KEY") do |options|
  #   "VALUE"
  # end
  #
  # value
  # # => "VALUE"
  #
  # value = cache.use("KEY") do |options|
  #   "ANOTHER VALUE"
  # end
  #
  # value
  # # => "VALUE"
  # ```
  #
  # @param key [String] The key to lookup.
  # @param options [Hash] A list of options for managing this key.
  # @param block [Proc] An optional block to run to compute the value for the key if nothing is found.
  # @return [Object|Entry] The found or newly-set value associated to the key.
  # @see .setup_options
  def use(key, options = {}, &block)
    rv = nil

    # Get options
    options = setup_options(options, key)

    # Check if the storage has the value (if we don't have to skip the cache)
    rv = choose_backend(options).read(options[:hash]) if options[:force] == false && options[:ttl] > 0
    rv = compute_value(options, &block) if rv.nil? && block # Try to compute the value from the block

    # Return value
    options[:as_entry] ? rv : rv.value.dup
  end

  # Reads a value from the cache.
  #
  # @param key [String] The key to lookup.
  # @param backend [Backends::Base|NilClass] The backend to use. Defaults to the current backend.
  # @return [Object|NilClass] The read value or `nil`.
  def read(key, backend = nil)
    choose_backend({backend: backend}).read(key)
  end

  # Writes a value to the cache.
  #
  # @param key [String] The key to associate the value with.
  # @param value [Object] The value to write. Setting a value to `nil` **doesn't** mean *deleting* the value.
  # @param options [Hash] A list of options for writing.
  # @see .setup_options
  # @return [Object] The value itself.
  def write(key, value, options = {})
    choose_backend(options).write(key, value, setup_options(options, key))
  end

  # Deletes a value from the cache.
  #
  # @param key [String] The key to delete.
  # @param backend [Backends::Base|NilClass] The backend to use. Defaults to the current backend.
  # @return [Boolean] `true` if the key was in the cache, `false` otherwise.
  def delete(key, backend = nil)
    choose_backend({backend: backend}).delete(key)
  end

  # Checks if a key exists in the cache.
  #
  # @param key [String] The key to lookup.
  # @param backend [Backends::Base|NilClass] The backend to use. Defaults to the current backend.
  # @return [Boolean] `true` if the key is in the cache, `false` otherwise.
  def exists?(key, backend = nil)
    choose_backend({backend: backend}).exists?(key)
  end

  # Setups options for use into the framework.
  # Valid options are:
  #
  #   * **:ttl**: The TTL (time to live, in milliseconds) of the entry. It means how long will the value stay in cache. Setting it to 0 or less means never cache the entry.
  #   * **:force**: Setting it to `true` will always skip the cache.
  #   * **:key**: The key associated to this value. **You should never set this option directly.**
  #   * **:prefix**: The prefix used in cache. This is used to avoid conflicts with other caching frameworks.
  #   * **:complete_key**: The complete key used for computing the hash. By default is concatenation of `:key` and `:prefix` options.
  #   * **:hash**: The hash used to store the key in the cache. Should be unique
  #   * **:as_entry**: In `Elephas::Cache.use`, setting this to `true` will return the entire `Entry` object rather than the value only.
  #
  # @param options [Object] An initial setup.
  # @param key [String] The key to associate to this options.
  # @return [Hash] An options hash.
  def setup_options(options, key)
    options = {ttl: 1.hour * 1000, force: false, as_entry: false}.merge(options.ensure_hash({}))

    # Sanitize options.
    options = sanitize_options(options, key)

    # Wrap the final key to ensure we don't have colliding namespaces.
    options[:complete_key] ||= "#{options[:prefix]}[#{options[:key]}]"

    # Compute the hash key used for referencing this value.
    options[:hash] ||= ::Elephas::Entry.hashify_key(options[:complete_key])

    options
  end

  private
    # Computes a new value and saves it to the cache.
    #
    # @param options [Hash] A list of options for managing the value.
    # @param block [Proc] The block to run to compute the value.
    # @return [Object|Entry] The new value.
    def compute_value(options, &block)
      rv = block.call(options)
      rv = ::Elephas::Entry.ensure(rv, options[:complete_key], options) # Make sure is an entry
      write(rv.hash, rv, options) if !rv.value.nil? && options[:ttl] > 0 # We have a value and we have to store it
      rv
    end

    # Sanitizes options for safe usage.
    #
    # @param options [Object] An initial setup.
    # @param key [String] The key to associate to this options.
    # @return [Hash] An options hash.
    def sanitize_options(options, key)
      options[:key] ||= key
      options[:ttl] == options[:ttl].blank? ? 1.hour * 1000 : [options[:ttl].to_integer, 0].max
      options[:force] = options[:force].to_boolean
      options[:prefix] = options[:prefix].present? ? options[:prefix] : prefix

      options
    end

    # Choose a backend to use.
    #
    # @param options [Backends::Base|Hash] The backend to use. Defaults to the current backend.
    def choose_backend(options)
      backend = options.ensure_hash({}).symbolize_keys[:backend]
      backend.is_a?(Elephas::Backends::Base) ? backend : self.backend
    end
end

#prefixString

Returns The default prefix for cache entries.

Returns:

  • (String)

    The default prefix for cache entries.



14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
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
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
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
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
# File 'lib/elephas/cache.rb', line 14

class Cache
  attr_accessor :backend
  attr_accessor :prefix

  # Initialize the cache.
  #
  # @param backend [Backends::Base] The backend to use. By default uses an Hash backend.
  def initialize(backend = nil)
    @backend = backend || Elephas::Backends::Hash.new
    @prefix = "elephas-#{::Elephas::Version::STRING}-cache"
  end

  # This is the main method of the framework.
  #
  # It tries reading a key from the cache.
  #
  # If it doesn't find it, it uses the provided block (which receives options as argument) to compute its value and then store it into the cache for later usages.
  #
  # ```ruby
  # cache = Elephas::Cache.new(Elephas::Backends::Hash.new)
  #
  # value = cache.use("KEY") do |options|
  #   "VALUE"
  # end
  #
  # value
  # # => "VALUE"
  #
  # value = cache.use("KEY") do |options|
  #   "ANOTHER VALUE"
  # end
  #
  # value
  # # => "VALUE"
  # ```
  #
  # @param key [String] The key to lookup.
  # @param options [Hash] A list of options for managing this key.
  # @param block [Proc] An optional block to run to compute the value for the key if nothing is found.
  # @return [Object|Entry] The found or newly-set value associated to the key.
  # @see .setup_options
  def use(key, options = {}, &block)
    rv = nil

    # Get options
    options = setup_options(options, key)

    # Check if the storage has the value (if we don't have to skip the cache)
    rv = choose_backend(options).read(options[:hash]) if options[:force] == false && options[:ttl] > 0
    rv = compute_value(options, &block) if rv.nil? && block # Try to compute the value from the block

    # Return value
    options[:as_entry] ? rv : rv.value.dup
  end

  # Reads a value from the cache.
  #
  # @param key [String] The key to lookup.
  # @param backend [Backends::Base|NilClass] The backend to use. Defaults to the current backend.
  # @return [Object|NilClass] The read value or `nil`.
  def read(key, backend = nil)
    choose_backend({backend: backend}).read(key)
  end

  # Writes a value to the cache.
  #
  # @param key [String] The key to associate the value with.
  # @param value [Object] The value to write. Setting a value to `nil` **doesn't** mean *deleting* the value.
  # @param options [Hash] A list of options for writing.
  # @see .setup_options
  # @return [Object] The value itself.
  def write(key, value, options = {})
    choose_backend(options).write(key, value, setup_options(options, key))
  end

  # Deletes a value from the cache.
  #
  # @param key [String] The key to delete.
  # @param backend [Backends::Base|NilClass] The backend to use. Defaults to the current backend.
  # @return [Boolean] `true` if the key was in the cache, `false` otherwise.
  def delete(key, backend = nil)
    choose_backend({backend: backend}).delete(key)
  end

  # Checks if a key exists in the cache.
  #
  # @param key [String] The key to lookup.
  # @param backend [Backends::Base|NilClass] The backend to use. Defaults to the current backend.
  # @return [Boolean] `true` if the key is in the cache, `false` otherwise.
  def exists?(key, backend = nil)
    choose_backend({backend: backend}).exists?(key)
  end

  # Setups options for use into the framework.
  # Valid options are:
  #
  #   * **:ttl**: The TTL (time to live, in milliseconds) of the entry. It means how long will the value stay in cache. Setting it to 0 or less means never cache the entry.
  #   * **:force**: Setting it to `true` will always skip the cache.
  #   * **:key**: The key associated to this value. **You should never set this option directly.**
  #   * **:prefix**: The prefix used in cache. This is used to avoid conflicts with other caching frameworks.
  #   * **:complete_key**: The complete key used for computing the hash. By default is concatenation of `:key` and `:prefix` options.
  #   * **:hash**: The hash used to store the key in the cache. Should be unique
  #   * **:as_entry**: In `Elephas::Cache.use`, setting this to `true` will return the entire `Entry` object rather than the value only.
  #
  # @param options [Object] An initial setup.
  # @param key [String] The key to associate to this options.
  # @return [Hash] An options hash.
  def setup_options(options, key)
    options = {ttl: 1.hour * 1000, force: false, as_entry: false}.merge(options.ensure_hash({}))

    # Sanitize options.
    options = sanitize_options(options, key)

    # Wrap the final key to ensure we don't have colliding namespaces.
    options[:complete_key] ||= "#{options[:prefix]}[#{options[:key]}]"

    # Compute the hash key used for referencing this value.
    options[:hash] ||= ::Elephas::Entry.hashify_key(options[:complete_key])

    options
  end

  private
    # Computes a new value and saves it to the cache.
    #
    # @param options [Hash] A list of options for managing the value.
    # @param block [Proc] The block to run to compute the value.
    # @return [Object|Entry] The new value.
    def compute_value(options, &block)
      rv = block.call(options)
      rv = ::Elephas::Entry.ensure(rv, options[:complete_key], options) # Make sure is an entry
      write(rv.hash, rv, options) if !rv.value.nil? && options[:ttl] > 0 # We have a value and we have to store it
      rv
    end

    # Sanitizes options for safe usage.
    #
    # @param options [Object] An initial setup.
    # @param key [String] The key to associate to this options.
    # @return [Hash] An options hash.
    def sanitize_options(options, key)
      options[:key] ||= key
      options[:ttl] == options[:ttl].blank? ? 1.hour * 1000 : [options[:ttl].to_integer, 0].max
      options[:force] = options[:force].to_boolean
      options[:prefix] = options[:prefix].present? ? options[:prefix] : prefix

      options
    end

    # Choose a backend to use.
    #
    # @param options [Backends::Base|Hash] The backend to use. Defaults to the current backend.
    def choose_backend(options)
      backend = options.ensure_hash({}).symbolize_keys[:backend]
      backend.is_a?(Elephas::Backends::Base) ? backend : self.backend
    end
end

Instance Method Details

#delete(key, backend = nil) ⇒ Boolean

Deletes a value from the cache.

Parameters:

  • key (String)

    The key to delete.

  • backend (Backends::Base|NilClass) (defaults to: nil)

    The backend to use. Defaults to the current backend.

Returns:

  • (Boolean)

    true if the key was in the cache, false otherwise.



94
95
96
# File 'lib/elephas/cache.rb', line 94

def delete(key, backend = nil)
  choose_backend({backend: backend}).delete(key)
end

#exists?(key, backend = nil) ⇒ Boolean

Checks if a key exists in the cache.

Parameters:

  • key (String)

    The key to lookup.

  • backend (Backends::Base|NilClass) (defaults to: nil)

    The backend to use. Defaults to the current backend.

Returns:

  • (Boolean)

    true if the key is in the cache, false otherwise.



103
104
105
# File 'lib/elephas/cache.rb', line 103

def exists?(key, backend = nil)
  choose_backend({backend: backend}).exists?(key)
end

#read(key, backend = nil) ⇒ Object|NilClass

Reads a value from the cache.

Parameters:

  • key (String)

    The key to lookup.

  • backend (Backends::Base|NilClass) (defaults to: nil)

    The backend to use. Defaults to the current backend.

Returns:

  • (Object|NilClass)

    The read value or nil.



74
75
76
# File 'lib/elephas/cache.rb', line 74

def read(key, backend = nil)
  choose_backend({backend: backend}).read(key)
end

#setup_options(options, key) ⇒ Hash

Setups options for use into the framework. Valid options are:

  • :ttl: The TTL (time to live, in milliseconds) of the entry. It means how long will the value stay in cache. Setting it to 0 or less means never cache the entry.
  • :force: Setting it to true will always skip the cache.
  • :key: The key associated to this value. You should never set this option directly.
  • :prefix: The prefix used in cache. This is used to avoid conflicts with other caching frameworks.
  • :complete_key: The complete key used for computing the hash. By default is concatenation of :key and :prefix options.
  • :hash: The hash used to store the key in the cache. Should be unique
  • :as_entry: In Elephas::Cache.use, setting this to true will return the entire Entry object rather than the value only.

Parameters:

  • options (Object)

    An initial setup.

  • key (String)

    The key to associate to this options.

Returns:

  • (Hash)

    An options hash.



121
122
123
124
125
126
127
128
129
130
131
132
133
134
# File 'lib/elephas/cache.rb', line 121

def setup_options(options, key)
  options = {ttl: 1.hour * 1000, force: false, as_entry: false}.merge(options.ensure_hash({}))

  # Sanitize options.
  options = sanitize_options(options, key)

  # Wrap the final key to ensure we don't have colliding namespaces.
  options[:complete_key] ||= "#{options[:prefix]}[#{options[:key]}]"

  # Compute the hash key used for referencing this value.
  options[:hash] ||= ::Elephas::Entry.hashify_key(options[:complete_key])

  options
end

#use(key, options = {}, &block) ⇒ Object|Entry

This is the main method of the framework.

It tries reading a key from the cache.

If it doesn't find it, it uses the provided block (which receives options as argument) to compute its value and then store it into the cache for later usages.

cache = Elephas::Cache.new(Elephas::Backends::Hash.new)

value = cache.use("KEY") do |options|
  "VALUE"
end

value
# => "VALUE"

value = cache.use("KEY") do |options|
  "ANOTHER VALUE"
end

value
# => "VALUE"

Parameters:

  • key (String)

    The key to lookup.

  • options (Hash) (defaults to: {})

    A list of options for managing this key.

  • block (Proc)

    An optional block to run to compute the value for the key if nothing is found.

Returns:

  • (Object|Entry)

    The found or newly-set value associated to the key.

See Also:



55
56
57
58
59
60
61
62
63
64
65
66
67
# File 'lib/elephas/cache.rb', line 55

def use(key, options = {}, &block)
  rv = nil

  # Get options
  options = setup_options(options, key)

  # Check if the storage has the value (if we don't have to skip the cache)
  rv = choose_backend(options).read(options[:hash]) if options[:force] == false && options[:ttl] > 0
  rv = compute_value(options, &block) if rv.nil? && block # Try to compute the value from the block

  # Return value
  options[:as_entry] ? rv : rv.value.dup
end

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

Writes a value to the cache.

Parameters:

  • key (String)

    The key to associate the value with.

  • value (Object)

    The value to write. Setting a value to nil doesn't mean deleting the value.

  • options (Hash) (defaults to: {})

    A list of options for writing.

Returns:

  • (Object)

    The value itself.

See Also:



85
86
87
# File 'lib/elephas/cache.rb', line 85

def write(key, value, options = {})
  choose_backend(options).write(key, value, setup_options(options, key))
end