flipper-redis 1.2.2 → 1.3.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/flipper/adapters/redis_cache.rb +21 -123
- data/lib/flipper/version.rb +1 -1
- data/spec/flipper/adapters/redis_cache_spec.rb +73 -4
- metadata +5 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8469e0a68d8a08cc42f6e8a50ca2b3121495f3200510a9ebe71e3c4a2304a2af
|
4
|
+
data.tar.gz: bca16917e5dd42cfdbae647ff87c83f5420f12855c61930247fb6b50cecda34e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d765d8c9434676e1ed307ffbae3e9aa4652712c91208d72ddbdd3b8ca1114af8bf7aa0590821ed55b51c103ef9f2248e63461f592071fe56897662bc010c6ab4
|
7
|
+
data.tar.gz: 1e43b7077112e805baf33f8ad6df5528b009b1d23bc3477d43d249cc1537fa907daf66835eed3cfb24933590001cd433ad6b5d695c260878956775c29367c7a4
|
@@ -1,147 +1,45 @@
|
|
1
1
|
require 'redis'
|
2
2
|
require 'flipper'
|
3
|
+
require 'flipper/adapters/cache_base'
|
3
4
|
|
4
5
|
module Flipper
|
5
6
|
module Adapters
|
6
7
|
# Public: Adapter that wraps another adapter with the ability to cache
|
7
|
-
# adapter calls in Redis
|
8
|
-
class RedisCache
|
9
|
-
|
10
|
-
|
11
|
-
# Internal
|
12
|
-
attr_reader :cache
|
13
|
-
|
14
|
-
# Public
|
15
|
-
def initialize(adapter, cache, ttl = 3600)
|
16
|
-
@adapter = adapter
|
17
|
-
@cache = cache
|
18
|
-
@ttl = ttl
|
19
|
-
|
20
|
-
@version = 'v1'.freeze
|
21
|
-
@namespace = "flipper/#{@version}".freeze
|
22
|
-
@features_key = "#{@namespace}/features".freeze
|
23
|
-
@get_all_key = "#{@namespace}/get_all".freeze
|
24
|
-
end
|
25
|
-
|
26
|
-
# Public
|
27
|
-
def features
|
28
|
-
read_feature_keys
|
29
|
-
end
|
30
|
-
|
31
|
-
# Public
|
32
|
-
def add(feature)
|
33
|
-
result = @adapter.add(feature)
|
34
|
-
@cache.del(@features_key)
|
35
|
-
result
|
36
|
-
end
|
37
|
-
|
38
|
-
# Public
|
39
|
-
def remove(feature)
|
40
|
-
result = @adapter.remove(feature)
|
41
|
-
@cache.del(@features_key)
|
42
|
-
@cache.del(key_for(feature.key))
|
43
|
-
result
|
44
|
-
end
|
45
|
-
|
46
|
-
# Public
|
47
|
-
def clear(feature)
|
48
|
-
result = @adapter.clear(feature)
|
49
|
-
@cache.del(key_for(feature.key))
|
50
|
-
result
|
51
|
-
end
|
52
|
-
|
53
|
-
# Public
|
54
|
-
def get(feature)
|
55
|
-
fetch(key_for(feature.key)) do
|
56
|
-
@adapter.get(feature)
|
57
|
-
end
|
58
|
-
end
|
59
|
-
|
60
|
-
def get_multi(features)
|
61
|
-
read_many_features(features)
|
62
|
-
end
|
63
|
-
|
64
|
-
def get_all
|
65
|
-
if @cache.setnx(@get_all_key, Time.now.to_i)
|
66
|
-
@cache.expire(@get_all_key, @ttl)
|
67
|
-
response = @adapter.get_all
|
68
|
-
response.each do |key, value|
|
69
|
-
set_with_ttl key_for(key), value
|
70
|
-
end
|
71
|
-
set_with_ttl @features_key, response.keys.to_set
|
72
|
-
response
|
73
|
-
else
|
74
|
-
features = read_feature_keys.map { |key| Flipper::Feature.new(key, self) }
|
75
|
-
read_many_features(features)
|
76
|
-
end
|
77
|
-
end
|
78
|
-
|
79
|
-
# Public
|
80
|
-
def enable(feature, gate, thing)
|
81
|
-
result = @adapter.enable(feature, gate, thing)
|
82
|
-
@cache.del(key_for(feature.key))
|
83
|
-
result
|
84
|
-
end
|
85
|
-
|
86
|
-
# Public
|
87
|
-
def disable(feature, gate, thing)
|
88
|
-
result = @adapter.disable(feature, gate, thing)
|
89
|
-
@cache.del(key_for(feature.key))
|
90
|
-
result
|
8
|
+
# adapter calls in Redis.
|
9
|
+
class RedisCache < CacheBase
|
10
|
+
def initialize(adapter, cache, ttl = 3600, prefix: nil)
|
11
|
+
super
|
91
12
|
end
|
92
13
|
|
93
14
|
private
|
94
15
|
|
95
|
-
def
|
96
|
-
|
97
|
-
end
|
98
|
-
|
99
|
-
def read_feature_keys
|
100
|
-
fetch(@features_key) { @adapter.features }
|
101
|
-
end
|
102
|
-
|
103
|
-
def read_many_features(features)
|
104
|
-
keys = features.map(&:key)
|
105
|
-
cache_result = Hash[keys.zip(multi_cache_get(keys))]
|
106
|
-
uncached_features = features.reject { |feature| cache_result[feature.key] }
|
107
|
-
|
108
|
-
if uncached_features.any?
|
109
|
-
response = @adapter.get_multi(uncached_features)
|
110
|
-
response.each do |key, value|
|
111
|
-
set_with_ttl(key_for(key), value)
|
112
|
-
cache_result[key] = value
|
113
|
-
end
|
114
|
-
end
|
115
|
-
|
116
|
-
result = {}
|
117
|
-
features.each do |feature|
|
118
|
-
result[feature.key] = cache_result[feature.key]
|
119
|
-
end
|
120
|
-
result
|
121
|
-
end
|
122
|
-
|
123
|
-
def fetch(cache_key)
|
124
|
-
cached = @cache.get(cache_key)
|
16
|
+
def cache_fetch(key, &block)
|
17
|
+
cached = @cache.get(key)
|
125
18
|
if cached
|
126
19
|
Marshal.load(cached)
|
127
20
|
else
|
128
21
|
to_cache = yield
|
129
|
-
|
22
|
+
cache_write key, to_cache
|
130
23
|
to_cache
|
131
24
|
end
|
132
25
|
end
|
133
26
|
|
134
|
-
def
|
135
|
-
|
136
|
-
end
|
27
|
+
def cache_read_multi(keys)
|
28
|
+
return {} if keys.empty?
|
137
29
|
|
138
|
-
|
139
|
-
return [] if keys.empty?
|
140
|
-
|
141
|
-
cache_keys = keys.map { |key| key_for(key) }
|
142
|
-
@cache.mget(*cache_keys).map do |value|
|
30
|
+
values = @cache.mget(*keys).map do |value|
|
143
31
|
value ? Marshal.load(value) : nil
|
144
32
|
end
|
33
|
+
|
34
|
+
Hash[keys.zip(values)]
|
35
|
+
end
|
36
|
+
|
37
|
+
def cache_write(key, value)
|
38
|
+
@cache.setex(key, @ttl, Marshal.dump(value))
|
39
|
+
end
|
40
|
+
|
41
|
+
def cache_delete(key)
|
42
|
+
@cache.del(key)
|
145
43
|
end
|
146
44
|
end
|
147
45
|
end
|
data/lib/flipper/version.rb
CHANGED
@@ -11,7 +11,7 @@ RSpec.describe Flipper::Adapters::RedisCache do
|
|
11
11
|
let(:memory_adapter) do
|
12
12
|
Flipper::Adapters::OperationLogger.new(Flipper::Adapters::Memory.new)
|
13
13
|
end
|
14
|
-
let(:adapter) { described_class.new(memory_adapter, client) }
|
14
|
+
let(:adapter) { described_class.new(memory_adapter, client, 10) }
|
15
15
|
let(:flipper) { Flipper.new(adapter) }
|
16
16
|
|
17
17
|
subject { adapter }
|
@@ -24,6 +24,73 @@ RSpec.describe Flipper::Adapters::RedisCache do
|
|
24
24
|
|
25
25
|
it_should_behave_like 'a flipper adapter'
|
26
26
|
|
27
|
+
it "knows ttl" do
|
28
|
+
expect(adapter.ttl).to eq(10)
|
29
|
+
end
|
30
|
+
|
31
|
+
it "knows features_cache_key" do
|
32
|
+
expect(adapter.features_cache_key).to eq("flipper/v1/features")
|
33
|
+
end
|
34
|
+
|
35
|
+
it "can expire features cache" do
|
36
|
+
# cache the features
|
37
|
+
adapter.features
|
38
|
+
expect(client.get("flipper/v1/features")).not_to be(nil)
|
39
|
+
|
40
|
+
# expire cache
|
41
|
+
adapter.expire_features_cache
|
42
|
+
expect(client.get("flipper/v1/features")).to be(nil)
|
43
|
+
end
|
44
|
+
|
45
|
+
it "can expire feature cache" do
|
46
|
+
# cache the features
|
47
|
+
adapter.get(flipper[:stats])
|
48
|
+
expect(client.get("flipper/v1/feature/stats")).not_to be(nil)
|
49
|
+
|
50
|
+
# expire cache
|
51
|
+
adapter.expire_feature_cache("stats")
|
52
|
+
expect(client.get("flipper/v1/feature/stats")).to be(nil)
|
53
|
+
end
|
54
|
+
|
55
|
+
it "can generate feature cache key" do
|
56
|
+
expect(adapter.feature_cache_key("stats")).to eq("flipper/v1/feature/stats")
|
57
|
+
end
|
58
|
+
|
59
|
+
context "when using a prefix" do
|
60
|
+
let(:adapter) { described_class.new(memory_adapter, client, 3600, prefix: "foo/") }
|
61
|
+
it_should_behave_like 'a flipper adapter'
|
62
|
+
|
63
|
+
it "knows features_cache_key" do
|
64
|
+
expect(adapter.features_cache_key).to eq("foo/flipper/v1/features")
|
65
|
+
end
|
66
|
+
|
67
|
+
it "can generate feature cache key" do
|
68
|
+
expect(adapter.feature_cache_key("stats")).to eq("foo/flipper/v1/feature/stats")
|
69
|
+
end
|
70
|
+
|
71
|
+
it "uses the prefix for all keys" do
|
72
|
+
# check individual feature get cached with prefix
|
73
|
+
adapter.get(flipper[:stats])
|
74
|
+
expect(Marshal.load(client.get("foo/flipper/v1/feature/stats"))).not_to be(nil)
|
75
|
+
|
76
|
+
# check individual feature expired with prefix
|
77
|
+
adapter.remove(flipper[:stats])
|
78
|
+
expect(client.get("foo/flipper/v1/feature/stats")).to be(nil)
|
79
|
+
|
80
|
+
# enable some stuff
|
81
|
+
flipper.enable_percentage_of_actors(:search, 10)
|
82
|
+
flipper.enable(:stats)
|
83
|
+
|
84
|
+
# populate the cache
|
85
|
+
adapter.get_all
|
86
|
+
|
87
|
+
# verify cached with prefix
|
88
|
+
expect(Marshal.load(client.get("foo/flipper/v1/features"))).to eq(Set["stats", "search"])
|
89
|
+
expect(Marshal.load(client.get("foo/flipper/v1/feature/search"))[:percentage_of_actors]).to eq("10")
|
90
|
+
expect(Marshal.load(client.get("foo/flipper/v1/feature/stats"))[:boolean]).to eq("true")
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
27
94
|
describe '#remove' do
|
28
95
|
it 'expires feature' do
|
29
96
|
feature = flipper[:stats]
|
@@ -82,17 +149,19 @@ RSpec.describe Flipper::Adapters::RedisCache do
|
|
82
149
|
adapter.get_all
|
83
150
|
expect(Marshal.load(client.get("flipper/v1/feature/#{stats.key}"))[:boolean]).to eq('true')
|
84
151
|
expect(Marshal.load(client.get("flipper/v1/feature/#{search.key}"))[:boolean]).to be(nil)
|
85
|
-
expect(client.get("flipper/v1/
|
152
|
+
expect(Marshal.load(client.get("flipper/v1/features"))).to eq(Set["stats", "search"])
|
86
153
|
end
|
87
154
|
|
88
155
|
it 'returns same result when already cached' do
|
89
156
|
expect(adapter.get_all).to eq(adapter.get_all)
|
90
157
|
end
|
91
158
|
|
92
|
-
it 'only invokes
|
159
|
+
it 'only invokes two calls to wrapped adapter (for features set and gate data for each feature in set)' do
|
93
160
|
memory_adapter.reset
|
94
161
|
5.times { adapter.get_all }
|
95
|
-
expect(memory_adapter.count(:
|
162
|
+
expect(memory_adapter.count(:features)).to eq(1)
|
163
|
+
expect(memory_adapter.count(:get_multi)).to eq(1)
|
164
|
+
expect(memory_adapter.count).to eq(2)
|
96
165
|
end
|
97
166
|
end
|
98
167
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: flipper-redis
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- John Nunemaker
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-04-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: flipper
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: 1.
|
19
|
+
version: 1.3.0
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: 1.
|
26
|
+
version: 1.3.0
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: redis
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -70,7 +70,7 @@ metadata:
|
|
70
70
|
homepage_uri: https://www.flippercloud.io
|
71
71
|
source_code_uri: https://github.com/flippercloud/flipper
|
72
72
|
bug_tracker_uri: https://github.com/flippercloud/flipper/issues
|
73
|
-
changelog_uri: https://github.com/flippercloud/flipper/releases/tag/v1.
|
73
|
+
changelog_uri: https://github.com/flippercloud/flipper/releases/tag/v1.3.0
|
74
74
|
post_install_message:
|
75
75
|
rdoc_options: []
|
76
76
|
require_paths:
|