require "msgpack" module ActiveCachedResource module CachingStrategies class Base # Reads the value associated with the given key from the cache. # # @param key [String, Symbol] the key to read from the cache. # # @return [Object, nil] the decompressed value associated with the key, or nil if the key is not found. def read(key) raise ArgumentError, "key must be a String or Symbol" unless key.is_a?(String) || key.is_a?(Symbol) raw_value = read_raw(hash_key(key)) raw_value && decompress(raw_value) end # Writes an object to the cache with the specified key and options. # # @param key [String, Symbol] The key used to store the object in the cache. # @param value [String] The value to be stored in the cache. # @param options [Hash] Options for the cache write operation. Must include `:expires_in`. # @option options [Integer] :expires_in The expiration time in seconds for the cached object. (required) # # @raise [ArgumentError] If the `:expires_in` option is missing. # # @return [Boolean] `true` if the object was successfully written to the cache, `false` otherwise. def write(key, value, options) raise ArgumentError, "`expires_in` option is required" unless options[:expires_in] write_raw(hash_key(key), compress(value), options) end # Clears the cache based on the given pattern. # # @param pattern [String] the pattern to match cache keys that need to be cleared. # # @return [Boolean] `true` if the cache was successfully cleared, `false` otherwise. def clear(pattern) clear_raw(pattern) end protected # Reads the value associated with the given key from the cache. # # @param key [Object] the key to read from the cache. # # @note This method must be implemented by the subclass. # # @return [Object, nil] the decompressed value associated with the key, or nil if the key is not found. def read_raw(key) raise NotImplementedError, "#{self.class} must implement `read_raw`" end # Writes an object to the cache with the specified key and options. # # @param key [String, Symbol] The key used to store the object in the cache. # @param value [String] The value to be stored in the cache. # @param options [Hash] Options for the cache write operation. Must include `:expires_in`. # @option options [Integer] :expires_in The expiration time in seconds for the cached object. (required) # # @note This method must be implemented by the subclass. # # @return [Boolean] `true` if the object was successfully written to the cache, `false` otherwise. def write_raw(key, value, options) raise NotImplementedError, "#{self.class} must implement `write_raw`" end # Clears cache entry for the given pattern. # # @param pattern [String, nil] The pattern representing the cache entry to be cleared. # # @note This method must be implemented by the subclass. # # @return [Boolean] `true` if the cache was successfully cleared, `false` otherwise. def clear_raw(pattern) raise NotImplementedError, "#{self.class} must implement `clear_raw`" end private # Generates a hashed key for caching purposes. # # The method splits the provided key into a prefix and the remaining part, # then combines the prefix with a SHA256 hash of the remaining part. # The resulting key is prefixed with a global prefix defined in the # ActiveCachedResource::Caching module. # # @example Hashing a key # hash_key("prefix-key") #=> "acr/prefix/Digest::SHA256.hexdigest(key)" # # @param key [String] the original key to be hashed. It is expected to have a prefix and the key separated by a dash. # @return [String] the generated hashed key with the global prefix and the prefix from the original key. def hash_key(key) # Prefix of keys are expected to be the first part of key separated by a dash. prefix, k = key.split("-", 2) if prefix.nil? || k.nil? raise ArgumentError, "Key must have a prefix and a key separated by a dash" end "#{prefix}/" + Digest::SHA256.hexdigest(k) end def compress(value) MessagePack.pack(value) end def decompress(value) MessagePack.unpack(value) rescue MessagePack::UnpackError nil end end end end