1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159
|
require 'delegate'
module Flipper
module Adapters
# Internal: Adapter that wraps another adapter with the ability to memoize
# adapter calls in memory. Used by flipper dsl and the memoizer middleware
# to make it possible to memoize adapter calls for the duration of a request.
class Memoizable < SimpleDelegator
include ::Flipper::Adapter
FeaturesKey = :flipper_features
GetAllKey = :all_memoized
# Internal
attr_reader :cache
# Public: The name of the adapter.
attr_reader :name
# Internal: The adapter this adapter is wrapping.
attr_reader :adapter
# Private
def self.key_for(key)
"feature/#{key}"
end
# Public
def initialize(adapter, cache = nil)
super(adapter)
@adapter = adapter
@name = :memoizable
@cache = cache || {}
@memoize = false
end
# Public
def features
if memoizing?
cache.fetch(FeaturesKey) { cache[FeaturesKey] = @adapter.features }
else
@adapter.features
end
end
# Public
def add(feature)
@adapter.add(feature).tap { expire_features_set }
end
# Public
def remove(feature)
@adapter.remove(feature).tap do
expire_features_set
expire_feature(feature)
end
end
# Public
def clear(feature)
@adapter.clear(feature).tap { expire_feature(feature) }
end
# Public
def get(feature)
if memoizing?
cache.fetch(key_for(feature.key)) { cache[key_for(feature.key)] = @adapter.get(feature) }
else
@adapter.get(feature)
end
end
# Public
def get_multi(features)
if memoizing?
uncached_features = features.reject { |feature| cache[key_for(feature.key)] }
if uncached_features.any?
response = @adapter.get_multi(uncached_features)
response.each do |key, hash|
cache[key_for(key)] = hash
end
end
result = {}
features.each do |feature|
result[feature.key] = cache[key_for(feature.key)]
end
result
else
@adapter.get_multi(features)
end
end
def get_all
if memoizing?
response = nil
if cache[GetAllKey]
response = {}
cache[FeaturesKey].each do |key|
response[key] = cache[key_for(key)]
end
else
response = @adapter.get_all
response.each do |key, value|
cache[key_for(key)] = value
end
cache[FeaturesKey] = response.keys.to_set
cache[GetAllKey] = true
end
# Ensures that looking up other features that do not exist doesn't
# result in N+1 adapter calls.
response.default_proc = ->(memo, key) { memo[key] = default_config }
response
else
@adapter.get_all
end
end
# Public
def enable(feature, gate, thing)
@adapter.enable(feature, gate, thing).tap { expire_feature(feature) }
end
# Public
def disable(feature, gate, thing)
@adapter.disable(feature, gate, thing).tap { expire_feature(feature) }
end
# Internal: Turns local caching on/off.
#
# value - The Boolean that decides if local caching is on.
def memoize=(value)
cache.clear
@memoize = value
end
# Internal: Returns true for using local cache, false for not.
def memoizing?
!!@memoize
end
private
def key_for(key)
self.class.key_for(key)
end
def expire_feature(feature)
cache.delete(key_for(feature.key)) if memoizing?
end
def expire_features_set
cache.delete(FeaturesKey) if memoizing?
end
end
end
end
|