Class: Dalli::KeyManager

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

Overview

This class manages and validates keys sent to Memcached, ensuring that they meet Memcached key length requirements, and supporting the implementation of optional namespaces on a per-Dalli client basis.

Constant Summary collapse

MAX_KEY_LENGTH =
250
NAMESPACE_SEPARATOR =
':'
TRUNCATED_KEY_SEPARATOR =

This is a hard coded md5 for historical reasons

':md5:'
TRUNCATED_KEY_TARGET_SIZE =

This is 249 for historical reasons

249
DEFAULTS =
{
  digest_class: ::Digest::MD5
}.freeze
OPTIONS =
%i[digest_class namespace].freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(client_options) ⇒ KeyManager

Returns a new instance of KeyManager.



31
32
33
34
35
36
37
# File 'lib/dalli/key_manager.rb', line 31

def initialize(client_options)
  @key_options =
    DEFAULTS.merge(client_options.select { |k, _| OPTIONS.include?(k) })
  validate_digest_class_option(@key_options)

  @namespace = namespace_from_options
end

Instance Attribute Details

#namespaceObject (readonly)

Returns the value of attribute namespace.



29
30
31
# File 'lib/dalli/key_manager.rb', line 29

def namespace
  @namespace
end

Instance Method Details

#digest_classObject



73
74
75
# File 'lib/dalli/key_manager.rb', line 73

def digest_class
  @digest_class ||= @key_options[:digest_class]
end

#evaluate_namespaceObject



97
98
99
100
101
# File 'lib/dalli/key_manager.rb', line 97

def evaluate_namespace
  return namespace.call.to_s if namespace.is_a?(Proc)

  namespace
end

#key_with_namespace(key) ⇒ Object

Returns the key with the namespace prefixed, if a namespace is defined. Otherwise just returns the key



61
62
63
64
65
# File 'lib/dalli/key_manager.rb', line 61

def key_with_namespace(key)
  return key if namespace.nil?

  "#{evaluate_namespace}#{NAMESPACE_SEPARATOR}#{key}"
end

#key_without_namespace(key) ⇒ Object



67
68
69
70
71
# File 'lib/dalli/key_manager.rb', line 67

def key_without_namespace(key)
  return key if namespace.nil?

  key.sub(namespace_regexp, '')
end

#namespace_from_optionsObject



89
90
91
92
93
94
95
# File 'lib/dalli/key_manager.rb', line 89

def namespace_from_options
  raw_namespace = @key_options[:namespace]
  return nil unless raw_namespace
  return raw_namespace.to_s unless raw_namespace.is_a?(Proc)

  raw_namespace
end

#namespace_regexpObject



77
78
79
80
81
# File 'lib/dalli/key_manager.rb', line 77

def namespace_regexp
  return /\A#{Regexp.escape(evaluate_namespace)}:/ if namespace.is_a?(Proc)

  @namespace_regexp ||= /\A#{Regexp.escape(namespace)}:/.freeze unless namespace.nil?
end

#prefix_length(digest) ⇒ Object



113
114
115
116
117
118
119
# File 'lib/dalli/key_manager.rb', line 113

def prefix_length(digest)
  return TRUNCATED_KEY_TARGET_SIZE - (TRUNCATED_KEY_SEPARATOR.length + digest.length) if namespace.nil?

  # For historical reasons, truncated keys with namespaces had a length of 250 rather
  # than 249
  TRUNCATED_KEY_TARGET_SIZE + 1 - (TRUNCATED_KEY_SEPARATOR.length + digest.length)
end

#truncated_key(key) ⇒ Object

Produces a truncated key, if the raw key is longer than the maximum allowed length. The truncated key is produced by generating a hex digest of the key, and appending that to a truncated section of the key.



108
109
110
111
# File 'lib/dalli/key_manager.rb', line 108

def truncated_key(key)
  digest = digest_class.hexdigest(key)
  "#{key[0, prefix_length(digest)]}#{TRUNCATED_KEY_SEPARATOR}#{digest}"
end

#validate_digest_class_option(opts) ⇒ Object

Raises:

  • (ArgumentError)


83
84
85
86
87
# File 'lib/dalli/key_manager.rb', line 83

def validate_digest_class_option(opts)
  return if opts[:digest_class].respond_to?(:hexdigest)

  raise ArgumentError, 'The digest_class object must respond to the hexdigest method'
end

#validate_key(key) ⇒ Object

Validates the key, and transforms as needed.

If the key is nil or empty, raises ArgumentError. Whitespace characters are allowed for historical reasons, but likely shouldn’t be used. If the key (with namespace) is shorter than the memcached maximum allowed key length, just returns the argument key Otherwise computes a “truncated” key that uses a truncated prefix combined with a 32-byte hex digest of the whole key.

Raises:

  • (ArgumentError)


50
51
52
53
54
55
# File 'lib/dalli/key_manager.rb', line 50

def validate_key(key)
  raise ArgumentError, 'key cannot be blank' unless key&.length&.positive?

  key = key_with_namespace(key)
  key.length > MAX_KEY_LENGTH ? truncated_key(key) : key
end