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 160 161 162 163 164 165 166 167
|
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)
result = @adapter.add(feature)
expire_features_set
result
end
# Public
def remove(feature)
result = @adapter.remove(feature)
expire_features_set
expire_feature(feature)
result
end
# Public
def clear(feature)
result = @adapter.clear(feature)
expire_feature(feature)
result
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)
result = @adapter.enable(feature, gate, thing)
expire_feature(feature)
result
end
# Public
def disable(feature, gate, thing)
result = @adapter.disable(feature, gate, thing)
expire_feature(feature)
result
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
|