redis-memo 0.1.4 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/redis_memo.rb +38 -57
- data/lib/redis_memo/after_commit.rb +2 -2
- data/lib/redis_memo/batch.rb +36 -11
- data/lib/redis_memo/cache.rb +27 -22
- data/lib/redis_memo/connection_pool.rb +4 -3
- data/lib/redis_memo/errors.rb +9 -0
- data/lib/redis_memo/future.rb +22 -13
- data/lib/redis_memo/memoizable.rb +109 -72
- data/lib/redis_memo/memoizable/bump_version.lua +39 -0
- data/lib/redis_memo/memoizable/dependency.rb +7 -10
- data/lib/redis_memo/memoizable/invalidation.rb +67 -68
- data/lib/redis_memo/memoize_method.rb +169 -131
- data/lib/redis_memo/memoize_query.rb +135 -93
- data/lib/redis_memo/memoize_query/cached_select.rb +59 -46
- data/lib/redis_memo/memoize_query/cached_select/connection_adapter.rb +7 -7
- data/lib/redis_memo/memoize_query/invalidation.rb +22 -20
- data/lib/redis_memo/memoize_query/memoize_table_column.rb +1 -0
- data/lib/redis_memo/middleware.rb +1 -1
- data/lib/redis_memo/options.rb +106 -5
- data/lib/redis_memo/railtie.rb +11 -0
- data/lib/redis_memo/redis.rb +15 -1
- data/lib/redis_memo/testing.rb +4 -10
- data/lib/redis_memo/thread_local_var.rb +16 -0
- data/lib/redis_memo/tracer.rb +1 -0
- data/lib/redis_memo/util.rb +19 -0
- metadata +79 -4
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 8b3cb72a8a4bf3dcd6d30bb112e387710c65d09d60c3dc687e10db649da1e91a
         | 
| 4 | 
            +
              data.tar.gz: b2452e1d4b2b7a3d588c13822eaffc1fb4336c3a4666f8a4ca473dbbd961578f
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 4d9193ddb53419db1e14a5eda4d4ab73b4a96620f592998c764abe3a118fb266761b04a929ff49d15194a9bcf959f9cb7c82f45f783387a85f4e1be2b1315ada
         | 
| 7 | 
            +
              data.tar.gz: 383b2b053917ceeb3991de1c7c399fa83cd8e2bc326f735a0d0924767cea5d6157cf2f3e4a07b3d5d6524b54cae7e62276fb0723daad6a66ebaa6d0b6da64745
         | 
    
        data/lib/redis_memo.rb
    CHANGED
    
    | @@ -3,11 +3,20 @@ | |
| 3 3 | 
             
            require 'active_support/all'
         | 
| 4 4 | 
             
            require 'digest'
         | 
| 5 5 | 
             
            require 'json'
         | 
| 6 | 
            +
            require 'ruby2_keywords'
         | 
| 6 7 | 
             
            require 'securerandom'
         | 
| 7 8 |  | 
| 8 9 | 
             
            module RedisMemo
         | 
| 10 | 
            +
              require 'redis_memo/thread_local_var'
         | 
| 11 | 
            +
             | 
| 12 | 
            +
              ThreadLocalVar.define :without_memoization
         | 
| 13 | 
            +
              ThreadLocalVar.define :connection_attempts_count
         | 
| 14 | 
            +
              ThreadLocalVar.define :max_connection_attempts
         | 
| 15 | 
            +
             | 
| 16 | 
            +
              require 'redis_memo/errors'
         | 
| 9 17 | 
             
              require 'redis_memo/memoize_method'
         | 
| 10 | 
            -
              require 'redis_memo/memoize_query'
         | 
| 18 | 
            +
              require 'redis_memo/memoize_query' if defined?(ActiveRecord)
         | 
| 19 | 
            +
              require 'redis_memo/railtie' if defined?(Rails) && defined?(Rails::Railtie)
         | 
| 11 20 |  | 
| 12 21 | 
             
              # A process-level +RedisMemo::Options+ instance that stores the global
         | 
| 13 22 | 
             
              # options. This object can be modified by +RedisMemo.configure+.
         | 
| @@ -17,11 +26,6 @@ module RedisMemo | |
| 17 26 | 
             
              # +DefaultOptions+ as the default value.
         | 
| 18 27 | 
             
              DefaultOptions = RedisMemo::Options.new
         | 
| 19 28 |  | 
| 20 | 
            -
              # @todo Move thread keys to +RedisMemo::ThreadKey+
         | 
| 21 | 
            -
              THREAD_KEY_WITHOUT_MEMO = :__redis_memo_without_memo__
         | 
| 22 | 
            -
              THREAD_KEY_CONNECTION_ATTEMPTS_COUNT = :__redis_memo_connection_attempts_count__
         | 
| 23 | 
            -
              THREAD_KEY_MAX_CONNECTION_ATTEMPTS = :__redis_memo_max_connection_attempts__
         | 
| 24 | 
            -
             | 
| 25 29 | 
             
              # Configure global-level default options. Those options will be used unless
         | 
| 26 30 | 
             
              # some options specified at +memoize_method+ callsite level. See
         | 
| 27 31 | 
             
              # +RedisMemo::Options+ for all the possible options.
         | 
| @@ -37,10 +41,10 @@ module RedisMemo | |
| 37 41 | 
             
              # to Redis.
         | 
| 38 42 | 
             
              # - Batches cannot be nested
         | 
| 39 43 | 
             
              # - When a batch is still open (while still in the +RedisMemo.batch+ block)
         | 
| 40 | 
            -
              # | 
| 41 | 
            -
              # | 
| 44 | 
            +
              #   the return value of any memoized method is a +RedisMemo::Future+ instead of
         | 
| 45 | 
            +
              #   the actual method value
         | 
| 42 46 | 
             
              # - The actual method values are returned as a list, in the same order as
         | 
| 43 | 
            -
              # | 
| 47 | 
            +
              #   invoking, after exiting the block
         | 
| 44 48 | 
             
              #
         | 
| 45 49 | 
             
              # @example
         | 
| 46 50 | 
             
              #   results = RedisMemo.batch do
         | 
| @@ -58,75 +62,52 @@ module RedisMemo | |
| 58 62 | 
             
                RedisMemo::Batch.close
         | 
| 59 63 | 
             
              end
         | 
| 60 64 |  | 
| 61 | 
            -
              # @todo Move this method out of the top namespace
         | 
| 62 | 
            -
              def self.checksum(serialized)
         | 
| 63 | 
            -
                Digest::SHA1.base64digest(serialized)
         | 
| 64 | 
            -
              end
         | 
| 65 | 
            -
             | 
| 66 | 
            -
              # @todo Move this method out of the top namespace
         | 
| 67 | 
            -
              def self.uuid
         | 
| 68 | 
            -
                SecureRandom.uuid
         | 
| 69 | 
            -
              end
         | 
| 70 | 
            -
             | 
| 71 | 
            -
              # @todo Move this method out of the top namespace
         | 
| 72 | 
            -
              def self.deep_sort_hash(orig_hash)
         | 
| 73 | 
            -
                {}.tap do |new_hash|
         | 
| 74 | 
            -
                  orig_hash.sort.each do |k, v|
         | 
| 75 | 
            -
                    new_hash[k] = v.is_a?(Hash) ? deep_sort_hash(v) : v
         | 
| 76 | 
            -
                  end
         | 
| 77 | 
            -
                end
         | 
| 78 | 
            -
              end
         | 
| 79 | 
            -
             | 
| 80 65 | 
             
              # Whether the current execution context has been configured to skip
         | 
| 81 66 | 
             
              # memoization and use the uncached code path.
         | 
| 82 67 | 
             
              #
         | 
| 83 68 | 
             
              # @return [Boolean]
         | 
| 84 | 
            -
              def self. | 
| 85 | 
            -
                 | 
| 69 | 
            +
              def self.without_memoization?
         | 
| 70 | 
            +
                RedisMemo::DefaultOptions.disable_all || ThreadLocalVar.without_memoization == true
         | 
| 86 71 | 
             
              end
         | 
| 87 72 |  | 
| 88 73 | 
             
              # Configure the wrapped code in the block to skip memoization.
         | 
| 89 74 | 
             
              #
         | 
| 90 75 | 
             
              # @yield [] no_args The block of code to skip memoization.
         | 
| 91 | 
            -
              def self. | 
| 92 | 
            -
                prev_value =  | 
| 93 | 
            -
                 | 
| 76 | 
            +
              def self.without_memoization
         | 
| 77 | 
            +
                prev_value = ThreadLocalVar.without_memoization
         | 
| 78 | 
            +
                ThreadLocalVar.without_memoization = true
         | 
| 94 79 | 
             
                yield
         | 
| 95 80 | 
             
              ensure
         | 
| 96 | 
            -
                 | 
| 81 | 
            +
                ThreadLocalVar.without_memoization = prev_value
         | 
| 97 82 | 
             
              end
         | 
| 98 83 |  | 
| 99 | 
            -
              # Set the max connection attempts to Redis per code block. If we fail to | 
| 100 | 
            -
              # times, the rest of the code block | 
| 84 | 
            +
              # Set the max connection attempts to Redis per code block. If we fail to
         | 
| 85 | 
            +
              # connect to Redis more than `max_attempts` times, the rest of the code block
         | 
| 86 | 
            +
              # will fall back to the uncached flow, `RedisMemo.without_memoization`.
         | 
| 101 87 | 
             
              #
         | 
| 102 88 | 
             
              # @param [Integer] The max number of connection attempts.
         | 
| 103 89 | 
             
              # @yield [] no_args the block of code to set the max attempts for.
         | 
| 104 90 | 
             
              def self.with_max_connection_attempts(max_attempts)
         | 
| 105 | 
            -
                prev_value =  | 
| 106 | 
            -
                 | 
| 107 | 
            -
             | 
| 108 | 
            -
             | 
| 109 | 
            -
                end
         | 
| 91 | 
            +
                prev_value = ThreadLocalVar.without_memoization
         | 
| 92 | 
            +
                ThreadLocalVar.connection_attempts_count = 0
         | 
| 93 | 
            +
                ThreadLocalVar.max_connection_attempts = max_attempts
         | 
| 94 | 
            +
             | 
| 110 95 | 
             
                yield
         | 
| 111 96 | 
             
              ensure
         | 
| 112 | 
            -
                 | 
| 113 | 
            -
                 | 
| 114 | 
            -
                 | 
| 97 | 
            +
                ThreadLocalVar.without_memoization = prev_value
         | 
| 98 | 
            +
                ThreadLocalVar.connection_attempts_count = nil
         | 
| 99 | 
            +
                ThreadLocalVar.max_connection_attempts = nil
         | 
| 115 100 | 
             
              end
         | 
| 116 101 |  | 
| 117 | 
            -
               | 
| 118 | 
            -
             | 
| 119 | 
            -
                return if Thread.current[THREAD_KEY_MAX_CONNECTION_ATTEMPTS].nil? || Thread.current[THREAD_KEY_CONNECTION_ATTEMPTS_COUNT].nil?
         | 
| 102 | 
            +
              private_class_method def self.incr_connection_attempts # :nodoc:
         | 
| 103 | 
            +
                return unless ThreadLocalVar.max_connection_attempts && ThreadLocalVar.connection_attempts_count
         | 
| 120 104 |  | 
| 121 | 
            -
                # The connection attempts count and max connection attempts are reset in | 
| 122 | 
            -
                 | 
| 123 | 
            -
                 | 
| 124 | 
            -
             | 
| 125 | 
            -
             | 
| 126 | 
            -
              end
         | 
| 105 | 
            +
                # The connection attempts count and max connection attempts are reset in
         | 
| 106 | 
            +
                # RedisMemo.with_max_connection_attempts
         | 
| 107 | 
            +
                ThreadLocalVar.connection_attempts_count += 1
         | 
| 108 | 
            +
                return if ThreadLocalVar.connection_attempts_count <
         | 
| 109 | 
            +
                          ThreadLocalVar.max_connection_attempts
         | 
| 127 110 |  | 
| 128 | 
            -
             | 
| 129 | 
            -
               | 
| 130 | 
            -
              class RuntimeError < ::RuntimeError; end
         | 
| 131 | 
            -
              class WithoutMemoization < Exception; end
         | 
| 111 | 
            +
                ThreadLocalVar.without_memoization = true
         | 
| 112 | 
            +
              end
         | 
| 132 113 | 
             
            end
         | 
| @@ -1,4 +1,5 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 2 3 | 
             
            # TODO: -> RedisMemo::Memoizable::AfterCommit
         | 
| 3 4 |  | 
| 4 5 | 
             
            class RedisMemo::AfterCommit
         | 
| @@ -34,8 +35,6 @@ class RedisMemo::AfterCommit | |
| 34 35 | 
             
                connection.transaction_open? && connection.current_transaction.joinable?
         | 
| 35 36 | 
             
              end
         | 
| 36 37 |  | 
| 37 | 
            -
              private
         | 
| 38 | 
            -
             | 
| 39 38 | 
             
              def self.after_commit(&blk)
         | 
| 40 39 | 
             
                connection.add_transaction_record(
         | 
| 41 40 | 
             
                  RedisMemo::AfterCommit::Callback.new(connection, committed: blk),
         | 
| @@ -50,6 +49,7 @@ class RedisMemo::AfterCommit | |
| 50 49 |  | 
| 51 50 | 
             
              def self.reset_after_transaction
         | 
| 52 51 | 
             
                return if @@callback_added
         | 
| 52 | 
            +
             | 
| 53 53 | 
             
                @@callback_added = true
         | 
| 54 54 |  | 
| 55 55 | 
             
                after_commit do
         | 
    
        data/lib/redis_memo/batch.rb
    CHANGED
    
    | @@ -1,30 +1,54 @@ | |
| 1 | 
            -
             | 
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 2 3 | 
             
            require_relative 'cache'
         | 
| 3 4 | 
             
            require_relative 'tracer'
         | 
| 4 5 |  | 
| 6 | 
            +
            ##
         | 
| 7 | 
            +
            # This class facilitates the batching of Redis calls triggered by +memoize_method+
         | 
| 8 | 
            +
            # to minimize the number of round trips to Redis.
         | 
| 9 | 
            +
            #
         | 
| 10 | 
            +
            # - Batches cannot be nested
         | 
| 11 | 
            +
            # - When a batch is still open (while still in the +RedisMemo.batch+ block)
         | 
| 12 | 
            +
            #   the return value of any memoized method is a +RedisMemo::Future+ instead of
         | 
| 13 | 
            +
            #   the actual method value
         | 
| 14 | 
            +
            # - The actual method values are returned as a list, in the same order as
         | 
| 15 | 
            +
            #   invoking, after exiting the block
         | 
| 16 | 
            +
            #
         | 
| 17 | 
            +
            # @example
         | 
| 18 | 
            +
            #   results = RedisMemo.batch do
         | 
| 19 | 
            +
            #     5.times { |i| memoized_calculation(i) }
         | 
| 20 | 
            +
            #     nil # Not the return value of the block
         | 
| 21 | 
            +
            #   end
         | 
| 22 | 
            +
            #   results # [1,2,3,4,5] (results from the memoized_calculation calls)
         | 
| 5 23 | 
             
            class RedisMemo::Batch
         | 
| 6 | 
            -
               | 
| 24 | 
            +
              RedisMemo::ThreadLocalVar.define :batch
         | 
| 7 25 |  | 
| 26 | 
            +
              # Opens a new batch. If a batch is already open, raises an error
         | 
| 27 | 
            +
              # to prevent nested batches.
         | 
| 8 28 | 
             
              def self.open
         | 
| 9 29 | 
             
                if current
         | 
| 10 | 
            -
                  raise RedisMemo::RuntimeError | 
| 30 | 
            +
                  raise RedisMemo::RuntimeError.new('Batch can not be nested')
         | 
| 11 31 | 
             
                end
         | 
| 12 32 |  | 
| 13 | 
            -
                 | 
| 33 | 
            +
                RedisMemo::ThreadLocalVar.batch = []
         | 
| 14 34 | 
             
              end
         | 
| 15 35 |  | 
| 36 | 
            +
              # Closes the current batch, returning the futures in that batch.
         | 
| 16 37 | 
             
              def self.close
         | 
| 17 | 
            -
                 | 
| 18 | 
            -
             | 
| 19 | 
            -
             | 
| 20 | 
            -
             | 
| 21 | 
            -
                 | 
| 38 | 
            +
                return unless current
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                futures = current
         | 
| 41 | 
            +
                RedisMemo::ThreadLocalVar.batch = nil
         | 
| 42 | 
            +
                futures
         | 
| 22 43 | 
             
              end
         | 
| 23 44 |  | 
| 45 | 
            +
              # Retrieves the current open batch.
         | 
| 24 46 | 
             
              def self.current
         | 
| 25 | 
            -
                 | 
| 47 | 
            +
                RedisMemo::ThreadLocalVar.batch
         | 
| 26 48 | 
             
              end
         | 
| 27 49 |  | 
| 50 | 
            +
              # Executes all the futures in the current batch using batched calls to
         | 
| 51 | 
            +
              # Redis and closes it.
         | 
| 28 52 | 
             
              def self.execute
         | 
| 29 53 | 
             
                futures = close
         | 
| 30 54 | 
             
                return unless futures
         | 
| @@ -33,7 +57,8 @@ class RedisMemo::Batch | |
| 33 57 | 
             
                method_cache_keys = nil
         | 
| 34 58 |  | 
| 35 59 | 
             
                RedisMemo::Tracer.trace('redis_memo.cache.batch.read', nil) do
         | 
| 36 | 
            -
                  method_cache_keys = RedisMemo::MemoizeMethod. | 
| 60 | 
            +
                  method_cache_keys = RedisMemo::MemoizeMethod.__send__(
         | 
| 61 | 
            +
                    :method_cache_keys,
         | 
| 37 62 | 
             
                    futures.map(&:context),
         | 
| 38 63 | 
             
                  )
         | 
| 39 64 |  | 
    
        data/lib/redis_memo/cache.rb
    CHANGED
    
    | @@ -1,14 +1,17 @@ | |
| 1 | 
            -
             | 
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 2 3 | 
             
            require_relative 'options'
         | 
| 3 4 | 
             
            require_relative 'redis'
         | 
| 4 5 | 
             
            require_relative 'connection_pool'
         | 
| 5 6 |  | 
| 6 7 | 
             
            class RedisMemo::Cache < ActiveSupport::Cache::RedisCacheStore
         | 
| 7 | 
            -
               | 
| 8 | 
            +
              # This needs to be an Exception since RedisCacheStore rescues all
         | 
| 9 | 
            +
              # RuntimeErrors
         | 
| 10 | 
            +
              class Rescuable < Exception; end # rubocop:disable Lint/InheritException
         | 
| 8 11 |  | 
| 9 | 
            -
               | 
| 10 | 
            -
               | 
| 11 | 
            -
               | 
| 12 | 
            +
              RedisMemo::ThreadLocalVar.define :local_cache
         | 
| 13 | 
            +
              RedisMemo::ThreadLocalVar.define :local_dependency_cache
         | 
| 14 | 
            +
              RedisMemo::ThreadLocalVar.define :raise_error
         | 
| 12 15 |  | 
| 13 16 | 
             
              @@redis = nil
         | 
| 14 17 | 
             
              @@redis_store = nil
         | 
| @@ -17,13 +20,15 @@ class RedisMemo::Cache < ActiveSupport::Cache::RedisCacheStore | |
| 17 20 | 
             
                RedisMemo::DefaultOptions.redis_error_handler&.call(exception, method)
         | 
| 18 21 | 
             
                RedisMemo::DefaultOptions.logger&.warn(exception.full_message)
         | 
| 19 22 |  | 
| 20 | 
            -
                 | 
| 23 | 
            +
                if exception.is_a?(Redis::BaseConnectionError)
         | 
| 24 | 
            +
                  RedisMemo.__send__(:incr_connection_attempts)
         | 
| 25 | 
            +
                end
         | 
| 21 26 |  | 
| 22 | 
            -
                if  | 
| 27 | 
            +
                if RedisMemo::ThreadLocalVar.raise_error
         | 
| 23 28 | 
             
                  raise RedisMemo::Cache::Rescuable
         | 
| 24 | 
            -
                else
         | 
| 25 | 
            -
                  returning
         | 
| 26 29 | 
             
                end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                returning
         | 
| 27 32 | 
             
              end
         | 
| 28 33 |  | 
| 29 34 | 
             
              def self.redis
         | 
| @@ -50,38 +55,38 @@ class RedisMemo::Cache < ActiveSupport::Cache::RedisCacheStore | |
| 50 55 | 
             
              # ActiveSupport::Cache::Entry object -- which is slower comparing to a simple
         | 
| 51 56 | 
             
              # hash storing object references
         | 
| 52 57 | 
             
              def self.local_cache
         | 
| 53 | 
            -
                 | 
| 58 | 
            +
                RedisMemo::ThreadLocalVar.local_cache
         | 
| 54 59 | 
             
              end
         | 
| 55 60 |  | 
| 56 61 | 
             
              def self.local_dependency_cache
         | 
| 57 | 
            -
                 | 
| 62 | 
            +
                RedisMemo::ThreadLocalVar.local_dependency_cache
         | 
| 58 63 | 
             
              end
         | 
| 59 64 |  | 
| 60 65 | 
             
              # See https://github.com/rails/rails/blob/fe76a95b0d252a2d7c25e69498b720c96b243ea2/activesupport/lib/active_support/cache/redis_cache_store.rb#L477
         | 
| 61 66 | 
             
              # We overwrite this private method so we can also rescue ConnectionPool::TimeoutErrors
         | 
| 62 67 | 
             
              def failsafe(method, returning: nil)
         | 
| 63 68 | 
             
                yield
         | 
| 64 | 
            -
              rescue ::Redis::BaseError, ::ConnectionPool::TimeoutError =>  | 
| 65 | 
            -
                handle_exception exception:  | 
| 69 | 
            +
              rescue ::Redis::BaseError, ::ConnectionPool::TimeoutError => error
         | 
| 70 | 
            +
                handle_exception exception: error, method: method, returning: returning
         | 
| 66 71 | 
             
                returning
         | 
| 67 72 | 
             
              end
         | 
| 68 73 | 
             
              private :failsafe
         | 
| 69 74 |  | 
| 70 75 | 
             
              class << self
         | 
| 71 | 
            -
                def with_local_cache | 
| 72 | 
            -
                   | 
| 73 | 
            -
                   | 
| 74 | 
            -
                   | 
| 76 | 
            +
                def with_local_cache
         | 
| 77 | 
            +
                  RedisMemo::ThreadLocalVar.local_cache = {}
         | 
| 78 | 
            +
                  RedisMemo::ThreadLocalVar.local_dependency_cache = {}
         | 
| 79 | 
            +
                  yield
         | 
| 75 80 | 
             
                ensure
         | 
| 76 | 
            -
                   | 
| 77 | 
            -
                   | 
| 81 | 
            +
                  RedisMemo::ThreadLocalVar.local_cache = nil
         | 
| 82 | 
            +
                  RedisMemo::ThreadLocalVar.local_dependency_cache = nil
         | 
| 78 83 | 
             
                end
         | 
| 79 84 |  | 
| 80 85 | 
             
                # RedisCacheStore doesn't read from the local cache before reading from redis
         | 
| 81 86 | 
             
                def read_multi(*keys, raw: false, raise_error: false)
         | 
| 82 87 | 
             
                  return {} if keys.empty?
         | 
| 83 88 |  | 
| 84 | 
            -
                   | 
| 89 | 
            +
                  RedisMemo::ThreadLocalVar.raise_error = raise_error
         | 
| 85 90 |  | 
| 86 91 | 
             
                  local_entries = local_cache&.slice(*keys) || {}
         | 
| 87 92 |  | 
| @@ -100,7 +105,7 @@ class RedisMemo::Cache < ActiveSupport::Cache::RedisCacheStore | |
| 100 105 | 
             
                end
         | 
| 101 106 |  | 
| 102 107 | 
             
                def write(key, value, disable_async: false, raise_error: false, **options)
         | 
| 103 | 
            -
                   | 
| 108 | 
            +
                  RedisMemo::ThreadLocalVar.raise_error = raise_error
         | 
| 104 109 |  | 
| 105 110 | 
             
                  if local_cache
         | 
| 106 111 | 
             
                    local_cache[key] = value
         | 
| @@ -111,7 +116,7 @@ class RedisMemo::Cache < ActiveSupport::Cache::RedisCacheStore | |
| 111 116 | 
             
                    redis_store.write(key, value, **options)
         | 
| 112 117 | 
             
                  else
         | 
| 113 118 | 
             
                    async.call do
         | 
| 114 | 
            -
                       | 
| 119 | 
            +
                      RedisMemo::ThreadLocalVar.raise_error = raise_error
         | 
| 115 120 | 
             
                      redis_store.write(key, value, **options)
         | 
| 116 121 | 
             
                    end
         | 
| 117 122 | 
             
                  end
         | 
| @@ -1,4 +1,5 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 2 3 | 
             
            require 'connection_pool'
         | 
| 3 4 | 
             
            require_relative 'redis'
         | 
| 4 5 |  | 
| @@ -11,17 +12,17 @@ class RedisMemo::ConnectionPool | |
| 11 12 | 
             
              end
         | 
| 12 13 |  | 
| 13 14 | 
             
              # Avoid method_missing when possible for better performance
         | 
| 14 | 
            -
              %i | 
| 15 | 
            +
              %i[get mget mapped_mget set eval evalsha run_script].each do |method_name|
         | 
| 15 16 | 
             
                define_method method_name do |*args, &blk|
         | 
| 16 17 | 
             
                  @connection_pool.with do |redis|
         | 
| 17 | 
            -
                    redis. | 
| 18 | 
            +
                    redis.__send__(method_name, *args, &blk)
         | 
| 18 19 | 
             
                  end
         | 
| 19 20 | 
             
                end
         | 
| 20 21 | 
             
              end
         | 
| 21 22 |  | 
| 22 23 | 
             
              def method_missing(method_name, *args, &blk)
         | 
| 23 24 | 
             
                @connection_pool.with do |redis|
         | 
| 24 | 
            -
                  redis. | 
| 25 | 
            +
                  redis.__send__(method_name, *args, &blk)
         | 
| 25 26 | 
             
                end
         | 
| 26 27 | 
             
              end
         | 
| 27 28 | 
             
            end
         | 
    
        data/lib/redis_memo/future.rb
    CHANGED
    
    | @@ -1,4 +1,5 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 2 3 | 
             
            require_relative 'cache'
         | 
| 3 4 | 
             
            require_relative 'tracer'
         | 
| 4 5 |  | 
| @@ -11,14 +12,14 @@ class RedisMemo::Future | |
| 11 12 | 
             
                method_args,
         | 
| 12 13 | 
             
                dependent_memos,
         | 
| 13 14 | 
             
                cache_options,
         | 
| 14 | 
            -
                 | 
| 15 | 
            +
                method_name_without_memoization
         | 
| 15 16 | 
             
              )
         | 
| 16 17 | 
             
                @ref = ref
         | 
| 17 18 | 
             
                @method_id = method_id
         | 
| 18 19 | 
             
                @method_args = method_args
         | 
| 19 20 | 
             
                @dependent_memos = dependent_memos
         | 
| 20 21 | 
             
                @cache_options = cache_options
         | 
| 21 | 
            -
                @ | 
| 22 | 
            +
                @method_name_without_memoization = method_name_without_memoization
         | 
| 22 23 | 
             
                @method_cache_key = nil
         | 
| 23 24 | 
             
                @cache_hit = false
         | 
| 24 25 | 
             
                @cached_result = nil
         | 
| @@ -33,18 +34,26 @@ class RedisMemo::Future | |
| 33 34 |  | 
| 34 35 | 
             
              def method_cache_key
         | 
| 35 36 | 
             
                @method_cache_key ||=
         | 
| 36 | 
            -
                  RedisMemo::MemoizeMethod.method_cache_keys | 
| 37 | 
            +
                  RedisMemo::MemoizeMethod.__send__(:method_cache_keys, [context])&.first || ''
         | 
| 37 38 | 
             
              end
         | 
| 38 39 |  | 
| 39 | 
            -
              def  | 
| 40 | 
            +
              def validate_cache_result
         | 
| 41 | 
            +
                cache_validation_sample_percentage =
         | 
| 42 | 
            +
                  @cache_options[:cache_validation_sample_percentage] || RedisMemo::DefaultOptions.cache_validation_sample_percentage
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                if cache_validation_sample_percentage.nil?
         | 
| 45 | 
            +
                  false
         | 
| 46 | 
            +
                else
         | 
| 47 | 
            +
                  cache_validation_sample_percentage > Random.rand(0...100)
         | 
| 48 | 
            +
                end
         | 
| 49 | 
            +
              end
         | 
| 50 | 
            +
             | 
| 51 | 
            +
              def execute(cached_results = nil)
         | 
| 40 52 | 
             
                if RedisMemo::Batch.current
         | 
| 41 | 
            -
                  raise RedisMemo::RuntimeError | 
| 53 | 
            +
                  raise RedisMemo::RuntimeError.new('Cannot execute future when a batch is still open')
         | 
| 42 54 | 
             
                end
         | 
| 43 55 |  | 
| 44 56 | 
             
                if cache_hit?(cached_results)
         | 
| 45 | 
            -
                  validate_cache_result =
         | 
| 46 | 
            -
                    RedisMemo::DefaultOptions.cache_validation_sampler&.call(@method_id)
         | 
| 47 | 
            -
             | 
| 48 57 | 
             
                  if validate_cache_result && cached_result != fresh_result
         | 
| 49 58 | 
             
                    RedisMemo::DefaultOptions.cache_out_of_date_handler&.call(
         | 
| 50 59 | 
             
                      @ref,
         | 
| @@ -64,7 +73,7 @@ class RedisMemo::Future | |
| 64 73 |  | 
| 65 74 | 
             
              def result
         | 
| 66 75 | 
             
                unless @computed_cached_result
         | 
| 67 | 
            -
                  raise RedisMemo::RuntimeError | 
| 76 | 
            +
                  raise RedisMemo::RuntimeError.new('Future has not been executed')
         | 
| 68 77 | 
             
                end
         | 
| 69 78 |  | 
| 70 79 | 
             
                @fresh_result || @cached_result
         | 
| @@ -72,13 +81,13 @@ class RedisMemo::Future | |
| 72 81 |  | 
| 73 82 | 
             
              private
         | 
| 74 83 |  | 
| 75 | 
            -
              def cache_hit?(cached_results=nil)
         | 
| 84 | 
            +
              def cache_hit?(cached_results = nil)
         | 
| 76 85 | 
             
                cached_result(cached_results)
         | 
| 77 86 |  | 
| 78 87 | 
             
                @cache_hit
         | 
| 79 88 | 
             
              end
         | 
| 80 89 |  | 
| 81 | 
            -
              def cached_result(cached_results=nil)
         | 
| 90 | 
            +
              def cached_result(cached_results = nil)
         | 
| 82 91 | 
             
                return @cached_result if @computed_cached_result
         | 
| 83 92 |  | 
| 84 93 | 
             
                @cache_hit = false
         | 
| @@ -102,7 +111,7 @@ class RedisMemo::Future | |
| 102 111 |  | 
| 103 112 | 
             
                RedisMemo::Tracer.trace('redis_memo.cache.write', @method_id) do
         | 
| 104 113 | 
             
                  # cache miss
         | 
| 105 | 
            -
                  @fresh_result = @ref. | 
| 114 | 
            +
                  @fresh_result = @ref.__send__(@method_name_without_memoization, *@method_args)
         | 
| 106 115 | 
             
                  if @cache_options.include?(:expires_in) && @cache_options[:expires_in].respond_to?(:call)
         | 
| 107 116 | 
             
                    @cache_options[:expires_in] = @cache_options[:expires_in].call(@fresh_result)
         | 
| 108 117 | 
             
                  end
         | 
| @@ -114,7 +123,7 @@ class RedisMemo::Future | |
| 114 123 | 
             
                        # result)
         | 
| 115 124 | 
             
                        @cache_hit && @cached_result != @fresh_result
         | 
| 116 125 | 
             
                      )
         | 
| 117 | 
            -
             | 
| 126 | 
            +
                    )
         | 
| 118 127 | 
             
                    RedisMemo::Cache.write(method_cache_key, @fresh_result, **@cache_options)
         | 
| 119 128 | 
             
                  end
         | 
| 120 129 | 
             
                end
         |