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

#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?

  "#{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



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

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

  raw_namespace.to_s
end

#namespace_regexpObject



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

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

#prefix_length(digest) ⇒ Object



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

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.



100
101
102
103
# File 'lib/dalli/key_manager.rb', line 100

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)


81
82
83
84
85
# File 'lib/dalli/key_manager.rb', line 81

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