Class: Dalli::KeyManager
- Inherits:
-
Object
- Object
- Dalli::KeyManager
- 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- DEFAULT_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, namespace_separator: DEFAULT_NAMESPACE_SEPARATOR }.freeze
- OPTIONS =
%i[digest_class namespace namespace_separator].freeze
- VALID_NAMESPACE_SEPARATORS =
Valid separators: non-alphanumeric, single printable ASCII characters Excludes: alphanumerics, whitespace, control characters
/\A[^a-zA-Z0-9 \x00-\x1F\x7F]\z/
Instance Attribute Summary collapse
-
#namespace ⇒ Object
readonly
Returns the value of attribute namespace.
-
#namespace_separator ⇒ Object
readonly
Returns the value of attribute namespace_separator.
Instance Method Summary collapse
- #digest_class ⇒ Object
- #evaluate_namespace ⇒ Object
-
#initialize(client_options) ⇒ KeyManager
constructor
A new instance of KeyManager.
-
#key_with_namespace(key) ⇒ Object
Returns the key with the namespace prefixed, if a namespace is defined.
- #key_without_namespace(key) ⇒ Object
- #namespace_from_options ⇒ Object
- #namespace_regexp ⇒ Object
- #prefix_length(digest) ⇒ Object
-
#truncated_key(key) ⇒ Object
Produces a truncated key, if the raw key is longer than the maximum allowed length.
- #validate_digest_class_option(opts) ⇒ Object
-
#validate_key(key) ⇒ Object
Validates the key, and transforms as needed.
- #validate_namespace_separator_option(opts) ⇒ Object
Constructor Details
#initialize(client_options) ⇒ KeyManager
Returns a new instance of KeyManager.
36 37 38 39 40 41 42 43 44 |
# File 'lib/dalli/key_manager.rb', line 36 def initialize() @key_options = DEFAULTS.merge(.slice(*OPTIONS)) validate_digest_class_option(@key_options) validate_namespace_separator_option(@key_options) @namespace = @namespace_separator = @key_options[:namespace_separator] end |
Instance Attribute Details
#namespace ⇒ Object (readonly)
Returns the value of attribute namespace.
30 31 32 |
# File 'lib/dalli/key_manager.rb', line 30 def namespace @namespace end |
#namespace_separator ⇒ Object (readonly)
Returns the value of attribute namespace_separator.
30 31 32 |
# File 'lib/dalli/key_manager.rb', line 30 def namespace_separator @namespace_separator end |
Instance Method Details
#digest_class ⇒ Object
80 81 82 |
# File 'lib/dalli/key_manager.rb', line 80 def digest_class @digest_class ||= @key_options[:digest_class] end |
#evaluate_namespace ⇒ Object
112 113 114 115 116 |
# File 'lib/dalli/key_manager.rb', line 112 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
68 69 70 71 72 |
# File 'lib/dalli/key_manager.rb', line 68 def key_with_namespace(key) return key if namespace.nil? "#{evaluate_namespace}#{namespace_separator}#{key}" end |
#key_without_namespace(key) ⇒ Object
74 75 76 77 78 |
# File 'lib/dalli/key_manager.rb', line 74 def key_without_namespace(key) return key if namespace.nil? key.sub(namespace_regexp, '') end |
#namespace_from_options ⇒ Object
104 105 106 107 108 109 110 |
# File 'lib/dalli/key_manager.rb', line 104 def 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_regexp ⇒ Object
84 85 86 87 88 |
# File 'lib/dalli/key_manager.rb', line 84 def namespace_regexp return /\A#{Regexp.escape(evaluate_namespace)}#{Regexp.escape(namespace_separator)}/ if namespace.is_a?(Proc) @namespace_regexp ||= /\A#{Regexp.escape(namespace)}#{Regexp.escape(namespace_separator)}/ unless namespace.nil? end |
#prefix_length(digest) ⇒ Object
128 129 130 131 132 133 134 |
# File 'lib/dalli/key_manager.rb', line 128 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.
123 124 125 126 |
# File 'lib/dalli/key_manager.rb', line 123 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
90 91 92 93 94 |
# File 'lib/dalli/key_manager.rb', line 90 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.
57 58 59 60 61 62 |
# File 'lib/dalli/key_manager.rb', line 57 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 |
#validate_namespace_separator_option(opts) ⇒ Object
96 97 98 99 100 101 102 |
# File 'lib/dalli/key_manager.rb', line 96 def validate_namespace_separator_option(opts) sep = opts[:namespace_separator] return if VALID_NAMESPACE_SEPARATORS.match?(sep) raise ArgumentError, 'namespace_separator must be a single non-alphanumeric character (e.g., ":", "/", "|")' end |